跳转至

第四篇:RCU 诊断 — 拖延检测、加速 GP 与回调卸载

源码:kernel/rcu/tree_stall.h (1185行) | tree_exp.h (1118行) | tree_nocb.h (1702行) | tree_plugin.h (1369行)

系列目录:RCU 内核源码深度解析


1. RCU CPU 拖延检测 —— Stall Detection

1.1 什么是 RCU Stall?

当 CPU 在 RCU 读端临界区内停留过久,或 GP kthread 长时间没有进展时,RCU 子系统发出 "RCU CPU stall warning"。这通常意味着内核存在性能问题或死锁。

正常情况:                             Stall 情况:
CPU: [rcu_read_lock() ─ 工作 ─ rcu_read_unlock()]
      ←────  几百微秒到几毫秒  ────→

                                    CPU: [rcu_read_lock() ── 死循环/spinlock ── ...]
                                          ←────  数十秒  ────→
                                                                ↑ stall detected!

1.2 拖延阈值常量

来自 tree_stall.h:74-80

// tree_stall.h:74-80
#ifdef CONFIG_PROVE_RCU
#define RCU_STALL_DELAY_DELTA       (5 * HZ)    // PROVE_RCU 下增加 5 秒容限
#else
#define RCU_STALL_DELAY_DELTA       0
#endif
#define RCU_STALL_MIGHT_DIV         8
#define RCU_STALL_MIGHT_MIN         (2 * HZ)    // 最小 2 秒

最终的超时计算在 rcu_jiffies_till_stall_check()tree_stall.h:111-128):

// tree_stall.h:111-128
int rcu_jiffies_till_stall_check(void)
{
    int till_stall_check = READ_ONCE(rcu_cpu_stall_timeout);

    // 限制在 3~300 秒范围内
    if (till_stall_check < 3) {
        WRITE_ONCE(rcu_cpu_stall_timeout, 3);
        till_stall_check = 3;
    } else if (till_stall_check > 300) {
        WRITE_ONCE(rcu_cpu_stall_timeout, 300);
        till_stall_check = 300;
    }
    return till_stall_check * HZ + RCU_STALL_DELAY_DELTA;
}

默认超时 = CONFIG_RCU_CPU_STALL_TIMEOUT × HZ,可通过 /sys/module/rcupdate/parameters/rcu_cpu_stall_timeout 调整。

1.3 sysctl 与 sysfs 接口

sysctl 接口tree_stall.h:20-50):

// tree_stall.h:20-50
static int sysctl_panic_on_rcu_stall __read_mostly;       // 检测到 stall 即内核 panic
static int sysctl_max_rcu_stall_to_panic __read_mostly;  // 最多容忍多少次 stall 后 panic

static int __init init_rcu_stall_sysctl(void)
{
    register_sysctl_init("kernel", rcu_stall_sysctl_table);
    return 0;
}
subsys_initcall(init_rcu_stall_sysctl);  // 内建于 RCU 初始化中

可通过 /proc/sys/kernel/panic_on_rcu_stall/proc/sys/kernel/max_rcu_stall_to_panic 控制。

sysfs 接口tree_stall.h:52-70):

// tree_stall.h:52-70
static ssize_t rcu_stall_count_show(struct kobject *kobj, ...)
{
    return sysfs_emit(page, "%u\n", rcu_stall_count);  // 累加计数器
}
late_initcall(kernel_rcu_stall_sysfs_init);  // 较晚注册 → /sys/kernel/rcu_stall_count

1.4 Stall 抑制机制

内核 panic 时抑制 stall 警告(tree_stall.h:144-152):

// tree_stall.h:144-152
static int rcu_panic(struct notifier_block *this, unsigned long ev, void *ptr)
{
    rcu_cpu_stall_suppress = 1;  // 抑制所有 stall 输出
    return NOTIFY_DONE;
}
early_initcall(check_cpu_stall_init);

警告输出期间使用 nbcon_cpu_emergency_enter/exit() 确保可抢占控制台。

1.5 Stall 检测流程

