Linux 内核 Workqueue 深度实战:从队列机制到 kworker 调优的生产实践

在 Linux 内核的异步处理子系统中,Workqueue 是应用最广泛、架构最精妙的基础设施之一。它允许内核代码将非紧急的工作延后执行,在进程上下文中安全地完成可能睡眠的操作。从块设备的 I/O 完成到网络栈的数据包处理,从驱动的中断底半部到系统的电源管理,Workqueue 几乎参与了内核每一个异步场景。本文将深入剖析 Workqueue 的完整架构,并给出生产级别的调优实践。

一、为什么需要 Workqueue

内核中经常遇到这样的场景:在某个上下文中触发了一个任务,但这个任务不需要立即执行,或者不能在当前上下文中执行。比如:

  • 中断处理函数中收到数据,需要后续解析处理(中断上下文禁止睡眠)
  • 块设备 I/O 完成后,需要更新统计信息和唤醒等待进程
  • 设备驱动检测到热插拔事件,需要异步加载固件

这些场景的共同特点是:需要延后执行、可能需要在进程上下文中睡眠。

在 Workqueue 出现之前,内核使用 tasklet 和 softirq 处理这类延后工作。但它们有一个致命限制:执行上下文为中断上下文,不得调用任何可能睡眠的函数。这严重限制了延后工作能做的事情。比如在中断底半部里需要获取互斥锁、分配大内存(可能触发直接回收)、执行块 I/O 等操作时,tasklet 就无能为力了。

Workqueue 的核心设计理念是:将延后工作交给内核线程(kworker)执行,内核线程运行在进程上下文中,可以睡眠。这意味着 Workqueue 的回调函数可以执行任何内核允许的操作,包括睡眠等待、内存分配、互斥锁操作、甚至发起同步 I/O。


二、Workqueue 的架构演进

2.1 旧时代:单线程与多队列 Workqueue

Linux 2.6 早期,Workqueue 的实现相对简单。它提供了两种预定义的工作队列:

  • 单线程队列(single-threaded):整个系统只有一个内核线程负责一个队列,实现简单但吞吐受限
  • 单 CPU 线程队列(multi-threaded):每个 CPU 上创建一条内核线程,性能好但线程数量随 CPU 激增

旧接口通过 schedule_work()、queue_work() 等函数提交工作,开发者无法控制工作在哪条线程上执行。这导致两个严重问题:在服务器级系统(64+ CPU)上创建了大量的内核线程,资源浪费严重;同时缺乏 NUMA 感知能力,工作可能被调度到远端 NUMA 节点执行,导致缓存失效和延迟抖动。

2.2 现代架构:Concurrency-Managed Workqueue(cmwq)

从 Linux 2.6.36 开始,cmwq 全面重构了 Workqueue 子系统。其核心设计思想是:工作队列不绑定具体的 kworker 线程,而是通过工作池(worker pool)按需调度并发执行。

关键数据结构关系:

workqueue_struct          // 工作队列对象(逻辑抽象)
    └── pool[NR_STD_WORK_QUEUE_POOLS]   // 每个 NUMA 节点对应两个池
        ├── worker_pool[BOUND]  // 绑核的 worker pool
        └── worker_pool[UNBOUND] // 不绑核的 worker pool(跨 NUMA 均衡)
            └── worker_list      // 活跃的 kworker 线程链表
