Linux设备驱动系列(17) —— 内核线程

1,005 阅读7分钟

关注微信公众号:Linux内核拾遗

文章来源:mp.weixin.qq.com/s/VC_SRGp4z…

本文将重点介绍内核线程,涵盖Linux中进程和线程的概念以及它们的区别和各自的优势,同时还将深入探讨内核线程编程技术。

1 什么是内核线程

在介绍内核线程之前,首先需要认识Linux内核中的进程和线程的基本概念。

  • 进程:
    • 程序的执行实例,有时候也把执行中的程序被称为“任务”。
    • 进程是重量级的,进程之间的上下文切换开销大且非常耗时。
  • 线程:
    • 是共享同一进程地址空间的独立执行流。
    • 一方面,线程资源开销较小,上下文切换速度较快,因此线程也被称为轻量级进程。
    • 另一方面,由于同一进程内的所有线程共享相同的地址空间,因此线程之间的通信比进程之间的通信更容易且更省时,但需要同步机制来处理并发问题。

由于线程容易导致并发问题,因此需要同步机制来处理,这就是线程管理。

线程是一系列指令,CPU一次只能处理一条指令。为了在并行线程之间切换指令,需要保存执行状态。执行状态的最简单形式是程序计数器和CPU寄存器,前者告诉CPU下一条要执行的指令,后这保存执行参数,例如操作数。

因此,线程管理实际上就是线程之间切换时,完成状态保存、状态恢复以及决定下一个执行的线程等工作。

Linux中有两类线程:

  • 用户级线程:内核不感知这类型的线程,所有线程操作由用户线程库维护。该线程库包含创建和销毁线程、在线程之间传递消息和数据、调度线程执行以及保存和恢复线程上下文等的相关代码,并且所有这些操作都在用户空间中进行。
  • 内核级线程:内核级线程是内核中运行的轻量级线程,具有独立的控制流,它由操作系统内核管理和执行,因此线程操作在内核代码中实现。

2 内核线程管理函数

内核线程管理函数声明在linux/kthread.h头文件中,包含创建、启动、停止内核线程等,接下来将逐一进行介绍。

2.1 创建内核线程

函数kthread_create用于创建一个内核线程:

struct task_struct *kthread_create (int (*threadfn)(void *data),
                                    void *data, const char namefmt[], ...);

内核线程创建之后,需要手动唤醒该线程。线程唤醒后,它将运行threadfn()函数,参数为data

如果一个内核线程是独立线程,即没有人会调用kthread_stop,那么threadfn()中可以直接调用do_exit()退出函数的执行;否则应该在kthread_should_stop()为真的时候(意味着kthread_stop()被调用了)退出函数执行。

2.2 启动内核线程

函数wake_up_process用于唤醒特定的进程,并将其移动到可运行进程集中。

int wake_up_process (struct task_struct *p);

返回1表示进程已被唤醒,返回0表示进程已在运行。

2.3 运行内核线程

Linux内核中还提供了另一个函数,同时完成线程创建和启动操作,即kthread_run()

kthread_run (threadfn, data, namefmt, ...);

实际上它就是kthread_create()wake_up_process()的简单包装函数。

2.4 停止内核线程

函数kthread_stop用于停止由kthread_create创建的线程。

int kthread_stop (struct task_struct *k);

该函数实际上是让线程k的kthread_should_stop()调用返回true,然后唤醒该线程并等待其退出。

如果我们期望创建的内核线程是通过kthread_stop()这种方式来退出的,那么内核线程函数内部不得自行调用do_exit()退出执行。

如果该函数是在kthread_create()之后、wake_up_process()之前调用,这是合法的,此时内核线程将在不调用线程函数的情况下退出。

2.5 其他内核线程函数

2.5.1 kthread_should_stop

前面已经提到过了,它的语义是“此内核线程现在是否应返回”。通常与kthread_stop()配合使用。

int kthread_should_stop (void);

2.5.2 kthread_bind

用于将刚创建的内核线程绑定到指定的CPU。

void kthread_bind (struct task_struct *k, unsigned int cpu);

3 内核线程代码演示

使用内核线程应该遵循一些常用模式:

  • 如果该线程是长时间运行的线程,则需要每次检查kthread_should_stop(),因为任何函数都可能调用kthread_stop。如果任何函数调用了kthread_stop,那么kthread_should_stop将返回true,此时必须退出我们的线程函数。
  • 否则如果线程函数不是长时间运行的,则让该线程完成其任务并使用do_exit自行终止。

下面是完整的内核线程示例代码:

kernel_driver.c

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/err.h>
#include <linux/smp.h>

volatile int my_value = 0;

struct my_node
{
    struct list_head list;
    int data;
};
static LIST_HEAD(my_list);

static struct workqueue_struct *own_workqueue;

static void static_wq_fn(struct work_struct *work)
{
    struct my_node *node = NULL;

    pr_info("Static workqueue function called on CPU[%d]\n", smp_processor_id());

    node = kmalloc(sizeof(struct my_node), GFP_KERNEL);
    node->data = my_value;
    INIT_LIST_HEAD(&node->list);
    list_add_tail(&node->list, &my_list);
}
static DECLARE_WORK(static_work, static_wq_fn);

