Linux RT 分析Linux 6.12.107

参数 01 · isolcpus / nohz_full / rcu_nocbs / irqaffinity:四条隔离参数深解

结论:这四条参数做的是同一件事的四个侧面——把内核的"家务"从指定 CPU 上挪走。isolcpus=domain 让隔离核退出调度域(负载均衡不再碰它),isolcpus=managed_irq 让多队列设备的自动亲和避开它,nohz_full 停掉它的周期 tick 并把定时器、unbound workqueue、内核线程、杂务的默认落点改到 housekeeping(同时自动 offload 它的 RCU 回调),rcu_nocbs 把 RCU 回调执行交给 housekeeping 上的 rcuo 线程,irqaffinity 改掉新申请设备中断的默认落点。它们全部在启动早期解析成几张 cpumask(housekeeping.cpumasks[]tick_nohz_full_maskrcu_nocb_maskirq_default_affinity),之后由调度器、tick、RCU、中断子系统在各自的决策点读取。效果是隔离核上只剩 arch timer 单次到期、IPI、per-CPU 线程和任务自己的内核路径;边界是:只在启动时生效、CPU0 必留、nohz_fullisolcpus 集合必须一致、多任务时 tick 会恢复、已存在中断不受 irqaffinity 影响。

第一层:配置前后的 CPU 视图

先看结论:未配置时四类干扰源随机落到每颗核;配置后家务集中到 housekeeping,隔离核只剩不可避免的残余。

未配置:每颗 CPU 都同时承担 tick、中断、内核线程、RCU 回调与负载均衡
未配置:每颗 CPU 都同时承担 tick、中断、内核线程、RCU 回调与负载均衡
配置后:四条参数各管一类干扰
配置后:四条参数各管一类干扰

第二层:解析链与生效链

先看结论:解析只发生一次(early_param/__setup),落成四张掩码;生效发生在内核后续的每一次决策里——建调度域、设中断亲和、决定停不停 tick、call_rcu 入哪个队列。

解析链:启动参数 → housekeeping / tick / rcu_nocb / irq 默认亲和
解析链:启动参数 → housekeeping / tick / rcu_nocb / irq 默认亲和
生效链:四个掩码在内核里被谁读取,各自挡住了什么
生效链:四个掩码在内核里被谁读取,各自挡住了什么

公共底座:kernel/sched/isolation.c

参数 1:isolcpus=<flags>,<cpulist>

作用:按标志把 CPU 从「调度域」和/或「managed 中断」中摘出来。标志解析在 housekeeping_isolcpus_setup():205-253):nohz(等价于 nohz_full 那组标志)、domainHK_FLAG_DOMAIN)、managed_irqHK_FLAG_MANAGED_IRQ);不写标志默认 domain:58);非法标志 pr_warn("isolcpus: Invalid flag"):241)。

domain 怎么生效: - 建调度域时 doms_cur[0] = cpu_map ∩ housekeeping_cpumask(HK_TYPE_DOMAIN)kernel/sched/topology.c:2625),cpuset 变更重建时同样求交(:2717, 2752);不在任何域里的 CPU cpu_attach_domain(NULL, &def_root_domain, i):2648)。 - 于是 CFS 的周期负载均衡、select_task_rq_fair 的域内选核都不会把任务放到隔离核,也不会从隔离核搬走;RT 类的 push/pull 依然按 cpus_mask 工作(所以 RT 线程要显式绑核)。 - unbound workqueue 的默认 cpumask 也被限制到 HK_TYPE_WQ ∩ HK_TYPE_DOMAINkernel/workqueue.c:7800-7807)。 - /sys/devices/system/cpu/isolated 显示的就是非 DOMAIN 集(drivers/base/cpu.c:284-301)。

managed_irq 怎么生效irq_do_set_affinity()kernel/irq/manage.c:240-265)对带 IRQD_AFFINITY_MANAGED 的中断,先把驱动给的掩码与 housekeeping_cpumask(HK_TYPE_MANAGED_IRQ) 求交;交集里有在线 CPU 就只用交集,否则退回原掩码(注释解释:避免 housekeeping 发起的 I/O 完成中断落到隔离核)。CPU 热插拔迁移同理(kernel/irq/cpuhotplug.c:198)。

效果:隔离核不再被 CFS 搬动,NVMe/多队列网卡的队列中断避开它。边界:文档把 isolcpus 标为 Deprecated、建议 cpuset(kernel-parameters.txt:2436-2470)——cgroup v2 cpuset.cpus.partition=isolatedkernel/cgroup/cpuset.c:126)能在运行时做 domain 这一部分,但做不了 managed_irq、tick、RCU;某个 managed 队列的亲和若只包含隔离核,仍会落上去。

参数 2:nohz_full=<cpulist>

作用:让这些 CPU 在"只有一个可运行任务"时停掉周期 tick,并把定时器迁移、unbound workqueue、内核线程亲和、杂务统计的默认落点改到 housekeeping;同时自动把它们加入 RCU offload 集。