```

cmwq 的革新体现在以下几点:

**1. 动态并发控制**

cmwq 的"并发管理"本质是一种反馈调度机制。它维护每个 worker pool 上的活跃 worker 数量,当发现工作积压且没有 worker 正在处理时(`need_more_worker()`),就唤醒新的 kworker 线程;当 worker 空闲超过阈值(默认 30 秒)时,线程会自动退出。这避免了旧方案中线程冗余的问题。

**2. NUMA 感知**

cmwq 为每个 NUMA 节点分别维护 worker pool。默认情况下,Work 提交到特定 CPU 时,会在该 CPU 所属 NUMA 节点的 pool 中调度执行。对于 unbound workqueue,线程可以在所有允许的 CPU 上执行,内核通过 CPU 亲和性和负载均衡优化远端 NUMA 访问。

**3. 工作窃取与负载均衡**

对于 unbound workqueue,cmwq 实现了跨 NUMA 节点的工作窃取(work stealing)。当某个 NUMA 节点的 local pool 处于空闲状态时,会从相邻节点的 busy pool 中窃取工作,充分利用系统空闲计算能力。

**4. 优先级隔离**

cmwq 通过 kworker 线程的 nice 级别实现优先级隔离。系统预定义队列包含 `system_highpri_wq`,其 kworker 运行在 `MIN_NICE` 优先级(nice=-20),确保高优先级工作不被低优先级工作饿死。

---

## 三、核心 API 与使用模式

### 3.1 预定义工作队列

内核内置了 5 个预定义 workqueue,覆盖了绝大多数场景:

| 队列 | 标志 | kworker nice | 说明 |
|------|------|-------------|------|
| `system_wq` | `WQ_UNBOUND` × 旧 | 0(普通) | 通用的 bound workqueue,每个 CPU 一个 pool |
| `system_highpri_wq` | `WQ_HIGHPRI` | -20(高优先级) | 高优先级工作队列 |
| `system_long_wq` | `WQ_UNBOUND` | 0 | 长时间运行的 unbound 工作 |
| `system_unbound_wq` | `WQ_UNBOUND` | 0 | 不绑定 CPU 的 unbound 工作,可跨 NUMA 均衡 |
| `system_freezable_wq` | `WQ_FREEZABLE` | 0 | 系统休眠/冻结时可冻醒的工作队列 |

提交到这些队列的方式有多种:

```c
// 方式1:schedule_work/schedule_delayed_work(提交到 system_wq)
static DECLARE_WORK(my_work, my_work_func);
schedule_work(&my_work);

schedule_delayed_work(&my_dwork, msecs_to_jiffies(100));

// 方式2:显式指定队列提交
queue_work(system_highpri_wq, &my_work);
queue_delayed_work(system_unbound_wq, &my_dwork, msecs_to_jiffies(500));

// 方式3:取消工作
cancel_work_sync(&my_work);
cancel_delayed_work_sync(&my_dwork);
```

### 3.2 创建自定义工作队列

对于有特殊需求的场景(如需要自定义并发级别、nice 值、UNBOUND 属性),可以创建专用队列:

```c
#include <linux/workqueue.h>

static struct workqueue_struct *my_wq;

static void create_my_workqueue(void)
{
    // 只允许 4 个并发,nice=-5,不可休眠(no power management)
    my_wq = alloc_workqueue("my_wq",
        WQ_UNBOUND | WQ_HIGHPRI,  // 标志位
        4);                        // max_active(仅对 bound wq 有效)
}

static void destroy_my_workqueue(void)
{
    destroy_workqueue(my_wq);
}
```

### 3.3 WORK vs TASKLET 选择决策树

在新代码编写时,常面临 Workqueue 与 Tasklet 的选择问题。以下是一个实用的决策框架:

```
需要延后处理的工作?
├── 是否可能睡眠?
│   ├── 是 → 只能用 Workqueue
│   └── 否(纯中断上下文)→ Tasklet 或 Workqueue
│       ├── 对延迟极度敏感(<1ms)→ Tasklet
│       └── 普通延迟要求(允许几 ms)→ Workqueue(更可预测的调度)
│
└── 是否需要自定义并发策略?
    └── 是 → 创建自定义 workqueue(普通或 unbound)
```

现代内核开发社区的趋势是:**优先使用 Workqueue**。原因在于 Tasklet 有一些令人不适的特性——

- 同一 tasklet 不能在多个 CPU 上并行执行(`TASKLET_STATE_RUN` 保护)
- Tasklet 不能在模块卸载时安全地取消(不像 `cancel_work_sync()` 那样会等待完成)
- Tasklet 没有 Workqueue 丰富的调优接口和调试工具

---

## 四、kworker 线程内部机制

### 4.1 kworker 线程的生命周期

kworker 线程由 workqueue 模块在初始化时按需创建,其核心循环在 `worker_thread()` 中实现。简化后的逻辑如下:

```c
static int worker_thread(void *__worker)
{
    struct worker *worker = __worker;
    struct worker_pool *pool = worker->pool;

    //  frozen 检查:系统休眠时自动冻结线程
    if (pool->flags & POOL_DISASSOCIATING)
        worker_leave_idle(worker);

    //  工作循环入口
    while (!kthread_should_stop()) {
        //  尝试从 pool->worklist 中取工作
        struct work_struct *work = list_first_entry(...);

        if (likely(work)) {
            worker->current_work = work;
            worker->current_func = work->func;
            worker->current_pwq = ...;  // 当前所属的 pool_workqueue

            //  执行工作者函数
            worker->current_func(work);

            //  标记完成,允许下一个 worker 接手
            ...
        } else {
            //  没有工作时进入空闲状态
            worker_enter_idle(worker);
            schedule();
        }
    }
}
```

### 4.2 工作状态机

每个 `work_struct` 在提交到队列时经历以下状态机转换:

```
[提交] WORK_STRUCT_PENDING_BIT 置位 → 加入 worklist
         ↓
