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 并执行其回调函数。

工作队列三要素关系图 workqueue、work、worker 三要素协作关系 驱动/内核模块 (work 提交者) workqueue struct workqueue_struct 管理 work 队列 work 链表 (pending) worker 线程池 struct worker_pool worker-0 (kworker/0:0) worker-1 (kworker/0:1) 提交 work 取出 work 执行回调函数 工作队列执行流程 ① 驱动调用 queue_work() / schedule_work() 将 work 加入 workqueue ② worker 线程从 workqueue 的 pending 链表中取出 work ③ worker 在进程上下文中执行 work 的回调函数 (work->func) ④ 执行完成后,work 状态变为 idle,可被再次调度

1.3 workqueue 在内核中的角色定位

工作队列在内核中承担着“延迟执行 + 进程上下文”的独特角色,填补了中断上下文与内核线程之间的空白。下表对比了 Linux 内核中几种常见的异步执行机制:

机制 运行上下文 可睡眠 并发度 典型用途
软中断 (softirq) 中断上下文 否 同一类型可多 CPU 并行 网络收发包、定时器
tasklet 中断上下文 否 同一类型串行执行 中断下半部、简单后处理
workqueue 进程上下文 是 可配置,多 worker 并行 阻塞 I/O、持有锁、复杂后处理
内核线程 (kthread) 进程上下文 是 需手动管理 长期运行的后台服务
角色总结: workqueue 是中断下半部与内核线程之间的“中间层”——比 tasklet 更灵活(可睡眠),比直接创建内核线程更简单(无需手动管理线程生命周期和并发)。对于驱动开发者而言,当你的中断处理函数需要执行可能阻塞的操作(如 I2C/SPI 读写、内存分配、文件操作)时,workqueue 是首选方案。

1.4 work 的生命周期

一个 work 从创建到执行完成,经历以下状态转换:

  1. 初始化 (INIT) — 调用 INIT_WORK() 或 DECLARE_WORK() 初始化 work 结构体,绑定回调函数。
  2. 提交 (QUEUED) — 调用 queue_work() 或 schedule_work() 将 work 加入 workqueue 的 pending 链表。
  3. 等待 (PENDING) — work 在队列中等待 worker 线程调度。此时 work 处于 pending 状态,不可重复提交。
  4. 执行 (RUNNING) — worker 线程取出 work,调用其回调函数 work->func()。
  5. 完成 (IDLE) — 回调执行完毕,work 状态复位,可被再次提交。
work 生命周期状态机 work 生命周期状态转换 INIT 初始化 QUEUED 已提交 RUNNING 执行中 IDLE 空闲 queue_work() worker 调度 执行完毕 可再次提交 (re-schedule)

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。