4. CMWQ 核心数据结构:struct worker_pool、struct worker、struct work_struct、struct workqueue_struct
并发管理工作队列(CMWQ)是 Linux 内核中实现异步任务调度的核心框架。要深入理解其运作机制,必须首先掌握其四大核心数据结构:工作池(worker_pool)、工作者(worker)、工作项(work_struct)和工作队列(workqueue_struct)。它们之间的协作关系构成了 CMWQ 的基石。
4.1 结构总览与关系图
下图展示了四个核心数据结构之间的静态关系。一个 workqueue_struct 可以关联多个 worker_pool(通常每个 CPU 一个,以及一个或几个 unbound pool)。每个 worker_pool 管理一组 worker 线程,而每个 worker 线程负责从共享的链表中取出 work_struct 并执行其回调函数。
4.2 struct work_struct:最小调度单元
work_struct 是工作队列系统中最基本的数据结构,代表一个待执行的工作项。开发者通过填充此结构体并调用 queue_work() 系列函数,将任务提交给工作队列。
// 定义于 include/linux/workqueue.h
struct work_struct {
atomic_long_t data; // 低比特位存放标志,高比特位存放 worker_pool 指针或 timer
struct list_head entry; // 用于挂入 worker_pool 的 worklist 链表
work_func_t func; // 用户定义的工作回调函数
};
| 成员 | 类型 | 说明 |
|---|---|---|
data |
atomic_long_t |
存储工作项的状态标志(如 WORK_STRUCT_PENDING)和关联的 worker_pool 指针。通过原子操作实现无锁状态切换。 |
entry |
struct list_head |
链表节点,用于将工作项挂入所属 worker_pool 的 worklist 等待队列。 |
func |
work_func_t |
用户定义的回调函数指针,原型为 void (*work_func_t)(struct work_struct *work)。 |
DECLARE_WORK(name, function) 宏可以静态声明一个 work_struct。动态初始化则使用 INIT_WORK(work, func)。注意 data 字段的低比特位由内核管理,开发者不应直接修改。
4.3 struct worker_pool:线程资源池
worker_pool 是 CMWQ 引入的关键抽象,它代表一个线程资源池。每个 pool 管理一组内核线程(worker),并维护一个待执行的工作项链表。pool 分为两种类型:per-CPU pool 和 unbound pool。
// 定义于 kernel/workqueue_internal.h
struct worker_pool {
spinlock_t lock; // 保护 pool 内部数据结构的自旋锁
struct list_head worklist; // 待执行的工作项链表(核心队列)
int nr_workers; // 当前 pool 中 worker 线程总数
int nr_idle; // 当前空闲 worker 线程数
struct list_head idle_list; // 空闲 worker 线程链表
struct list_head workers; // 所有 worker 线程链表
struct timer_list idle_timer; // 空闲 worker 销毁定时器
struct workqueue_attrs *attrs; // pool 属性(nice 值、cpumask 等)
int cpu; // 绑定的 CPU(per-CPU pool 有效)
int id; // pool 的唯一标识符
unsigned long flags; // 标志位(如 POOL_MANAGER_ACTIVE)
};
| 成员 | 说明 |
|---|---|
lock |
保护 worklist、workers 链表等内部状态的自旋锁。所有对 pool 的修改操作都必须持有此锁。 |
worklist |
核心队列,所有待执行的 work_struct 都挂在此链表上。worker 线程通过此链表获取任务。 |
nr_workers / nr_idle |
用于动态调整线程数量。当空闲 worker 过多时,idle_timer 会触发销毁机制;当工作项积压时,管理者线程会创建新 worker。 |
attrs |
保存 pool 的调度属性,包括 nice 值、绑定的 CPU 掩码等。unbound pool 通过 attrs 实现自定义调度策略。 |
cpu |
对于 per-CPU pool,此字段记录绑定的 CPU 编号。unbound pool 的此字段为 -1。 |
worker_pool 和 workqueue_struct。一个工作队列可以对应多个 pool(通过 pool_workqueue 连接),但一个 pool 只能服务于一个 CPU 或一组属性相同的 unbound 工作队列。pool 是线程资源的管理单元,而工作队列是任务提交的接口。
4.4 struct worker:内核工作者线程
worker 结构体代表一个实际执行工作项的内核线程。每个 worker 都隶属于一个 worker_pool,并在其生命周期内反复从 pool 的 worklist 中取出工作项执行。
// 定义于 kernel/workqueue_internal.h
struct worker {
union {
struct list_head entry; // 挂入 pool->workers 或 pool->idle_list
struct rcu_head rcu; // 用于 RCU 销毁
};
struct work_struct *current_work; // 当前正在执行的工作项
work_func_t current_func; // 当前执行的回调函数
struct pool_workqueue *current_pwq; // 当前关联的 pool_workqueue
struct list_head scheduled; // 被重新调度的 work list(嵌套执行场景)
struct task_struct *task; // 指向内核线程的 task_struct
struct worker_pool *pool; // 所属的工作池
int id; // worker 的唯一 ID
unsigned long last_active; // 最后一次活跃时间戳(用于空闲判断)
};
| 成员 | 说明 |
|---|---|
entry |
用于将 worker 挂入 pool 的 workers 链表(所有 worker)或 idle_list(空闲 worker)。 |
current_work |
指向 worker 当前正在处理的工作项。如果为 NULL,表示 worker 处于空闲状态。 |
scheduled |
当工作项在执行过程中再次调用 queue_work() 将自身或其他工作项加入队列时,这些新工作项会被暂存于此链表,待当前工作项完成后处理。 |
task |
指向内核线程的 task_struct,通过此字段可以修改线程的调度策略、优先级或 CPU 亲和性。 |
pool |
反向指针,标识该 worker 所属的 worker_pool。 |
4.5 struct workqueue_struct:任务提交接口
workqueue_struct 是开发者直接交互的对象。它对外提供统一的 queue_work() 接口,对内通过 pool_workqueue 桥接到一个或多个 worker_pool。
// 定义于 include/linux/workqueue.h
struct workqueue_struct {
struct list_head pwqs; // 所有关联的 pool_workqueue 链表
struct list_head list; // 全局工作队列链表
struct workqueue_attrs *unbound_attrs; // unbound 工作队列的属性
struct pool_workqueue *cpu_pwqs; // per-CPU 的 pool_workqueue 数组
struct pool_workqueue *numa_pwq_tbl[]; // NUMA 节点对应的 pwq 表
char name[WQ_NAME_LEN]; // 工作队列名称(便于调试)
unsigned int flags; // 创建标志(WQ_UNBOUND, WQ_HIGHPRI 等)
struct worker_pool *pool; // 对于 per-CPU 队列,指向第一个 pool(简化访问)
};
| 成员 | 说明 |
|---|---|
pwqs |
链表头,串联所有与该工作队列关联的 pool_workqueue 实例。每个 pwq 连接一个 worker_pool。 |
cpu_pwqs |
per-CPU 工作队列的 pwq 数组,按 CPU 编号索引。通过 per_cpu_ptr() 访问。 |
flags |
控制工作队列行为,如 WQ_UNBOUND(不绑定特定 CPU)、WQ_HIGHPRI(高优先级)、WQ_MEM_RECLAIM(内存回收安全)等。 |
unbound_attrs |
仅对 unbound 工作队列有效,定义其调度属性(nice 值、cpumask)。per-CPU 队列此字段为 NULL。 |
4.6 桥接结构:struct pool_workqueue
在 CMWQ 中,workqueue_struct 和 worker_pool 并不直接相连,而是通过 pool_workqueue (pwq) 进行桥接。这种设计实现了一对多的灵活映射:一个工作队列可以对应多个 pool(例如每个 CPU 一个),一个 pool 也可以被多个工作队列共享。
// 定义于 kernel/workqueue_internal.h
struct pool_workqueue {
struct worker_pool *pool; // 指向关联的 worker_pool
struct workqueue_struct *wq; // 指向所属的 workqueue_struct
int nr_active; // 当前活跃的工作项数量
int max_active; // 最大并发活跃工作项限制
struct list_head delayed_works; // 延迟执行的工作项链表(受 max_active 限制)
struct list_head pwqs_node; // 挂入 wq->pwqs 链表的节点
struct list_head mayday_node; // 用于向管理者线程请求帮助的节点
};
max_active 机制实现了工作项的并发控制。当某个 pool 中来自该 wq 的活跃工作项达到 max_active 时,后续的工作项会被暂存到 delayed_works 链表,直到有活跃工作项完成。这防止了单个工作队列耗尽整个 pool 的资源。
4.7 数据结构协作流程
下面通过一个典型的任务提交与执行流程,展示这四个结构体如何协同工作:
- 提交阶段:驱动调用
queue_work(wq, &work)。内核根据 wq 的类型(per-CPU 或 unbound)找到对应的pool_workqueue,进而定位到worker_pool。 - 入队阶段:将
work_struct通过其entry链表节点挂入worker_pool->worklist。此操作在pool->lock保护下完成。 - 唤醒阶段:检查 pool 中是否有空闲 worker(
nr_idle > 0)。如果有,从idle_list中取出一个 worker 并唤醒其内核线程;如果没有,且未达到最大线程数,则创建新的 worker。 - 执行阶段:worker 线程(
struct worker)被唤醒后,从worklist中取出work_struct,设置current_work和current_func,然后调用func(work)执行回调。 - 完成阶段:回调执行完毕后,worker 清除
current_work,检查scheduled链表是否有嵌套提交的工作项,然后返回 idle 状态或继续处理下一个工作项。
4.8 数据结构对比总结
| 数据结构 | 核心职责 | 关键成员 | 生命周期 |
|---|---|---|---|
work_struct |
封装待执行的任务(回调 + 数据) | data, entry, func |
由开发者管理,通常为全局或堆分配 |
worker_pool |
管理线程资源和工作项队列 | worklist, workers, idle_list |
系统初始化时创建,常驻内存 |
worker |
代表一个执行工作项的内核线程 | current_work, task, pool |
动态创建和销毁,受 pool 管理 |
workqueue_struct |
对外接口,管理 pwq 映射 | pwqs, flags, name |
由 alloc_workqueue() 创建,destroy_workqueue() 销毁 |
pool_workqueue |
桥接 wq 和 pool,实现并发控制 | pool, wq, max_active |
与 wq 同生命周期,随 wq 创建而创建 |
kernel/workqueue.c 中的 queue_work_on() 函数入手,跟踪其如何通过 pwq 找到 pool,再将 work 插入 worklist。这是理解整个 CMWQ 数据流的最佳起点。