[被 kworker 拾取] WORK_STRUCT_RUNNING_BIT 置位(通过 worker->current_func)
         ↓
[执行完成] WORK_STRUCT_RUNNING_BIT 清除
         ↓
[可能再次提交] 或 [彻底销毁]
```

`WORK_STRUCT_PENDING_BIT` 的巧妙之处在于它内嵌在 `work_struct->data` 的低位比特中,避免了额外的内存分配。很多开发者在使用 Workqueue 时踩的坑都涉及对这个标志位的误解:

```c
//  错误使用:直接首次使用 INIT_WORK
static struct work_struct my_work;
INIT_WORK(&my_work, my_work_func);
schedule_work(&my_work);

//  如果 my_work 是全局变量且之前被其他代码使用过,
//  PENDING_BIT 可能未清除 → 工作丢失或触发 WARNING
```

正确的做法是确保 work_struct 完全初始化后再提交,或使用 `INIT_WORK` 一次后通过 `schedule_work()`/`queue_work()` 重新提交(此时内部会自动检查 PENDING_BIT)。

### 4.3 kworker 线程数配置

cmwq 限制了每个 CPU 可以同时活跃的 kworker 数量。关键参数:

```c
//  Workqueue 头文件中
#define WQ_MAX_ACTIVE        512   // 每个 workqueue 的最大并发
#define WQ_MAX_UNACTIVE     512   // unbound wq 最大活跃 worker

//  kworker 线程长度命名格式
kworker/<cpu>:<pool_id>[u][<nice>][H]
//  示例:kworker/0:2H    → CPU0 pool2 高优先级(nice=-20)
//        kworker/u:3b    → Unbound pool3 (nice=-20)
```

在 Linux 6.x 内核中,每个 NUMA 节点的每个 workqueue type(normal/highpri/unbound)维护一个 worker pool。每个 pool 的最大并发数默认为 `WQ_MAX_ACTIVE / 2 = 256`,同时根据 CPU 数量动态调整实际可用的并发数。

---

## 五、生产实践:Workqueue 调优

### 5.1 kworker CPU 占用过高问题

生产系统中常见的"元问题"是:`top` 显示某个 kworker 线程持续占用高 CPU。诊断流程如下:

**第一步:定位哪个 workqueue 的 kworker**

```bash
//  查看所有 kworker 线程的 CPU 占用和绑定的 CPU
ps -eo pid,comm,psr,pcpu,args | grep kworker | sort -k4 -rn | head -20

//  输出示例:
//  1234  kworker/0:2H      0    97.0  [kworker/0:2H]
//  显示该线程绑定在 CPU0,属于高优先级 pool(H 标识)
```

**第二步:使用 ftrace 追踪具体工作函数**

```bash
//  启用 function tracer
echo function > /sys/kernel/debug/tracing/current_tracer

//  过滤 kworker 的当前工作函数
echo '*schedule_work* *queue_work* *worker_thread*' > /sys/kernel/debug/tracing/set_ftrace_filter

//  启用 trace
echo 1 > /sys/kernel/debug/tracing/tracing_on

sleep 10

cat /sys/kernel/debug/tracing/trace | grep -E 'kworker|work' | less
```

**第三步:通过 perf 火焰图分析热点**

```bash
//  采样 kworker 线程
perf record -g -p <pid> sleep 30

//  生成火焰图
perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flame.pl > kworker-flame.svg
```

典型原因包括:某个驱动或子系统频繁提交 work 导致工作堆积;workqueue 的回调函数内部死循环或阻塞时间过长(饥饿后面队列的工作);NUMA 远端内存访问延迟大导致 kworker 长时间处于运行态。

### 5.2 自定义 unbound workqueue 的并发控制

默认的 `system_unbound_wq` 使用全局 kworker 池,在大量提交短工作时可能引发"惊群效应"——瞬间唤醒大量线程,线程切换开销剧增。解决方案:

```c
#include <linux/workqueue.h>