static void dynanic_wq_fn(struct work_struct *work)
{
    pr_info("Dynamic workqueue function called on CPU[%d]\n", smp_processor_id());
}
static struct work_struct dynamic_work;

#define IRQ_NO 63

static irqreturn_t irq_handler(int irq, void *dev_id)
{
    pr_info("Shared IRQ[%d]: Interrupt Occurred\n", irq);
    queue_work(own_workqueue, &static_work);
    queue_work_on(3, own_workqueue, &dynamic_work);
    return IRQ_HANDLED;
}

static char thread_data1[] = "my_thread1";
static char thread_data2[] = "my_thread2";
static struct task_struct *my_thread1 = NULL;
static struct task_struct *my_thread2 = NULL;

static int thread_fn(void *data)
{
    char *name = (char *)data;
    int i = 0;

    while (!kthread_should_stop())
    {
        pr_info("In My Thread Function [%s] %d\n", name, i++);
        msleep(1000);
    }

    return 0;
}

dev_t dev = 0;
static struct class *dev_class;
static struct cdev my_cdev;
struct kobject *kobj_ref;

static ssize_t sysfs_show(struct kobject *kobj,
                          struct kobj_attribute *attr, char *buf)
{
    return sprintf(buf, "%d", my_value);
}

static ssize_t sysfs_store(struct kobject *kobj,
                           struct kobj_attribute *attr, const char *buf, size_t count)
{
    sscanf(buf, "%d", &my_value);
    return count;
}

struct kobj_attribute my_attr = __ATTR(my_value, 0660, sysfs_show, sysfs_store);

static ssize_t my_read(struct file *filp,
                       char __user *buf, size_t len, loff_t *off)
{
    struct my_node *node;
    int i = 0;

    list_for_each_entry(node, &my_list, list)
    {
        pr_info("Node[%d] { data = %d }\n", i++, node->data);
    }

    pr_info("Total Nodes = %d\n", i);
    return 0;
}

static ssize_t my_write(struct file *filp,
                        const char __user *buf, size_t len, loff_t *off)
{
    char __buf[10] = {0};

    if (copy_from_user(__buf, buf, len) == 0)
    {
        pr_info("Write: %s", __buf);
        sscanf(__buf, "%d", &my_value);
    }
    generic_handle_irq(IRQ_NO);
    return len;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = my_read,
    .write = my_write,
};

static int __init my_driver_init(void)
{
    if ((alloc_chrdev_region(&dev, 0, 1, "my_dev")) < 0)
        return -1;

    cdev_init(&my_cdev, &fops);
    my_cdev.owner = THIS_MODULE;
    my_cdev.ops = &fops;

    if ((cdev_add(&my_cdev, dev, 1)) < 0)
        goto r_class;

    if (IS_ERR(dev_class = class_create(THIS_MODULE, "my_class")))
        goto r_class;

    if (IS_ERR(device_create(dev_class, NULL, dev, NULL, "my_device")))
        goto r_device;

    kobj_ref = kobject_create_and_add("my_sysfs", kernel_kobj);
    if (sysfs_create_file(kobj_ref, &my_attr.attr))
        goto r_sysfs;

    if (request_irq(IRQ_NO, irq_handler, IRQF_SHARED,
                    "my_device", (void *)(irq_handler)))
        goto irq;

    INIT_WORK(&dynamic_work, dynanic_wq_fn);

    own_workqueue = create_workqueue("own_wq");

    if ((my_thread1 = kthread_create(thread_fn, &thread_data1, "My Thread 1")))
        wake_up_process(my_thread1);
    else
        goto r_device;

    if (!(my_thread2 = kthread_run(thread_fn, &thread_data2, "My Thread 2")))
        goto r_device;

    return 0;
irq:
    free_irq(IRQ_NO, (void *)(irq_handler));
r_sysfs:
    kobject_put(kobj_ref);
    sysfs_remove_file(kernel_kobj, &my_attr.attr);

r_device:
    device_destroy(dev_class, dev);
    class_destroy(dev_class);
r_class:
    unregister_chrdev_region(dev, 1);
    cdev_del(&my_cdev);
    return -1;
}

static void __exit my_driver_exit(void)
{
    struct my_node *cur, *next;

    if (my_thread1)
        kthread_stop(my_thread1);
    if (my_thread2)
        kthread_stop(my_thread2);
    list_for_each_entry_safe(cur, next, &my_list, list)
    {
        list_del(&cur->list);
        kfree(cur);
    }
    destroy_workqueue(own_workqueue);
    free_irq(IRQ_NO, (void *)(irq_handler));
    kobject_put(kobj_ref);
    sysfs_remove_file(kernel_kobj, &my_attr.attr);
    device_destroy(dev_class, dev);
    class_destroy(dev_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev, 1);
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("feifei <feifei@gmail.com>");
MODULE_DESCRIPTION("A simple device driver");
MODULE_VERSION("1.16");

代码编译运行,结果如下图所示:

image-20240608233748883

image-20240608233800294

关注微信公众号:Linux内核拾遗

文章来源:mp.weixin.qq.com/s/VC_SRGp4z…