写点什么

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

  • 2024-06-10
    浙江
  • 本文字数:4430 字

    阅读完需:约 15 分钟

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

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

文章来源:https://mp.weixin.qq.com/s/VC_SRGp4zoYvUyn6-LoZ3g


本文将重点介绍内核线程,涵盖 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");
复制代码


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




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

文章来源:https://mp.weixin.qq.com/s/VC_SRGp4zoYvUyn6-LoZ3g


发布于: 28 分钟前阅读数: 5
用户头像

聚沙成塔 2023-01-12 加入

分享Linux内核开发相关的编程语言、开发调试工具链、计算机组成及操作系统内核知识、Linux社区最新资讯等

评论

发布
暂无评论
Linux设备驱动系列(17) —— 内核线程_C语言_Linux内核拾遗_InfoQ写作社区