解析housekeeping_nohz_full_setup()isolation.c:194-203)一次给出 HK_FLAG_TICK | WQ | TIMER | RCU | MISC | KTHREADHK_FLAG_TICK 触发 tick_nohz_full_setup(cpumask)kernel/time/tick-sched.c:608-612)设置 tick_nohz_full_masktick_nohz_full_running = true。需要 CONFIG_NO_HZ_FULL=y,否则 pr_warn("Housekeeping: nohz unsupported")isolation.c:124-126)。

tick 怎么停:每次从中断/系统调用返回时 tick_nohz_full_update_tick 调用 can_stop_full_tick()tick-sched.c:366-386):先问调度器 sched_can_stop_tick()kernel/sched/core.c:1272-1310:有 DL 任务在跑、超过 1 个 SCHED_RR、或多于 1 个可运行任务 → 不停;恰好 1 个 FIFO 或 1 个普通任务 → 可以停),再检查四层 tick_dep_mask(全局、本 CPU、当前任务、当前进程组):TICK_DEP_BIT_POSIX_TIMERPERF_EVENTSSCHEDCLOCK_UNSTABLERCURCU_EXPinclude/linux/tick.h:112-117)任一置位都不停。停掉后 hrtimer 只按下一个真实到期编程,周期性 tick 消失;任何依赖出现时 tick_nohz_full_kick_cpu():414)用 IPI 把 tick 拉回来。

统计怎么补:停了 tick 的核不再做调度统计,housekeeping 核用 sched_tick_remote()core.c:5693-5760)每秒(queue_delayed_work(system_unbound_wq, …, HZ))替它跑一次 task_tick,所以 top 里的 CPU 时间仍然准确。

其它五类家务:定时器迁移(kernel/time/hrtimer.c:225, 2275core.c:1140-1155)、unbound workqueue(workqueue.c:7800)、kthreadd 及新内核线程亲和(kernel/kthread.c:371, 746)、vmstat/看门狗线程(kernel/watchdog.c:1249kernel/sched/fair.c:12354)。

自动 RCU offload:文档明确"这些 CPU 的 RCU 回调会被 offload,就像也写进了 rcu_nocbs="(kernel-parameters.txt:3990-3992);实现是 rcu_init_nohz()tick_nohz_full_mask 并入 rcu_nocb_maskkernel/rcu/tree_nocb.h:1294-1322)。

效果:单线程独占隔离核时,每秒少 HZ 次(250 或 1000)的 tick 中断与随之而来的调度器/RCU/统计工作。边界:boot CPU 被强制排除在外(kernel-parameters.txt:3988-3989);核上一旦有第二个可运行任务、或应用用了 perf 计数器/POSIX CPU 定时器,tick 立即恢复;nohz_full 的集合必须与 isolcpus 一致(isolation.c:169)。

参数 3:rcu_nocbs=<cpulist>

作用:把这些 CPU 的 RCU 回调(call_rcu/kfree_rcu 排队的函数)从本核挪到专用线程执行。

解析rcu_nocb_setup()tree_nocb.h:59-71)解析到 rcu_nocb_mask(写 rcu_nocbs 不带列表或列表非法 → cpumask_setall);rcu_init_nohz():1294-1322)再并入 nohz_full 集,并在 CONFIG_RCU_NOCB_CPU_DEFAULT_ALL 时默认全部 offload(参数优先,kernel-parameters.txt:3994-3995)。需要 CONFIG_RCU_NOCB_CPU=y

怎么生效:offload 的 CPU 的 rcu_data.cblist 被标记为 offloaded;call_rcu() 入队后走 rcu_nocb_try_bypass() / __call_rcu_nocb_wake()tree_nocb.h:393, 606)唤醒该组的 rcuog/N 线程(:1399),由它按宽限期推进再唤醒各 CPU 的 rcuo 回调线程(rcu_nocb_cb_kthread :950nocb_cb_wait :883)执行;这些线程 housekeeping_affine(HK_TYPE_RCU)kernel/rcu/tree_plugin.h:1378)落在 housekeeping。rcu_nocb_poll:18, 73-78)让 rcuog 轮询而不靠 call_rcu 侧唤醒,进一步减少对隔离核的打扰(NVIDIA Thor 官方参数里用了它)。RT 内核下 RCU_NOCB_CPU_CB_BOOST 默认 y,rcuo 以 SCHED_FIFO 运行(kernel/rcu/Kconfig:279-284)。

效果:隔离核上的 rcuc/N 不再跑回调批(案例 01 里 munmap 压测的主因消失),本核也不再有回调造成的缓存污染。边界:回调延后到 housekeeping 执行,kfree_rcu 的内存归还更慢;rcuo 线程与 housekeeping 上其它负载竞争;运行时可通过 cpuset 隔离分区触发 offload/deoffload(rcu_nocb_cpu_offload()/rcu_nocb_cpu_deoffload()tree_nocb.h:1184, 1116),但生产环境仍以启动参数为准。线程名:rcuog/N:1399)与 rcuop/Nrcuo%c/%d:1412p = preempt RCU)。

