title: workqueue
categories:
- linux
- kernel
tags: - linux
- kernel
abbrlink: db1bbe40
date: 2025-10-03 09:01:49
文章目录
- [kernel/workqueue.c 内核工作队列(Kernel Workqueues) 通用的内核后台任务处理框架](#kernel/workqueue.c 内核工作队列(Kernel Workqueues) 通用的内核后台任务处理框架)
-
-
- 历史与背景
-
- [这项技术是为了解决什么特定 "问题而诞生的?](#这项技术是为了解决什么特定 "问题而诞生的?)
- 它的发展经历了哪些重要的里程碑或版本迭代?
- 目前该技术的社区活跃度和主流应用情况如何?
- 核心原理与设计
- 使用场景
- 对比分析
-
- [请将其 与 其他相似技术 进行详细对比。](#请将其 与 其他相似技术 进行详细对比。)
-
- include/linux/workqueue.h
-
- INIT_WORK
- [queue_delayed_work 延迟后对 workqueue 上的工作进行排队](#queue_delayed_work 延迟后对 workqueue 上的工作进行排队)
- kernel/workqueue_internal.h
- kernel/workqueue.c
-
- [init_cpu_worker_pool 初始化 CPU 工作池](#init_cpu_worker_pool 初始化 CPU 工作池)
- [init_pwq 初始化 pwq](#init_pwq 初始化 pwq)
- [`__wq_cpumask_show`: 显示CPU掩码的核心实现函数](#
__wq_cpumask_show: 显示CPU掩码的核心实现函数) - [`cpumask_requested_show` & `DEVICE_ATTR_RO(cpumask_requested)`: 显示 "requested" CPU掩码](#
cpumask_requested_show&DEVICE_ATTR_RO(cpumask_requested): 显示 "requested" CPU掩码) - [`cpumask_isolated_show` & `DEVICE_ATTR_RO(cpumask_isolated)`: 显示 "isolated" CPU掩码](#
cpumask_isolated_show&DEVICE_ATTR_RO(cpumask_isolated): 显示 "isolated" CPU掩码) - [`cpumask_show`, `cpumask_store` & `DEVICE_ATTR_RW(cpumask)`: 显示和修改当前的CPU掩码](#
cpumask_show,cpumask_store&DEVICE_ATTR_RW(cpumask): 显示和修改当前的CPU掩码) - [`wq_sysfs_cpumask_attrs` & `ATTRIBUTE_GROUPS`: 定义属性组](#
wq_sysfs_cpumask_attrs&ATTRIBUTE_GROUPS: 定义属性组) - [`wq_sysfs_init` & `core_initcall`: 注册`sysfs`接口](#
wq_sysfs_init&core_initcall: 注册sysfs接口) - [format_worker_id 格式化工作线程 ID](#format_worker_id 格式化工作线程 ID)
- [worker_enter_idle 进入空闲状态](#worker_enter_idle 进入空闲状态)
- [set_pf_worker 设置当前线程为工作队列工作线程](#set_pf_worker 设置当前线程为工作队列工作线程)
- [worker_leave_idle - 离开空闲状态](#worker_leave_idle - 离开空闲状态)
- [need_more_worker 判断是否需要唤醒或创建更多的工作线程(worker)来处理工作队列中的任务](#need_more_worker 判断是否需要唤醒或创建更多的工作线程(worker)来处理工作队列中的任务)
- [may_start_working 判断是否可以开始工作](#may_start_working 判断是否可以开始工作)
- [wake_up_process 唤醒特定进程](#wake_up_process 唤醒特定进程)
- [worker_thread - 工作者线程函数](#worker_thread - 工作者线程函数)
- [create_worker 创建一个新的工作队列工作线程](#create_worker 创建一个新的工作队列工作线程)
- [maybe_create_worker 可能创建一个新的工作线程](#maybe_create_worker 可能创建一个新的工作线程)
- [manage_workers 管理工作池](#manage_workers 管理工作池)
- [find_worker_executing_work 查找正在执行工作项的工作线程](#find_worker_executing_work 查找正在执行工作项的工作线程)
- [move_linked_works 移动关联的工作项](#move_linked_works 移动关联的工作项)
- [assign_work 分配工作项](#assign_work 分配工作项)
- [keep_working 判断是否需要继续工作](#keep_working 判断是否需要继续工作)
- [get_pwq 获取指定pool_workqueue的额外引用](#get_pwq 获取指定pool_workqueue的额外引用)
- [put_pwq 取消对 @pwq 的引用。如果它的引用计数降为零,安排销毁它。调用者应该持有匹配的 pool->lock。](#put_pwq 取消对 @pwq 的引用。如果它的引用计数降为零,安排销毁它。调用者应该持有匹配的 pool->lock。)
- [get_work_color 从 work_data 中提取工作项的颜色(color)](#get_work_color 从 work_data 中提取工作项的颜色(color))
- [pwq_dec_nr_in_flight 减少 pwq 的 nr_in_flight](#pwq_dec_nr_in_flight 减少 pwq 的 nr_in_flight)
- [first_idle_worker 返回第一个空闲的 worker](#first_idle_worker 返回第一个空闲的 worker)
- [need_more_worker 判断工作池(worker_pool)是否需要唤醒更多的工作线程(worker)](#need_more_worker 判断工作池(worker_pool)是否需要唤醒更多的工作线程(worker))
- [kick_bh_pool 用于唤醒软中断(Bottom Half,简称 BH)工作池中的工作线程](#kick_bh_pool 用于唤醒软中断(Bottom Half,简称 BH)工作池中的工作线程)
- [kick_pool 如有必要,唤醒空闲的 worker](#kick_pool 如有必要,唤醒空闲的 worker)
- [process_one_work 处理单个工作项](#process_one_work 处理单个工作项)
- [process_scheduled_works 处理调度的工作](#process_scheduled_works 处理调度的工作)
- [set_pf_worker 标识一个任务是否为工作队列(workqueue)的工作者线程](#set_pf_worker 标识一个任务是否为工作队列(workqueue)的工作者线程)
- [worker_thread 工作线程函数](#worker_thread 工作线程函数)
- [wqattrs_hash 计算工作队列属性的哈希值](#wqattrs_hash 计算工作队列属性的哈希值)
- [set_worker_dying 设置工作线程为死亡状态](#set_worker_dying 设置工作线程为死亡状态)
- [detach_worker 分离工作线程](#detach_worker 分离工作线程)
- [detach_dying_workers 分离死亡的工作线程](#detach_dying_workers 分离死亡的工作线程)
- [work_grab_pending 从 WorkList 中窃取工作项并禁用 IRQ](#work_grab_pending 从 WorkList 中窃取工作项并禁用 IRQ)
-
- [`offqd` 的作用](#
offqd的作用) - [`offqd` 的使用场景](#
offqd的使用场景) - 总结
- [flush_work: 等待一个工作项完成其最后的执行](#flush_work: 等待一个工作项完成其最后的执行)
- [`offqd` 的作用](#
- [insert_wq_barrier: 向工作队列中插入一个屏障工作项](#insert_wq_barrier: 向工作队列中插入一个屏障工作项)
- [start_flush_work: 启动一个工作项的冲刷操作](#start_flush_work: 启动一个工作项的冲刷操作)
- [__flush_work: 冲刷(flush)一个工作项的核心实现](#__flush_work: 冲刷(flush)一个工作项的核心实现)
- [cancel_work 取消工作项](#cancel_work 取消工作项)
- [put_unbound_pool 释放工作池](#put_unbound_pool 释放工作池)
- [get_unbound_pool 获取具有指定属性的工作池](#get_unbound_pool 获取具有指定属性的工作池)
- [alloc_unbound_pwq 用于根据给定的属性 attrs 获取一个合适的 worker pool](#alloc_unbound_pwq 用于根据给定的属性 attrs 获取一个合适的 worker pool)
- [apply_wqattrs_prepare 应用工作队列属性](#apply_wqattrs_prepare 应用工作队列属性)
- [apply_wqattrs_commit 应用新的工作队列属性(wqattrs),并将准备好的 pwqs(per-CPU workqueues)安装到工作队列中](#apply_wqattrs_commit 应用新的工作队列属性(wqattrs),并将准备好的 pwqs(per-CPU workqueues)安装到工作队列中)
- [apply_workqueue_attrs_locked 在加锁状态下为 workqueue(工作队列)应用新的属性](#apply_workqueue_attrs_locked 在加锁状态下为 workqueue(工作队列)应用新的属性)
- [alloc_and_link_pwqs 分配并链接工作队列池(PWQ)](#alloc_and_link_pwqs 分配并链接工作队列池(PWQ))
- [alloc_workqueue 分配工作队列](#alloc_workqueue 分配工作队列)
- [workqueue_init_early 工作队初始化](#workqueue_init_early 工作队初始化)
- [for_each_pool 遍历工作池](#for_each_pool 遍历工作池)
- [is_chained_work 判断当前工作项是否是由同一工作队列执行的另一个工作项触发](#is_chained_work 判断当前工作项是否是由同一工作队列执行的另一个工作项触发)
- [work_struct_pwq 将工作项的 data 字段转换为 pool_workqueue](#work_struct_pwq 将工作项的 data 字段转换为 pool_workqueue)
- [get_work_pool 返回与给定作品关联的worker_pool](#get_work_pool 返回与给定作品关联的worker_pool)
- [find_worker_executing_work 查找正在执行工作的 worker](#find_worker_executing_work 查找正在执行工作的 worker)
- [set_work_data 设置工作项的 data 字段](#set_work_data 设置工作项的 data 字段)
- [set_work_pwq 设置工作项的工作池(pool_workqueue)](#set_work_pwq 设置工作项的工作池(pool_workqueue))
- [insert_work 将一个工作项(work_struct)插入到工作池(pool_workqueue)的指定位置](#insert_work 将一个工作项(work_struct)插入到工作池(pool_workqueue)的指定位置)
- [wq_node_nr_active 用于确定工作队列(workqueue_struct)在指定 NUMA 节点上的 wq_node_nr_active 数据结构](#wq_node_nr_active 用于确定工作队列(workqueue_struct)在指定 NUMA 节点上的 wq_node_nr_active 数据结构)
- [tryinc_node_nr_active 尝试增加 wq_node_nr_active 结构体中的活动计数(nr)](#tryinc_node_nr_active 尝试增加 wq_node_nr_active 结构体中的活动计数(nr))
- [pwq_tryinc_nr_active 尝试增加指定工作队列(pool_workqueue,简称 pwq)的活动工作项计数(nr_active)](#pwq_tryinc_nr_active 尝试增加指定工作队列(pool_workqueue,简称 pwq)的活动工作项计数(nr_active))
- [__queue_work 工作队列添加工作](#__queue_work 工作队列添加工作)
- [__queue_delayed_work 工作队列添加延时工作](#__queue_delayed_work 工作队列添加延时工作)
- [queue_delayed_work_on 延迟后对特定 CPU 上的工作进行排队](#queue_delayed_work_on 延迟后对特定 CPU 上的工作进行排队)
- [wq_worker_sleeping 处理一个工作线程(worker)进入睡眠状态时的相关逻辑](#wq_worker_sleeping 处理一个工作线程(worker)进入睡眠状态时的相关逻辑)
- [wq_cpu_intensive_thresh_init 初始化并动态调整wq_cpu_intensive_thresh_us](#wq_cpu_intensive_thresh_init 初始化并动态调整wq_cpu_intensive_thresh_us)
- [workqueue_init 使工作队列子系统完全上线](#workqueue_init 使工作队列子系统完全上线)
- [init_pod_type 根据特定的物理拓扑关系对系统中的所有CPU进行分组](#init_pod_type 根据特定的物理拓扑关系对系统中的所有CPU进行分组)
- [unbound_wq_update_pwq 专门用于处理非绑定(unbound)工作队列在CPU热插拔(hotplug)事件发生时的状态更新](#unbound_wq_update_pwq 专门用于处理非绑定(unbound)工作队列在CPU热插拔(hotplug)事件发生时的状态更新)
- [workqueue_init_topology 为非绑定工作队列初始化CPU "pod"(荚/分组)](#workqueue_init_topology 为非绑定工作队列初始化CPU "pod"(荚/分组))

kernel/workqueue.c 内核工作队列(Kernel Workqueues) 通用的内核后台任务处理框架
历史与背景
这项技术是为了解决什么特定 "问题而诞生的?
kernel/workqueue.c 实现了**工作队列(Workqueues)**机制,它的诞生是为了解决内核中一个极其普遍的需求:将一个函数的执行推迟(defer)到一个安全的进程上下文中去完成,特别是在中断处理程序中。
在内核中,代码的执行上下文非常重要,主要分为两种:
- 进程上下文(Process Context) :代码代表一个特定的进程(或内核线程)在运行。在这种上下文中,代码可以做任何可能导致**睡眠(blocking/sleep)**的操作,例如:获取互斥锁(
mutex)、分配大块内存(kmalloc(GFP_KERNEL))、与用户空间拷贝数据、执行磁盘I/O等。 - 中断上下文(Interrupt Context) :代码是作为对一个硬件中断的响应而运行的。中断处理程序必须尽快 完成,并且绝对不能睡眠。如果它睡眠了,可能会导致整个系统死锁或错过其他重要的硬件中断。
这就产生了一个经典问题:一个中断处理程序(例如,网卡驱动的中断处理函数在收到一个数据包后)可能需要执行一些复杂且耗时的操作,其中某些操作还可能会睡眠。它显然不能在中断上下文中直接完成这些工作。
工作队列就是为了解决这个问题而设计的通用"下半部"(bottom-half)处理机制 。它允许中断处理程序(或其他任何不能睡眠的代码)将一个需要睡眠或耗时较长的工作任务,"排队"给一个在安全的进程上下文中运行的内核线程去执行。
它的发展经历了哪些重要的里程碑或版本迭代?
工作队列是内核中演进最复杂的子系统之一,其核心驱动力是性能、并发性和易用性。
- 早期实现 (基于
keventd) :最初,内核有一个全局的事件守护进程keventd,所有模块都将工作排队给它。这非常简单,但也存在严重问题:所有工作都在一个单独的线程中串行执行,一个耗时的工作会阻塞所有其他工作;同时也无法很好地利用多核CPU的优势。 - 引入Per-CPU工作线程:为了提高并发性,工作队列模型演变为为每个CPU核心都创建一组工作者线程(worker threads)。这使得工作可以在多个CPU上并行执行,是一个巨大的进步。
- 并发管理工作队列 (Concurrency-Managed Workqueues, CMWQ) :这是由Tejun Heo主导的一次革命性的重构 。CMWQ在Linux 3.x内核中被引入,它彻底改变了工作队列的实现。CMWQ的目标是动态地、按需地管理工作者线程的数量,而不是为每个工作队列都创建固定的线程。它通过一个复杂的池化和调度算法,在提供高并发性的同时,极大地减少了系统中不必要的内核线程数量,从而降低了内存消耗和调度开销。这是当前工作队列的实现基础。
目前该技术的社区活跃度和主流应用情况如何?
工作队列是Linux内核中最基础、使用最广泛的异步执行机制,没有之一。
- 主流应用 :几乎内核的每一个子系统都在使用工作队列。
- 设备驱动:绝大多数驱动的中断处理下半部。
- 文件系统:执行延迟写入(writeback)、日志提交等。
- 网络:处理一些复杂的协议栈任务。
- RCU :
call_rcu()的回调函数通常就是通过一个工作队列来执行的。
核心原理与设计
它的核心工作原理是什么?
现代工作队列(CMWQ)的核心是一个动态的、per-CPU的工作者线程池(worker pool)。
-
核心数据结构:
struct work_struct:这是开发者使用的基本单元。它只包含一个指向要执行的函数(func)的指针和一个用于链接的数据域。struct workqueue_struct:代表一个工作队列。开发者可以创建自己的工作队列,以获得特定的属性(如高优先级、非绑定等)。worker_pool:这是CMWQ的核心。它管理着一组工作者线程和排队等待执行的工作。
-
工作流程:
- 初始化工作 (INIT_WORK) :开发者在自己的数据结构中嵌入一个
work_struct,并使用INIT_WORK宏将其与一个回调函数关联起来。 - 排队工作 (queue_work) :
a. 当一个事件发生时(例如,在中断处理程序中),代码调用queue_work(wq, work)。wq是要使用的workqueue_struct,work是要排队的工作。
b. 内核会找到与该工作队列关联的工作者池(worker pool) 。
c. 工作(work)会被添加到该池的待处理工作链表 中。
d. 排队操作会确保池中至少有一个空闲的(idle)工作者线程被唤醒。 - 执行工作 (由工作者线程完成) :
a. 被唤醒的工作者线程(是一个名为kworker/uX:Y的内核线程)开始运行。
b. 它会从自己所属的池的待处理链表中取出工作。
c. 然后,它直接调用该工作关联的回调函数(work->func)。
d. 因为工作者线程是在进程上下文 中运行的,所以这个回调函数可以安全地执行任何可能睡眠的操作。
e. 执行完一个工作后,线程会再次检查链表中是否还有更多工作。如果有,就继续执行;如果没有,它就会把自己标记为空闲,并可能在一段时间后进入睡眠。
- 初始化工作 (INIT_WORK) :开发者在自己的数据结构中嵌入一个
-
CMWQ的动态管理:
- CMWQ会持续监控每个池的负载情况。如果一个池中的工作大量堆积,而所有工作者线程都在忙碌(例如,都在睡眠等待I/O),CMWQ会自动创建新的工作者线程加入到这个池中,以提高并发度。
- 反之,如果一个池中的工作者线程长时间处于空闲状态,CMWQ会自动销毁它们,以回收资源。
它的主要优势体现在哪些方面?
- 上下文安全:完美地解决了在中断上下文中执行睡眠操作的问题。
- 简单易用 :为内核开发者提供了一个极其简单的API(
INIT_WORK,queue_work)来利用复杂的后台处理能力。 - 高效与并发:CMWQ的动态线程池管理提供了出色的性能和并发性,同时又避免了不必要的资源浪费。
- 通用性:是一个高度通用的框架,可被任何需要后台处理的内核子系统使用。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 延迟:从工作被排队到它真正开始执行,中间存在一定的调度延迟。因此,工作队列不适合用于有严格、低延迟时间要求的"硬实时"任务。
- -非时间确定性:一个工作的执行可能会被其他更高优先级的进程或同一池中的其他工作所延迟,其执行时间点是不保证的。
- 资源消耗:虽然CMWQ已经很高效,但内核线程本身仍然是内核中的一种资源,大量的后台工作仍然会消耗CPU时间和内存。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
工作队列是从原子上下文(如中断、持有自旋锁)中推迟可能睡眠或耗时较长的工作的首选解决方案。
- 中断处理下半部 :一个网卡驱动的中断处理程序(上半部)在收到数据包后,可能会快速地确认中断、从硬件DMA缓冲区中取出数据,然后调用
queue_work()来安排一个工作,由该工作在进程上下文中去处理复杂的协议栈逻辑。 - 延迟的I/O操作:一个文件系统在需要将脏数据写回磁盘时,不会在系统调用路径上同步等待I/O完成,而是创建一个工作来异步地执行写操作。
- 驱动中的复杂状态机:一个USB驱动在处理设备枚举或状态变更时,涉及多个步骤,其中一些可能需要睡眠。这些通常都是通过工作队列来驱动的。
是否有不推荐使用该技术的场景?为什么?
- 硬实时任务:如果一个任务必须在事件发生后的几百微秒内被处理,使用工作队列是不合适的,因为其调度延迟不可预测。这种场景可能需要使用线程化中断(threaded IRQs)。
- 极简、高性能的下半部 :如果下半部的工作非常简单,保证不会睡眠,且对延迟要求较高,那么使用开销更低的Tasklets 或Softirqs可能会更合适。然而,由于工作队列的易用性和安全性,现在内核社区更倾向于优先使用工作队列。
对比分析
请将其 与 其他相似技术 进行详细对比。
在Linux内核中,有多种"下半部"处理机制,它们在性能、复杂性和使用限制上各不相同。
| 特性 | 工作队列 (Workqueues) | Tasklets | Softirqs (软中断) |
| :--- | :--- | :--- | :--- | :--- |
| 执行上下文 | 进程上下文 | 软中断上下文 | 软中断上下文 |
| 是否可睡眠 | 是 | 否 | 否 |
| 并发模型 | 并发 。工作可以在不同CPU上的不同内核线程中并行执行。 | 串行 。同一个tasklet在同一时间只能在一个CPU上运行。 | 并发 。同一个软中断的处理函数可以在多个CPU上同时运行(需要处理函数自身是可重入的)。 |
| 性能/延迟 | 较低/延迟较高 。涉及线程调度。 | 较高/延迟较低 。 | 最高/延迟最低 。是性能最高的机制。 |
| 使用复杂度 | 简单 。API直观,无需担心睡眠问题。 | 中等 。API简单,但不能睡眠。 | 复杂 。需要静态编译时定义,且处理函数必须是高度优化的可重入代码。 |
| 适用场景 | 通用的、需要睡眠或耗时较长的下半部处理。 | 延迟敏感、不睡眠、相对简单的下半部。 | 性能极其敏感的核心子系统,如网络包接收和定时器处理。 |
include/linux/workqueue.h
INIT_WORK
c
/*
* initialize all of a work item in one go
*
* NOTE! No point in using "atomic_long_set()": using a direct
* assignment of the work data initializer allows the compiler
* to generate better code.
*/
#ifdef CONFIG_LOCKDEP
#else
#define __INIT_WORK_KEY(_work, _func, _onstack, _key) \
do { \
__init_work((_work), _onstack); \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
INIT_LIST_HEAD(&(_work)->entry); \
(_work)->func = (_func); \
} while (0)
#endif
#define __INIT_WORK(_work, _func, _onstack) \
do { \
static __maybe_unused struct lock_class_key __key; \
\
__INIT_WORK_KEY(_work, _func, _onstack, &__key); \
} while (0)
#define INIT_WORK(_work, _func) \
__INIT_WORK((_work), (_func), 0)
queue_delayed_work 延迟后对 workqueue 上的工作进行排队
c
/**
* queue_delayed_work - 延迟后对 workqueue 上的工作进行排队
* @wq:要使用的 workqueue
* @dwork:可延迟排队的工作
* @delay:排队前要等待的 jiffies 数
*
* 等效于 queue_delayed_work_on(),但尝试使用本地 CPU。
*/
static inline bool queue_delayed_work(struct workqueue_struct *wq,
struct delayed_work *dwork,
unsigned long delay)
{
return queue_delayed_work_on(WORK_CPU_UNBOUND, wq, dwork, delay);
}
kernel/workqueue_internal.h
c
/**
* current_wq_worker - 如果 %current 是 workqueue worker,则返回 struct worker
*/
static inline struct worker *current_wq_worker(void)
{
/* 判断当前上下文是否属于任务线程
* 检查当前线程的标志位是否包含 PF_WQ_WORKER。该标志位表示线程是工作队列的工作线程
*/
if (in_task() && (current->flags & PF_WQ_WORKER))
/* 通过 kthread_data(current) 获取该线程的私有数据。
对于工作队列的工作线程,这个私有数据通常是一个 struct worker,
它包含了工作线程的相关信息 */
return kthread_data(current);
return NULL;
}
kernel/workqueue.c
init_cpu_worker_pool 初始化 CPU 工作池
c
/**
* init_worker_pool - 初始化一个新的 zalloc worker_pool
* @pool:worker_pool初始化
*
* 初始化新 zalloc 的 @pool。 它还分配 @pool->attrs。
*
* 返回:成功时为 0,失败时为 -errno。 即使在失败时,@pool 中的所有字段都会被初始化,并且可以安全地在 @pool 上调用 put_unbound_pool() 来释放它。
*/
static int init_worker_pool(struct worker_pool *pool)
{
raw_spin_lock_init(&pool->lock);
pool->id = -1;
pool->cpu = -1;
pool->node = NUMA_NO_NODE;
/* 设置工作池为非关联状态,通常用于非绑定工作队列。 */
pool->flags |= POOL_DISASSOCIATED;
/* 初始化工作池的看门狗时间戳,用于监控工作池的状态 */
pool->watchdog_ts = jiffies;
INIT_LIST_HEAD(&pool->worklist);
INIT_LIST_HEAD(&pool->idle_list);
hash_init(pool->busy_hash);
/* 设置一个可延迟的定时器,用于处理空闲线程的超时。 */
timer_setup(&pool->idle_timer, idle_worker_timeout, TIMER_DEFERRABLE);
/* 初始化一个工作项,用于清理空闲线程 */
INIT_WORK(&pool->idle_cull_work, idle_cull_fn);
/* 设置一个定时器,用于在工作池需要帮助时发出 "Mayday" 信号 */
timer_setup(&pool->mayday_timer, pool_mayday_timeout, 0);
INIT_LIST_HEAD(&pool->workers); /* 初始化工作池的工作线程链表 */
ida_init(&pool->worker_ida); /* 初始化 ID 分配器,用于分配工作线程的唯一 ID */
INIT_HLIST_NODE(&pool->hash_node); /* 初始化工作池的哈希节点,用于将其插入全局哈希表 */
pool->refcnt = 1;
/* shouldn't fail above this point */
pool->attrs = alloc_workqueue_attrs();
if (!pool->attrs)
return -ENOMEM;
wqattrs_clear_for_pool(pool->attrs);
return 0;
}
static void __init init_cpu_worker_pool(struct worker_pool *pool, int cpu, int nice)
{
BUG_ON(init_worker_pool(pool));
pool->cpu = cpu;
/* 将目标 CPU 的掩码复制到工作池的属性中,表示该工作池只能在指定的 CPU 上运行 */
cpumask_copy(pool->attrs->cpumask, cpumask_of(cpu));
cpumask_copy(pool->attrs->__pod_cpumask, cpumask_of(cpu));
pool->attrs->nice = nice;
/* 设置工作池的严格亲和性(strict affinity),确保任务只能在绑定的 CPU 上运行 */
pool->attrs->affn_strict = true;
pool->node = cpu_to_node(cpu);
/* alloc pool ID */
mutex_lock(&wq_pool_mutex);
BUG_ON(worker_pool_assign_id(pool));
mutex_unlock(&wq_pool_mutex);
}
init_pwq 初始化 pwq
c
/* initialize newly allocated @pwq which is associated with @wq and @pool */
static void init_pwq(struct pool_workqueue *pwq, struct workqueue_struct *wq,
struct worker_pool *pool)
{
BUG_ON((unsigned long)pwq & ~WORK_STRUCT_PWQ_MASK);
memset(pwq, 0, sizeof(*pwq));
pwq->pool = pool;
pwq->wq = wq;
pwq->flush_color = -1;
pwq->refcnt = 1;
INIT_LIST_HEAD(&pwq->inactive_works);
INIT_LIST_HEAD(&pwq->pending_node);
INIT_LIST_HEAD(&pwq->pwqs_node);
INIT_LIST_HEAD(&pwq->mayday_node);
kthread_init_work(&pwq->release_work, pwq_release_workfn);
}
__wq_cpumask_show: 显示CPU掩码的核心实现函数
这个内部静态函数是所有只读 _show 函数的核心。它接收一个cpumask变量, 并将其格式化为人类可读的字符串, 然后放入用户的缓冲区。
c
/*
* 这是一个内部静态函数, 负责将一个给定的CPU掩码格式化并写入到缓冲区 buf 中.
*
* @dev: 指向 struct device 的指针. 在此上下文中, 它代表/sys/devices/virtual/workqueue设备.
* @attr: 指向 struct device_attribute 的指针, 描述了被访问的 sysfs 属性 (文件).
* @buf: 一个字符数组 (缓冲区), 用于存放格式化后的输出字符串, 这个字符串将展示给用户.
* @mask: 一个 cpumask_var_t 类型的变量, 这就是要被显示的目标CPU掩码.
* @return: 返回写入缓冲区的字节数, 或者一个负值的错误码.
*/
static ssize_t __wq_cpumask_show(struct device *dev,
struct device_attribute *attr, char *buf, cpumask_var_t mask)
{
/*
* 定义一个整型变量 written, 用于存储 scnprintf 函数的返回值 (即写入的字节数).
*/
int written;
/*
* 对全局的工作队列池互斥锁 wq_pool_mutex 进行加锁.
* 即使在单核系统上, 这也是必要的, 以防止在读取 mask 变量时, 发生内核抢占,
* 而另一个任务可能会修改这个 mask, 从而导致读取到不一致的数据.
* 锁确保了对 mask 变量的访问是原子的.
*/
mutex_lock(&wq_pool_mutex);
/*
* 调用 scnprintf 函数将 mask 的内容格式化成字符串, 并存入 buf.
* @ buf: 目标缓冲区.
* @ PAGE_SIZE: 缓冲区的最大长度, 防止溢出. PAGE_SIZE 是一个宏, 代表一个内存页的大小.
* @ "%*pb\n": 这是格式化字符串.
* - %pb 是一个内核专用的格式说明符, 用于打印位图(bitmap), 在这里特指CPU掩码.
* - * 表示宽度由参数动态指定.
* - \n 表示在末尾添加一个换行符.
* @ cpumask_pr_args(mask): 这是一个宏, 它为 %*pb 提供所需的参数 (掩码指针和长度).
* scnprintf 返回实际写入缓冲区的字符数.
*/
written = scnprintf(buf, PAGE_SIZE, "%*pb\n", cpumask_pr_args(mask));
/*
* 解锁互斥锁, 允许其他任务访问工作队列池的数据.
*/
mutex_unlock(&wq_pool_mutex);
/*
* 返回写入的字节数. 这是 sysfs _show 方法的标准返回值.
*/
return written;
}
cpumask_requested_show & DEVICE_ATTR_RO(cpumask_requested): 显示 "requested" CPU掩码
这个函数和宏组合在一起, 创建了一个名为 cpumask_requested 的只读 sysfs 文件。
c
/*
* cpumask_requested_show: sysfs 文件 'cpumask_requested' 的读操作处理函数.
* 当用户执行 'cat /sys/devices/virtual/workqueue/cpumask_requested' 时, 此函数会被调用.
*/
static ssize_t cpumask_requested_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
/*
* 此函数直接调用核心实现函数 __wq_cpumask_show,
* 并将全局变量 wq_requested_unbound_cpumask 作为要显示的掩码传递给它.
* 这个全局变量保存了在系统启动时所请求的非绑定工作队列CPU掩码.
*/
return __wq_cpumask_show(dev, attr, buf, wq_requested_unbound_cpumask);
}
/*
* DEVICE_ATTR_RO 是一个辅助宏, 用于快速定义一个只读 (Read-Only) 的设备属性.
* 它会创建一个名为 'dev_attr_cpumask_requested' 的 static struct device_attribute 实例,
* 并将其 .show 回调函数设置为 cpumask_requested_show.
* 这最终会在 sysfs 中创建一个名为 'cpumask_requested' 的只读文件.
*/
static DEVICE_ATTR_RO(cpumask_requested);
cpumask_isolated_show & DEVICE_ATTR_RO(cpumask_isolated): 显示 "isolated" CPU掩码
这个函数和宏组合在一起, 创建了一个名为 cpumask_isolated 的只读 sysfs 文件。
c
/*
* cpumask_isolated_show: sysfs 文件 'cpumask_isolated' 的读操作处理函数.
* 当用户执行 'cat /sys/devices/virtual/workqueue/cpumask_isolated' 时, 此函数会被调用.
*/
static ssize_t cpumask_isolated_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
/*
* 此函数直接调用核心实现函数 __wq_cpumask_show,
* 并将全局变量 wq_isolated_cpumask 作为要显示的掩码传递给它.
* 这个全局变量保存了通过 'isolcpus' 内核启动参数设置的被隔离的CPU掩码.
* 在单核系统上, 无法隔离CPU, 所以这个掩码通常是空的.
*/
return __wq_cpumask_show(dev, attr, buf, wq_isolated_cpumask);
}
/*
* 使用 DEVICE_ATTR_RO 宏定义一个名为 'cpumask_isolated' 的只读 sysfs 文件.
* 其 .show 回调函数为 cpumask_isolated_show.
*/
static DEVICE_ATTR_RO(cpumask_isolated);
cpumask_show, cpumask_store & DEVICE_ATTR_RW(cpumask): 显示和修改当前的CPU掩码
这组函数和宏创建了一个名为 cpumask 的可读写 sysfs 文件, 这是用户最常交互的文件。
c
/*
* cpumask_show: sysfs 文件 'cpumask' 的读操作处理函数.
* 当用户执行 'cat /sys/devices/virtual/workqueue/cpumask' 时, 此函数会被调用.
*/
static ssize_t cpumask_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
/*
* 此函数调用核心实现函数 __wq_cpumask_show,
* 并将全局变量 wq_unbound_cpumask作为要显示的掩码传递给它.
* 这个全局变量代表了当前非绑定工作队列实际生效的CPU掩码.
*/
return __wq_cpumask_show(dev, attr, buf, wq_unbound_cpumask);
}
/*
* cpumask_store: sysfs 文件 'cpumask' 的写操作处理函数.
* 当用户执行 'echo "1" > /sys/devices/virtual/workqueue/cpumask' 时, 此函数会被调用.
*
* @dev: 指向 struct device 的指针.
* @attr: 指向 struct device_attribute 的指针.
* @buf: 指向用户写入内容的缓冲区的指针.
* @count: 写入内容的字节数.
* @return: 返回已处理的字节数表示成功, 或一个负值的错误码.
*/
static ssize_t cpumask_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
/*
* 定义一个 cpumask_var_t 类型的变量 cpumask. cpumask_var_t 是一种可以动态分配的cpumask.
*/
cpumask_var_t cpumask;
/*
* 定义一个整型变量 ret, 用于存储函数调用的返回值 (错误码).
*/
int ret;
/*
* 尝试为 cpumask 变量分配内存.
* @ &cpumask: 指向要被分配内存的变量的指针.
* @ GFP_KERNEL: 分配内存的标志, 表示此次分配可以在需要时睡眠(阻塞).
* 如果内存分配失败, zalloc_cpumask_var 返回 false.
*/
if (!zalloc_cpumask_var(&cpumask, GFP_KERNEL))
/*
* 如果分配失败, 返回 -ENOMEM (内存不足) 错误码.
*/
return -ENOMEM;
/*
* 调用 cpumask_parse 函数, 将用户写入的字符串(buf)解析成内核的cpumask格式.
* 例如, 用户写入 "1", 该函数会设置 cpumask 的第0位.
* 如果解析成功, 返回0.
*/
ret = cpumask_parse(buf, cpumask);
/*
* 如果前面的解析成功了 (ret == 0).
*/
if (!ret)
/*
* 调用 workqueue_set_unbound_cpumask 函数, 将新解析出的 cpumask 应用到系统中.
* 这个函数会更新全局的 wq_unbound_cpumask, 并调整工作队列线程的亲和性.
* 该函数的返回值会赋给 ret.
*/
ret = workqueue_set_unbound_cpumask(cpumask);
/*
* 释放之前为 cpumask 变量动态分配的内存, 防止内存泄漏.
*/
free_cpumask_var(cpumask);
/*
* 这是 sysfs _store 方法的标准返回逻辑.
* 如果 ret 不为0 (表示中间有错误发生), 则返回错误码 ret.
* 如果 ret 为0 (表示操作成功), 则返回已处理的字节数 count.
*/
return ret ? ret : count;
}
/*
* DEVICE_ATTR_RW 是一个辅助宏, 用于快速定义一个可读写 (Read-Write) 的设备属性.
* 它会创建一个名为 'dev_attr_cpumask' 的实例,
* 并将其 .show 回调设置为 cpumask_show, .store 回调设置为 cpumask_store.
* 这最终会在 sysfs 中创建一个名为 'cpumask' 的可读写文件.
*/
static DEVICE_ATTR_RW(cpumask);
wq_sysfs_cpumask_attrs & ATTRIBUTE_GROUPS: 定义属性组
这部分代码将前面定义的所有属性文件打包成一个组, 以便一次性注册。
c
/*
* 定义一个静态的 struct attribute 指针数组.
* 这个数组包含了所有我们希望在 sysfs 目录下创建的文件的属性定义.
*/
static struct attribute *wq_sysfs_cpumask_attrs[] = {
/*
* &dev_attr_cpumask.attr: 'cpumask' 文件的属性结构体地址.
*/
&dev_attr_cpumask.attr,
/*
* &dev_attr_cpumask_requested.attr: 'cpumask_requested' 文件的属性结构体地址.
*/
&dev_attr_cpumask_requested.attr,
/*
* &dev_attr_cpumask_isolated.attr: 'cpumask_isolated' 文件的属性结构体地址.
*/
&dev_attr_cpumask_isolated.attr,
/*
* 数组必须以 NULL 结尾, 作为结束的标记.
*/
NULL,
};
/*
* ATTRIBUTE_GROUPS 是一个宏, 用于将属性数组包装成一个或多个属性组.
* 这里我们只定义了一个组, 名为 wq_sysfs_cpumask_groups.
* 这个组包含了 wq_sysfs_cpumask_attrs 数组中定义的所有属性.
*/
ATTRIBUTE_GROUPS(wq_sysfs_cpumask);
wq_sysfs_init & core_initcall: 注册sysfs接口
这是最后的初始化函数, 它在内核启动时被调用, 用来完成 sysfs 目录和文件的创建。
c
/*
* wq_sysfs_init: 工作队列 sysfs 接口的初始化函数.
* 这是一个静态函数, 标记为 __init, 表示它仅在内核初始化期间使用,
* 在初始化完成后, 其占用的内存可能会被释放.
*/
static int __init wq_sysfs_init(void)
{
/*
* 调用 subsys_virtual_register 函数来注册一个新的虚拟子系统.
* @ &wq_subsys: 这是要注册的工作队列子系统.
* @ wq_sysfs_cpumask_groups: 这是要在这个子系统下创建的属性组 (即文件集合).
* 这个函数调用会在 /sys/devices/virtual/ 目录下创建一个名为 'workqueue' 的目录,
* 并在该目录中创建 'cpumask', 'cpumask_requested', 'cpumask_isolated' 这三个文件.
* 函数返回0表示成功, 负值表示错误.
*/
return subsys_virtual_register(&wq_subsys, wq_sysfs_cpumask_groups);
}
/*
* core_initcall 是一个宏, 它将 wq_sysfs_init 函数注册为一个核心初始化函数.
* 这意味着 wq_sysfs_init 会在内核启动过程中的一个较早阶段被调用,
* 确保这些 sysfs 接口在系统完全启动前就已经可用.
*/
core_initcall(wq_sysfs_init);
format_worker_id 格式化工作线程 ID
c
static int format_worker_id(char *buf, size_t size, struct worker *worker,
struct worker_pool *pool)
{
if (worker->rescue_wq)
return scnprintf(buf, size, "kworker/R-%s",
worker->rescue_wq->name);
if (pool) {
if (pool->cpu >= 0)
return scnprintf(buf, size, "kworker/%d:%d%s",
pool->cpu, worker->id,
pool->attrs->nice < 0 ? "H" : "");
else
return scnprintf(buf, size, "kworker/u%d:%d",
pool->id, worker->id);
} else {
return scnprintf(buf, size, "kworker/dying");
}
}
worker_enter_idle 进入空闲状态
c
/**
* worker_enter_idle - 进入空闲状态
* @worker: 正在进入空闲状态的工作者 *
* @worker 正在进入空闲状态。如果有必要,更新统计信息和空闲计时器。 *
* 锁定: * raw_spin_lock_irq(pool->lock).
*/
static void worker_enter_idle(struct worker *worker)
{
struct worker_pool *pool = worker->pool;
if (WARN_ON_ONCE(worker->flags & WORKER_IDLE) ||
WARN_ON_ONCE(!list_empty(&worker->entry) &&
(worker->hentry.next || worker->hentry.pprev)))
return;
/* can't use worker_set_flags(), also called from create_worker() */
worker->flags |= WORKER_IDLE;
pool->nr_idle++;
worker->last_active = jiffies;
/* 空闲列表采用后进先出(LIFO)的方式管理,以便优先复用最近进入空闲状态的线程*/
list_add(&worker->entry, &pool->idle_list);
/* 检查线程池中的 worker 是否超过需求 检查空闲计时器是否已激活*/
if (too_many_workers(pool) && !timer_pending(&pool->idle_timer))
mod_timer(&pool->idle_timer, jiffies + IDLE_WORKER_TIMEOUT); /* 通过 mod_timer 设置空闲计时器 */
/* Sanity check nr_running. */
WARN_ON_ONCE(pool->nr_workers == pool->nr_idle && pool->nr_running);
}
set_pf_worker 设置当前线程为工作队列工作线程
c
static void set_pf_worker(bool val)
{
mutex_lock(&wq_pool_attach_mutex);
if (val)
current->flags |= PF_WQ_WORKER;
else
current->flags &= ~PF_WQ_WORKER;
mutex_unlock(&wq_pool_attach_mutex);
}
worker_leave_idle - 离开空闲状态
c
/**
* worker_leave_idle - 离开空闲状态
* @worker: 处于空闲状态的工作者
*
* @worker 正在离开空闲状态。更新统计信息。
*
* LOCKING:
* raw_spin_lock_irq(pool->lock).
*/
static void worker_leave_idle(struct worker *worker)
{
struct worker_pool *pool = worker->pool;
if (WARN_ON_ONCE(!(worker->flags & WORKER_IDLE)))
return;
worker_clr_flags(worker, WORKER_IDLE);
pool->nr_idle--;
list_del_init(&worker->entry);
}
need_more_worker 判断是否需要唤醒或创建更多的工作线程(worker)来处理工作队列中的任务
c
/*
* 需要唤醒一个工作者吗?只能从当前未运行的工作者调用。
* 请注意,由于无绑定工作者从不对 nr_running 贡献,因此只要工作列表不为空,此函数对于无绑定池将始终返回 %true。
*/
static bool need_more_worker(struct worker_pool *pool)
{
/* 如果任务队列为空,则不需要更多的工作线程,
当前正在运行的工作线程数量。如果没有正在运行的线程(pool->nr_running == 0),
则返回 true,表示需要唤醒或创建更多的工作线程。*/
return !list_empty(&pool->worklist) && !pool->nr_running;
}
may_start_working 判断是否可以开始工作
c
/* 函数直接返回 pool->nr_idle 的值。nr_idle 是线程池中当前处于空闲状态的工作线程数量。
如果 nr_idle > 0,表示线程池中有空闲线程,函数返回 true,允许线程开始工作。
如果 nr_idle == 0,表示线程池中没有空闲线程,函数返回 false,不允许线程开始工作。 */
/* Can I start working? Called from busy but !running workers. */
static bool may_start_working(struct worker_pool *pool)
{
return pool->nr_idle;
}
wake_up_process 唤醒特定进程
c
/**
* wake_up_process - 唤醒特定进程
* @p:要唤醒的过程。
*
* 尝试唤醒指定的进程并将其移动到 runnable 的集合中
*过程。
*
* 返回:如果进程已唤醒,则为 1,如果进程已在运行,则为 0。
*
* 此函数在访问 task 状态之前执行一个完整的内存屏障。
*/
int wake_up_process(struct task_struct *p)
{
return try_to_wake_up(p, TASK_NORMAL, 0);
}
EXPORT_SYMBOL(wake_up_process);
worker_thread - 工作者线程函数
- worker_thread是所有工作队列的工作者内核线程(kworker)的主体函数。当一个kworker被创建时,它的入口点就被设置为这个函数。
它的核心作用是:在一个无限循环中,不断地从其所属的工作者池(worker_pool)的公共工作链表(pool->worklist)中取出待处理的工作项(work_struct),并执行它。当没有工作时,它会进入睡眠,直到被唤醒。
c
/**
* worker_thread - 工作者线程函数
* @__worker: 指向worker自身的指针
*
* 工作者线程函数。所有的工作者都属于一个工作者池(worker_pool) ------
* 要么是一个per-cpu的池,要么是一个动态的非绑定池。这些工作者处理所有
* 的工作项,无论它们具体的目标工作队列是什么。唯一的例外是那些属于
* 带有救援线程(rescuer)的工作队列的工作项,这将在rescuer_thread()中解释。
*
* 返回值: 0
*/
static int worker_thread(void *__worker)
{
/* worker: 指向当前工作者自身的worker结构体。*/
struct worker *worker = __worker;
/* pool: 指向该工作者所属的工作者池。*/
struct worker_pool *pool = worker->pool;
/* 告诉调度器,这是一个工作队列的工作者线程。*/
set_pf_worker(true);
woke_up: /* 这是线程被唤醒后的主循环入口点。*/
/* 获取保护整个工作者池状态的自旋锁,并禁用中断。*/
raw_spin_lock_irq(&pool->lock);
/* 我应该死掉吗?*/
/* 检查WORKER_DIE标志,如果被设置,则线程需要退出。*/
if (unlikely(worker->flags & WORKER_DIE)) {
/* 释放池锁。*/
raw_spin_unlock_irq(&pool->lock);
/* 清除工作者线程身份标志。*/
set_pf_worker(false);
/*
* 工作者已死,且PF_WQ_WORKER已被清除,不应再访问worker->pool,
* 为以防万一,将其重置为NULL。
*/
worker->pool = NULL;
/* 将工作者的ID归还给ID分配器。*/
ida_free(&pool->worker_ida, worker->id);
/* 线程函数返回,线程生命周期结束。*/
return 0;
}
/* 调用此函数,将工作者从池的空闲链表中移除,并更新空闲计数。*/
worker_leave_idle(worker);
recheck: /* 这是一个内部检查点,在状态改变后可能需要重新检查。*/
/* 不再需要更多的工作者了吗?*/
if (!need_more_worker(pool))
/* 如果池中活跃的工作者已足够,则跳转到sleep准备睡眠。*/
goto sleep;
/* 我们需要进行管理吗?*/
/* 如果当前不允许开始工作,并且manage_workers()返回true(表示它进行了一些管理操作),
* 则需要重新检查所有条件。*/
if (unlikely(!may_start_working(pool)) && manage_workers(worker))
goto recheck;
/*
* ->scheduled链表只在一个工作者准备处理或正在处理工作时才能被填充。
* 确保在我睡觉的时候,没有人动过它。
*/
/* 这是一个健壮性检查,确保工作者自己的"已调度"链表是空的。*/
WARN_ON_ONCE(!list_empty(&worker->scheduled));
/*
* 完成准备(PREP)阶段。我们保证了至少有一个空闲工作者,或者
* 已经有别人承担了管理者的角色。从这里开始,@worker如果适用的话,
* 会开始参与并发管理,并且在被重新绑定后,并发管理会恢复。
* 详见rebind_workers()。
*/
/* 清除WORKER_PREP和WORKER_REBOUND标志,表示准备工作完成。*/
worker_clr_flags(worker, WORKER_PREP | WORKER_REBOUND);
/*
* 进入工作处理循环。
*/
do {
/* 从池的公共工作链表(pool->worklist)的头部,获取第一个工作项。*/
struct work_struct *work =
list_first_entry(&pool->worklist,
struct work_struct, entry);
/*
* 调用assign_work将工作项分配给当前工作者,如果成功,
* 则调用process_scheduled_works来执行该工作项的回调函数。
*/
if (assign_work(work, worker, NULL))
process_scheduled_works(worker);
} while (keep_working(pool)); /* keep_working检查池中是否还有工作且我们应该继续。*/
/* 工作处理循环结束,重新将自己标记为准备状态。*/
worker_set_flags(worker, WORKER_PREP);
sleep:
/*
* pool->lock被持有,并且没有工作要处理,也不需要进行管理,就睡眠。
* 工作者只在持有pool->lock时或从本地CPU被唤醒,所以在释放pool->lock
* 之前设置当前状态,足以防止丢失任何事件。
*/
/* 调用此函数,将工作者加入池的空闲链表,并更新空闲计数。*/
worker_enter_idle(worker);
/* 将任务的调度器状态设置为一个特殊的、不可中断的睡眠状态。*/
__set_current_state(TASK_IDLE);
/* 先释放池锁。*/
raw_spin_unlock_irq(&pool->lock);
/* 然后调用调度器,主动放弃CPU。*/
schedule();
/* 当被唤醒后,直接跳转到主循环的入口点,开始新一轮的检查和工作。*/
goto woke_up;
}
create_worker 创建一个新的工作队列工作线程
c
/** * create_worker - 创建一个新的工作队列工作线程
* @pool: 新工作线程将属于的池 *
* 创建并启动一个新的工作线程,该线程附加到 @pool。 *
* 上下文: * 可能会休眠。执行 GFP_KERNEL 分配。 *
* 返回: * 指向新创建的工作线程的指针。
*/
static struct worker *create_worker(struct worker_pool *pool)
{
struct worker *worker;
int id;
/* 需要ID来确定kthread名称*/
id = ida_alloc(&pool->worker_ida, GFP_KERNEL);
if (id < 0) {
pr_err_once("workqueue: Failed to allocate a worker ID: %pe\n",
ERR_PTR(id));
return NULL;
}
worker = alloc_worker(pool->node);
if (!worker) {
pr_err_once("workqueue: Failed to allocate a worker\n");
goto fail;
}
worker->id = id;
if (!(pool->flags & POOL_BH)) {
char id_buf[WORKER_ID_LEN];
/* 使用 format_worker_id 函数生成线程名,并存储到 id_buf 中 */
format_worker_id(id_buf, sizeof(id_buf), worker, pool);
/* 调用 kthread_create_on_node 创建一个内核线程,线程的入口函数为 worker_thread,参数为 worker */
worker->task = kthread_create_on_node(worker_thread, worker,
pool->node, "%s", id_buf);
if (IS_ERR(worker->task)) {
if (PTR_ERR(worker->task) == -EINTR) {
pr_err("workqueue: Interrupted when creating a worker thread \"%s\"\n",
id_buf);
} else {
pr_err_once("workqueue: Failed to create a worker thread: %pe",
worker->task);
}
goto fail;
}
set_user_nice(worker->task, pool->attrs->nice);/* 设置线程优先级 */
kthread_bind_mask(worker->task, pool_allowed_cpus(pool)); /* 绑定 CPU */
}
/* successful, attach the worker to the pool */
worker_attach_to_pool(worker, pool); /* 新创建的 worker 附加到指定的工作池中 */
/* start the newly created worker */
raw_spin_lock_irq(&pool->lock);
worker->pool->nr_workers++; /* 更新 worker 数量 */
worker_enter_idle(worker); /* 设置 worker 状态为 idle */
/*
*@worker 正在 kthread() 中等待完成,如果不尽快被唤醒,将触发挂起检查。由于如果 @pool 为空,kick_pool() 是无操作,因此需要显式唤醒它。
*/
if (worker->task)
wake_up_process(worker->task); /* 如果 worker->task 不为空,调用 wake_up_process 唤醒线程 */
raw_spin_unlock_irq(&pool->lock);
return worker;
fail:
ida_free(&pool->worker_ida, id);
kfree(worker);
return NULL;
}
maybe_create_worker 可能创建一个新的工作线程
c
- __releases(&pool->lock)
这个标记表示函数在执行过程中会释放 pool->lock 锁。它告诉静态分析工具,在进入函数时,调用者必须已经持有 pool->lock,并且函数在某些操作中释放该锁。
- __acquires(&pool->lock)
这个标记表示函数在执行过程中会重新获取 pool->lock 锁。它告诉静态分析工具,函数在退出时会确保重新持有该锁。
- 作用
这些标记的主要作用是帮助静态分析工具(如 sparse)验证锁的使用是否正确。例如:
确保在调用 maybe_create_worker 之前,调用者已经持有 pool->lock。
确保函数在退出时重新获取了 pool->lock,以避免锁的状态不一致。
```c
/**
* maybe_create_worker - 如有必要,创建一个新工作者
* @pool:池以创建一个新的工作者
*
* 如果需要,为@pool创建一个新工人。确保@pool在从此函数返回时至少有一个空闲工人。如果创建新工人所需的时间超过MAYDAY_INTERVAL,可能会向所有调度在@pool上的救援者发送求助信号,以解决可能的分配死锁问题。
*
* 在返回时,need_to_create_worker() 保证为假,而 may_start_working() 为真。
*
* LOCKING:
* raw_spin_lock_irq(pool->lock) 可能会被多次释放和重新获取。执行 GFP_KERNEL 分配。仅从管理器调用。
*/
static void maybe_create_worker(struct worker_pool *pool)
__releases(&pool->lock)
__acquires(&pool->lock)
{
restart:
raw_spin_unlock_irq(&pool->lock);
/* 在尝试创建 worker 的过程中,函数启动了一个定时器(mayday_timer),
如果在 MAYDAY_INITIAL_TIMEOUT 时间内无法创建新的 worker,会触发 "mayday" 信号,
通知其他救援线程以解决可能的分配死锁 */
//修改池->mayday_timer的超时值
mod_timer(&pool->mayday_timer, jiffies + MAYDAY_INITIAL_TIMEOUT);
while (true) {
if (create_worker(pool) || !need_to_create_worker(pool))
break;
/* 如果线程创建失败,函数会进入一个循环,等待一段时间(CREATE_COOLDOWN),然后再次检查是否需要创建新的 worker。
这种机制避免了频繁尝试创建线程导致的资源浪费。 */
schedule_timeout_interruptible(CREATE_COOLDOWN);
if (!need_to_create_worker(pool))
break;
}
timer_delete_sync(&pool->mayday_timer);//删除定时器
raw_spin_lock_irq(&pool->lock);
/*
* 即使在新工作者刚刚成功创建后,这也是必要的,因为 @pool->lock 已经被释放,而新的工作者可能已经变得忙碌。
*/
if (need_to_create_worker(pool))
goto restart;
}
manage_workers 管理工作池
c
/**
* manage_workers - 管理工作池
* @worker: self
*
* 假设经理角色并管理属于@worker的工作人员池。在任何时候,每个池中只能有零个或一个经理。此排除由该功能自动处理。
*
* 调用者可以安全地在假返回上开始处理工作。对于真返回,保证need_to_create_worker()为假,may_start_working()为真。
*
* CONTEXT:
* raw_spin_lock_irq(pool->lock) 可能会被多次释放和重新获取。执行 GFP_KERNEL 分配。
*
* Return:
* 如果池子不需要管理,并且调用者可以安全地开始处理工作,% 如果管理功能被执行,并且调用者在调用函数之前验证的条件可能不再成立,返回true。
*/
static bool manage_workers(struct worker *worker)
{
struct worker_pool *pool = worker->pool;
/* 如果线程池的标志位 POOL_MANAGER_ACTIVE 已经被设置,说明当前线程池已经有一个管理者在执行管理操作。此时函数直接返回 false,表示无需进一步管理,调用者可以安全地开始处理任务 */
if (pool->flags & POOL_MANAGER_ACTIVE)
return false;
/* 如果没有其他管理者,当前线程将自身标记为管理者:
设置 POOL_MANAGER_ACTIVE 标志位。
将 pool->manager 指向当前线程 */
pool->flags |= POOL_MANAGER_ACTIVE;
pool->manager = worker;
/* 调用 maybe_create_worker(pool),根据线程池的状态决定是否需要创建新的工作线程。这是管理操作的核心部分,用于动态调整线程池的大小以适应任务负载*/
maybe_create_worker(pool);
/* 在完成管理操作后,清除管理者标志位并将 pool->manager 设置为 NULL,表示当前线程不再是管理者 */
pool->manager = NULL;
pool->flags &= ~POOL_MANAGER_ACTIVE;
/* 唤醒可能等待管理者完成的其他线程 */
rcuwait_wake_up(&manager_wait);
return true;
}
find_worker_executing_work 查找正在执行工作项的工作线程
c
/**
* find_worker_executing_work - 寻找正在执行工作的工人
* @pool: 兴趣池
* @work:努力寻找工人
*
* 通过搜索 @pool->busy_hash(该哈希表的键是 @work 的地址),找到正在 @pool 中执行 @work 的工作者。为了使工作者匹配,它的当前执行必须与 @work 的地址和它的工作函数相匹配。这是为了避免通过在仍被执行时回收的工作项,导致无关的工作执行之间产生不必要的依赖关系。
*
* 这有点棘手。一个工作项在执行开始后可能会被释放,并且没有任何东西阻止被释放的区域被回收用于另一个工作项。如果在原始执行完成之前,同一个工作项的地址被重复使用,工作队列将会将回收的工作项识别为当前正在执行,并使其等待直到当前执行完成,从而引入了一个不必要的依赖。
*
* 此功能检查作业项地址和作业函数,以避免误报。 请注意,这并不完整,因为有人可能构造一个工作函数,通过重复使用的工作项引入对自身的依赖。 好吧,如果有人想要这样自毁,那么我们能做的也有限,如果确实发生这样的死锁,找出罪魁祸首的工作函数应该很容易。
*
* CONTEXT:
* raw_spin_lock_irq(pool->lock).
*
* Return:
*指向正在执行@work的工作者的指针,如果找到则返回,若未找到则返回%NULL.
*/
static struct worker *find_worker_executing_work(struct worker_pool *pool,
struct work_struct *work)
{
struct worker *worker;
hash_for_each_possible(pool->busy_hash, worker, hentry,
(unsigned long)work)
if (worker->current_work == work &&
worker->current_func == work->func)
return worker;
return NULL;
}
move_linked_works 移动关联的工作项
c
/**
* move_linked_works - 将链接的作品移动到列表中
* @work: start of series of works to be scheduled
* @head: target list to append @work to
* @nextp: out parameter for nested worklist walking
*
* 调度链接的工作从@work开始到@head。要调度的工作系列从@work开始,包括任何在其前导中设置了WORK_STRUCT_LINKED的连续工作。有关@nextp的详细信息,请参见assign_work()。
*
* CONTEXT:
* raw_spin_lock_irq(pool->lock).
*/
static void move_linked_works(struct work_struct *work, struct list_head *head,
struct work_struct **nextp)
{
struct work_struct *n;
/*
* 链接工作列表将始终在列表末尾之前结束,使用 NULL 作为列表头部。
*/
list_for_each_entry_safe_from(work, n, NULL, entry) {
list_move_tail(&work->entry, head);
/* 检查当前工作项的 WORK_STRUCT_LINKED 标志。如果标志未设置,说明当前工作项是链表的末尾,停止遍历 */
if (!(*work_data_bits(work) & WORK_STRUCT_LINKED))
break;
}
/*
* 如果我们已经在安全列表遍历中,并且将多个作品移动到计划队列中,则需要更新下一个位置。
*/
if (nextp)
*nextp = n;
}
assign_work 分配工作项
c
/**
* assign_work - 将工作项及其关联的工作项分配给某个工作人员
* @work: 工作分配
* @worker: 分配给工人
* @nextp: 用于嵌套工作列表遍历的输出参数
*
* 将 @work 和其关联的工作项分配给 @worker。如果 @work 已经由同一个池中的其他工作者执行,则将其转交给他们。*
* 如果 @nextp 为 NULL,它将更新为指向最后调度工作下一个工作的指针。这允许 assign_work() 嵌套在 list_for_each_entry_safe() 里面。
*
* 如果 @work 成功分配给 @worker,则返回 %true。如果 @work 已被转交给另一个正在执行它的工作者,则返回 %false。
*/
static bool assign_work(struct work_struct *work, struct worker *worker,
struct work_struct **nextp)
{
struct worker_pool *pool = worker->pool;
struct worker *collision;
lockdep_assert_held(&pool->lock);
/*
* 并发执行检查: 函数首先调用 find_worker_executing_work 检查是否有其他 worker 正在执行该工作项。如果发现冲突(即 collision 不为 NULL),
* 说明该工作项已经在同一个线程池中被其他 worker 执行。此时,函数会将工作项及其关联的工作项移动到冲突 worker 的调度队列中,并返回 false,表示工作被推迟到当前正在执行它的 worker
*/
collision = find_worker_executing_work(pool, work);
if (unlikely(collision)) {
move_linked_works(work, &collision->scheduled, nextp);
return false;
}
/* 将工作项及其关联的工作项移动到目标 worker 的调度队列 */
move_linked_works(work, &worker->scheduled, nextp);
return true;
}
keep_working 判断是否需要继续工作
c
/* Do I need to keep working? Called from currently running workers. */
static bool keep_working(struct worker_pool *pool)
{
/* 如果没有正在运行的线程(pool->nr_running == 0) */
return !list_empty(&pool->worklist) && (pool->nr_running <= 1);
}
get_pwq 获取指定pool_workqueue的额外引用
c
/**
* get_pwq - 获取指定pool_workqueue的额外引用
* @pwq:pool_workqueue获取
*
* 在 @pwq 上获取额外的参考资料。 调用方应保证@pwq具有正 refcnt 并持有匹配的 pool->lock。
*/
static void get_pwq(struct pool_workqueue *pwq)
{
lockdep_assert_held(&pwq->pool->lock);
WARN_ON_ONCE(pwq->refcnt <= 0);
pwq->refcnt++;
}
put_pwq 取消对 @pwq 的引用。如果它的引用计数降为零,安排销毁它。调用者应该持有匹配的 pool->lock。
c
/**
* put_pwq - 放一个 pool_workqueue 引用
* @pwq: pool_workqueue to put
*
* 取消对 @pwq 的引用。如果它的引用计数降为零,安排销毁它。调用者应该持有匹配的 pool->lock。
*/
static void put_pwq(struct pool_workqueue *pwq)
{
lockdep_assert_held(&pwq->pool->lock);
if (likely(--pwq->refcnt))
return;
/*
* @pwq不能在pool->lock下释放,应该跳转到专用的kthread_worker以避免A-A死锁。
*/
kthread_queue_work(pwq_release_worker, &pwq->release_work);
}
get_work_color 从 work_data 中提取工作项的颜色(color)
c
static int get_work_color(unsigned long work_data)
{
return (work_data >> WORK_STRUCT_COLOR_SHIFT) &
((1 << WORK_STRUCT_COLOR_BITS) - 1);
}
pwq_dec_nr_in_flight 减少 pwq 的 nr_in_flight
c
/**
* pwq_dec_nr_in_flight - decrement pwq's nr_in_flight
* @pwq: pwq of interest
* @work_data: work_data of work which left the queue
*
* 减少 pool_workqueue(简称 pwq)中某个颜色的正在执行的工作项计数(nr_in_flight),并在必要时触发与工作队列刷新(flush)相关的逻辑。
管理工作项的执行状态: 每个 pwq 维护一个 nr_in_flight 数组,用于记录不同颜色的工作项的执行数量。pwq_dec_nr_in_flight 减少指定颜色的计数,表示某个工作项已经完成或被移除。
支持工作队列的刷新机制: 在工作队列中,刷新(flush)操作需要确保所有正在执行的工作项都完成。pwq_dec_nr_in_flight 会检查当前颜色的工作项是否已经全部完成。如果完成,它会更新 pwq->flush_color 并通知等待的刷新操作。
激活挂起的工作项: 如果 pwq 中有挂起的工作项(inactive works),pwq_dec_nr_in_flight 会尝试激活它们(即将它们从 inactive_works 移动到 worklist 中
*
* CONTEXT:
* raw_spin_lock_irq(pool->lock).
*/
static void pwq_dec_nr_in_flight(struct pool_workqueue *pwq, unsigned long work_data)
{
int color = get_work_color(work_data);
if (!(work_data & WORK_STRUCT_INACTIVE))
pwq_dec_nr_active(pwq);
/* 减少指定颜色的工作项计数 */
pwq->nr_in_flight[color]--;
/* is flush in progress and are we at the flushing tip? */
if (likely(pwq->flush_color != color))
goto out_put;
/* are there still in-flight works? */
if (pwq->nr_in_flight[color])
goto out_put;
/* this pwq is done, clear flush_color */
pwq->flush_color = -1;
/*
* 如果这是最后一个pwq,请唤醒第一个冲水器。它会处理剩下的事情。
*/
if (atomic_dec_and_test(&pwq->wq->nr_pwqs_to_flush))
complete(&pwq->wq->first_flusher->done);
out_put:
put_pwq(pwq);
}
first_idle_worker 返回第一个空闲的 worker
c
/* 返回第一个空闲的 worker。 在持有 pool->lock 的情况下被调用. */
static struct worker *first_idle_worker(struct worker_pool *pool)
{
if (unlikely(list_empty(&pool->idle_list)))
return NULL;
return list_first_entry(&pool->idle_list, struct worker, entry);
}
need_more_worker 判断工作池(worker_pool)是否需要唤醒更多的工作线程(worker)
c
/*
* 需要唤醒 worker? 从除当前正在运行的 worker 之外的任何位置调用。
*
* 请注意,由于 unbound worker 永远不会对 nr_running 做出贡献,因此只要 worklist 不为空,此函数将始终为未绑定的池返回 %true。
*/
static bool need_more_worker(struct worker_pool *pool)
{
/* 检查工作池中是否有挂起的工作项需要处理。
pool->nr_running 表示当前正在运行的工作线程数量 */
return !list_empty(&pool->worklist) && !pool->nr_running;
}
kick_bh_pool 用于唤醒软中断(Bottom Half,简称 BH)工作池中的工作线程
c
static void kick_bh_pool(struct worker_pool *pool)
{
/* 如果优先级为高(HIGHPRI_NICE_LEVEL),触发高优先级软中断(HI_SOFTIRQ) */
if (pool->attrs->nice == HIGHPRI_NICE_LEVEL)
raise_softirq_irqoff(HI_SOFTIRQ);
else
/* 触发普通任务软中断 */
raise_softirq_irqoff(TASKLET_SOFTIRQ);
}
kick_pool 如有必要,唤醒空闲的 worker
c
/**
* kick_pool - 如有必要,唤醒空闲的 worker
* @pool:台球踢球
*
* @pool 可能有待处理的工作项。如有必要,唤醒 worker。返回 worker 是否已唤醒。
*/
static bool kick_pool(struct worker_pool *pool)
{
/* 获取工作池中最近进入空闲状态的工作线程 */
struct worker *worker = first_idle_worker(pool);
struct task_struct *p;
lockdep_assert_held(&pool->lock);
/* 检查是否需要更多工作线程(need_more_worker)以及是否有空闲线程可用 */
if (!need_more_worker(pool) || !worker)
return false;
/* 如果工作池是软中断类型(POOL_BH) */
if (pool->flags & POOL_BH) {
kick_bh_pool(pool);
return true;
}
p = worker->task;
/* 唤醒工作线程,使其开始处理挂起的工作项 */
wake_up_process(p);
return true;
}
process_one_work 处理单个工作项
c
/**
* process_one_work - process single work
* @worker: self
* @work: work to process
*
* 处理@工作。此功能包含处理单个工作的所有逻辑,包括与同一CPU上的其他工作者的同步和交互、排队和刷新。只要满足上下文要求,任何工作者都可以调用此功能来处理工作。
*
* CONTEXT:
* raw_spin_lock_irq(pool->lock) which is released and regrabbed.
*/
static void process_one_work(struct worker *worker, struct work_struct *work)
__releases(&pool->lock)
__acquires(&pool->lock)
{
struct pool_workqueue *pwq = get_work_pwq(work);
struct worker_pool *pool = worker->pool;
unsigned long work_data;
int lockdep_start_depth, rcu_start_depth;
bool bh_draining = pool->flags & POOL_BH_DRAINING;
/* 确保我们在正确的CPU上 */
WARN_ON_ONCE(!(pool->flags & POOL_DISASSOCIATED) &&
raw_smp_processor_id() != pool->cpu);
/* 声明和出队 */
debug_work_deactivate(work);
hash_add(pool->busy_hash, &worker->hentry, (unsigned long)work);
worker->current_work = work;
worker->current_func = work->func;
worker->current_pwq = pwq;
if (worker->task)
worker->current_at = worker->task->se.sum_exec_runtime;
work_data = *work_data_bits(work);
/* 用于记录当前正在执行的工作项的颜色(color)。颜色是工作队列中用于区分不同批次工作项的标识。 */
worker->current_color = get_work_color(work_data);
/*
* Record wq name for cmdline and debug reporting, may get
* overridden through set_worker_desc().
*/
strscpy(worker->desc, pwq->wq->name, WORKER_DESC_LEN);
list_del_init(&work->entry);
/* 如果工作队列标记为 CPU 密集型(WQ_CPU_INTENSIVE),将 worker 标记为 CPU 密集型,以便调度器优化其运行 */
if (unlikely(pwq->wq->flags & WQ_CPU_INTENSIVE))
worker_set_flags(worker, WORKER_CPU_INTENSIVE);
/*
*必要时在池中踢出。此时,对于每个 CPU 工作者池,由于 nr_running 始终大于或等于 1,因此始终为 noop。
这用于链接尚未执行的工作项,针对那些 WORKER_NOT_RUNNING 的工作者,例如 UNBOUND 和 CPU_INTENSIVE 的工作者。
*/
kick_pool(pool);
/*
* 记录最后一个池,并清除PENDING,这应该是对@work的最后更新。同时,在@pool->lock内部执行此操作,以便在禁用IRQ时PENDING和排队状态更改一起发生。
*/
set_work_pool_and_clear_pending(work, pool->id, pool_offq_flags(pool));
pwq->stats[PWQ_STAT_STARTED]++;
raw_spin_unlock_irq(&pool->lock);
worker->current_func(work);
pwq->stats[PWQ_STAT_COMPLETED]++;
raw_spin_lock_irq(&pool->lock);
/*
* In addition to %WQ_CPU_INTENSIVE, @worker may also have been marked
* CPU intensive by wq_worker_tick() if @work hogged CPU longer than
* wq_cpu_intensive_thresh_us. Clear it.
*/
worker_clr_flags(worker, WORKER_CPU_INTENSIVE);
/* tag the worker for identification in schedule() */
worker->last_func = worker->current_func;
/* we're done with it, release */
hash_del(&worker->hentry);
worker->current_work = NULL;
worker->current_func = NULL;
worker->current_pwq = NULL;
worker->current_color = INT_MAX;
/* must be the last step, see the function comment */
pwq_dec_nr_in_flight(pwq, work_data);
}
process_scheduled_works 处理调度的工作
c
/**
* process_scheduled_works - 处理所有计划的工作。
* @worker: self
*
* 处理所有计划的工作。 请注意,在处理工作时,计划列表可能会发生变化,因此该功能会反复从顶部获取工作并执行。
*
* CONTEXT:
* raw_spin_lock_irq(pool->lock),可能会被多次释放和重新获取.
*/
static void process_scheduled_works(struct worker *worker)
{
struct work_struct *work;
bool first = true;
while ((work = list_first_entry_or_null(&worker->scheduled,
struct work_struct, entry))) {
if (first) {
worker->pool->watchdog_ts = jiffies;
first = false;
}
process_one_work(worker, work);
}
}
set_pf_worker 标识一个任务是否为工作队列(workqueue)的工作者线程
c
static void set_pf_worker(bool val)
{
mutex_lock(&wq_pool_attach_mutex);
if (val)
current->flags |= PF_WQ_WORKER;
else
current->flags &= ~PF_WQ_WORKER;
mutex_unlock(&wq_pool_attach_mutex);
}
worker_thread 工作线程函数
c
/**
* worker_thread - 工作线程函数
* @__worker: self
*
* 工作线程功能。所有工作线程都归属于一个工作池------可以是每CPU一个的工作池,也可以是动态无绑定的工作池。
* 这些工作线程处理所有工作项,而不考虑它们具体的目标工作队列。
* 唯一的例外是属于具有救援者的工作队列的工作项,救援者的内容将在rescue_thread()中进行说明。.
*
* Return: 0
*/
static int worker_thread(void *__worker)
{
struct worker *worker = __worker;
struct worker_pool *pool = worker->pool;
/* 告诉调度程序这是一个工作队列工作者 */
set_pf_worker(true);
woke_up:
raw_spin_lock_irq(&pool->lock);
/*我应该死吗? */
if (unlikely(worker->flags & WORKER_DIE)) {
raw_spin_unlock_irq(&pool->lock);
set_pf_worker(false);
/*
* worker已死,PF_WQ_WORKER 被清除,worker->pool 不应被访问,如果未这样做,请将其重置为 NULL。
*/
worker->pool = NULL;
ida_free(&pool->worker_ida, worker->id);
return 0;
}
worker_leave_idle(worker);
recheck:
/* 检查是否需要更多工作线程,不需要进入睡眠,需要执行任务 */
if (!need_more_worker(pool))
goto sleep;
/* 管理线程(may_start_working 返回 false),调用 manage_workers 进行管理后重新检查 */
if (unlikely(!may_start_working(pool)) && manage_workers(worker))
goto recheck;
/*
* 确保线程的 ->scheduled 列表为空,避免在休眠期间被意外修改
*/
WARN_ON_ONCE(!list_empty(&worker->scheduled));
/*
* 清除线程的 WORKER_PREP 和 WORKER_REBOUND 标志,表示线程已准备好参与并发管理
*/
worker_clr_flags(worker, WORKER_PREP | WORKER_REBOUND);
do {
/* 提取第一个任务 */
struct work_struct *work =
list_first_entry(&pool->worklist,
struct work_struct, entry);
if (assign_work(work, worker, NULL))
/* 处理已调度的任务 */
process_scheduled_works(worker);
} while (keep_working(pool));
worker_set_flags(worker, WORKER_PREP);
sleep:
/*
*池->锁被持有时没有工作需要处理,也没有管理的必要,休眠。
* 当持有池->锁或来自本地CPU时,工人会被唤醒,所以在释放池->锁之前设置当前状态就足以防止丢失任何事件。
*/
worker_enter_idle(worker);
__set_current_state(TASK_IDLE);
raw_spin_unlock_irq(&pool->lock);
schedule();
goto woke_up;
}
wqattrs_hash 计算工作队列属性的哈希值
c
/* hash value of the content of @attr */
static u32 wqattrs_hash(const struct workqueue_attrs *attrs)
{
u32 hash = 0;
hash = jhash_1word(attrs->nice, hash);
hash = jhash_1word(attrs->affn_strict, hash);
hash = jhash(cpumask_bits(attrs->__pod_cpumask),
BITS_TO_LONGS(nr_cpumask_bits) * sizeof(long), hash);
if (!attrs->affn_strict)
hash = jhash(cpumask_bits(attrs->cpumask),
BITS_TO_LONGS(nr_cpumask_bits) * sizeof(long), hash);
return hash;
}
set_worker_dying 设置工作线程为死亡状态
c
/**
* set_worker_dying - Tag a worker for destruction
* @worker: worker to be destroyed
* @list: transfer worker away from its pool->idle_list and into list
*
* Tag @worker for destruction and adjust @pool stats accordingly. The worker
* should be idle.
*
* CONTEXT:
* raw_spin_lock_irq(pool->lock).
*/
static void set_worker_dying(struct worker *worker, struct list_head *list)
{
struct worker_pool *pool = worker->pool;
lockdep_assert_held(&pool->lock);
lockdep_assert_held(&wq_pool_attach_mutex);
/* sanity check frenzy */
if (WARN_ON(worker->current_work) ||
WARN_ON(!list_empty(&worker->scheduled)) ||
WARN_ON(!(worker->flags & WORKER_IDLE)))
return;
pool->nr_workers--;
pool->nr_idle--;
worker->flags |= WORKER_DIE;
list_move(&worker->entry, list);
/* get an extra task struct reference for later kthread_stop_put() */
get_task_struct(worker->task);
}
detach_worker 分离工作线程
c
static void unbind_worker(struct worker *worker)
{
lockdep_assert_held(&wq_pool_attach_mutex);
kthread_set_per_cpu(worker->task, -1);
if (cpumask_intersects(wq_unbound_cpumask, cpu_active_mask))
WARN_ON_ONCE(set_cpus_allowed_ptr(worker->task, wq_unbound_cpumask) < 0);
else
WARN_ON_ONCE(set_cpus_allowed_ptr(worker->task, cpu_possible_mask) < 0);
}
static void detach_worker(struct worker *worker)
{
lockdep_assert_held(&wq_pool_attach_mutex);
unbind_worker(worker);
list_del(&worker->node);
}
detach_dying_workers 分离死亡的工作线程
c
static void detach_dying_workers(struct list_head *cull_list)
{
struct worker *worker;
list_for_each_entry(worker, cull_list, entry)
detach_worker(worker);
}
work_grab_pending 从 WorkList 中窃取工作项并禁用 IRQ
在 Linux 内核的 workqueue.c 文件中,offqd 是一个缩写,表示 off-queue data ,它是与 work_struct 相关的一个状态信息,用于描述工作项(work item)在从队列中移除后的状态。
具体来说,offqd 是一个结构体 work_offq_data 的实例,该结构体定义如下:
c
struct work_offq_data {
u32 pool_id; // 表示工作项所属的 worker_pool 的 ID
u32 disable; // 表示工作项的禁用计数
u32 flags; // 表示工作项的状态标志
};
offqd 的作用
offqd 的主要作用是存储工作项在从队列中移除后的一些元数据,包括:
pool_id:工作项所属的worker_pool的 ID,用于标识工作项之前在哪个工作池中。disable:工作项的禁用计数,表示该工作项是否被禁用以及禁用的深度。禁用的工作项无法被重新排队。flags:工作项的状态标志,用于记录工作项的其他状态信息,例如是否是 BH(软中断)工作项。
offqd 的使用场景
offqd 主要在以下场景中使用:
-
工作项从队列中移除时 :
当一个工作项从队列中移除时,其状态会被转换为
off-queue状态,此时会使用offqd来存储相关的元数据。 -
工作项禁用和启用 :
offqd的disable字段用于记录工作项的禁用状态。通过disable_work()和enable_work()函数,可以增加或减少禁用计数,从而控制工作项是否可以被重新排队。 -
工作项状态标志的管理 :
offqd的flags字段用于存储工作项的额外状态信息,例如是否是 BH 类型的工作项(通过WORK_OFFQ_BH标志表示)。
总结
offqd 是 workqueue 子系统中用于管理工作项状态的一个辅助数据结构,主要用于记录工作项在从队列中移除后的元数据,包括所属的工作池 ID、禁用计数和状态标志。它在工作项的禁用、启用以及状态转换过程中起到了重要作用。
c
/**
* try_to_grab_pending - 从 WorkList 中窃取工作项并禁用 IRQ
* @work: work item to steal
* @cflags: %WORK_CANCEL_ flags
* @irq_flags: place to store irq state
*
try_to_grab_pending 的作用是尝试获取工作项的 PENDING 状态,并根据不同的情况返回以下结果:
返回 1:表示工作项处于 PENDING 状态,并且成功获取了该状态。
返回 0:表示工作项处于空闲状态,并成功将其标记为 PENDING。
返回 -EAGAIN:表示暂时无法获取 PENDING 状态,调用方可以安全地重试。
该函数能够处理工作项的多种状态,包括空闲状态、延迟状态(由定时器管理)以及工作队列中的状态
*/
static int try_to_grab_pending(struct work_struct *work, u32 cflags,
unsigned long *irq_flags)
{
struct worker_pool *pool;
struct pool_workqueue *pwq;
local_irq_save(*irq_flags);
/* try to steal the timer if it exists */
if (cflags & WORK_CANCEL_DELAYED) {
struct delayed_work *dwork = to_delayed_work(work);
/*
* 尝试通过 timer_delete 删除延迟工作项的定时器。如果删除成功,说明工作项已不在定时器中,返回 1
*/
if (likely(timer_delete(&dwork->timer)))
return 1;
}
/* 表示工作项处于空闲状态 */
if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work)))
return 0;
rcu_read_lock();
/*
* 如果工作项已经在队列中,则需要进一步尝试从队列中"偷取"它
*/
pool = get_work_pool(work);
if (!pool)
goto fail;
raw_spin_lock(&pool->lock);
/*
* work->data 保证在 work 项目排队在 pwq->wq 时仅指向 pwq,而在排队时将 work->data 更新为指向 pwq 和在出队时更新为指向池的操作都是在 pwq->pool->lock 下进行的。
* 这反过来保证了,如果 work->data 指向与被锁定的池相关联的 pwq,则工作项目目前在该池中排队。
*/
pwq = get_work_pwq(work);
if (pwq && pwq->pool == pool) {
unsigned long work_data = *work_data_bits(work);
debug_work_deactivate(work);
/*
* A cancelable inactive work item must be in the
* pwq->inactive_works since a queued barrier can't be
* canceled (see the comments in insert_wq_barrier()).
*
* An inactive work item cannot be deleted directly because
* it might have linked barrier work items which, if left
* on the inactive_works list, will confuse pwq->nr_active
* management later on and cause stall. Move the linked
* barrier work items to the worklist when deleting the grabbed
* item. Also keep WORK_STRUCT_INACTIVE in work_data, so that
* it doesn't participate in nr_active management in later
* pwq_dec_nr_in_flight().
*/
if (work_data & WORK_STRUCT_INACTIVE)
move_linked_works(work, &pwq->pool->worklist, NULL);
list_del_init(&work->entry);
/*
* work->data points to pwq iff queued. Let's point to pool. As
* this destroys work->data needed by the next step, stash it.
*/
set_work_pool_and_keep_pending(work, pool->id,
pool_offq_flags(pool));
/* must be the last step, see the function comment */
pwq_dec_nr_in_flight(pwq, work_data);
raw_spin_unlock(&pool->lock);
rcu_read_unlock();
return 1;
}
raw_spin_unlock(&pool->lock);
fail:
rcu_read_unlock();
local_irq_restore(*irq_flags);
return -EAGAIN;
}
/**
* work_grab_pending - 从工作列表中盗取工作项并禁用中断请求
* @work: work item to steal
* @cflags: %WORK_CANCEL_ flags
* @irq_flags: place to store IRQ state
*
* 获取 @work 的 PENDING 位。@work 可以处于任何稳定状态 - 空闲、计时或在工作列表中。
*
* 可以在任何上下文中调用。IRQ在返回时被禁用,IRQ状态存储在*@irq_flags中。调用者负责使用local_irq_restore()重新启用它。
*
* 如果@work处于待处理状态,则返回%true。否则为闲置.
*/
static bool work_grab_pending(struct work_struct *work, u32 cflags,
unsigned long *irq_flags)
{
int ret;
while (true) {
ret = try_to_grab_pending(work, cflags, irq_flags);
if (ret >= 0)
return ret;
cpu_relax();
}
}
static void work_offqd_unpack(struct work_offq_data *offqd, unsigned long data)
{
WARN_ON_ONCE(data & WORK_STRUCT_PWQ);
offqd->pool_id = shift_and_mask(data, WORK_OFFQ_POOL_SHIFT,
WORK_OFFQ_POOL_BITS);
offqd->disable = shift_and_mask(data, WORK_OFFQ_DISABLE_SHIFT,
WORK_OFFQ_DISABLE_BITS);
offqd->flags = data & WORK_OFFQ_FLAG_MASK;
}
static void work_offqd_disable(struct work_offq_data *offqd)
{
const unsigned long max = (1lu << WORK_OFFQ_DISABLE_BITS) - 1;
if (likely(offqd->disable < max))
offqd->disable++;
else
WARN_ONCE(true, "workqueue: work disable count overflowed\n");
}
flush_work: 等待一个工作项完成其最后的执行
此函数是一个同步辅助函数. 它的作用是阻塞 当前的执行流程, 直到指定的工作项(work)执行完毕. 当此函数返回时, 可以保证该工作项处于空闲状态 (除非在flush_work开始执行后, 有其他代码又重新将其加入了队列). 这在设备驱动中非常关键, 例如, 在卸载一个驱动模块或关闭一个设备之前, 必须确保所有由该驱动提交的后台任务(work items)都已处理完毕, flush_work就是用来实现这种同步等待的.
在STM32H750单核系统上, 虽然不存在多核并行执行, 但并发性依然存在. 一个work是由后台的kworker内核线程执行的, 而调用flush_work的可能是另一个内核线程或进程上下文. 调用flush_work会使当前任务进入睡眠状态, 内核调度器会切换到其他任务, 比如kworker线程, 让其有机会完成待处理的工作. 因此, 即使在单核上, 这个函数对于保证操作的正确顺序也是必不可少的.
c
/**
* bool flush_work(struct work_struct *work) - 等待一个工作项完成其最后的排队实例
* @work: 需要被冲刷(flush)的工作项.
*
* 等待直到 @work 完成其执行. 如果从 flush 开始后 @work 没有被重新排队,
* 那么可以保证在函数返回时 @work 是空闲的.
*
* 返回值:
* 如果 flush_work() 等待了该工作项的完成, 则返回 %true.
* 如果该工作项在调用时已经处于空闲状态, 则返回 %false.
*/
bool flush_work(struct work_struct *work)
{
/*
* 调用 might_sleep() 宏. 这是一个调试辅助工具, 它会告知内核的锁验证器(lockdep),
* 当前函数可能会进入睡眠(阻塞)状态. 如果在不能睡眠的上下文(如持有自旋锁时)调用了此函数,
* 内核会打印警告信息. 它在运行时没有开销, 仅用于开发和调试阶段.
*/
might_sleep();
/*
* 直接调用内部核心实现函数 __flush_work.
* 第二个参数 from_cancel 被设置为 false, 表示这是一个常规的冲刷(flush)操作,
* 而不是取消(cancel)操作的一部分.
*/
return __flush_work(work, false);
}
/*
* 将 flush_work 函数导出, 以便其他GPL许可的内核模块可以使用它.
*/
EXPORT_SYMBOL_GPL(flush_work);
insert_wq_barrier: 向工作队列中插入一个屏障工作项
此函数是工作项冲刷(flush)机制的核心实现, 它的作用是原子性地将一个作为同步点的"屏障"工作项 (barr) 插入到一个工作队列中, 紧跟在需要被等待的"目标"工作项 (target) 之后. 该函数通过两种不同的策略来实现这一目标, 具体取决于目标工作项是正在执行还是仍在队列中等待.
c
static void wq_barrier_func(struct work_struct *work)
{
struct wq_barrier *barr = container_of(work, struct wq_barrier, work);
complete(&barr->done);
}
/*
* 静态函数声明: insert_wq_barrier
* 向工作队列中插入一个屏障工作项.
*
* @pwq: 需要插入屏障的目标 pool_workqueue (工作队列在池中的实例).
* @barr: 需要被插入的 wq_barrier 结构体.
* @target: 屏障需要等待的目标工作项.
* @worker: 如果 target 正在被执行, 此参数指向执行它的 worker 线程; 否则为 NULL.
*
* @barr 会被链接到 @target 之后, 这样 @barr 只有在 @target 执行完毕后才会被完成.
* 请注意, 顺序保证仅相对于 @target 和本地CPU有效.
*
* 注意: 当 @worker 不为 NULL 时, @target 的内容可能在底层被修改,
* 因此我们不能可靠地从 @target 推断出 pwq.
*
* CONTEXT (调用上下文):
* 必须在持有 pool->lock 的情况下调用此函数 (raw_spin_lock_irq).
*/
static void insert_wq_barrier(struct pool_workqueue *pwq,
struct wq_barrier *barr,
struct work_struct *target, struct worker *worker)
{
/*
* 为锁依赖检查器(lockdep)定义两个静态的锁类别密钥. __maybe_unused 表示如果内核未开启相关配置,
* 这些变量可能未使用, 以避免编译器警告.
* BH (Bottom Half) 和 线程化(threaded)工作队列需要不同的密钥,
* 以便 lockdep 能够正确区分它们的锁上下文, 避免误报死锁.
*/
static __maybe_unused struct lock_class_key bh_key, thr_key;
/*
* 定义一个无符号整型 work_flags, 用于累积要设置到屏障工作项中的标志位. 初始化为0.
*/
unsigned int work_flags = 0;
/*
* 定义一个无符号整型 work_color, 用于存储目标工作项的"颜色".
* "颜色"是用于NUMA系统上进行优化的机制, 用于将工作项和worker线程保持在同一节点.
* 在非NUMA的STM32H750上, 只有一个颜色(通常是0).
*/
unsigned int work_color;
/*
* 定义一个指向 list_head 的指针, 它将指向屏障工作项需要被插入的链表位置.
*/
struct list_head *head;
/*
* 初始化在栈上创建的屏障工作项 barr->work.
* INIT_WORK_ONSTACK_KEY 是一个宏, 用于初始化一个在栈上分配的 work_struct.
* wq_barrier_func 是这个屏障工作项被执行时会调用的函数.
* 根据工作队列的标志位判断它是BH型还是线程型, 并为其关联正确的lockdep密钥.
*/
INIT_WORK_ONSTACK_KEY(&barr->work, wq_barrier_func,
(pwq->wq->flags & WQ_BH) ? &bh_key : &thr_key);
/*
* 使用 __set_bit 原子地设置屏障工作项的 PENDING 位.
* 这将其标记为"待处理"状态.
*/
__set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(&barr->work));
/*
* 初始化屏障中的完成变量(completion) barr->done.
* 调用 flush_work 的任务将会睡眠在这个 completion 上, 等待被唤醒.
* 同时, 它会继承目标工作项的 lockdep 映射, 以便进行依赖性检查.
*/
init_completion_map(&barr->done, &target->lockdep_map);
/*
* 记录下发起此次冲刷操作的任务(进程), 用于调试.
*/
barr->task = current;
/*
* 将屏障工作项标记为 INACTIVE.
* 这意味着它不会计入工作队列的 "nr_active" (活动工作项数量)中.
* 因为屏障本身只是一个同步标记, 而不是一个真正需要消耗资源的"工作".
*/
work_flags |= WORK_STRUCT_INACTIVE;
/*
* 这是此函数的核心逻辑分歧点.
* 如果 worker 指针不为 NULL, 说明目标工作项 target 正在被这个 worker 执行.
* 否则, 说明 target 仍在队列中等待, 尚未开始执行.
*/
if (worker) {
/*
* 目标正在执行: 屏障必须被插入到该 worker 的私有 scheduled 链表的头部,
* 这样在当前工作完成后, 它会是下一个被执行的项.
*/
head = worker->scheduled.next;
/*
* 屏障的颜色继承自正在执行它的 worker 的当前颜色.
*/
work_color = worker->current_color;
} else {
/*
* 目标正在排队:
* 获取指向 target 数据域的指针.
*/
unsigned long *bits = work_data_bits(target);
/*
* 屏障必须被插入到队列中紧跟在 target 后面的位置.
*/
head = target->entry.next;
/*
* 如果 target 后面已经有其他链接的工作项, 屏障需要继承这个 LINKED 标志,
* 以维持这个执行链.
*/
work_flags |= *bits & WORK_STRUCT_LINKED;
/*
* 屏障的颜色继承自仍在队列中的 target 工作项的颜色.
*/
work_color = get_work_color(*bits);
/*
* 在目标工作项 target 上设置 LINKED 标志位.
* 这个标志告诉工作队列处理器: "在执行完这个工作项后, 不要去队列头取下一个,
* 而是直接执行紧跟在我后面的那个工作项". 这是实现屏障插入的关键.
*/
__set_bit(WORK_STRUCT_LINKED_BIT, bits);
}
/*
* 增加对应颜色的 "in_flight" (在途)工作项计数.
* 这个计数器帮助工作队列管理器决定是否需要创建或销毁 worker 线程.
*/
pwq->nr_in_flight[work_color]++;
/*
* 将颜色信息编码到屏障工作项的 flags 中.
*/
work_flags |= work_color_to_flags(work_color);
/*
* 调用 insert_work 函数, 将设置好所有标志的屏障工作项 barr->work,
* 插入到前面确定的链表位置 head 中.
*/
insert_work(pwq, &barr->work, head, work_flags);
}
start_flush_work: 启动一个工作项的冲刷操作
此函数是 __flush_work 的核心辅助函数, 它的主要职责是在一个原子性的上下文中, 检查一个给定的工作项 (work) 是否正处于活动状态 (即, 排队中或正在执行). 如果工作项是活动的, 此函数会将一个特殊的"屏障"工作项 (barr) 插入到与目标工作项相同的执行队列中, 并返回 true, 表示调用者需要等待. 如果目标工作项已经是空闲的, 则此函数直接返回 false, 表示无需等待.
c
/*
* 静态函数声明: start_flush_work
* 尝试启动一个工作项的冲刷操作, 如果成功, 则返回 true.
* @work: 目标工作项.
* @barr: 将要被插入的屏障工作项.
* @from_cancel: 布尔值, 指示此次调用是否源于取消操作.
*/
static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr,
bool from_cancel)
{
/*
* 定义一个指向 worker 结构体的指针, worker 代表一个执行工作项的内核线程. 初始化为NULL.
*/
struct worker *worker = NULL;
/*
* 定义一个指向 worker_pool 结构体的指针, worker_pool 是 worker 线程的集合.
*/
struct worker_pool *pool;
/*
* 定义一个指向 pool_workqueue 结构体的指针, 它代表了一个工作队列在一个工作池中的实例.
*/
struct pool_workqueue *pwq;
/*
* 定义一个指向 workqueue_struct 结构体的指针, 它代表一个工作队列.
*/
struct workqueue_struct *wq;
/*
* 进入RCU读侧临界区. 这可以防止在读取 work->data (其中包含pool信息)时,
* work 被并发地从一个工作池迁移到另一个. 在单核可抢占内核上, 这会禁用内核抢占.
*/
rcu_read_lock();
/*
* 调用 get_work_pool 获取 work 所在的 worker_pool.
* 这个信息编码在 work->data 中.
*/
pool = get_work_pool(work);
/*
* 如果 get_work_pool 返回 NULL, 说明这个 work 当前不属于任何工作池, 即它处于空闲状态.
*/
if (!pool) {
/*
* 既然 work 是空闲的, 就不需要等待. 退出RCU临界区.
*/
rcu_read_unlock();
/*
* 返回 false, 告知调用者无需冲刷.
*/
return false;
}
/*
* 获取工作池的自旋锁并禁用中断. 这是为了原子地检查 work 的状态并插入屏障.
* 在单核系统上, 这保证了在操作期间不会发生任务抢占或中断处理.
*/
raw_spin_lock_irq(&pool->lock);
/*
* 首先, 假设 work 正在队列中等待执行, 尝试获取它的 pwq (pool workqueue) 实例.
*/
pwq = get_work_pwq(work);
/*
* 如果 pwq 不为 NULL, 说明 work 确实在某个队列里.
*/
if (pwq) {
/*
* 一个罕见的竞态条件检查: 如果 pwq 所属的 pool 不是我们刚刚锁定的 pool,
* 说明 work 在我们获取锁的瞬间被迁移到了别的池并完成了执行.
* 这种情况下, 它已经 "消失" 了.
*/
if (unlikely(pwq->pool != pool))
goto already_gone;
} else {
/*
* 如果 pwq 为 NULL, 说明 work 不在任何等待队列中. 它可能正在被某个 worker 线程执行.
* 调用 find_worker_executing_work 在当前池中查找正在执行 work 的 worker 线程.
*/
worker = find_worker_executing_work(pool, work);
/*
* 如果没有找到 worker, 说明 work 既不在队列中也未在执行. 它已经 "消失" 了.
*/
if (!worker)
goto already_gone;
/*
* 如果找到了正在执行它的 worker, 那么从这个 worker 的状态中获取它当前的 pwq.
*/
pwq = worker->current_pwq;
}
/*
* 此时, 我们已经成功定位了 work 所在的 pwq. 从 pwq 中获取其所属的 workqueue (wq).
*/
wq = pwq->wq;
/*
* 调用锁依赖检查器, 检查是否存在循环依赖, 比如一个 work 冲刷它自己的工作队列, 这可能导致死锁.
* 这是一个调试功能, 在生产内核中通常没有开销.
*/
check_flush_dependency(wq, work, from_cancel);
/*
* 这是关键操作: 调用 insert_wq_barrier, 将屏障 barr 插入到 pwq 的工作队列中.
* 这个函数会把 barr 放置在合适的位置, 以确保它在 work 执行完毕后才会被执行.
*/
insert_wq_barrier(pwq, barr, work, worker);
/*
* 释放工作池的锁, 并恢复中断.
*/
raw_spin_unlock_irq(&pool->lock);
/*
* 接触(touch)工作项的锁映射, 通知锁依赖检查器.
*/
touch_work_lockdep_map(work, wq);
/*
* 这是一项针对死锁的预防性检查. 如果一个工作队列是单线程的(max_active == 1),
* 或者它配备了救援者线程(rescuer), 在其工作项内部调用 flush_work() 会导致死锁.
* 这行代码会模拟一次锁获取, 以便让锁依赖检查器(lockdep)能够捕获这种不当用法.
* 这个检查仅在常规冲刷(from_cancel 为 false)时进行.
*/
if (!from_cancel && (wq->saved_max_active == 1 || wq->rescuer))
touch_wq_lockdep_map(wq);
/*
* 退出RCU读侧临界区. 在单核上, 重新启用内核抢占.
*/
rcu_read_unlock();
/*
* 返回 true, 表明屏障已成功插入, 调用者需要等待.
*/
return true;
already_gone:
/*
* 这是 "work 已经消失" 的统一出口.
* 释放池锁和RCU锁.
*/
raw_spin_unlock_irq(&pool->lock);
rcu_read_unlock();
/*
* 返回 false, 告知调用者无需等待.
*/
return false;
}
__flush_work: 冲刷(flush)一个工作项的核心实现
这是flush_work和cancel_work_sync等函数的内部核心实现. 它的基本原理是在目标工作项work所在的CPU的工作队列上, 插入一个特殊的"屏障"(barrier)工作项. 因为同一个工作队列中的工作项是按顺序执行的, 所以当这个屏障工作项开始执行时, 就意味着它前面的所有工作项(包括我们关心的work)都已经完成了. 然后, 调用者只需等待这个屏障工作项完成即可.
c
/*
* 静态函数声明: __flush_work
* 这是冲刷工作项的内部核心函数.
* @work: 需要被冲刷的工作项.
* @from_cancel: 一个布尔值, 如果为 true, 表示此次调用来自取消操作(cancel_work_sync),
* 需要处理一些特殊的竞态条件.
* @return: 如果函数实际等待了工作项, 返回 true, 否则返回 false.
*/
static bool __flush_work(struct work_struct *work, bool from_cancel)
{
/*
* 在栈上定义一个 wq_barrier 结构体变量 barr.
* wq_barrier 是一种特殊的工作项, 用于在工作队列中设置一个同步点.
*/
struct wq_barrier barr;
/*
* 使用 WARN_ON 宏进行检查. 如果工作队列子系统尚未在线(wq_online 为 false),
* 说明系统处于早期启动或关闭阶段, 此时冲刷工作项是不安全或无意义的.
* 如果条件为真, 内核会打印一条警告信息, 并返回 false.
*/
if (WARN_ON(!wq_online))
return false;
/*
* 检查工作项的 func 成员是否为 NULL. 一个没有执行函数的工作项是无效的.
* 如果为 NULL, 打印警告并返回.
*/
if (WARN_ON(!work->func))
return false;
/*
* 调用 start_flush_work 函数. 这是实现冲刷的关键步骤.
* 这个函数会尝试将屏障 barr 插入到工作队列中.
* 如果目标工作项 work 当时并不在任何队列中也未在执行(即处于空闲),
* start_flush_work 会直接返回 false, 表示无需等待.
* 如果 work 正在排队或执行, 它会设置好屏障并返回 true, 表示我们需要等待.
*/
if (!start_flush_work(work, &barr, from_cancel))
return false;
/*
* 如果代码执行到这里, 说明 start_flush_work() 返回了 true, 我们需要等待.
* 下面是针对 from_cancel 标志的特殊处理逻辑.
*/
if (from_cancel) {
/*
* 这段逻辑主要由 cancel_work_sync() 使用.
* 如果 from_cancel 为真, 我们知道 work 必须是在 start_flush_work() 执行期间正在被执行,
* 并且当前不可能被重新排队. 其 work_data_bits 中必须包含 OFFQ(已离队)标志位.
* 如果 work 是一个在 BH(Bottom Half)上下文中执行的工作队列项, 我们可以安全地对其进行忙等待.
*/
unsigned long data = *work_data_bits(work);
/*
* 如果 work 不是一个 PENDING/INFLIGHT 工作项 (这是预期的, 但用WARN_ON_ONCE检查以防万一),
* 并且它是一个 BH 工作项 (由 WORK_OFFQ_BH 标志指示), 则进入忙等待循环.
*/
if (!WARN_ON_ONCE(data & WORK_STRUCT_PWQ) &&
(data & WORK_OFFQ_BH)) {
/*
* 这是一个忙等待循环, 不断尝试检查屏障是否已完成.
* try_wait_for_completion 不会使任务睡眠.
*/
while (!try_wait_for_completion(&barr.done)) {
/*
* 在实时(PREEMPT_RT)内核中, 为了防止当前任务抢占了软中断处理,
* 从而导致死锁, 这里通过开关本地BH来给软中断执行的机会.
*/
if (IS_ENABLED(CONFIG_PREEMPT_RT)) {
local_bh_disable();
local_bh_enable();
} else {
/*
* 在非实时内核中, 调用 cpu_relax() 是一个给处理器的提示,
* 表示当前处于一个自旋等待循环中, CPU可以进行一些优化, 比如降低功耗.
*/
cpu_relax();
}
}
/*
* 忙等待结束后, 跳转到清理步骤.
*/
goto out_destroy;
}
}
/*
* 这是常规的等待路径 (from_cancel 为 false, 或不满足上述特殊条件).
* 调用 wait_for_completion(), 这将使当前任务进入不可中断的睡眠状态(TASK_UNINTERRUPTIBLE),
* 直到屏障工作项执行完毕并调用 complete(&barr.done) 来唤醒我们. 这是高效的等待方式.
*/
wait_for_completion(&barr.done);
out_destroy:
/*
* 调用 destroy_work_on_stack 清理在栈上创建的屏障工作项.
* 这会确保工作项与工作队列完全解绑.
*/
destroy_work_on_stack(&barr.work);
/*
* 返回 true, 表明我们的确执行了等待操作.
*/
return true;
}
cancel_work 取消工作项
c
static bool __cancel_work(struct work_struct *work, u32 cflags)
{
struct work_offq_data offqd;
unsigned long irq_flags;
int ret;
ret = work_grab_pending(work, cflags, &irq_flags);
work_offqd_unpack(&offqd, *work_data_bits(work));
if (cflags & WORK_CANCEL_DISABLE)
work_offqd_disable(&offqd);
set_work_pool_and_clear_pending(work, offqd.pool_id,
work_offqd_pack_flags(&offqd));
local_irq_restore(irq_flags);
return ret;
}
static bool __cancel_work_sync(struct work_struct *work, u32 cflags)
{
bool ret;
ret = __cancel_work(work, cflags | WORK_CANCEL_DISABLE);
if (*work_data_bits(work) & WORK_OFFQ_BH)
WARN_ON_ONCE(in_hardirq());
else
might_sleep();
/*
* Skip __flush_work() during early boot when we know that @work isn't
* executing. This allows canceling during early boot.
*/
if (wq_online)
__flush_work(work, true);
if (!(cflags & WORK_CANCEL_DISABLE))
enable_work(work);
return ret;
}
/*
* See cancel_delayed_work()
*/
bool cancel_work(struct work_struct *work)
{
return __cancel_work(work, 0);
}
EXPORT_SYMBOL(cancel_work);
put_unbound_pool 释放工作池
c
/**
* put_unbound_pool - put a worker_pool
* @pool: worker_pool to put
*
* 放置到@pool。如果它的引用计数达到零,它将以RCU安全的方式被销毁。get_unbound_pool()在其失败路径上调用此函数,并且此函数应该能够释放那些成功或未成功经过init_worker_pool()的池。
*
* 应该在持有 wq_pool_mutex 时调用。
*/
static void put_unbound_pool(struct worker_pool *pool)
{
struct worker *worker;
LIST_HEAD(cull_list);
lockdep_assert_held(&wq_pool_mutex);
if (--pool->refcnt)
return;
/* sanity checks */
if (WARN_ON(!(pool->cpu < 0)) ||
WARN_ON(!list_empty(&pool->worklist)))
return;
/* release id and unhash */
if (pool->id >= 0)
idr_remove(&worker_pool_idr, pool->id);
hash_del(&pool->hash_node);
while (true) {
/* 通过循环等待,确保当前线程成为工作池的管理者 */
rcuwait_wait_event(&manager_wait,
!(pool->flags & POOL_MANAGER_ACTIVE),
TASK_UNINTERRUPTIBLE);
mutex_lock(&wq_pool_attach_mutex);
raw_spin_lock_irq(&pool->lock);
if (!(pool->flags & POOL_MANAGER_ACTIVE)) {
pool->flags |= POOL_MANAGER_ACTIVE;
break;
}
raw_spin_unlock_irq(&pool->lock);
mutex_unlock(&wq_pool_attach_mutex);
}
/* 清理工作线程 */
while ((worker = first_idle_worker(pool)))
set_worker_dying(worker, &cull_list);
WARN_ON(pool->nr_workers || pool->nr_idle);
raw_spin_unlock_irq(&pool->lock);
detach_dying_workers(&cull_list);
mutex_unlock(&wq_pool_attach_mutex);
reap_dying_workers(&cull_list);
/* 删除与工作池相关的定时器 */
timer_delete_sync(&pool->idle_timer);
cancel_work_sync(&pool->idle_cull_work);
timer_delete_sync(&pool->mayday_timer);
/* 使用 call_rcu 延迟销毁工作池,以确保在其他线程可能仍然引用该工作池时不会立即释放 */
call_rcu(&pool->rcu, rcu_free_pool);
}
get_unbound_pool 获取具有指定属性的工作池
c
/* PL: hash of all unbound pools keyed by pool->attrs */
static DEFINE_HASHTABLE(unbound_pool_hash, UNBOUND_POOL_HASH_ORDER);
/**get_unbound_pool - 获取具有指定属性的工作池
* @attrs: 要获取的工作池的属性
* 获取具有与@attrs相同属性的工作池,增加引用计数并返回它。
* 如果已经存在匹配的工作池,则将使用它;否则,此函数将尝试创建一个新的。
* 应在持有wq_pool_mutex时调用。
* 返回:成功时,返回一个具有与@attrs相同属性的工作池;失败时,返回%NULL。
*/
static struct worker_pool *get_unbound_pool(const struct workqueue_attrs *attrs)
{
struct wq_pod_type *pt = &wq_pod_types[WQ_AFFN_NUMA];
u32 hash = wqattrs_hash(attrs);
struct worker_pool *pool;
int pod, node = NUMA_NO_NODE;
lockdep_assert_held(&wq_pool_mutex);
/*我们已经有一个匹配的池子了吗?*/
/* 通过哈希表 unbound_pool_hash 查找是否有属性匹配的 pool,若找到则增加引用计数并返回 */
hash_for_each_possible(unbound_pool_hash, pool, hash_node, hash) {
if (wqattrs_equal(pool->attrs, attrs)) {
pool->refcnt++;
return pool;
}
}
/*根据属性中的 pod cpumask 判断应该分配到哪个 NUMA 节点,并在该节点上分配新的 worker_pool 结构体*/
for (pod = 0; pod < pt->nr_pods; pod++) {
if (cpumask_subset(attrs->__pod_cpumask, pt->pod_cpus[pod])) {
node = pt->pod_node[pod];
break;
}
}
/*不,创建一个新的*/
pool = kzalloc_node(sizeof(*pool), GFP_KERNEL, node);
if (!pool || init_worker_pool(pool) < 0)
goto fail;
pool->node = node;
copy_workqueue_attrs(pool->attrs, attrs);
wqattrs_clear_for_pool(pool->attrs);
if (worker_pool_assign_id(pool) < 0)
goto fail;
/* 创建并启动初始工作线程 */
if (wq_online && !create_worker(pool))
goto fail;
/* install */
hash_add(unbound_pool_hash, &pool->hash_node, hash);
return pool;
fail:
if (pool)
put_unbound_pool(pool);
return NULL;
}
alloc_unbound_pwq 用于根据给定的属性 attrs 获取一个合适的 worker pool
c
/* 获取一个与 @attr 匹配的池,并创建一个将该池与 @wq 关联的 pwq */
static struct pool_workqueue *alloc_unbound_pwq(struct workqueue_struct *wq,
const struct workqueue_attrs *attrs)
{
struct worker_pool *pool;
struct pool_workqueue *pwq;
lockdep_assert_held(&wq_pool_mutex);
pool = get_unbound_pool(attrs); //根据属性查找或创建一个不绑定到特定 CPU 的 worker pool
if (!pool)
return NULL;
//使用 kmem_cache_alloc_node 在 pool 所在的 NUMA 节点上分配一个 pool_workqueue 结构体
pwq = kmem_cache_alloc_node(pwq_cache, GFP_KERNEL, pool->node);
if (!pwq) {
put_unbound_pool(pool);
return NULL;
}
init_pwq(pwq, wq, pool); //调用 init_pwq 对新分配的 pool_workqueue 进行初始化
return pwq;
}
apply_wqattrs_prepare 应用工作队列属性
c
/* 为后续安装分配属性和pwqs*/
static struct apply_wqattrs_ctx *
apply_wqattrs_prepare(struct workqueue_struct *wq,
const struct workqueue_attrs *attrs,
const cpumask_var_t unbound_cpumask)
{
struct apply_wqattrs_ctx *ctx;
struct workqueue_attrs *new_attrs;
int cpu;
lockdep_assert_held(&wq_pool_mutex);
if (WARN_ON(attrs->affn_scope < 0 ||
attrs->affn_scope >= WQ_AFFN_NR_TYPES))
return ERR_PTR(-EINVAL);
ctx = kzalloc(struct_size(ctx, pwq_tbl, nr_cpu_ids), GFP_KERNEL);
new_attrs = alloc_workqueue_attrs();
if (!ctx || !new_attrs)
goto out_free;
/*
* If something goes wrong during CPU up/down, we'll fall back to
* the default pwq covering whole @attrs->cpumask. Always create
* it even if we don't use it immediately.
*/
copy_workqueue_attrs(new_attrs, attrs);
wqattrs_actualize_cpumask(new_attrs, unbound_cpumask);
cpumask_copy(new_attrs->__pod_cpumask, new_attrs->cpumask);
ctx->dfl_pwq = alloc_unbound_pwq(wq, new_attrs);
if (!ctx->dfl_pwq)
goto out_free;
for_each_possible_cpu(cpu) {
if (new_attrs->ordered) {
ctx->dfl_pwq->refcnt++;
ctx->pwq_tbl[cpu] = ctx->dfl_pwq;
} else {
wq_calc_pod_cpumask(new_attrs, cpu);
ctx->pwq_tbl[cpu] = alloc_unbound_pwq(wq, new_attrs);
if (!ctx->pwq_tbl[cpu])
goto out_free;
}
}
/* save the user configured attrs and sanitize it. */
copy_workqueue_attrs(new_attrs, attrs);
cpumask_and(new_attrs->cpumask, new_attrs->cpumask, cpu_possible_mask);
cpumask_copy(new_attrs->__pod_cpumask, new_attrs->cpumask);
ctx->attrs = new_attrs;
/*
* For initialized ordered workqueues, there should only be one pwq
* (dfl_pwq). Set the plugged flag of ctx->dfl_pwq to suspend execution
* of newly queued work items until execution of older work items in
* the old pwq's have completed.
*/
if ((wq->flags & __WQ_ORDERED) && !list_empty(&wq->pwqs))
ctx->dfl_pwq->plugged = true;
ctx->wq = wq;
return ctx;
out_free:
free_workqueue_attrs(new_attrs);
apply_wqattrs_cleanup(ctx);
return ERR_PTR(-ENOMEM);
}
apply_wqattrs_commit 应用新的工作队列属性(wqattrs),并将准备好的 pwqs(per-CPU workqueues)安装到工作队列中
c
/* set attrs and install prepared pwqs, @ctx points to old pwqs on return */
static void apply_wqattrs_commit(struct apply_wqattrs_ctx *ctx)
{
int cpu;
/* all pwqs have been created successfully, let's install'em */
mutex_lock(&ctx->wq->mutex);
copy_workqueue_attrs(ctx->wq->unbound_attrs, ctx->attrs);
/* save the previous pwqs and install the new ones */
for_each_possible_cpu(cpu)
/* 为每个 CPU 安装新的工作队列 */
ctx->pwq_tbl[cpu] = install_unbound_pwq(ctx->wq, cpu,
ctx->pwq_tbl[cpu]);
/* 为默认工作队列(ctx->dfl_pwq)安装新的配置 */
ctx->dfl_pwq = install_unbound_pwq(ctx->wq, -1, ctx->dfl_pwq);
/* 更新工作队列节点的最大活动工作项数 */
wq_update_node_max_active(ctx->wq, -1);
/* 更新救援线程的 CPU 掩码,使其符合新的工作队列配置*/
if (ctx->wq->rescuer)
set_cpus_allowed_ptr(ctx->wq->rescuer->task,
unbound_effective_cpumask(ctx->wq));
mutex_unlock(&ctx->wq->mutex);
}
apply_workqueue_attrs_locked 在加锁状态下为 workqueue(工作队列)应用新的属性
c
static int apply_workqueue_attrs_locked(struct workqueue_struct *wq,
const struct workqueue_attrs *attrs)
{
struct apply_wqattrs_ctx *ctx;
/* 只有未绑定的工作队列才能更改属性 */
if (WARN_ON(!(wq->flags & WQ_UNBOUND)))
return -EINVAL;
//为属性变更做准备工作
ctx = apply_wqattrs_prepare(wq, attrs, wq_unbound_cpumask);
if (IS_ERR(ctx))
return PTR_ERR(ctx);
/* 上下文已经成功准备,接下来我们来提交它 */
apply_wqattrs_commit(ctx); //提交属性变更
apply_wqattrs_cleanup(ctx); //清理相关资源
return 0;
}
alloc_and_link_pwqs 分配并链接工作队列池(PWQ)
c
static int alloc_and_link_pwqs(struct workqueue_struct *wq)
{
bool highpri = wq->flags & WQ_HIGHPRI;
int cpu, ret;
lockdep_assert_held(&wq_pool_mutex);
wq->cpu_pwq = alloc_percpu(struct pool_workqueue *);
if (!wq->cpu_pwq)
goto enomem;
/* 表示这是一个绑定型工作队列,任务需要绑定到特定的 CPU 上执行。只有在这种情况下,代码才会继续执行后续逻辑。 */
if (!(wq->flags & WQ_UNBOUND)) {
struct worker_pool __percpu *pools;
if (wq->flags & WQ_BH)
pools = bh_worker_pools;
else
pools = cpu_worker_pools;
for_each_possible_cpu(cpu) {
struct pool_workqueue **pwq_p;
struct worker_pool *pool;
pool = &(per_cpu_ptr(pools, cpu)[highpri]);
pwq_p = per_cpu_ptr(wq->cpu_pwq, cpu);
*pwq_p = kmem_cache_alloc_node(pwq_cache, GFP_KERNEL,
pool->node);
if (!*pwq_p)
goto enomem;
init_pwq(*pwq_p, wq, pool);
mutex_lock(&wq->mutex);
link_pwq(*pwq_p);
mutex_unlock(&wq->mutex);
}
return 0;
}
if (wq->flags & __WQ_ORDERED) {
struct pool_workqueue *dfl_pwq;
ret = apply_workqueue_attrs_locked(wq, ordered_wq_attrs[highpri]);
/* there should only be single pwq for ordering guarantee */
dfl_pwq = rcu_access_pointer(wq->dfl_pwq);
WARN(!ret && (wq->pwqs.next != &dfl_pwq->pwqs_node ||
wq->pwqs.prev != &dfl_pwq->pwqs_node),
"ordering guarantee broken for workqueue %s\n", wq->name);
} else {
ret = apply_workqueue_attrs_locked(wq, unbound_std_wq_attrs[highpri]);
}
return ret;
enomem:
if (wq->cpu_pwq) {
for_each_possible_cpu(cpu) {
struct pool_workqueue *pwq = *per_cpu_ptr(wq->cpu_pwq, cpu);
if (pwq)
kmem_cache_free(pwq_cache, pwq);
}
free_percpu(wq->cpu_pwq);
wq->cpu_pwq = NULL;
}
return -ENOMEM;
}
alloc_workqueue 分配工作队列
c
__printf(1, 0)
static struct workqueue_struct *__alloc_workqueue(const char *fmt,
unsigned int flags,
int max_active, va_list args)
{
struct workqueue_struct *wq;
size_t wq_size;
int name_len;
if (flags & WQ_BH) {
if (WARN_ON_ONCE(flags & ~__WQ_BH_ALLOWS))
return NULL;
if (WARN_ON_ONCE(max_active))
return NULL;
}
/* 请参阅上面的注释 WQ_POWER_EFFICIENT 的定义 */
if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)
flags |= WQ_UNBOUND;
/* */
if (flags & WQ_UNBOUND)
wq_size = struct_size(wq, node_nr_active, nr_node_ids + 1);
else
wq_size = sizeof(*wq);
wq = kzalloc(wq_size, GFP_KERNEL);
if (!wq)
return NULL;
if (flags & WQ_UNBOUND) {
wq->unbound_attrs = alloc_workqueue_attrs();
if (!wq->unbound_attrs)
goto err_free_wq;
}
name_len = vsnprintf(wq->name, sizeof(wq->name), fmt, args);
if (name_len >= WQ_NAME_LEN)
pr_warn_once("workqueue: name exceeds WQ_NAME_LEN. Truncating to: %s\n",
wq->name);
if (flags & WQ_BH) {
/*
* BH workqueues always share a single execution context per CPU
* and don't impose any max_active limit.
*/
max_active = INT_MAX;
} else {
max_active = max_active ?: WQ_DFL_ACTIVE;
max_active = wq_clamp_max_active(max_active, flags, wq->name);
}
/* init wq */
wq->flags = flags;
wq->max_active = max_active;
wq->min_active = min(max_active, WQ_DFL_MIN_ACTIVE);
wq->saved_max_active = wq->max_active;
wq->saved_min_active = wq->min_active;
mutex_init(&wq->mutex);
atomic_set(&wq->nr_pwqs_to_flush, 0); //工作队列需要刷新多少个 pwq
INIT_LIST_HEAD(&wq->pwqs); //工作队列池列表
INIT_LIST_HEAD(&wq->flusher_queue); //刷新队列
INIT_LIST_HEAD(&wq->flusher_overflow); //溢出刷新队列
INIT_LIST_HEAD(&wq->maydays); //工作池的紧急任务列表
INIT_LIST_HEAD(&wq->list);
/* 包含 WQ_UNBOUND 标志,表示这是一个无绑定的工作队列(可能不绑定到特定 CPU) */
if (flags & WQ_UNBOUND) {
if (alloc_node_nr_active(wq->node_nr_active) < 0)
goto err_free_wq;
}
/*
* wq_pool_mutex 保护工作队列列表、PWQ 分配和全局冻结状态。
*/
apply_wqattrs_lock(); //mutex_lock(&wq_pool_mutex);
/* 分配并链接工作队列池(PWQ) */
if (alloc_and_link_pwqs(wq) < 0)
goto err_unlock_free_node_nr_active;
mutex_lock(&wq->mutex);
/* 调整工作队列的最大活动任务数,确保其符合当前的配置或状态 */
wq_adjust_max_active(wq);
mutex_unlock(&wq->mutex);
/* 将新创建的工作队列(wq)添加到全局工作队列列表(workqueues)的尾部 */
list_add_tail_rcu(&wq->list, &workqueues);
/* 初始化救援线程(rescuer thread)。救
援线程用于处理工作队列中可能被阻塞的任务,确保系统不会因为资源不足而陷入死锁。 */
if (wq_online && init_rescuer(wq) < 0)
goto err_unlock_destroy;
apply_wqattrs_unlock(); /* mutex_unlock(&wq_pool_mutex); */
/* 调用 workqueue_sysfs_register(wq) 将工作队列注册到 sysfs(系统文件系统)。sysfs 是 Linux 内核提供的一种虚拟文件系统,用于向用户空间暴露内核对象。 */
if ((wq->flags & WQ_SYSFS) && workqueue_sysfs_register(wq))
goto err_destroy;
return wq;
err_unlock_free_node_nr_active:
apply_wqattrs_unlock();
/*
* Failed alloc_and_link_pwqs() may leave pending pwq->release_work,
* flushing the pwq_release_worker ensures that the pwq_release_workfn()
* completes before calling kfree(wq).
*/
if (wq->flags & WQ_UNBOUND) {
kthread_flush_worker(pwq_release_worker);
free_node_nr_active(wq->node_nr_active);
}
err_free_wq:
free_workqueue_attrs(wq->unbound_attrs);
kfree(wq);
return NULL;
err_unlock_destroy:
apply_wqattrs_unlock();
err_destroy:
destroy_workqueue(wq);
return NULL;
}
__printf(1, 4)
struct workqueue_struct *alloc_workqueue(const char *fmt,
unsigned int flags,
int max_active, ...)
{
struct workqueue_struct *wq;
va_list args;
va_start(args, max_active);
wq = __alloc_workqueue(fmt, flags, max_active, args);
va_end(args);
if (!wq)
return NULL;
wq_init_lockdep(wq);
return wq;
}
EXPORT_SYMBOL_GPL(alloc_workqueue);
workqueue_init_early 工作队初始化
c
#define for_each_bh_worker_pool(pool, cpu) \
for ((pool) = &per_cpu(bh_worker_pools, cpu)[0]; \
(pool) < &per_cpu(bh_worker_pools, cpu)[NR_STD_WORKER_POOLS]; \
(pool)++)
#define for_each_cpu_worker_pool(pool, cpu) \
for ((pool) = &per_cpu(cpu_worker_pools, cpu)[0]; \
(pool) < &per_cpu(cpu_worker_pools, cpu)[NR_STD_WORKER_POOLS]; \
(pool)++)
/**
1. 函数目的与调用时机
该函数在内核启动的早期阶段调用,当内存分配、CPU 掩码(cpumasks)和 IDR(ID 分配器)等基础设施已经准备就绪时执行。它的主要任务是:
初始化工作队列的核心数据结构。
创建系统级工作队列。
允许早期引导代码创建工作队列并排队或取消工作项。
需要注意的是,实际的工作项执行要等到内核线程(kthreads)可以被创建和调度时才会开始。
*/
void __init workqueue_init_early(void)
{
/* wq_pod_types 是一个全局数组,存储了不同类型的工作队列 Pods 的配置。
WQ_AFFN_SYSTEM 表示系统级的工作队列 Pods。
该变量的作用是为系统级工作队列 Pods 的初始化提供一个入口点。 */
struct wq_pod_type *pt = &wq_pod_types[WQ_AFFN_SYSTEM];
/* 定义了标准工作池(worker pool)的默认优先级(nice 值)
数组的初始化值为 { 0, HIGHPRI_NICE_LEVEL }:
0 表示普通优先级(默认优先级)。
HIGHPRI_NICE_LEVEL 表示高优先级,用于高优先级工作队列。*/
int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL };
/* bh_pool_kick_normal 是用于普通优先级中断工作的处理函数。
bh_pool_kick_highpri 是用于高优先级中断工作的处理函数。
这些函数在初始化底半部(BH,Bottom Half)工作池时被调用,用于设置中断工作处理逻辑 */
void (*irq_work_fns[2])(struct irq_work *) = { bh_pool_kick_normal,
bh_pool_kick_highpri };
int i, cpu;
pwq_cache = KMEM_CACHE(pool_workqueue, SLAB_PANIC); //分配工作队列缓存
unbound_wq_update_pwq_attrs_buf = alloc_workqueue_attrs(); //attrs = kzalloc(sizeof(*attrs), GFP_KERNEL);
BUG_ON(!unbound_wq_update_pwq_attrs_buf);
/* initialize WQ_AFFN_SYSTEM pods */
pt->pod_cpus = kcalloc(1, sizeof(pt->pod_cpus[0]), GFP_KERNEL); //用于存储与 Pod 关联的 CPU 掩码
pt->pod_node = kcalloc(1, sizeof(pt->pod_node[0]), GFP_KERNEL); //用于存储与 Pod 关联的 NUMA 节点。
pt->cpu_pod = kcalloc(nr_cpu_ids, sizeof(pt->cpu_pod[0]), GFP_KERNEL); //用于存储每个 CPU 所属的 Pod 索引。
BUG_ON(!pt->pod_cpus || !pt->pod_node || !pt->cpu_pod);
pt->nr_pods = 1;
cpumask_copy(pt->pod_cpus[0], cpu_possible_mask);
pt->pod_node[0] = NUMA_NO_NODE;
pt->cpu_pod[0] = 0;
/* initialize BH and CPU pools */
for_each_possible_cpu(cpu) {
struct worker_pool *pool;
i = 0;
/* 初始化底半部(BH)工作池 */
for_each_bh_worker_pool(pool, cpu) {
init_cpu_worker_pool(pool, cpu, std_nice[i]);/* 设置其优先级 */
pool->flags |= POOL_BH;/* 设置工作池的标志位,表明这是一个底半部工作池 */
init_irq_work(bh_pool_irq_work(pool), irq_work_fns[i]);/* 初始化与中断相关的工作机制,指定中断处理函数 */
i++;
}
i = 0;
/* 初始化普通 CPU 工作池 */
for_each_cpu_worker_pool(pool, cpu)
init_cpu_worker_pool(pool, cpu, std_nice[i++]); /* 设置其优先级 */
}
/* 创建默认的未绑定和有序 WQ attrs*/
for (i = 0; i < NR_STD_WORKER_POOLS; i++) {
struct workqueue_attrs *attrs;
BUG_ON(!(attrs = alloc_workqueue_attrs()));
attrs->nice = std_nice[i];
unbound_std_wq_attrs[i] = attrs;
/*
* 有序的 wq 应该只有一个 pwq,因为 ordered 由 max_active 保证,而 pwqs 强制执行。
*/
BUG_ON(!(attrs = alloc_workqueue_attrs()));
attrs->nice = std_nice[i];
attrs->ordered = true;
ordered_wq_attrs[i] = attrs;
}
/* system_wq: 默认的工作队列,用于普通的异步任务。
system_highpri_wq: 高优先级工作队列,用于需要更高调度优先级的任务。
system_long_wq: 用于长时间运行的任务,避免阻塞其他短时任务。
system_unbound_wq: 非绑定工作队列,任务可以在任意 CPU 上运行,适用于不需要 CPU 亲和性的任务。
system_freezable_wq: 可冻结的工作队列,在系统挂起时可以被冻结。
system_power_efficient_wq: 节能工作队列,任务调度时优先考虑能耗优化。
system_freezable_power_efficient_wq: 结合了可冻结和节能特性的工作队列。
system_bh_wq: 底半部(Bottom Half)工作队列,用于处理软中断相关任务。
system_bh_highpri_wq: 高优先级的底半部工作队列,用于需要快速响应的软中断任务。 */
system_wq = alloc_workqueue("events", 0, 0);
system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0);
system_long_wq = alloc_workqueue("events_long", 0, 0);
system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND,
WQ_MAX_ACTIVE);
system_freezable_wq = alloc_workqueue("events_freezable",
WQ_FREEZABLE, 0);
system_power_efficient_wq = alloc_workqueue("events_power_efficient",
WQ_POWER_EFFICIENT, 0);
system_freezable_power_efficient_wq = alloc_workqueue("events_freezable_pwr_efficient",
WQ_FREEZABLE | WQ_POWER_EFFICIENT,
0);
system_bh_wq = alloc_workqueue("events_bh", WQ_BH, 0);
system_bh_highpri_wq = alloc_workqueue("events_bh_highpri",
WQ_BH | WQ_HIGHPRI, 0);
BUG_ON(!system_wq || !system_highpri_wq || !system_long_wq ||
!system_unbound_wq || !system_freezable_wq ||
!system_power_efficient_wq ||
!system_freezable_power_efficient_wq ||
!system_bh_wq || !system_bh_highpri_wq);
}
for_each_pool 遍历工作池
c
/**
* for_each_pool - 遍历系统中的所有工作池
* @pool: 迭代游标 * @pi: 用于迭代的整数 *
* 这必须在持有 wq_pool_mutex 或 RCU 读锁的情况下调用。如果池需要在生效的锁定之外使用,调用者有责任确保池保持在线。 *
* if/else 条件仅用于 lockdep 断言,可以忽略。
*/
#define for_each_pool(pool, pi) \
idr_for_each_entry(&worker_pool_idr, pool, pi) \
if (({ assert_rcu_or_pool_mutex(); false; })) { } \
else
is_chained_work 判断当前工作项是否是由同一工作队列执行的另一个工作项触发
c
/*
* 用于判断当前工作项是否是由同一工作队列(workqueue_struct)中的另一个工作项触发的。
* 这种情况通常被称为"链式工作"(chained work),即一个工作项在执行过程中将另一个工作项添加到同一工作队列中。。
*/
static bool is_chained_work(struct workqueue_struct *wq)
{
struct worker *worker;
/* 获取当前正在执行工作的线程 */
worker = current_wq_worker();
/*
* worker 是否非空,确保当前线程是一个工作线程
* 当前线程是否正在执行属于该工作队列的工作项
*/
return worker && worker->current_pwq->wq == wq;
}
work_struct_pwq 将工作项的 data 字段转换为 pool_workqueue
- 该函数用于从工作项的 data 字段中提取与之关联的工作池(pool_workqueue)。
- 该函数假设 data 字段包含了一个指向 pool_workqueue 的指针,并且使用 WORK_STRUCT_PWQ_MASK 进行掩码操作。
- 注意:此函数仅在工作项确实与一个 pool_workqueue 相关联时才有效。
c
static inline struct pool_workqueue *work_struct_pwq(unsigned long data)
{
return (struct pool_workqueue *)(data & WORK_STRUCT_PWQ_MASK);
}
get_work_pool 返回与给定作品关联的worker_pool
c
/**
* get_work_pool - 返回与给定作品关联的worker_pool
* @work:感兴趣的工作项
*
* 池在 wq_pool_mutex 下创建和销毁,并允许在 RCU 读取锁定下进行读取访问。 因此,应在 wq_pool_mutex 或 rcu_read_lock() 区域内调用此函数。
*
* 只要上述锁定有效,就可以访问返回的池的所有字段。 如果需要在关键部分之外使用返回的池,则调用方负责确保返回的池处于联机状态并保持联机状态。
*
* 返回:上次关联的worker_pool @work。 %NULL 如果无。
*/
static struct worker_pool *get_work_pool(struct work_struct *work)
{
/* 该字段包含了工作项的状态信息以及与工作池相关的标识 */
unsigned long data = atomic_long_read(&work->data);
int pool_id;
assert_rcu_or_pool_mutex();
/* 包含 WORK_STRUCT_PWQ 标志,则表示工作项直接关联到一个 pool_workqueue */
if (data & WORK_STRUCT_PWQ)
return work_struct_pwq(data)->pool;
/* 从 data 字段中提取工作池 ID */
pool_id = data >> WORK_OFFQ_POOL_SHIFT;
/* 表示工作项未关联到任何工作池 */
if (pool_id == WORK_OFFQ_POOL_NONE)
return NULL;
/* worker_pool_idr 是一个 ID 映射表,用于管理工作池的生命周期 */
return idr_find(&worker_pool_idr, pool_id);
}
find_worker_executing_work 查找正在执行工作的 worker
c
/**
* find_worker_executing_work - 查找正在执行工作的 worker
* @pool:兴趣池
* @work:寻找工人的工作
*
* 通过搜索 @pool->busy_hash(以 @work 地址为键),找到在 @pool 上执行@work的 worker。
* 要使 worker 匹配,其当前执行应与 @work 的地址及其 work function 匹配。
* 这是为了避免不相关的工作执行之间不必要的依赖关系,因为在执行时回收工作项。
*
* 这有点棘手。 工作项的执行开始后,可以释放该工作项,并且没有什么可以阻止释放的区域被回收用于另一个工作项。 * 如果相同的工作项地址在原始执行完成之前被重复使用,workqueue 会将回收的工作项标识为当前正在执行,
* 并使其等待当前执行完成,从而引入不需要的依赖关系。
*
* 该函数会检查工作项地址和工作函数,避免误报。
* 请注意,这并不完整,因为可能会构造一个工作函数,该函数可以通过回收的工作项引入对自身的依赖。
* 好吧,如果有人想那么狠地搬起石头砸自己的脚,我们能做的就只有这么多了,
* 如果这样的僵局真的发生,应该很容易找到罪魁祸首。
*
*上下文:
* raw_spin_lock_irq(pool->lock).
*
*返回:
* 指向正在执行的 worker 的指针 @work如果找到,否则为 %NULL。
*/
static struct worker *find_worker_executing_work(struct worker_pool *pool,
struct work_struct *work)
{
struct worker *worker;
hash_for_each_possible(pool->busy_hash, worker, hentry,
(unsigned long)work)
if (worker->current_work == work &&
worker->current_func == work->func)
return worker;
return NULL;
}
set_work_data 设置工作项的 data 字段
- 队列状态:
- 当工作项被排队时,WORK_STRUCT_PWQ 标志位被设置,表示工作项已关联到一个工作队列(pwq)。
- 此时,data 的非标志位部分存储指向队列的指针(pwq)。
- 一旦工作项开始执行,WORK_STRUCT_PWQ 标志位被清除,data 的高位部分存储脱队(OFFQ)标志和工作池 ID。
- 状态设置函数:
- set_work_pwq()、set_work_pool_and_clear_pending() 和 mark_work_canceling() 是用于设置工作项状态的函数。这些函数可以设置工作队列(pwq)、工作池(pool)或清除 work->data。
- 这些函数只能在工作项被"拥有"(即 PENDING 位被设置)时调用,确保工作项的状态不会被其他线程竞争修改。
- 状态获取函数:
- get_work_pool() 和 get_work_pwq() 用于获取工作项关联的工作池或工作队列。
- 工作池信息在工作项被排队后一直可用,直到工作项被同步取消。
- 工作队列信息仅在工作项处于队列中时可用。
c
/*
* 排队时,%WORK_STRUCT_PWQ 被设置,并且作品数据的非标志位包含指向排队 pwq 的指针。 执行开始后,标志将被清除,高位包含 OFFQ 标志和池 ID。
*
* set_work_pwq()、set_work_pool_and_clear_pending() 和 mark_work_canceling() 可用于设置 pwq、pool 或清除 work->数据。这些函数只能在作品被拥有时调用 - 即。当 PENDING 位被设置时。
*
* get_work_pool() 和 get_work_pwq() 可用于获取作品对应的 pool 或 pwq。 一旦工作在初始化后的任何位置排队,直到它被取消同步,Pool 就可用。 PWQ 仅在工作项排队时可用。
*/
static inline void set_work_data(struct work_struct *work, unsigned long data)
{
WARN_ON_ONCE(!work_pending(work));
atomic_long_set(&work->data, data | work_static(work));
}
set_work_pwq 设置工作项的工作池(pool_workqueue)
c
static void set_work_pwq(struct work_struct *work, struct pool_workqueue *pwq,
unsigned long flags)
{
/* WORK_STRUCT_PENDING:标志位,表示工作项处于挂起状态,尚未执行。
WORK_STRUCT_PWQ:标志位,表示工作项已关联到工作队列。
flags:额外的标志位,用于设置工作项的其他属性 */
set_work_data(work, (unsigned long)pwq | WORK_STRUCT_PENDING |
WORK_STRUCT_PWQ | flags);
}
insert_work 将一个工作项(work_struct)插入到工作池(pool_workqueue)的指定位置
c
/**
* insert_work - 将一个工作项(work_struct)插入到工作池(pool_workqueue)的指定位置
* @pwq:PWQ @work 属于
* @work:插入工件
* @head:插入点
* @extra_flags:要设置的额外 WORK_STRUCT_* 标志
*
* @head后插入属于 @pwq 的@work。 @extra_flags 是 OR'd 来work_struct标志。
*
*上下文:
* raw_spin_lock_irq(pool->lock).
*/
static void insert_work(struct pool_workqueue *pwq, struct work_struct *work,
struct list_head *head, unsigned int extra_flags)
{
debug_work_activate(work);
/* record the work call stack in order to print it in KASAN reports */
kasan_record_aux_stack(work);
/* 将工作项与工作池(pwq)关联,并设置额外的标志(extra_flags) */
set_work_pwq(work, pwq, extra_flags);
/* 将工作项插入到链表的尾部(head 指定插入点) */
list_add_tail(&work->entry, head);
/* 调用 get_pwq 增加工作池的引用计数,确保工作池在工作项执行期间不会被释放 */
get_pwq(pwq);
}
wq_node_nr_active 用于确定工作队列(workqueue_struct)在指定 NUMA 节点上的 wq_node_nr_active 数据结构
c
/**
* wq_node_nr_active - 确定要使用的wq_node_nr_active
* @wq:感兴趣的 workqueue
* @node:NUMA 节点,可以是 %NUMA_NO_NODE
*
* 确定用于 @node @wq 的wq_node_nr_active。返回:
*
* - %NULL 用于每 CPU 工作队列,因为它们不需要使用共享nr_active。
*
* - 如果 @node 为 %NUMA_NO_NODE,则node_nr_active[nr_node_ids]。
*
* - 否则,node_nr_active[@node]。
*/
static struct wq_node_nr_active *wq_node_nr_active(struct workqueue_struct *wq,
int node)
{
/* 工作队列不是无绑定类型(WQ_UNBOUND),直接返回 NULL
绑定工作队列不需要使用共享的活动计数 */
if (!(wq->flags & WQ_UNBOUND))
return NULL;
/* 传入的 NUMA 节点为 NUMA_NO_NODE,表示未指定具体节点。此时使用全局活动计数
(node_nr_active[nr_node_ids]) */
if (node == NUMA_NO_NODE)
node = nr_node_ids;
return wq->node_nr_active[node];
}
tryinc_node_nr_active 尝试增加 wq_node_nr_active 结构体中的活动计数(nr)
c
static bool tryinc_node_nr_active(struct wq_node_nr_active *nna)
{
int max = READ_ONCE(nna->max);
while (true) {
int old, tmp;
old = atomic_read(&nna->nr);
if (old >= max)
return false;
/* 果活动计数未达到最大值,尝试以线程安全的方式增加活动计数 */
tmp = atomic_cmpxchg_relaxed(&nna->nr, old, old + 1);
if (tmp == old)
return true;
}
}
pwq_tryinc_nr_active 尝试增加指定工作队列(pool_workqueue,简称 pwq)的活动工作项计数(nr_active)
c
/**
* pwq_tryinc_nr_active - 尝试增加 pwq 的 nr_active
* @pwq:感兴趣的pool_workqueue
* @fill:max_active可能已增加,请尝试提高并发级别
*
* 尝试增加 @pwq 的 nr_active。如果成功获取 nr_active 计数,则返回 %true。否则 LSE。
*/
static bool pwq_tryinc_nr_active(struct pool_workqueue *pwq, bool fill)
{
struct workqueue_struct *wq = pwq->wq;
struct worker_pool *pool = pwq->pool;
struct wq_node_nr_active *nna = wq_node_nr_active(wq, pool->node);
bool obtained = false;
lockdep_assert_held(&pool->lock);
/* 工作队列是绑定到特定 CPU 的(nna 为 NULL) */
if (!nna) {
/* BH 或per-CPU 工作队列,pwq->nr_active 就足够了 */
obtained = pwq->nr_active < READ_ONCE(wq->max_active);
goto out;
}
/* 标记为"已插入"(plugged),表示当前不允许增加活动 */
if (unlikely(pwq->plugged))
return false;
/*
* 未绑定的工作队列使用每个节点的共享nr_active $nna。
* 如果 @pwq 已在等待 $nna,则 pwq_dec_nr_active() 将保持并发级别。不要插队。
*
* max_active 增加后,我们需要忽略待处理的测试,因为 pwq_dec_nr_active() 只能保持并发水平,不能提高并发水平。这由 @fill 表示。
*/
/* 工作队列已经在等待增加活动计数(pending_node 非空),且未设置 fill 标志 */
if (!list_empty(&pwq->pending_node) && likely(!fill))
goto out;
/* 尝试无锁增加活动计数 */
obtained = tryinc_node_nr_active(nna);
if (obtained)
goto out;
/*
* 无锁采集失败。锁定,将自己添加到 $nna->pending_pwqs 并重试。smp_mb() 与 pwq_dec_nr_active() 中 atomic_dec_return() 的隐含内存屏障配对,以确保我们看到递减的 $nna->nr,或者他们看到非空的 $nna->pending_pwqs。
*/
raw_spin_lock(&nna->lock);
if (list_empty(&pwq->pending_node))
list_add_tail(&pwq->pending_node, &nna->pending_pwqs);
else if (likely(!fill))
goto out_unlock;
smp_mb();
obtained = tryinc_node_nr_active(nna);
/*
* 如果成功增加活动计数且未设置 fill 标志,从等待列表中移除当前工作队列
*/
if (obtained && likely(!fill))
list_del_init(&pwq->pending_node);
out_unlock:
raw_spin_unlock(&nna->lock);
out:
if (obtained)
pwq->nr_active++;
return obtained;
}
__queue_work 工作队列添加工作
c
static void __queue_work(int cpu, struct workqueue_struct *wq,
struct work_struct *work)
{
struct pool_workqueue *pwq;
struct worker_pool *last_pool, *pool;
unsigned int work_flags;
unsigned int req_cpu = cpu;
/*
*确保在调用此函数时中断被禁用。这是为了避免竞争条件,因为工作队列的操作可能涉及多个 CPU
*/
lockdep_assert_irqs_disabled();
/*
* 如果工作队列正在销毁(__WQ_DESTROYING)或排空(__WQ_DRAINING),则不允许添加新的工作项
*/
if (unlikely(wq->flags & (__WQ_DESTROYING | __WQ_DRAINING) &&
WARN_ONCE(!is_chained_work(wq), "workqueue: cannot queue %ps on wq %s\n",
work->func, wq->name))) {
return;
}
rcu_read_lock();
retry:
/* 如果工作队列是"无绑定"(WORK_CPU_UNBOUND),则选择一个合适的 CPU */
if (req_cpu == WORK_CPU_UNBOUND) {
if (wq->flags & WQ_UNBOUND)
cpu = wq_select_unbound_cpu(raw_smp_processor_id());
else
cpu = raw_smp_processor_id();
}
/* 读取工作池队列指针 */
pwq = rcu_dereference(*per_cpu_ptr(wq->cpu_pwq, cpu));
pool = pwq->pool;
/*
* 如果 @work 以前位于其他存储池中,它可能仍在该存储池中运行,在这种情况下,需要在该存储池中对工作进行排队,以保证不可重入。
*
* 对于有序工作队列,工作项必须在最新的 pwq 上排队,以便准确地管理订单。 保证订单还保证不可重入。 请参阅上面的注释 unplug_oldest_pwq()。
*/
last_pool = get_work_pool(work);
/* 检查工作项是否已经在另一个工作池中执行
并且工作队列不是有序的(__WQ_ORDERED)*/
if (last_pool && last_pool != pool && !(wq->flags & __WQ_ORDERED)) {
struct worker *worker;
/* 通过锁定上一个工作池(last_pool */
raw_spin_lock(&last_pool->lock);
/* 找到正在执行该工作项的工作线程(worker) */
worker = find_worker_executing_work(last_pool, work);
/* 找到的工作线程确实属于当前工作队列(wq) */
if (worker && worker->current_pwq->wq == wq) {
/* 更新目标工作池为该线程的当前工作池(current_pwq) */
pwq = worker->current_pwq;
pool = pwq->pool;
WARN_ON_ONCE(pool != last_pool);
} else {
/* 释放上一个工作池的锁,并锁定当前目标工作池,准备将工作项队列到当前池中 */
raw_spin_unlock(&last_pool->lock);
raw_spin_lock(&pool->lock);
}
} else {
raw_spin_lock(&pool->lock);
}
/*
* 如果工作池的引用计数为零(可能由于竞争条件导致),对于无绑定队列,会重试选择工作池
*/
if (unlikely(!pwq->refcnt)) {
if (wq->flags & WQ_UNBOUND) {
raw_spin_unlock(&pool->lock);
cpu_relax();
goto retry;
}
/* oops */
/* "oops",它通常用于表示某种错误、意外或问题。
在技术文档或代码注释中,"oops" 可能是开发者用来标记某个需要注意的地方 */
WARN_ONCE(true, "workqueue: per-cpu pwq for %s on cpu%d has 0 refcnt",
wq->name, cpu);
}
/* pwq determined, queue */
trace_workqueue_queue_work(req_cpu, pwq, work);
if (WARN_ON(!list_empty(&work->entry)))
goto out;
pwq->nr_in_flight[pwq->work_color]++;
work_flags = work_color_to_flags(pwq->work_color);
/*
* 如果工作项可以立即激活(pwq_tryinc_nr_active 返回 true),则将其插入到活动工作列表中
*/
if (list_empty(&pwq->inactive_works) && pwq_tryinc_nr_active(pwq, false)) {
if (list_empty(&pool->worklist))
pool->watchdog_ts = jiffies;
trace_workqueue_activate_work(work);
insert_work(pwq, work, &pool->worklist, work_flags);
kick_pool(pool);
} else {
/* 将其标记为非活动(WORK_STRUCT_INACTIVE),并插入到非活动工作列表中 */
work_flags |= WORK_STRUCT_INACTIVE;
insert_work(pwq, work, &pwq->inactive_works, work_flags);
}
out:
raw_spin_unlock(&pool->lock);
rcu_read_unlock();
}
__queue_delayed_work 工作队列添加延时工作
c
static void __queue_delayed_work(int cpu, struct workqueue_struct *wq,
struct delayed_work *dwork, unsigned long delay)
{
struct timer_list *timer = &dwork->timer;
struct work_struct *work = &dwork->work;
/* 例如工作队列是否为空、定时器函数是否正确、定时器是否已经挂起,以及工作项是否已经在队列中 */
WARN_ON_ONCE(!wq);
WARN_ON_ONCE(timer->function != delayed_work_timer_fn);
WARN_ON_ONCE(timer_pending(timer));
WARN_ON_ONCE(!list_empty(&work->entry));
/*
* 如果 @delay 为 0,则立即排队 @dwork->work。
* 这既是为了优化,也是为了正确性。
* @timer最早可以在最近的下一个时钟周期到期,delayed_work用户依赖于当 @delay 为 0 时没有这样的延迟。
*/
if (!delay) {
__queue_work(cpu, wq, &dwork->work);
return;
}
WARN_ON_ONCE(cpu != WORK_CPU_UNBOUND && !cpu_online(cpu));
dwork->wq = wq;
dwork->cpu = cpu;
/* ,设置定时器的到期时间为 jiffies + delay */
timer->expires = jiffies + delay;
/* 如果启用,表示某些 CPU 专门用于处理定时器等后台任务。 */
if (housekeeping_enabled(HK_TYPE_TIMER)) {
/*如果当前 cpu 是 housekeeping cpu,请使用它。 */
cpu = smp_processor_id();
if (!housekeeping_test_cpu(cpu, HK_TYPE_TIMER))
/* 获取一个合适的 housekeeping CPU */
cpu = housekeeping_any_cpu(HK_TYPE_TIMER);
/* 将定时器添加到选定的 CPU */
add_timer_on(timer, cpu);
} else {
if (likely(cpu == WORK_CPU_UNBOUND))
/* 使用全局计时器 */
add_timer_global(timer);
else
add_timer_on(timer, cpu);
}
}
queue_delayed_work_on 延迟后对特定 CPU 上的工作进行排队
c
/**
* queue_delayed_work_on - 延迟后对特定 CPU 上的工作进行排队
* @cpu:要执行工作的 CPU 编号
* @wq:要使用的 workqueue
* @dwork:要排队的工作
* @delay:排队前要等待的 jiffies 数
*
* 我们将delayed_work排队到特定的 CPU,对于非零延迟,调用者必须确保它在线且无法消失。
* 无法确保这一点的调用方可能会得到 @dwork->timer 排队到离线的 CPU,这将阻止 @dwork-> 的排队,除非离线的 CPU 再次联机。
*
* 返回:如果 @work 已在队列中,则返回 lse,否则为 %true。 如果 @delay 为零且 @dwork 处于空闲状态,则将安排立即执行。
*/
bool queue_delayed_work_on(int cpu, struct workqueue_struct *wq,
struct delayed_work *dwork, unsigned long delay)
{
struct work_struct *work = &dwork->work;
bool ret = false;
unsigned long irq_flags;
/* read the comment in __queue_work() */
local_irq_save(irq_flags);
/* 检查工作项是否已经在队列中。如果工作项已经排队,则返回 false */
if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work)) &&
/* 调用 clear_pending_if_disabled(work) 检查工作项是否被禁用。如果工作项被禁用,则不会继续排队 */
!clear_pending_if_disabled(work)) {
/* 将工作项添加到指定的 CPU 和工作队列中,并设置延迟时间 */
__queue_delayed_work(cpu, wq, dwork, delay);
ret = true;
}
local_irq_restore(irq_flags);
return ret;
}
EXPORT_SYMBOL(queue_delayed_work_on);
wq_worker_sleeping 处理一个工作线程(worker)进入睡眠状态时的相关逻辑
c
/**
* wq_worker_sleeping - worker 将要进入睡眠状态
* @task:任务进入休眠状态
*
* 当繁忙的 worker 为
* 要睡觉。
*/
void wq_worker_sleeping(struct task_struct *task)
{
struct worker *worker = kthread_data(task);
struct worker_pool *pool;
/*
* 救援人员,可能没有像平常那样设置所有字段
* 工人,也到这里,我们之前什么都不要访问
* 检查 NOT_RUNNING。
* 这是一个保护机制,避免对未运行的线程执行后续操作
*/
if (worker->flags & WORKER_NOT_RUNNING)
return;
pool = worker->pool;
/* 如果在到达 wq_worker_running() 之前被抢占,则返回*/
if (READ_ONCE(worker->sleeping))
return;
WRITE_ONCE(worker->sleeping, 1);
raw_spin_lock_irq(&pool->lock);
/*
* 如果 unbind_workers() 抢占了我们,请重新检查。我们没有
* 希望在 worker 解绑后递减 nr_running
* 和 nr_running 已被重置。
*/
if (worker->flags & WORKER_NOT_RUNNING) {
raw_spin_unlock_irq(&pool->lock);
return;
}
pool->nr_running--;
/* 调用 kick_pool 检查是否需要唤醒其他线程以维持工作池的并发性 */
if (kick_pool(pool))
worker->current_pwq->stats[PWQ_STAT_CM_WAKEUP]++;
raw_spin_unlock_irq(&pool->lock);
}
wq_cpu_intensive_thresh_init 初始化并动态调整wq_cpu_intensive_thresh_us
- wq_cpu_intensive_thresh_us:该阈值定义了工作队列(workqueue)子系统判定一个工作项(work item)为"CPU密集型"的时间上限。
- 其工作原理基于一个自适应的启发式算法:
- CPU密集型判定: 在工作队列的执行逻辑中,若一个工作项在持有工作者池(worker pool)内部锁的情况下,其执行时间超过 wq_cpu_intensive_thresh_us 微秒,该工作项将被分类为CPU密集型,并可能触发调度器对其进行特殊处理,以避免抢占其他重要工作。
- 动态调整的必要性: 一个固定的时间阈值无法适应性能差异巨大的各种硬件平台。对于高性能处理器,10毫秒可能已是相当长的时间;而对于低性能微控制器,这可能是一个常规操作所需的时间。因此,必须根据当前系统的计算能力来动态设定此阈值。
- 基于BogoMIPS的性能估算: 该函数利用内核在早期启动阶段通过calibrate_delay()计算出的loops_per_jiffy全局变量,来估算一个非精确但具备参考价值的性能指标------BogoMIPS。BogoMIPS粗略地反映了CPU执行简单指令循环的速度。
- 阈值缩放(Scaling)算法: 函数设定一个10毫秒的基准阈值。若计算出的BogoMIPS值低于4000,则判定系统为低性能平台,并启动缩放算法。该算法将阈值与BogoMIPS值成反比进行放大(thresh * 4000 / bogo),但上限不超过1秒。此算法确保了在计算能力较低的硬件上,该阈值会被放宽,以避免错误的密集型任务判定。
c
/*
* 这是一个静态的、仅在初始化阶段调用的函数。
*/
static void __init wq_cpu_intensive_thresh_init(void)
{
unsigned long thresh;
unsigned long bogo;
/*
* 创建一个专用的内核线程工作者,命名为"pool_workqueue_release",
* 用于异步处理pool_workqueue的释放操作。
*/
pwq_release_worker = kthread_run_worker(0, "pool_workqueue_release");
/* 若创建失败(IS_ERR返回true),则触发内核致命错误(BUG_ON)。*/
BUG_ON(IS_ERR(pwq_release_worker));
/* 注释:如果用户已将其设置为特定值,则保留该值。*/
/* 检查全局变量是否不等于其默认的未设置值(ULONG_MAX)。*/
if (wq_cpu_intensive_thresh_us != ULONG_MAX)
return;
/*
* 注释:默认的10ms阈值源于这样一个事实:大多数现代处理器(截至2023年)
* 在10ms内可以完成大量工作,并且这个时间刚好低于大多数人能感知到的延迟。
* 然而,内核也运行在包括微控制器在内的许多慢速CPU上,
* 对它们来说这个阈值太低了。
*
* 注释:如果BogoMips低于4000,我们就将阈值最高放大到1秒。
* 这绝非精确,但也不必要求精确。即使阈值被完全放大,
* 这个机制仍然是有用的。而且,由于报告通常适用于所有人,
* 少数机器在较长的阈值下运行,并不会显著降低其有用性。
*/
/* 设定基准阈值为10毫秒。*/
thresh = 10 * USEC_PER_MSEC;
/* 注释:参见init/calibrate.c中的lpj -> BogoMIPS计算方法。*/
/* 基于loops_per_jiffy变量估算BogoMIPS值。max_t确保结果最小为1。*/
bogo = max_t(unsigned long, loops_per_jiffy / 500000 * HZ, 1);
/* 若估算的BogoMIPS值低于4000,则执行阈值缩放。*/
if (bogo < 4000)
/* 根据公式按BogoMIPS值反比放大阈值,并确保结果不超过1秒。*/
thresh = min_t(unsigned long, thresh * 4000 / bogo, USEC_PER_SEC);
/* 输出包含计算过程关键变量的调试信息。*/
pr_debug("wq_cpu_intensive_thresh: lpj=%lu BogoMIPS=%lu thresh_us=%lu\n",
loops_per_jiffy, bogo, thresh);
/* 将最终计算出的阈值赋给全局变量。*/
wq_cpu_intensive_thresh_us = thresh;
}
workqueue_init 使工作队列子系统完全上线
c
/**
* workqueue_init - 使工作队列子系统完全上线
*
* 这是工作队列子系统三阶段初始化中的第二步,在内核线程可以被创建和调度后
* 立即被调用。此时,工作队列可能已经被创建,工作项也已排队,但还没有
* 任何工作者线程(kworkers)来执行它们。本函数为工作者池填充初始的
* 工作者,并使能未来的工作者创建。
*/
void __init workqueue_init(void)
{
struct workqueue_struct *wq;
struct worker_pool *pool;
int cpu, bkt;
/* 初始化用于CPU密集型工作队列的调度阈值。*/
wq_cpu_intensive_thresh_init();
/* 获取wq_pool_mutex锁,以保护对全局工作者池数据结构的并发访问。*/
mutex_lock(&wq_pool_mutex);
/*
* 注释:早期创建的per-cpu池可能缺少NUMA节点提示,这里进行修复。
* 同时,为已请求的工作队列创建救援线程。
*/
/* 遍历系统中所有可能的CPU核心。*/
for_each_possible_cpu(cpu) {
/* 为每个CPU的BH池和普通per-cpu池,根据CPU拓扑设置其NUMA节点(node)属性。*/
for_each_bh_worker_pool(pool, cpu)
pool->node = cpu_to_node(cpu);
for_each_cpu_worker_pool(pool, cpu)
pool->node = cpu_to_node(cpu);
}
/* 遍历全局的workqueues链表,检查所有已创建的工作队列。*/
list_for_each_entry(wq, &workqueues, list) {
/* 为需要救援服务的早期工作队列创建救援线程。若创建失败,则发出内核警告。*/
WARN(init_rescuer(wq),
"workqueue: failed to create early rescuer for %s",
wq->name);
}
/* 释放wq_pool_mutex锁。*/
mutex_unlock(&wq_pool_mutex);
/*
* 注释:创建初始的工作者。BH池有一个伪工作者,它代表了共享的BH执行
* 上下文,因此不受CPU热插拔事件影响。在这里为所有可能的CPU
* 创建BH伪工作者。
*/
/* 为系统中所有可能CPU的BH工作者池创建第一个工作者线程。*/
for_each_possible_cpu(cpu)
for_each_bh_worker_pool(pool, cpu)
BUG_ON(!create_worker(pool)); /* 若create_worker返回false,则触发内核致命错误。*/
/* 仅为当前在线的CPU的普通per-cpu工作者池创建第一个工作者线程。*/
for_each_online_cpu(cpu) {
for_each_cpu_worker_pool(pool, cpu) {
/* 清除该池的POOL_DISASSOCIATED标志位,表示其已与CPU关联。*/
pool->flags &= ~POOL_DISASSOCIATED;
/* 创建工作者线程,若失败则触发内核致命错误。*/
BUG_ON(!create_worker(pool));
}
}
/* 遍历存储unbound池的哈希表,为每个非绑定的工作者池创建第一个工作者线程。*/
hash_for_each(unbound_pool_hash, bkt, pool, hash_node)
BUG_ON(!create_worker(pool));
/* 设置全局标志位wq_online为true,表明工作队列子系统已功能完备。*/
wq_online = true;
/* 初始化工作队列的看门狗(watchdog)监控机制。*/
// wq_watchdog_init();
}
init_pod_type 根据特定的物理拓扑关系对系统中的所有CPU进行分组
c
/*
* 通过根据 cpus_share_pod() 函数首先初始化 pt->cpu_pod[] 来初始化 @pt。
* 每个共享一个 pod 的 CPU 子集都被分配一个唯一且连续的 pod ID。
* @pt 的其余部分也相应地进行初始化。
*/
/* 这是一个静态的、仅在初始化阶段运行的函数。
* @pt: 指向 wq_pod_type 结构的指针,此函数将填充该结构。
* @cpus_share_pod: 一个函数指针,接收两个CPU ID作为参数,
* 如果它们共享一个pod,则返回true,否则返回false。
* 这使得本函数可以根据不同标准(如共享SMT、缓存、NUMA)进行分组。
*/
static void __init init_pod_type(struct wq_pod_type *pt,
bool (*cpus_share_pod)(int, int))
{
/* cur: 当前正在处理的CPU ID。
* pre: 在cur之前、已经被处理过的CPU ID。
* cpu: 用于通用CPU遍历的变量。
* pod: 用于pod ID遍历的变量。*/
int cur, pre, cpu, pod;
/* 初始化pod的数量为0。*/
pt->nr_pods = 0;
/* 根据 cpus_share_pod() 函数来初始化 pt->cpu_pod[] 数组 */
/* 为 cpu_pod 数组分配内存。该数组的索引是CPU ID,值是该CPU所属的pod ID。
* nr_cpu_ids 是系统中可能存在的最大CPU数量。
* GFP_KERNEL 是内核内存分配的标志,表示在正常的内核上下文中进行分配。*/
pt->cpu_pod = kcalloc(nr_cpu_ids, sizeof(pt->cpu_pod[0]), GFP_KERNEL);
/* BUG_ON 是一个内核断言。如果内存分配失败(返回NULL),则系统会立即崩溃。
* 在初始化代码中这是合理的,因为如果基本结构无法分配,系统也无法正常运行。*/
BUG_ON(!pt->cpu_pod);
/* 遍历系统上所有可能的CPU ID,作为当前要分组的CPU。*/
/* 对于单核来说,这里直接break,不会走到外部传入函数 */
for_each_possible_cpu(cur) {
/* 再次遍历所有可能的CPU ID,作为已经处理过的CPU,用于比较。*/
for_each_possible_cpu(pre) {
/* 如果 pre 大于等于 cur,说明对于当前的 cur,所有在它之前的 pre 都已经
* 检查完毕,且没有找到可以共享的pod。因此,cur 属于一个新的pod。*/
if (pre >= cur) {
/* 将新的pod ID赋给当前CPU,这个ID就是当前的pod总数。*/
pt->cpu_pod[cur] = pt->nr_pods++;
/* 已经为 cur 分配了pod,跳出内层循环。*/
break;
}
/* 调用外部传入的函数,判断 cur 和 pre 是否共享同一个pod。*/
if (cpus_share_pod(cur, pre)) {
/* 如果共享,则将 pre 的pod ID赋给 cur。*/
pt->cpu_pod[cur] = pt->cpu_pod[pre];
/* 已经为 cur 分配了pod,跳出内层循环。*/
break;
}
}
}
/* 初始化结构的其余部分,以匹配 pt->cpu_pod[] 的内容 */
/* 分配 pod_cpus 数组,该数组的索引是pod ID,值是一个指向cpumask的指针。
* cpumask是一个位图,用于表示该pod包含哪些CPU。*/
pt->pod_cpus = kcalloc(pt->nr_pods, sizeof(pt->pod_cpus[0]), GFP_KERNEL);
/* 分配 pod_node 数组,该数组的索引是pod ID,值是该pod所属的NUMA节点ID。*/
pt->pod_node = kcalloc(pt->nr_pods, sizeof(pt->pod_node[0]), GFP_KERNEL);
/* 检查内存分配是否成功。*/
BUG_ON(!pt->pod_cpus || !pt->pod_node);
/* 遍历所有已发现的pod。*/
for (pod = 0; pod < pt->nr_pods; pod++)
/* 为每个pod动态分配一个cpumask变量并清零。zalloc_cpumask_var确保位图初始为空。*/
BUG_ON(!zalloc_cpumask_var(&pt->pod_cpus[pod], GFP_KERNEL));
/* 再次遍历所有可能的CPU,以填充反向映射。*/
for_each_possible_cpu(cpu) {
/* 在对应pod的cpumask中,设置代表当前cpu的位。
* pt->cpu_pod[cpu] 获取cpu的pod ID,
* pt->pod_cpus[...] 获取该pod的cpumask指针,
* cpumask_set_cpu 将该cpu添加到该cpumask中。*/
cpumask_set_cpu(cpu, pt->pod_cpus[pt->cpu_pod[cpu]]);
/* 获取当前CPU所属的NUMA节点,并将其存入pod的节点信息中。
* 这里假设一个pod内的所有CPU都属于同一个NUMA节点。*/
pt->pod_node[pt->cpu_pod[cpu]] = cpu_to_node(cpu);
}
}
unbound_wq_update_pwq 专门用于处理非绑定(unbound)工作队列在CPU热插拔(hotplug)事件发生时的状态更新
- 当一个CPU被添加或移除时,系统中CPU的拓扑结构会发生变化。对于非绑定工作队列,其工作项(work item)可以在一组允许的CPU上执行。此函数的作用就是,在CPU变化后,重新计算和调整该工作队列在特定CPU上关联的 pool_workqueue (pwq) 结构,确保工作项能够被调度到符合最新CPU亲和性掩码(cpumask)的处理器池中,从而维持系统的高效运行。在像STM32H7这样通常不支持CPU热插拔的嵌入式系统上,这个函数在运行时可能永远不会被调用,但它是通用内核所必需的一部分。
带注释的代码
c
/**
* unbound_wq_update_pwq - 为CPU热插拔事件更新一个pwq槽位
* @wq: 目标工作队列
* @cpu: 需要为其更新pwq槽位的CPU
*
* 这个函数应该从 %CPU_DOWN_PREPARE, %CPU_ONLINE 和
* %CPU_DOWN_FAILED 这些CPU热插拔状态通知中被调用。@cpu 与
* 正在被热插拔的CPU位于同一个pod中。
*
* 如果因为内存分配失败导致pod亲和性无法调整,
* 它会回退到使用 @wq->dfl_pwq,这可能不是最优的,但总是正确的。
*
* 注意,当一个跨越多个pod的cpumask的工作队列,其某个pod中
* 最后一个允许的CPU下线时,那些已经在为该工作队列执行工作项的
* worker线程将会失去它们的CPU亲和性,并可能在任何CPU上执行。
* 这与per-cpu工作队列在CPU_DOWN时的行为类似。如果一个工作队列的
* 用户需要严格的亲和性,用户有责任在CPU_DOWN_PREPARE阶段刷新其
* 工作项。
*/
static void unbound_wq_update_pwq(struct workqueue_struct *wq, int cpu)
{
struct pool_workqueue *old_pwq = NULL, *pwq;
struct workqueue_attrs *target_attrs;
// 断言,确保调用此函数时已经持有了 wq_pool_mutex 锁,防止竞争。
lockdep_assert_held(&wq_pool_mutex);
// 如果工作队列不是非绑定的(WQ_UNBOUND),或者是"有序的"非绑定队列,则直接返回。
// 因为有序队列有特殊的处理方式,不适用此逻辑。
if (!(wq->flags & WQ_UNBOUND) || wq->unbound_attrs->ordered)
return;
/*
* 我们不希望为每个工作队列在每个CPU上都分配/释放wq_attrs。
* 让我们使用一个预先分配好的。下面的缓冲区受到CPU热插拔排他锁的保护。
*/
// 使用一个静态的预分配缓冲区来存放目标属性,避免在热插拔路径中进行内存分配。
target_attrs = unbound_wq_update_pwq_attrs_buf;
// 将工作队列当前的非绑定属性复制到目标属性结构中。
copy_workqueue_attrs(target_attrs, wq->unbound_attrs);
// 根据系统当前在线的CPU掩码,将属性中的cpumask实体化。
wqattrs_actualize_cpumask(target_attrs, wq_unbound_cpumask);
/* 如果目标cpumask与当前的pwq匹配,则无需任何操作 */
// 计算与给定cpu关联的pod(处理器组)的cpumask。
wq_calc_pod_cpumask(target_attrs, cpu);
// 比较计算出的目标属性与当前该cpu上pwq所关联的池的属性是否相同。
if (wqattrs_equal(target_attrs, unbound_pwq(wq, cpu)->pool->attrs))
return; // 如果相同,则无需更新,直接返回。
/* 创建一个新的 pwq */
// 根据新的目标属性,为工作队列分配一个新的 pool_workqueue 结构。
pwq = alloc_unbound_pwq(wq, target_attrs);
// 如果分配失败。
if (!pwq) {
// 打印警告信息。
pr_warn("workqueue: allocation failed while updating CPU pod affinity of \"%s\"\n",
wq->name);
// 跳转到使用默认pwq的备用逻辑。
goto use_dfl_pwq;
}
/* 安装新的 pwq. */
// 锁定工作队列的互斥锁,以安全地修改其内部结构。
mutex_lock(&wq->mutex);
// 将新创建的pwq安装到指定cpu的槽位上,并返回旧的pwq。
old_pwq = install_unbound_pwq(wq, cpu, pwq);
// 跳转到解锁和清理步骤。
goto out_unlock;
use_dfl_pwq:
// 这是分配失败时的回退路径。
// 锁定工作队列互斥锁。
mutex_lock(&wq->mutex);
// 获取工作队列的默认pwq(unbound_pwq的cpu参数为-1)。
pwq = unbound_pwq(wq, -1);
// 锁定pwq所在池的自旋锁,并增加pwq的引用计数。
raw_spin_lock_irq(&pwq->pool->lock);
get_pwq(pwq);
raw_spin_unlock_irq(&pwq->pool->lock);
// 将这个默认的pwq安装到指定cpu的槽位上,并返回旧的pwq。
old_pwq = install_unbound_pwq(wq, cpu, pwq);
out_unlock:
// 解锁工作队列的互斥锁。
mutex_unlock(&wq->mutex);
// 减少旧pwq的引用计数,如果引用计数归零,则释放它。
put_pwq_unlocked(old_pwq);
}
workqueue_init_topology 为非绑定工作队列初始化CPU "pod"(荚/分组)
c
/**
* workqueue_init_topology - 为非绑定工作队列初始化CPU "pod"(荚/分组)
*
* 这是工作队列子系统三阶段初始化中的第三步,在SMP(对称多处理)和
* 拓扑信息完全初始化之后被调用。它相应地初始化非绑定的CPU pod。
*/
void __init workqueue_init_topology(void)
{
/* 声明一个指向 workqueue_struct 的指针 wq,用于遍历工作队列列表。*/
struct workqueue_struct *wq;
/* 声明一个整型变量 cpu,用于遍历在线的CPU。*/
int cpu;
/* 初始化 'WQ_AFFN_CPU' 类型的 pod。'cpus_dont_share' 表示每个CPU自成一组。
* 这是最细粒度的分组。*/
init_pod_type(&wq_pod_types[WQ_AFFN_CPU], cpus_dont_share);
/* 初始化 'WQ_AFFN_SMT' 类型的 pod。'cpus_share_smt' 表示共享SMT(如超线程)的
* 逻辑CPU被分为一组。*/
init_pod_type(&wq_pod_types[WQ_AFFN_SMT], cpus_share_smt);
/* 初始化 'WQ_AFFN_CACHE' 类型的 pod。'cpus_share_cache' 表示共享缓存
* (通常是L2或L3)的CPU被分为一组。*/
init_pod_type(&wq_pod_types[WQ_AFFN_CACHE], cpus_share_cache);
/* 初始化 'WQ_AFFN_NUMA' 类型的 pod。'cpus_share_numa' 表示属于同一个NUMA节点
* 的CPU被分为一组。*/
init_pod_type(&wq_pod_types[WQ_AFFN_NUMA], cpus_share_numa);
/* 设置全局标志,表示工作队列的拓扑结构已经初始化完成。*/
wq_topo_initialized = true;
/* 锁住 wq_pool_mutex 互斥锁,以保护对全局工作队列池的并发访问。*/
mutex_lock(&wq_pool_mutex);
/*
* 在此函数执行前就分配的工作队列,会把所有CPU都关联到默认的 worker pool。
* 现在需要显式地对所有已存在的工作队列和CPU的组合调用 unbound_wq_update_pwq(),
* 来应用基于 pod 的共享策略。
*/
/* 遍历全局的 'workqueues' 链表,该链表包含了系统中所有已创建的工作队列。*/
list_for_each_entry(wq, &workqueues, list) {
/* 对于每一个工作队列,遍历系统里每一个在线的CPU。*/
for_each_online_cpu(cpu)
/* 更新这个工作队列(wq)在指定CPU(cpu)上的 per-queue-worker-pool (pwq)
* 信息,使其与新的拓扑分组对齐。*/
unbound_wq_update_pwq(wq, cpu);
/* 如果该工作队列是一个非绑定(UNBOUND)工作队列。*/
if (wq->flags & WQ_UNBOUND) {
/* 锁住该工作队列自身的互斥锁,以保护其内部数据。*/
mutex_lock(&wq->mutex);
/* 根据新的拓扑信息,更新该工作队列在NUMA节点上的最大并发工作数量。
* -1 表示让系统自动计算。*/
wq_update_node_max_active(wq, -1);
/* 解锁该工作队列的互斥锁。*/
mutex_unlock(&wq->mutex);
}
}
/* 解锁全局的工作队列池互斥锁。*/
mutex_unlock(&wq_pool_mutex);
}