2026/4/8 2:59:14
网站建设
项目流程
网站动态小图标,网站获取qq,取商标名字神器,上海企业所得税怎么征收一、Linux内核中断-工作队列
概述
为什么需要工作队列?
中断处理的限制:
中断处理程序(上半部)要求执行速度极快,不能阻塞,以快速响应硬件。
但在实际应用场景中,很多中断后需要进行的操作是耗时的,例如处理大量数据、读写磁盘、与用户空间通信等。
中断下半部机…一、Linux内核中断-工作队列概述为什么需要工作队列?中断处理的限制:中断处理程序(上半部)要求执行速度极快,不能阻塞,以快速响应硬件。但在实际应用场景中,很多中断后需要进行的操作是耗时的,例如处理大量数据、读写磁盘、与用户空间通信等。中断下半部机制的对比:•软中断(Softirq)和 Tasklet:运行在“中断上下文”,不允许睡眠或阻塞。虽然它们延迟了操作,但执行环境仍然有很大限制。•工作队列(Workqueue):运行在“进程上下文”,由内核线程执行。这意味着它可以睡眠、可以调度、可以执行任何复杂的操作,因为它拥有普通进程的所有特性。简单比喻:•中断上半部:像接到一个紧急电话,你立刻记下最关键的信息(比如“厨房着火了!”)。•Tasklet/软中断:像挂掉电话后,在便签上写下更详细的信息(但不能做需要长时间离开座位的事)。•工作队列:像你把便签交给一个专门的助手(内核线程),告诉他:“你去处理厨房着火的事,需要用水、打电话给消防局等,所有这些事你全权负责。” 然后你就可以继续接听其他电话了。工作队列的工作原理structwork_struct{ atomic_long_tdata; // 核心:包含状态标志和指向工作函数的指针 structlist_headentry; // 链表节点:用于将工作项链接到工作队列的链表中 work_func_tfunc; // 指向实际要执行的工作函数的指针 #ifdefCONFIG_LOCKDEP ... // 锁依赖跟踪相关(调试用) #endif };封装工作:将一个需要延迟执行的函数(通常称为work_func)和相关数据封装在一个称为work_struct的结构体中。提交队列:驱动程序或内核子系统将这个work_struct提交(调度)到一个工作队列中如上面代码中的struct list_head entry。线程执行:内核中存在一些名kworker/X:Y的其他工作线程。这些线程会不断地检查其绑定的工作队列中是否有待处理的工作。一旦发现有工作项,它们就会从队列中取出并执行其中封装的函数·work_func_t func)。如果没有工作,线程会进入睡眠状态,以节省资源。整个过程是异步的:提交工作的代码在提交后立即返回,而实际的工作函数则在未来的某个时间点由内核线程调用。工作队列主要分两种:共享工作队列和自定义工作队列共享工作队列共享工作队列是 Linux 内核中一种全局的、系统范围内的工作延迟处理机制。它允许多个不同的驱动程序或内核子系统将需要延迟执行的任务(称为“工作”)提交到同一个公共队列中,由内核预定义的一组工作者线程来统一处理。共享工作队列(如system_wq):这是一个公共的、全局的任务管道。所有需要处理的任务都进入这条唯一的流水线等待被处理。它代表了任务的集中地。物料箱(工作项/任work_struct)每个黄色的物料箱代表一个独立的工作单元。它由某个驱动程序或内核模块创建并提交,封装了需要延迟执行的特定函数和数据。机械臂(机器)(工作者线程,kworker/x:y)这是真正执行任务的“劳动者”。内核会创建一组(或一个)这样的工作者线程,它们的功能就是不断地从共享队列的头部获取工作项并执行。运行流程1.任务提交(投放物料)当中断处理程序或某个内核代码需要执行一个耗时操作时,它会将这个操作封装成一个“工作项”(创建一个黄色的物料箱),然后将其提交到共享工作队列(放到流水线上)。2.任务排队(在流水线上等待)新提交的任务会被添加到队列的末尾。如图所示,多个来自不同源头的工作项(物料箱)会按照提交的先后顺序,在流水线上排列等待。3.任务执行(机械臂处理)内核的工作者线程(机械臂)处于循环工作中:它会移动到流水线的起始端,检查是否有等待处理的工作项。一旦发现有工作项,它就会从队列头部取出一个,并执行该工作项中定义的函数。执行完毕后,工作者线程会继续处理流水线上的下一个工作项。共享与竞争“共享” 体现在:多条“生产流水”(不同的驱动程序)的“物料”(任务)都共享同一条“公共流水线”(队列)和同一台(或一组)“机器”(工作者线程)。这意味着,如果某个驱动程序提交了一个非常耗时的任务,它可能会阻塞后续其他驱动程序提交的任务,因为所有任务都在同一个队列中排队,等待同一个(或有限个)工作者线程来处理。工作队列初始化静态初始化函数:DECLARE_WORK#defineDECLARE_WORK(n, f) struct work_struct n = __WORK_INITIALIZER(n, f)在编译时静态地定义并初始化一个work_struct结构体。这个宏在代码编译阶段就完成了两件事:定义变量:创建一个名为n的struct work_struct类型的变量。初始化变量:使用__WORK_INITIALIZER宏将该结构体初始化,其中最重要的是将其func成员设置为用户指定的函数_f,并将其状态标志位(如WORK_STRUCT_PENDING)初始化为未激活状态。示例// 在文件顶部全局声明 DECLARE_WORK(my_work, my_work_function); staticintmy_init(void){ // 无需再次初始化,可直接使用 schedule_work(my_work); return0; }动态初始化函数原型#defineINIT_WORK(_work, _func) __INIT_WORK((_work), (_func), 0)作用:在运行时初始化一个已经存在的work_struct结构体。例子structmy_data{ structwork_structmy_work; intsome_data; }; staticvoidmy_work_function(structwork_struct *work){ structmy_data*data =container_of(work,structmy_data, my_work); // ... 使用 data-some_data ... } staticintmy_init(void){ structmy_data*data =kmalloc(sizeof(*data), GFP_KERNEL); if(!data)return-ENOMEM; // 动态初始化:初始化 data 结构体中的 my_work 成员 INIT_WORK(data-my_work, my_work_function); data-some_data =42; // 之后就可以使用 schedule_work(data-my_work); return0; }对比特性DECLARE_WORK (静态初始化)INIT_WORK (动态初始化)初始化时机编译时运行时内存管理定义并初始化全局变量只负责初始化,不分配内存灵活性较低,适用于全局固定对象高极,可嵌入任何自定义结构体典型应用场景全局、单一的工作项嵌入在设备私有数据结构中的工作项共享工作队列的代码/* * Linux 内核模块示例:演示工作队列作为中断下半部的使用 * 重点展示工作队列处理耗时操作的能力 */ #includelinux/module.h // 模块相关头文件 #includelinux/interrupt.h // 中断处理相关 #includelinux/workqueue.h // 工作队列相关 #includelinux/delay.h // 延时函数(msleep等) #includelinux/ktime.h // 高精度时间函数 #includelinux/timekeeping.h// 时间保持相关 #defineIRQ_NUM 19 // 示例中断号(实际应根据硬件确定) #defineMAX_OPERATIONS 5// 每个中断下半部执行的操作次数 /* * 设备私有数据结构 * 包含工作队列项和需要在中断上下文共享的数据 */ structmy_device{ structwork_structirq_work; // 工作队列项(核心数据结构) atomic_tirq_counter; // 原子中断计数器(用于安全计数) ktime_tstart_time; // 操作开始时间(高精度时间) chardevice_name[20]; // 设备名称标识 intoperation_count; // 当前操作计数(用于进度跟踪) }; staticstructmy_devicemy_dev; // 全局设备实例 /* * 模拟耗时操作函数 * 展示工作队列处理CPU密集型和I/O密集型操作的能力 */ staticvoidperform_long_operation(void) { inti; constintiterations =1000000;// 100万次迭代(模拟CPU密集型操作) // 模拟CPU密集型操作 for(i =0; i iterations; i++) { /* * 空循环消耗CPU时间 * barrier()防止编译器优化掉这个循环 */ barrier(); } /* * 模拟I/O等待操作(休眠) * 证明工作队列可以安全调用睡眠函数 * 这是与tasklet的关键区别(tasklet不能休眠) */ msleep(10);// 10毫秒休眠 } /* * 工作队列处理函数(中断下半部) * 在进程上下文中执行,可以安全进行耗时操作和睡眠 */ staticvoidwork_handler(structwork_struct *work) { /* * 通过container_of宏获取包含work_struct的设备结构 * 这是Linux内核中获取父结构的标准方法 */ structmy_device*dev =container_of(work,structmy_device, irq_work); // 记录开始时间(高精度时间) dev-start_time =ktime_get(); pr_info("Bottom half started at %lld ns\n",ktime_to_ns(dev-start_time)); /* * 执行一系列耗时操作 * 展示工作队列处理长时间任务的能力 */ for(dev-operation_count =0; dev-operation_count MAX_OPERATIONS; dev-operation_count++) { pr_info("Operation %d starting...\n", dev-operation_count +1); // 执行CPU密集型操作(模拟数据处理) perform_long_operation(); /* * 执行休眠操作(证明工作队列可以睡眠) * 在实际应用中可能是等待硬件响应或I/O完成 */ msleep(20);// 20毫秒休眠 // 报告操作完成时间 pr_info("Operation %d completed after %lld ns\n", dev-operation_count +1, ktime_to_ns(ktime_sub(ktime_get(), dev-start_time))); } // 报告总耗时 pr_info("All operations completed. Total time: %lld ns\n", ktime_to_ns(ktime_sub(ktime_get(), dev-start_time))); } /* * 中断处理函数(上半部) * 在中断上下文中执行,必须快速完成 */ staticirqreturn_tirq_handler(intirq,void*dev_id) { // 获取设备结构 structmy_device*dev = (structmy_device *)dev_id; /* * 快速处理:仅更新原子计数器 * 使用原子操作确保在SMP环境下的安全性 */ atomic_inc(dev-irq_counter); /* * 调度工作队列(下半部) * schedule_work()将工作项提交到全局共享工作队列 * 函数立即返回,不等待工作完成 */ schedule_work(dev-irq_work); /* * 记录中断处理完成时间(纳秒级) * 展示上半部处理的快速性 */ pr_info("Top half: IRQ %d handled in %lld ns\n", irq,ktime_to_ns(ktime_get())); returnIRQ_HANDLED; } /* * 模块初始化函数 * 在模块加载时执行 */ staticint__initmy_module_init(void) { intret; // 初始化设备结构 snprintf(my_dev.device_name,sizeof(my_dev.device_name),"workqueue_demo"); atomic_set(my_dev.irq_counter,0);// 初始化原子计数器 my_dev.operation_count =0; /* * 初始化工作队列 * INIT_WORK宏绑定工作项和处理函数 * 这是动态初始化方式(工作项嵌入在设备结构中