参数 4:irqaffinity=<cpulist>

作用:设置 irq_default_affinity——之后申请的、非 managed 中断的默认 CPU 掩码。

解析irq_affinity_setup()kernel/irq/irqdesc.c:27-38cpulist_parse强制加入 boot CPU:35);未指定时 init_irq_default_affinity() 置为全部 CPU(:40-46)。

怎么生效request_irq() 路径的 irq_setup_affinity()kernel/irq/manage.c:595-635):set = irq_default_affinity(managed 中断用自己的掩码),与在线 CPU 求交,再尽量收窄到中断所属 NUMA 节点,交给 irq_do_set_affinity() 写进中断控制器(arm64 上是 GIC 的 its_set_affinity/gic_set_affinity)。/proc/irq/default_smp_affinity 可运行时改默认值(kernel/irq/proc.c:229, 256),/proc/irq/N/smp_affinity[_list] 逐个改已存在的中断(:135-186)。

效果:驱动初始化时把 SPI 中断落到 housekeeping,irq/N-name 线程随之继承亲和(RT 下 IRQTF_AFFINITY,性能分析第 03 章)。边界:只影响参数生效之后申请的中断,早期驱动或固定亲和(IRQF_NOBALANCING/percpu)的中断不受影响;irqbalance 会按自己的策略改写,必须停掉或设 IRQBALANCE_BANNED_CPUS;arch timer PPI、IPI、PMU 中断本就是 per-CPU,与此参数无关。

组合使用:为什么是这四条、各管哪一块

干扰源 由谁管 没配会怎样
周期 tick、定时器迁移、unbound wq、内核线程、杂务 nohz_full 每秒 HZ 次 tick + kworker 随机落核
调度域 / 负载均衡 isolcpus=domain CFS 把普通任务搬进隔离核
多队列设备的 managed 中断 isolcpus=managed_irq NVMe/网卡队列中断落隔离核
RCU 回调 rcu_nocbsnohz_full 已隐含) 本核 rcuc 成批跑回调(案例 01)
其余设备中断 irqaffinity + /proc/irq/*/smp_affinity 新中断默认全核可落

推荐写法(12 核,0-3 housekeeping):isolcpus=domain,managed_irq,4-11 nohz_full=4-11 rcu_nocbs=4-11 irqaffinity=0-3rcu_nocbs 在这里与 nohz_full 重复,但当你想让某些核(比如接入簇)只 offload RCU 而不停 tick 时,就需要单独给 rcu_nocbs 一个更大的集合——性能分析第 14 章样板 A 的 rcu_nocbs=4-11nohz_full=8-11 正是这种用法。

验证

cat /sys/devices/system/cpu/isolated /sys/devices/system/cpu/nohz_full   # 4-11 / 4-11
cat /proc/irq/default_smp_affinity                                        # 0-3 的掩码
ls /sys/kernel/debug/sched/domains/cpu4/                                  # 应为空:不在调度域
grep -E 'Function call|Rescheduling' /proc/interrupts                     # 隔离核列的 IPI 计数
ps -eo pid,psr,cls,rtprio,comm | grep -E 'rcuo|rcuog'                      # 都在 0-3
osnoise -c 4-11 -d 5m                                                     # 隔离核噪声应接近 0
trace-cmd record -e timer:tick_stop -e timer:tick_stop_dependency -a sleep 5  # 看 tick 停/恢复及原因

源码定位

主题 位置
掩码与解析 include/linux/sched/isolation.h:10-19kernel/sched/isolation.c:12-27, 58, 118-253
调度域 kernel/sched/topology.c:2625, 2648, 2717, 2752
managed 中断 kernel/irq/manage.c:240-265, 441kernel/irq/cpuhotplug.c:198
tick kernel/time/tick-sched.c:323-326, 366-386, 402-534, 608-632include/linux/tick.h:112-122kernel/sched/core.c:1272-1310, 5693-5760
定时器 / wq / kthread / 杂务 kernel/time/hrtimer.c:225, 2275kernel/workqueue.c:7800-7807kernel/kthread.c:371, 746kernel/watchdog.c:1249kernel/sched/fair.c:12354
RCU offload kernel/rcu/tree_nocb.h:17-78, 393, 606, 883-950, 1116, 1184, 1294-1322, 1399, 1412kernel/rcu/tree_plugin.h:1378kernel/rcu/Kconfig:238-284
中断默认亲和 kernel/irq/irqdesc.c:27-46kernel/irq/manage.c:146, 595-635kernel/irq/proc.c:135-186, 229, 256
sysfs / 文档 drivers/base/cpu.c:284-309Documentation/admin-guide/kernel-parameters.txt:2399-2400, 2436-2470, 3984-3996, 4987-5020

小结