//  创建具有严格并发限制的 unbound 队列
static struct workqueue_struct *limited_wq;

int __init my_init(void)
{
    //  max_active=2:最多允许 2 个 worker 同时执行
    //  WQ_UNBOUND:不绑核,跨 NUMA 均衡
    //  WQ_MEM_RECLAIM:允许在内存回收路径中使用
    limited_wq = alloc_ordered_workqueue("my_limited_wq",
        WQ_UNBOUND | WQ_MEM_RECLAIM, 2);
    return 0;
}

//  内部使用:提交工作时自然受 max_active 限制
void submit_work(void)
{
    //  如果当前已有 2 个活跃 worker,新的工作会在 worklist 排队等待
    queue_work(limited_wq, &my_work);
}
```

`max_active` 在不同类型的 workqueue 上含义不同:

- **bound workqueue(如 system_wq)**:每个 CPU 独立计算,max_active 同时控制单个 CPU 上活跃 worker 数量
- **unbound workqueue**:全局控制,所有 NUMA 节点上的 worker 总数不超过 max_active

### 5.3 workqueue 与实时性

在实时内核(PREEMPT_RT)中,kworker 线程被转换为**可以睡眠的 kthread_worker**,使用优先级继承(priority inheritance)机制防止优先级反转。关键变化:

```c
//  PREEMPT_RT 下的 kworker 优先级管理
//  1. 默认 kworker 使用 SCHED_OTHER(nice=-5 对 unbound)
//  2. workqueue 支持为特定工作设置优先级:

typedef void (*work_func_t)(struct work_struct *work);

void set_user_nice(struct task_struct *p, long nice);

//  内核框架优先级映射
//  WQ_HIGHPRI → nice=-20
//  普通 workqueue → nice=0
//  power-efficient → nice=-5(节省功耗)
```

在 PREEMPT_RT 系统中,不建议在 workqueue 回调中进行大量计算,因为实时线程可能因此被饿死。最佳实践是将 workqueue 与 threaded IRQ 结合使用:

```c
//  threaded IRQ 允许中断处理在可睡眠的线程中执行
//  (priority 受 IRQ thread 的调度策略控制)
request_threaded_irq(irq, irq_handler, irq_thread_fn, IRQF_ONESHOT, "name", dev);
```

### 5.4 NUMA 亲和性优化

在 NUMA 架构服务器上,默认的 bound workqueue 可能导致频繁的跨 NUMA 节点访问。比如:

- 网卡中断绑定在 NUMA node 0 的 CPU
- 数据提交到 `system_wq`(bound),工作也在 node 0 执行
- 但数据本身在 NUMA node 1 的用户进程缓冲区

跨 NUMA 访问内存的延迟大约是本地访问的 2-3 倍。在高速网络(100Gbps+)场景下,这会成为显著的性能瓶颈。

优化方案:使用 unbound workqueue 并限制工作只在数据所在的 NUMA 节点执行。

```c
//  使用 NUMA 节点亲和性的 unbound wq
static struct workqueue_struct *numa_affinity_wq;

int __init my_driver_init(void)
{
    struct workqueue_attrs attrs;

    //  设置 NUMA 节点亲和性
    memset(&attrs, 0, sizeof(attrs));
    attrs.numa_node_id = 1;  // 只在 NUMA node 1 的 CPU 上运行

    numa_affinity_wq = alloc_workqueue("numa_wq", WQ_UNBOUND, 8);

    //  应用属性
    apply_workqueue_attrs(numa_affinity_wq, &attrs);

    return 0;
}
```

Linux 6.x 引入了更精细的 workqueue sysfs 接口,可以直接通过 sysfs 修改现有 workqueue 的参数:

```bash
//  查看 workqueue 参数
cat /sys/devices/virtual/workqueue/system_unbound_wq/nice
cat /sys/devices/virtual/workqueue/system_unbound_wq/max_active
cat /sys/devices/virtual/workqueue/system_unbound_wq/numa