时钟中断 → rcu_flavor_sched_clock_irq()    // tree.c
          check_cpu_stall(rdp)              // tree_stall.h:772
          ┌ gp_seq 未变化? ─→ return        // GP 已完成
          │  ↓ changed
          │ jiffies > jiffies_stall? ─→ return // 未到超时
          │  ↓ yes
          │ 自己是拖延者(self_detected)?
          │ ├─ yes → print_cpu_stall()      // tree_stall.h:709
          │ └─ no  → print_other_cpu_stall() // tree_stall.h:631
        (可选) panic_on_rcu_stall()          // tree_stall.h:162

1.6 check_cpu_stall() 核心逻辑(tree_stall.h:772-867

// tree_stall.h:772-867 (核心逻辑简化)
static void check_cpu_stall(struct rcu_data *rdp)
{
    // 检查是否应该抑制 stall 输出
    if ((rcu_stall_is_suppressed() && !rcu_kick_kthreads) || !rcu_gp_in_progress())
        return;

    // 通过 gp_seq 二次读取来消除误报(false positive)
    gs1 = READ_ONCE(rcu_state.gp_seq);
    smp_rmb();
    js = READ_ONCE(rcu_state.jiffies_stall);
    smp_rmb();
    gps = READ_ONCE(rcu_state.gp_start);
    smp_rmb();
    gs2 = READ_ONCE(rcu_state.gp_seq);

    // 如果两次 gp_seq 不同则 GP 已完成,退出
    if (gs1 != gs2 || ULONG_CMP_LT(j, js) || ULONG_CMP_GE(gps, js))
        return;

    self_detected = READ_ONCE(rnp->qsmask) & rdp->grpmask;
    if (self_detected) {
        print_cpu_stall(gs2, gps);       // 自己拖延
    } else {
        print_other_cpu_stall(gs2, gps); // 其他 CPU 拖延
    }
}

误报预防:通过两次读取 gp_seq、中间插入内存屏障,保证在一个完整 GP 的上下文中判断,避免跨 GP 的时序问题。

1.7 print_cpu_stall() — 自我拖延报告(tree_stall.h:709-767

// tree_stall.h:709-767
static void print_cpu_stall(unsigned long gp_seq, unsigned long gps)
{
    pr_err("INFO: %s self-detected stall on CPU\n", rcu_state.name);
    print_cpu_stall_info(smp_processor_id());  // 输出当前 CPU 状态
    // 输出:t=... jiffies g=... q=... ncpus=...
    rcu_check_gp_kthread_expired_fqs_timer();  // 检查 FQS 定时器是否过期
    rcu_check_gp_kthread_starvation();         // 检查 GP kthread 是否饥饿
    rcu_dump_cpu_stacks(gp_seq);               // dump 所有 CPU 栈
    set_need_resched_current();                // 强制重新调度
}

1.8 print_other_cpu_stall() — 标记他人拖延(tree_stall.h:631-707

// tree_stall.h:631-707
static void print_other_cpu_stall(unsigned long gp_seq, unsigned long gps)
{
    pr_err("INFO: %s detected stalls on CPUs/tasks:\n", rcu_state.name);
    rcu_for_each_leaf_node(rnp) {
        if (rnp->qsmask != 0) {
            for_each_leaf_node_possible_cpu(rnp, cpu)
                if (rnp->qsmask & leaf_node_cpu_bit(rnp, cpu)) {
                    print_cpu_stall_info(cpu);  // 逐 CPU 输出诊断信息
                    ndetected++;
                }
        }
        ndetected += rcu_print_task_stall(rnp, flags);  // 阻塞任务
    }
    pr_err("\t(detected by %d, t=%ld jiffies, g=%ld, q=%lu)\n", ...);
}

1.9 print_cpu_stall_info() — 单 CPU 诊断信息(tree_stall.h:518-566

输出格式:

  CPU#-OoN.: (2000 ticks this GP) idle=... softirq=... fqs=...

格式解析: - O → CPU 在线,o → qsmaskinit 中,N → qsmaskinitnext 中 - 第四个字符 → irq_work 状态(数字=待处理的 GP 数量,!=无待处理,?=未配置)

1.10 GP kthread 饥饿检测(tree_stall.h:569-599

// tree_stall.h:569-599
static void rcu_check_gp_kthread_starvation(void)
{
    if (rcu_is_gp_kthread_starving(&j)) {
        pr_err("%s kthread starved for %ld jiffies!...\n", ...);
        pr_err("\tUnless %s kthread gets sufficient CPU time, OOM is expected.\n", ...);
        sched_show_task(gpk);   // dump GP kthread 栈
        wake_up_process(gpk);   // 尝试唤醒
    }
}

1.11 典型 Stall 消息解读示例

INFO: rcu_preempt self-detected stall on CPU
    1-...: (21002 ticks this GP) idle=e3a/1/0 softirq=52304/52304 fqs=3431
    (t=21002 jiffies g=5897 q=129 ncpus=8)

含义: - CPU 1 自我检测到拖延,当前 GP 中已过 21002 个 tick - CPU 状态为在线、在 qsmaskinit 中、在 qsmaskinitnext 中、无待处理 IRQ work - softirq 处理计数 52304/52304(快照/当前)→ 表明 softirq handler 正常运行 - 当前 GP 过 21002 jiffies,总回调数 129


2. 加速 GP —— Expedited Grace Period

2.1 为什么需要加速 GP?

正常 RCU GP 的延迟在 10~100ms 级别。某些场景(如内存热插拔、设备移除)需要尽可能快地完成 GP。synchronize_rcu_expedited() 通过 IPI 轰炸所有 CPU 来强制获取静默态。

正常 GP 流程:                    加速 GP 流程:

synchronize_rcu()                 synchronize_rcu_expedited()
  ↓ 等待自然 GP                     ↓ IPI 所有 CPU
  CPU0: ...tick...qs...              CPU0: rcu_exp_handler → 立即返回 qs
  CPU1: ...tick...qs...              CPU1: rcu_exp_handler → 立即返回 qs
  CPU2: ...tick...qs...              CPU2: rcu_exp_handler → 立即返回 qs
  ↓ ~10ms 后                       ↓ ~几十微秒后
  GP 完成 ✓                         GP 完成 ✓

2.2 Expedited GP 序列号管理(tree_exp.h:20-68

// tree_exp.h:20-24,38-43,50-58,65-68
static void rcu_exp_gp_seq_start(void)  { rcu_seq_start(&rcu_state.expedited_sequence); }
static void rcu_exp_gp_seq_end(void)    { rcu_seq_end(&rcu_state.expedited_sequence); smp_mb(); }
static unsigned long rcu_exp_gp_seq_snap(void) { smp_mb(); return rcu_seq_snap(...); }
static bool rcu_exp_gp_seq_done(unsigned long s) { return rcu_seq_done(...); }

2.3 Funnel Lock — 多调用者串行化(tree_exp.h:301-352

多个线程同时调用 synchronize_rcu_expedited() 时,通过 funnel lock 收集起来,只让一个线程执行实际的 GP 工作:

// tree_exp.h:301-352
static bool exp_funnel_lock(unsigned long s)
{
    // 快速路径:低争用尝试
    if (ULONG_CMP_LT(rnp->exp_seq_rq, s) && (...)
        && mutex_trylock(&rcu_state.exp_mutex))
        goto fastpath;

    // 慢速路径:沿 rcu_node 树向上推进
    for (; rnp != NULL; rnp = rnp->parent) {
        if (sync_exp_work_done(s)) return true;  // piggyback:复用他人 GP

        spin_lock(&rnp->exp_lock);
        if (ULONG_CMP_GE(rnp->exp_seq_rq, s)) {
            // 已有其他线程在做,等待即可
            spin_unlock(&rnp->exp_lock);
            wait_event(rnp->exp_wq[...], sync_exp_work_done(s));
            return true;  // 复用他人 GP 完成
        }
        WRITE_ONCE(rnp->exp_seq_rq, s);  // 标记我们的请求
        spin_unlock(&rnp->exp_lock);
    }
    mutex_lock(&rcu_state.exp_mutex);  // 到达树根,由我们执行
fastpath:
    rcu_exp_gp_seq_start();
    return false;
}

Funnel lock 示意图:

CPU0            CPU1            CPU2            CPU3
  ↓               ↓               ↓               ↓
  ├─ leaf rnp    ├─ leaf rnp    ├─ leaf rnp    ├─ leaf rnp
  │  设置seq_rq   │  发现已有      │  发现已有      │  发现已有
  │      ↓        │  等待        │  等待        │  等待
  ├─ mid  rnp     └──────────────┴──────────────┘
  │  设置seq_rq
  │      ↓
  └─ root rnp
     获取exp_mutex ← 唯一执行者!
     执行GP ←─── GP 完成后唤醒所有等待者

2.4 CPU 选择与 IPI 发送(tree_exp.h:358-446

__sync_rcu_exp_select_node_cpus()expmask 中的每个 CPU:跳过自身和离线 CPU、检查 extended quiescent state(idle),对剩余 CPU 通过 smp_call_function_single() 发送 IPI。失败时重试或标记 CPU 离线。

2.5 rcu_exp_handler() — IPI 处理函数

PREEMPT_RCU 版本tree_exp.h:749-804):

// tree_exp.h:749-804
static void rcu_exp_handler(void *unused)
{
    int depth = rcu_preempt_depth();
    // 不在读端临界区内 → 立即报告静默态
    if (!depth) {
        if (!(preempt_count() & (PREEMPT_MASK | SOFTIRQ_MASK)) || idle)
            rcu_report_exp_rdp(rdp);
        else
            rcu_exp_need_qs();  // 设置标志,下次调度时报告
        return;
    }

    // 在读端临界区内 → 设置 deferred QS 标志
    if (depth > 0) {
        raw_spin_lock_irqsave_rcu_node(rnp, flags);
        if (rnp->expmask & rdp->grpmask) {
            WRITE_ONCE(rdp->cpu_no_qs.b.exp, true);    // 标记需要加速 QS
            t->rcu_read_unlock_special.b.exp_hint = true; // 提示 rcu_read_unlock
        }
        raw_spin_unlock_irqrestore_rcu_node(rnp, flags);
    }
}

非 PREEMPT_RCU 版本tree_exp.h:865-881):

// tree_exp.h:865-881
static void rcu_exp_handler(void *unused)
{
    if (rcu_is_cpu_rrupt_from_idle() || preempt_bh_enabled)
        rcu_report_exp_rdp(rdp);   // 立即报告
    else
        rcu_exp_need_qs();         // 推迟报告
}

2.6 等待 GP 完成(tree_exp.h:624-680

// tree_exp.h:624-680
static void synchronize_rcu_expedited_wait(void)
{
    jiffies_start = jiffies;

    // NO_HZ_FULL 优化:设置 tick 依赖,确保目标 CPU 收到 tick
    if (tick_nohz_full_enabled()) {
        rcu_for_each_leaf_node(rnp)
            for_each_leaf_node_cpu_mask(rnp, cpu, mask)
                tick_dep_set_cpu(cpu, TICK_DEP_BIT_RCU_EXP);
    }

    for (;;) {
        if (synchronize_rcu_expedited_wait_once(jiffies_stall))
            return;  // GP 完成!
        // 超时 → 打印 expedited stall 警告
        synchronize_rcu_expedited_stall(jiffies_start, j);
        panic_on_rcu_stall();
    }
}

2.7 热插拔处理(tree_exp.h:77-128

// tree_exp.h:77-128
static void sync_exp_reset_tree_hotplug(void)
{
    if (likely(ncpus == rcu_state.ncpus_snap))
        return;  // 无新 CPU 上线,快速返回

    rcu_for_each_leaf_node(rnp) {
        if (rnp->expmaskinit == rnp->expmaskinitnext)
            continue;  // 此节点无新 CPU
        oldmask = rnp->expmaskinit;
        rnp->expmaskinit = rnp->expmaskinitnext;

        // 向上传播新 CPU 的掩码
        if (!oldmask) {
            rnp_up = rnp->parent;
            while (rnp_up) {
                rnp_up->expmaskinit |= mask;
                rnp_up = rnp_up->parent;
            }
        }
    }
}

2.8 加速 GP 完整流程

synchronize_rcu_expedited()
exp_funnel_lock(s)              ← 与其他调用者串行化 / piggyback
sync_rcu_exp_select_cpus()      ← 为每个 leaf rcu_node 发起工作
    ├─ sync_exp_reset_tree()    ← 重置 expmask
    └─ 多 leaf rcu_node 并行:
        __sync_rcu_exp_select_node_cpus()
        ├─ 检查 EQS/离线 → 立即标记 QS
        └─ 对剩余 CPU 发送 IPI → rcu_exp_handler()
            ├─ 不在临界区 → rcu_report_exp_rdp()
            └─ 在临界区内 → 设置 exp/deferred_qs 标志
synchronize_rcu_expedited_wait()
    ├─ swait_event_timeout(expedited_wq, root done)
    │   → 等待所有 CPU 报告 QS
    └─ 超时 → synchronize_rcu_expedited_stall() 打印警告
rcu_exp_wait_wake(s)            ← 设置 exp_seq_rq、唤醒等待者

3. NO_CB CPUs —— 回调卸载

3.1 设计动机

正常 RCU 下每个 CPU 自行调用回调,这在 nohz_full/RT 场景下有问题(打断深度睡眠、回调延迟不可预测)。NO_CB 将回调处理卸载到专门的 kthread。

3.2 架构设计

NO_CB CPU 使用两级 kthread 架构

   ┌──────────────────────┐      ┌──────────────────────┐
   │ GP kthread (rcuog/N) │ ──→  │ CB kthread (rcuoc/N) │
   │ 管理回调、等待 GP      │      │ 仅调用回调函数         │
   └──────────────────────┘      └──────────────────────┘

tree_nocb.h:71: 启动参数 rcu_nocbs=0-3,7 指定卸载 CPU,rcu_nocb_poll 开启轮询模式。

3.3 Bypass 链表 —— 回调风暴时的优雅降级

call_rcu() 速率过高时,回调暂存到 ->nocb_bypass 链表(tree_nocb.h:85 阈值 16*1000/HZ per jiffy):

// tree_nocb.h:431-553 (核心逻辑)
static bool rcu_nocb_try_bypass(struct rcu_data *rdp, struct rcu_head *rhp, ...)
{
    // 低频率 → 直接入 cblist,同时 flush bypass
    if (rdp->nocb_nobypass_count < nocb_nobypass_lim_per_jiffy && !lazy) {
        rcu_nocb_flush_bypass(rdp, NULL, j, false);
        return false;  // 调用者直接入 cblist
    }
    // 高频率 → 回调进入 bypass
    rcu_cblist_enqueue(&rdp->nocb_bypass, rhp);
    return true;
}

Bypass 冲刷条件(tree_nocb.h:386-411):超过 qhimark 高水位,或超时(懒惰回调 10*HZ、普通 0-1 jiffy)时冲刷。

3.4 GP kthread 主循环(tree_nocb.h:658-846

遍历组内所有 NO_CB CPU,冲刷 bypass、推进回调、确定最小待等待 GP,然后 swait_event 等待该 GP 完成,最后唤醒对应的 CB kthread。

3.5 懒惰回调 —— LAZY Callbacks

// tree_nocb.h:250-252
#define LAZY_FLUSH_JIFFIES (10 * HZ)  // 懒惰回调最多延迟 10 秒冲刷
static unsigned long jiffies_lazy_flush = LAZY_FLUSH_JIFFIES;

懒惰回调是一种能效优化:将回调暂存在 bypass 链表中,最长延迟 10 秒批处理,减少 kthread 唤醒次数。


4. Preemptible RCU 优先级提升 —— Priority Boosting

CONFIG_PREEMPT_RCU=y 时,任务可能在 rcu_read_lock() 内被抢占。如果这个任务阻塞了 GP 进展,而调度器不给它 CPU 时间,就会导致 GP 无限延迟。

4.1 阻塞任务跟踪(tree_plugin.h:128-132

// tree_plugin.h:128-132
#define RCU_GP_TASKS   0x8   // 正常 GP 有等待的任务
#define RCU_EXP_TASKS  0x4   // 加速 GP 有等待的任务
#define RCU_GP_BLKD    0x2   // 正常 GP 被此 CPU 阻塞
#define RCU_EXP_BLKD   0x1   // 加速 GP 被此 CPU 阻塞

4.2 上下文切换中的阻塞队列(tree_plugin.h:324-374

// tree_plugin.h:324-374
void rcu_note_context_switch(bool preempt)
{
    if (rcu_preempt_depth() > 0 && !t->rcu_read_unlock_special.b.blocked) {
        // 在 RCU 读端临界区内被换出
        rnp = rdp->mynode;
        raw_spin_lock_rcu_node(rnp);
        t->rcu_read_unlock_special.b.blocked = true;
        t->rcu_blocked_node = rnp;

        // 决策:将任务入队到何处(链表头还是尾)
        rcu_preempt_ctxt_queue(rnp, rdp);  // 基于 GP/EXP 状态决策

        // 通知 RCU 此 CPU 的静默态(尽管任务被阻塞)
        rcu_qs();
    }
}

任务入队决策由 rcu_preempt_ctxt_queue()tree_plugin.h:162-279)根据当前的 GP 状态、EXP 状态、CPU 阻塞状态进行 10 种情况的分支判断。

4.3 优先级提升实现(tree_plugin.h:1154-1212

// tree_plugin.h:1154-1212
static int rcu_boost(struct rcu_node *rnp)
{
    // 优先提升阻塞加速 GP 的任务
    if (rnp->exp_tasks != NULL)
        tb = rnp->exp_tasks;
    else
        tb = rnp->boost_tasks;

    // 通过制造 rt_mutex 来提升目标任务的优先级
    // 任务 t 在外层 rcu_read_unlock 时会释放这个 rt_mutex
    t = container_of(tb, struct task_struct, rcu_node_entry);
    rt_mutex_init_proxy_locked(&rnp->boost_mtx.rtmutex, t);
    raw_spin_unlock_irqrestore_rcu_node(rnp, flags);
    rt_mutex_lock(&rnp->boost_mtx);    // 这会提升 t 的优先级!
    rt_mutex_unlock(&rnp->boost_mtx);
    rnp->n_boosts++;
}

4.4 Boost kthread(tree_plugin.h:1217-1247

每个 leaf rcu_node 有一个 boost kthread(rcub/N):

// tree_plugin.h:1217-1247
static int rcu_boost_kthread(void *arg)
{
    struct rcu_node *rnp = (struct rcu_node *)arg;
    for (;;) {
        WRITE_ONCE(rnp->boost_kthread_status, RCU_KTHREAD_WAITING);
        rcu_wait(READ_ONCE(rnp->boost_tasks) || READ_ONCE(rnp->exp_tasks));
        WRITE_ONCE(rnp->boost_kthread_status, RCU_KTHREAD_RUNNING);
        more2boost = rcu_boost(rnp);  // 提升一个任务
        if (spincnt > 10) {
            // 连续提升 10 次后 yield
            schedule_timeout_idle(2);
            spincnt = 0;
        }
    }
}

4.5 启动提升(tree_plugin.h:1259-1282

// tree_plugin.h:1259-1282
static void rcu_initiate_boost(struct rcu_node *rnp, unsigned long flags)
{
    if (!rnp->boost_kthread_task ||
        (!rcu_preempt_blocked_readers_cgp(rnp) && !rnp->exp_tasks)) {
        raw_spin_unlock_irqrestore_rcu_node(rnp, flags);
        return;
    }
    // 加速 GP 总是立刻提升
    // 正常 GP 需要等待 RCU_BOOST_DELAY (默认 500ms)
    if (rnp->exp_tasks != NULL ||
        (rnp->gp_tasks != NULL && rnp->boost_tasks == NULL &&
         rnp->qsmask == 0 && time_after(jiffies, rnp->boost_time))) {
        if (rnp->exp_tasks == NULL)
            rnp->boost_tasks = rnp->gp_tasks;
        rcu_wake_cond(rnp->boost_kthread_task, ...);
    }
}

4.6 rcu_preempt_deferred_qs_irqrestore() — 解锁特殊处理(tree_plugin.h:477-589

当一个被阻塞的任务最终执行 rcu_read_unlock() 时:

__rcu_read_unlock()
  → rcu_read_unlock_special(t)
    → rcu_preempt_deferred_qs_irqrestore(t, flags)
        ├─ 处理 need_qs 标志 → 报告正常 QS
        ├─ 处理 cpu_no_qs.b.exp → 报告加速 QS
        └─ 处理 blocked 标志
            ├─ 从 rcu_node->blkd_tasks 链表移除
            ├─ 更新 gp_tasks / exp_tasks / boost_tasks 指针
            ├─ 如果最后一任务 → 报告 QS 到上级
            └─ 如果持有 boost_mtx → 释放

4.7 启动时配置公告(tree_plugin.h:45-112

rcu_bootup_announce_oddness() 在启动时打印 fanout、boost、回调水线、softirq/kthread 模式等非默认配置,帮助确认当前 RCU 子系统的行为参数。


5. 诊断工具速查

工具/接口 来源 作用
rcu_cpu_stall_timeout /sys/module/rcupdate/parameters/ 调整 stall 检测超时 (3-300s)
panic_on_rcu_stall /proc/sys/kernel/panic_on_rcu_stall stall 时触发 panic
max_rcu_stall_to_panic /proc/sys/kernel/max_rcu_stall_to_panic 容忍 N 次 stall 后 panic
rcu_stall_count /sys/kernel/rcu_stall_count stall 事件累计计数
rcu_nocbs= 启动参数 指定 NO_CB CPU 列表
rcu_nocb_poll 启动参数 NO_CB kthread 轮询模式
show_rcu_gp_kthreads() sysrq-x / debugfs 显示 GP kthread 状态
rcu_expedited /sys/kernel/rcu_expedited 强制使用加速 GP
exp_holdoff /sys/module/srcutree/parameters/ SRCU 自动加速阈值 (ns)

5.1 关键 Kconfig 选项

CONFIG_RCU_STALL_COMMON          -- stall 警告基础支持
CONFIG_RCU_CPU_STALL_TIMEOUT    -- stall 超时秒数 (默认 21)
CONFIG_PROVE_RCU                -- 额外 5*HZ 容限 + lockdep 检查
CONFIG_RCU_BOOST                -- PREEMPT_RCU 优先级提升
CONFIG_RCU_BOOST_DELAY          -- 提升延迟 (默认 500ms)
CONFIG_RCU_NOCB_CPU             -- NO_CB 回调卸载支持
CONFIG_RCU_EXPERT               -- 显示专家级 sysctl 参数
CONFIG_RCU_LAZY                 -- 懒惰回调批处理
CONFIG_RCU_STRICT_GRACE_PERIOD  -- 严格 GP: 所有 GP 都当作加速 GP

系列结语

本系列四篇文章深度解析了 Linux 内核 RCU 子系统的四个维度:

篇目 内容 核心文件
第一篇 核心 API 与 Tree RCU 架构 rcupdate.h, tree.c, tree.h
第二篇 回调卸载、分段链表与 GP 状态机 rcu_segcblist.h, tree.c
第三篇 SRCU、Tasks RCU 与 Tiny RCU 变体 srcutree.c, tasks.h, tiny.c
第四篇 拖延检测、加速 GP 与回调卸载 tree_stall.h, tree_exp.h, tree_nocb.h, tree_plugin.h

RCU 是 Linux 内核中最精妙的同步机制之一。理解其内部实现不仅有助于编写正确的内核代码,更能帮助开发者利用各种诊断接口定位生产环境中的 RCU 相关问题。

涉及的核心源码文件总计(去重后 9 个文件,~9437 行):

kernel/rcu/srcutree.c  (2203 lines)  -- SRCU 树形实现
kernel/rcu/tasks.h     (1608 lines)  -- Tasks RCU 实现
kernel/rcu/tiny.c       (252 lines)  -- Tiny RCU for UP
kernel/rcu/tree_stall.h (1185 lines) -- Stall 检测与报告
kernel/rcu/tree_exp.h   (1118 lines) -- 加速 GP
kernel/rcu/tree_nocb.h  (1702 lines) -- NO_CB 回调卸载
kernel/rcu/tree_plugin.h(1369 lines) -- 抢占 RCU + 优先级提升

💬 评论