1. 工作队列基础概念:什么是 workqueue、work、worker,以及它们在内核中的角色
工作队列(workqueue)是 Linux 内核中一种重要的异步任务执行机制,它允许内核代码将需要延迟执行或后台处理的工作项(work)提交到一个队列中,由专门的内核线程(worker)在进程上下文中逐一执行。与软中断(softirq)和 tasklet 不同,工作队列运行在进程上下文,可以睡眠、持有锁、执行阻塞 I/O 操作,因此特别适合处理那些不能在中断上下文中完成的任务。
1.1 核心概念速览
| 概念 | 内核对象 | 角色描述 | 运行上下文 |
|---|---|---|---|
| workqueue | struct workqueue_struct |
管理工作项的队列容器,决定任务由哪个 worker 线程池处理 | 进程上下文 |
| work | struct work_struct |
封装待执行的工作函数和状态,是调度的基本单元 | 进程上下文 |
| worker | struct worker |
实际执行 work 的内核线程,从工作队列中取出 work 并运行其回调函数 | 进程上下文 |
关键区别: 工作队列与软中断/tasklet 最大的不同在于——工作队列运行在进程上下文,允许睡眠、持有互斥锁、执行文件系统操作;而软中断和 tasklet 运行在中断上下文,禁止睡眠和阻塞。选择哪种机制取决于任务是否允许阻塞。
1.2 三要素关系图
下面的 SVG 图展示了 workqueue、work 与 worker 三者之间的协作关系:驱动程序或内核子系统将 work 提交到 workqueue,worker 线程从 workqueue 中取出 work 并执行其回调函数。
1.3 workqueue 在内核中的角色定位
工作队列在内核中承担着“延迟执行 + 进程上下文”的独特角色,填补了中断上下文与内核线程之间的空白。下表对比了 Linux 内核中几种常见的异步执行机制:
| 机制 | 运行上下文 | 可睡眠 | 并发度 | 典型用途 |
|---|---|---|---|---|
| 软中断 (softirq) | 中断上下文 | 否 | 同一类型可多 CPU 并行 | 网络收发包、定时器 |
| tasklet | 中断上下文 | 否 | 同一类型串行执行 | 中断下半部、简单后处理 |
| workqueue | 进程上下文 | 是 | 可配置,多 worker 并行 | 阻塞 I/O、持有锁、复杂后处理 |
| 内核线程 (kthread) | 进程上下文 | 是 | 需手动管理 | 长期运行的后台服务 |
角色总结: workqueue 是中断下半部与内核线程之间的“中间层”——比 tasklet 更灵活(可睡眠),比直接创建内核线程更简单(无需手动管理线程生命周期和并发)。对于驱动开发者而言,当你的中断处理函数需要执行可能阻塞的操作(如 I2C/SPI 读写、内存分配、文件操作)时,workqueue 是首选方案。
1.4 work 的生命周期
一个 work 从创建到执行完成,经历以下状态转换:
- 初始化 (INIT) — 调用
INIT_WORK()或DECLARE_WORK()初始化 work 结构体,绑定回调函数。 - 提交 (QUEUED) — 调用
queue_work()或schedule_work()将 work 加入 workqueue 的 pending 链表。 - 等待 (PENDING) — work 在队列中等待 worker 线程调度。此时 work 处于 pending 状态,不可重复提交。
- 执行 (RUNNING) — worker 线程取出 work,调用其回调函数
work->func()。 - 完成 (IDLE) — 回调执行完毕,work 状态复位,可被再次提交。
1.5 代码示例:定义一个 work 并提交
以下代码展示了驱动模块中最基本的工作队列使用模式:
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/slab.h>
/* 1. 定义 work 回调函数 */
static void my_work_handler(struct work_struct *work)
{
/* 这里可以执行阻塞操作,例如 I2C 读写、内存分配等 */
pr_info("work handler executed in process context\n");
}
/* 2. 声明 work 结构体 */
static DECLARE_WORK(my_work, my_work_handler);
/* 或者使用 INIT_WORK 动态初始化 */
static struct work_struct my_dynamic_work;
static int __init my_driver_init(void)
{
/* 动态初始化 work */
INIT_WORK(&my_dynamic_work, my_work_handler);
/* 3. 提交 work 到系统默认工作队列 system_wq */
schedule_work(&my_work);
schedule_work(&my_dynamic_work);
return 0;
}
static void __exit my_driver_exit(void)
{
/* 可选:取消尚未执行的 work */
cancel_work_sync(&my_work);
cancel_work_sync(&my_dynamic_work);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
注意事项:
schedule_work()将 work 提交到系统默认的全局工作队列system_wq,该队列的 worker 线程与系统共享,不适合长时间阻塞的任务。- 如果 work 需要绑定到特定 CPU 或使用独立线程池,应使用
alloc_workqueue()创建专用 workqueue。 - 在模块卸载时,务必调用
cancel_work_sync()或flush_workqueue()确保所有 work 执行完毕,避免回调函数访问已释放的内存。
1.6 本章小结
- workqueue 是内核提供的“延迟任务 + 进程上下文”执行框架,由 workqueue、work、worker 三要素组成。
- work 是调度的基本单元,封装了回调函数和状态;worker 是实际执行 work 的内核线程;workqueue 是管理 work 的容器。
- 与软中断/tasklet 相比,workqueue 最大的优势是运行在进程上下文,允许睡眠和阻塞操作。
- 驱动开发者应理解 work 的生命周期(INIT → QUEUED → RUNNING → IDLE),并在模块卸载时正确清理未完成的 work。