4. CMWQ 核心数据结构:struct worker_pool、struct worker、struct work_struct、struct workqueue_struct

并发管理工作队列(CMWQ)是 Linux 内核中实现异步任务调度的核心框架。要深入理解其运作机制,必须首先掌握其四大核心数据结构:工作池(worker_pool)、工作者(worker)、工作项(work_struct)和工作队列(workqueue_struct)。它们之间的协作关系构成了 CMWQ 的基石。

核心要点:CMWQ 通过将工作队列与工作池解耦,实现了灵活的资源管理和高效的线程复用。理解这四个结构体,就等于拿到了驾驭内核延迟任务调度的钥匙。

4.1 结构总览与关系图

下图展示了四个核心数据结构之间的静态关系。一个 workqueue_struct 可以关联多个 worker_pool(通常每个 CPU 一个,以及一个或几个 unbound pool)。每个 worker_pool 管理一组 worker 线程,而每个 worker 线程负责从共享的链表中取出 work_struct 并执行其回调函数。

CMWQ 核心数据结构关系图 CMWQ 核心数据结构关系图 struct workqueue_struct (工作队列:任务提交入口) pwqs 链表 struct worker_pool (工作池:线程资源池) struct worker_pool (工作池:线程资源池) workers 链表 workers 链表 struct worker (工作者线程) struct worker (工作者线程) struct worker (工作者线程) struct worker (工作者线程) struct work_struct (工作项) struct work_struct (工作项) struct work_struct (工作项) struct work_struct (工作项) worker_pool->worklist (共享链表,由 pool->lock 保护)

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;    // 用于向管理者线程请求帮助的节点
};
设计精髓:pwq 的 max_active 机制实现了工作项的并发控制。当某个 pool 中来自该 wq 的活跃工作项达到 max_active 时,后续的工作项会被暂存到 delayed_works 链表,直到有活跃工作项完成。这防止了单个工作队列耗尽整个 pool 的资源。

4.7 数据结构协作流程

下面通过一个典型的任务提交与执行流程,展示这四个结构体如何协同工作:

  1. 提交阶段:驱动调用 queue_work(wq, &work)。内核根据 wq 的类型(per-CPU 或 unbound)找到对应的 pool_workqueue,进而定位到 worker_pool。
  2. 入队阶段:将 work_struct 通过其 entry 链表节点挂入 worker_pool->worklist。此操作在 pool->lock 保护下完成。
  3. 唤醒阶段:检查 pool 中是否有空闲 worker(nr_idle > 0)。如果有,从 idle_list 中取出一个 worker 并唤醒其内核线程;如果没有,且未达到最大线程数,则创建新的 worker。
  4. 执行阶段:worker 线程(struct worker)被唤醒后,从 worklist 中取出 work_struct,设置 current_work 和 current_func,然后调用 func(work) 执行回调。
  5. 完成阶段:回调执行完毕后,worker 清除 current_work,检查 scheduled 链表是否有嵌套提交的工作项,然后返回 idle 状态或继续处理下一个工作项。
工作项提交与执行流程 工作项提交与执行流程 1. queue_work() 提交 work_struct 2. 入队 worklist pool->worklist 3. 唤醒 worker idle_list → 唤醒 4. 执行 func 回调函数 各阶段涉及的核心数据结构 ① 提交:workqueue_struct → pool_workqueue → worker_pool ② 入队:work_struct.entry 挂入 worker_pool.worklist(pool->lock 保护) ③ 唤醒:从 worker_pool.idle_list 取出 struct worker,调用 wake_up_process() ④ 执行:worker.current_work = work; worker.current_func(work); ⑤ 完成:清除 current_work,处理 scheduled 链表,返回 idle 或取下一个 work

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 数据流的最佳起点。