少于 1 分钟阅读 次阅读

一、引言:为什么需要工作队列?

在Linux内核中,中断处理程序需要快速执行完毕,但有时我们有一些任务比较耗时或可能“睡眠”(比如等待磁盘读写),这些任务不适合在中断上下文中执行。为此,内核提供了多种“下半部”机制来延迟执行这些任务。工作队列(Work Queue)就是其中一种强大而灵活的选择,它允许任务在内核线程的进程上下文中安全运行,从而可以调用包括可能引发睡眠在内的几乎所有内核API。

一句话定义:工作队列是一种将任务(工作)推迟到进程上下文中执行的内核机制,由专门的内核线程(工作者线程)处理,其最大优势是允许任务睡眠和重新调度。

二、核心概念:工作队列的组成

工作队列机制主要涉及三个核心数据结构,它们的关系如附图所示(可参考您上传的第4张图“工作、工作队列和工作者线程之间的关系”):

  1. 工作(Work): 由 struct work_struct 表示,它封装了需要推迟执行的具体任务,核心是一个指向处理函数的指针。 一句话定义:一个work_struct结构体代表一个具体的延迟任务,包含待执行的函数及其参数。

  2. 工作队列(Workqueue): 由 struct workqueue_struct 表示,它是同类型工作者线程的集合管理器。 一句话定义:workqueue_struct是同一类工作者线程的抽象,系统允许多种类型的工作队列共存。

  3. 工作者线程(Worker Thread): 是执行工作的内核线程。每个CPU(或每个工作队列类型)通常都有一个这样的线程。缺省的通用线程名为 events/n一句话定义:工作者线程是执行工作的实体,它在进程上下文中运行,可以睡眠,每个CPU通常关联一个默认线程。

简单来说,你创建工作(定义函数),将其提交到一个工作队列,最终由关联的工作者线程取出并执行。

三、工作原理:工作者线程的生命周期

工作者线程的核心是一个循环,其简化逻辑如下(如您上传的第3张图所示):

  1. 休眠等待:线程将自己设置为可中断休眠状态,并加入等待队列。
  2. 检查工作:如果工作链表为空,则调用 schedule() 进入睡眠。
  3. 执行工作:当有工作被加入队列(schedule_work),线程被唤醒,将状态设为运行,并调用 run_workqueue() 函数。
  4. 遍历执行run_workqueue() 循环遍历工作链表,依次执行每个 work_struct 中预设的函数 (func)。
  5. 回归休眠:所有工作执行完毕后,线程再次进入第1步,等待新工作。

这个机制确保了任务在后台被有序、异步地处理。

四、如何使用工作队列?

1. 创建与初始化工作

有两种方式创建 struct work_struct

  • 静态创建(编译时)DECLARE_WORK(name, func, data)
  • 动态创建(运行时)INIT_WORK(&work, func, data)

其中 func 是工作处理函数,data 是传递给它的参数。

2. 编写处理函数

处理函数的原型是:void work_handler(void *data)。由于此函数在进程上下文中运行,因此可以安全地睡眠、使用信号量等同步机制,但不能直接访问用户空间内存(除非在特定的内核路径下)。

3. 调度工作

  • 立即调度schedule_work(&work) 将工作提交到系统默认的 events 工作队列。
  • 延迟调度schedule_delayed_work(&work, delay) 可以延迟指定的“滴答”数后再执行。

4. 刷新与取消

  • 刷新队列flush_scheduled_work() 等待默认队列中所有工作执行完毕。可能会睡眠。
  • 取消延迟工作cancel_delayed_work(&work) 尝试取消一个已调度的延迟工作。

5. 创建专用工作队列

如果默认队列无法满足需求(例如任务非常繁重,避免影响系统其他部分),可以创建自己的队列:

struct workqueue_struct *my_wq = create_workqueue("my_name");
queue_work(my_wq, &work); // 向该队列提交工作

五、关键抉择:何时使用工作队列?何时用Tasklet?

这是设计“下半部”时的核心决策点,可参考您提供的对比总结。

特性 工作队列 (Work Queue) 任务小函数 (Tasklet)
执行上下文 进程上下文 软中断上下文
可否睡眠/阻塞 可以 绝对不可以
并发特性 可在不同CPU上并行执行 同一tasklet在多个CPU上串行执行
延迟 较高,受线程调度影响 较低,响应更快
实现基础 内核线程 软中断

选择准则(黄金法则):

  • 如果需要执行可能睡眠或阻塞的操作(如:获取信号量、等待I/O、调用kmalloc(GFP_KERNEL)),必须使用工作队列
  • 如果只是进行快速、非阻塞的中断后处理(如:修改一些内部状态、统计计数),优先考虑使用tasklet或软中断以获得更低延迟。

六、总结

工作队列是Linux内核中功能最全面的延迟执行机制。它将工作交给独立的内核线程,在进程上下文中执行,从而获得了睡眠和调度的能力。对于大多数需要在内核中执行复杂、耗时或可能阻塞操作的驱动和子系统来说,工作队列是安全而方便的选择。在具体使用时,开发者应首先判断任务属性(是否需要睡眠),根据上述“黄金法则”在工作队列和tasklet之间做出正确选择。系统的缺省events队列足以应对大部分场景,而在有特殊性能隔离需求时,可以创建专用工作队列。

留下评论