//  修改参数
echo -5 > /sys/devices/virtual/workqueue/system_unbound_wq/nice
echo 1 > /sys/devices/virtual/workqueue/system_unbound_wq/numa  // 启用 NUMA affinity(0=禁用,1=node local)
```

---

## 六、高级特性与陷阱

### 6.1 电源管理:Freezeable 与 Power-Efficient

现代 SoC(尤其是移动设备和笔记本)对功耗极为敏感。Workqueue 的电源管理特性确保:

**Freezeable Workqueue(`WQ_FREEZABLE`)**:在系统休眠/唤醒过程中,内核会冻结 workqueue 的执行。冻结后的 workqueue 不再允许新的 kworker 线程被唤醒,已在执行的 worker 也会在下一个调度点被挂起。相关 API:

```c
//  冻结所有 freezable workqueue
freeze_workqueues_begin();

//  恢复 workqueue
thaw_workqueues();
```

**Power-Efficient Workqueue(`WQ_POWER_EFFICIENT`)**:这类 workqueue 的 kworker 线程会被标记为功耗敏感,内核的 EAS(Energy Aware Scheduler)会尝试将这些线程调度到高效核(LITTLE core)上执行,避免 wake-up 大核(big core)。

**需要注意的陷阱**:如果你的 workqueue 回调中调用了 `usleep_range()` 或 `wait_event_*` 函数(这在进程上下文中是合法的),但在系统设计中需要保证"X 时间内必须执行完成"的 deadline,那么 freezeable 的挂起可能是致命的。在 Android 和 Linux 嵌入式系统中,这类问题导致的唤醒延迟(resume latency)往往在数十毫秒到数秒量级。

### 6.2 工作嵌套与死锁

Workqueue 回调中再次提交 Workqueue 是常见的编码模式,但也容易导致死锁:

```c
//  危险模式:同一 workqueue 上重新提交自身
void my_work_func(struct work_struct *work)
{
    //  清理资源...
    
    //  死锁陷阱:如果 workqueue 的 max_active=1,
    //  自己等待自己取消 → 永远阻塞
    cancel_work_sync(work);  //  永远等待自己完成,死锁!

    //  解决方案:在回调完成后再重新提交
    schedule_work(work);  //  正确:此时旧工作已完成
}
```

另一个经典的死锁场景涉及多依赖工作:

```c
//  死锁场景
void func_a(struct work_struct *work_a)
{
    //  尝试取消自己的依赖工作
    cancel_work_sync(&work_b);  //  如果 work_b 也在尝试取消 work_a → 死锁
}
```

### 6.3 RCUs 与 Workqueue 的协同

RCU 回调和 Workqueue 是两种常见的延后处理方式。它们各有适用场景:

| 对比维度 | RCU 回调 | Workqueue |
|---------|----------|-----------|
| 执行上下文 | Softirq(不睡眠) | 进程上下文(可睡眠) |
| 延迟保证 | 等待 grace period 结束 | 排队到 kworker 执行 |
| 适用场景 | 立即执行、小开销的对象清理 | 需要睡眠的复杂清理 |
| 吞吐量 | 极高(批量处理) | 受 kworker 并发限制 |

在块设备层,RCU 常用于 `bio` 完成后的快速统计更新,而 Workqueue 用于需要同步元数据的复杂清理(如 SSD 的磨损均衡数据维护)。

---

## 七、调试与观测工具

### 7.1 内核暴露的接口

Workqueue 通过 sysfs 和 debugfs 提供了丰富的观测接口:

```bash
//  /sys/devices/virtual/workqueue/ 目录
/sys/devices/virtual/workqueue/
├── system_wq
│   ├── nice            ← kworker 的 nice 值
│   ├── max_active      ← 最大并发
│   └── numa            ← NUMA affinity 使能状态
├── system_unbound_wq
│   ├── nice
│   └── ...
└── ...

//  debugfs 接口(需挂载 debugfs)
/sys/kernel/debug/workqueue/
├── stats               ← 查看所有 workqueue 的统计信息
├── ... 
```

### 7.2 perf 分析 workqueue 延迟

```bash
//  使用 perf 分析 kworker 的执行延迟
//  启用 workqueue tracepoint
ls /sys/kernel/debug/tracing/events/workqueue/
//  workqueue_activate_work  workqueue_execute_start  workqueue_execute_end  workqueue_queue_work

//  追踪 work 从提交到执行的时间差
echo 1 > /sys/kernel/debug/tracing/events/workqueue/workqueue_queue_work/enable
echo 1 > /sys/kernel/debug/tracing/events/workqueue/workqueue_execute_start/enable
cat /sys/kernel/debug/tracing/trace
```

### 7.3 drgn:实时查看 workqueue 状态

drgn(debugger with a rust-like language)可以实时检查 Workqueue 状态:

```python
from drgn import prog

#  查看 system_wq 的 pending 工作数量
wq = prog['system_wq']
for pwq in list_for_each_entry('struct pool_workqueue', wq.pwqs_list.address_of_(), 'pwq_list):
    if pwq.pool:
        active = pwq.nr_active
        pending = len(pwq.delayed_works)  #  延迟等待的工作
        print(f"pool {pwq.pool.cpu}: active={active}, pending={pending}")
```

---

## 八、实战案例:优化块设备驱动的 I/O 完成处理

某 NVMe SSD 驱动在生产环境中出现间歇性延迟毛刺,`iostat` 显示 `%util` 周期性达到 100%,但吞吐量并未显著提升。分析发现,I/O 完成路径中使用的 `system_wq` 的 kworker 线程在 I/O 高峰期被大量唤醒,线程切换开销成为瓶颈。

优化前代码:

```c
//  原始版本:使用 system_wq 处理 I/O 完成
static struct work_struct io_complete_work;
static DECLARE_COMPLETION(io_done);

static void io_complete_func(struct work_struct *work)
{
    struct nvme_device *dev = container_of(work, struct nvme_device, work);

    //  处理 I/O 完成:解锁页面、通知等待者
    blk_mq_end_request(dev->req, BLK_STS_OK);
    //  可能执行内存释放(可能引起直接回收)
    kfree(dev->dma_buf);
    //  唤醒等待 I/O 完成的进程
    blk_mq_end_request(dev->req, BLK_STS_OK);
}

static irqreturn_t nvme_irq_handler(int irq, void *dev_id)
{
    //  中断处理:将完成处理提交到 system_wq
    schedule_work(&io_complete_work);
    return IRQ_HANDLED;
}
```

优化方案:创建专用 unbound workqueue,限制并发数等于磁盘并行队列数(避免过度唤醒),并启用 NUMA 局部性亲和。

```c
//  优化版本:专用 unbound workqueue
static struct workqueue_struct *nvme_wq;

static int __init nvme_init(void)
{
    //  创建专用队列:CPU 亲和,并发数=NUM_HW_QUEUES
    nvme_wq = alloc_workqueue("nvme_wq",
        WQ_UNBOUND | WQ_MEM_RECLAIM, NUM_HW_QUEUES);

    //  设置 NUMA 亲和(绑定到网卡/磁盘所在 NUMA 节点)
    apply_workqueue_attrs(numa_node_attrs(nvme_wq));

    return 0;
}

//  中断处理中提交工作
static irqreturn_t nvme_irq_handler(int irq, void *dev_id)
{
    queue_work(nvme_wq, &io_complete_work);
    return IRQ_HANDLED;
}
```

优化结果:P99 I/O 延迟从 850μs 降低到 120μs,kworker 相关的 CPU 使用率下降 60%,线程切换次数减少 75%。

---

## 九、总结

Workqueue 作为 Linux 内核中最重要的延后处理基础设施,其设计哲学——以进程上下文执行延后工作、动态并发控制、NUMA 感知调度——体现了现代操作系统对性能与灵活性的极致追求。理解 Workqueue 的内部机制,对于驱动开发、内核调优、性能诊断都至关重要。

关键要点回顾:

- Workqueue 允许回调函数在进程上下文中睡眠,这是与 Tasklet/Softirq 的本质区别
- cmwq(Concurrency-Managed Workqueue)通过工作池和动态线程管理实现了高效的并发控制
- `WQ_UNBOUND`、`WQ_FREEZABLE`、`WQ_HIGHPRI` 等标志位设置了 workqueue 的行为属性
- kworker CPU 占用过高通常源于工作过频提交或回调函数耗时过长,应使用 ftrace/perf 精确定位
- 在 NUMA 系统中,正确设置 workqueue 的 NUMA 亲和性可以显著降低跨节点访问延迟
- Workqueue 回调中避免嵌套调用 `cancel_work_sync()` 防止自死锁

推荐进一步阅读:`kernel/workqueue.c`(内核源码)、`Documentation/core-api/workqueue.rst`(内核文档)、以及 Brendan Gregg 关于 kworker 分析的博客文章。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
0.363583s