参数 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_mask、rcu_nocb_mask、irq_default_affinity),之后由调度器、tick、RCU、中断子系统在各自的决策点读取。效果是隔离核上只剩 arch timer 单次到期、IPI、per-CPU 线程和任务自己的内核路径;边界是:只在启动时生效、CPU0 必留、nohz_full与isolcpus集合必须一致、多任务时 tick 会恢复、已存在中断不受irqaffinity影响。
第一层:配置前后的 CPU 视图
先看结论:未配置时四类干扰源随机落到每颗核;配置后家务集中到 housekeeping,隔离核只剩不可避免的残余。
第二层:解析链与生效链
先看结论:解析只发生一次(
early_param/__setup),落成四张掩码;生效发生在内核后续的每一次决策里——建调度域、设中断亲和、决定停不停 tick、call_rcu入哪个队列。
公共底座:kernel/sched/isolation.c
- 九类家务各有一张掩码:
housekeeping.cpumasks[HK_TYPE_MAX](isolation.c:27),类型定义在include/linux/sched/isolation.h:10-19(TIMER、RCU、MISC、SCHED、TICK、DOMAIN、WQ、MANAGED_IRQ、KTHREAD)。 housekeeping_setup(str, flags)(:118-190):cpulist_parse得到 非 housekeeping 集(:134解析失败告警);housekeeping_staging = cpu_possible − non_hk;若其中没有任何在线/present CPU,就把当前(boot)CPU 塞回 housekeeping(:148)——这就是 CPU0 不能隔离 的来源,因为它承担 timekeeping。第二次调用(另一条参数)时,两者的 CPU 集必须一致,否则pr_warn("Housekeeping: nohz_full= must match isolcpus=")并忽略(:169)。最后按flags逐类型填掩码。- 读取接口:
housekeeping_cpumask(type)、housekeeping_any_cpu(type)、housekeeping_test_cpu(cpu, type)、housekeeping_affine(task, type);未配置时全部返回cpu_possible_mask,也就是"没有隔离"。 - 只在启动时解析(
__setup("nohz_full="):203、__setup("isolcpus="):253),运行时不能改集合。
参数 1:isolcpus=<flags>,<cpulist>
作用:按标志把 CPU 从「调度域」和/或「managed 中断」中摘出来。标志解析在 housekeeping_isolcpus_setup()(:205-253):nohz(等价于 nohz_full 那组标志)、domain(HK_FLAG_DOMAIN)、managed_irq(HK_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_DOMAIN(kernel/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=isolated(kernel/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 | KTHREAD;HK_FLAG_TICK 触发 tick_nohz_full_setup(cpumask)(kernel/time/tick-sched.c:608-612)设置 tick_nohz_full_mask 与 tick_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_TIMER、PERF_EVENTS、SCHED、CLOCK_UNSTABLE、RCU、RCU_EXP(include/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, 2275;core.c:1140-1155)、unbound workqueue(workqueue.c:7800)、kthreadd 及新内核线程亲和(kernel/kthread.c:371, 746)、vmstat/看门狗线程(kernel/watchdog.c:1249;kernel/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_mask(kernel/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 :950,nocb_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/N(rcuo%c/%d,:1412,p = preempt RCU)。
参数 4:irqaffinity=<cpulist>
作用:设置 irq_default_affinity——之后申请的、非 managed 中断的默认 CPU 掩码。
解析:irq_affinity_setup()(kernel/irq/irqdesc.c:27-38)cpulist_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_nocbs(nohz_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-3。rcu_nocbs 在这里与 nohz_full 重复,但当你想让某些核(比如接入簇)只 offload RCU 而不停 tick 时,就需要单独给 rcu_nocbs 一个更大的集合——性能分析第 14 章样板 A 的 rcu_nocbs=4-11 配 nohz_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-19;kernel/sched/isolation.c:12-27, 58, 118-253 |
| 调度域 | kernel/sched/topology.c:2625, 2648, 2717, 2752 |
| managed 中断 | kernel/irq/manage.c:240-265, 441;kernel/irq/cpuhotplug.c:198 |
| tick | kernel/time/tick-sched.c:323-326, 366-386, 402-534, 608-632;include/linux/tick.h:112-122;kernel/sched/core.c:1272-1310, 5693-5760 |
| 定时器 / wq / kthread / 杂务 | kernel/time/hrtimer.c:225, 2275;kernel/workqueue.c:7800-7807;kernel/kthread.c:371, 746;kernel/watchdog.c:1249;kernel/sched/fair.c:12354 |
| RCU offload | kernel/rcu/tree_nocb.h:17-78, 393, 606, 883-950, 1116, 1184, 1294-1322, 1399, 1412;kernel/rcu/tree_plugin.h:1378;kernel/rcu/Kconfig:238-284 |
| 中断默认亲和 | kernel/irq/irqdesc.c:27-46;kernel/irq/manage.c:146, 595-635;kernel/irq/proc.c:135-186, 229, 256 |
| sysfs / 文档 | drivers/base/cpu.c:284-309;Documentation/admin-guide/kernel-parameters.txt:2399-2400, 2436-2470, 3984-3996, 4987-5020 |
小结
- 四条参数 = 四张启动时固定的掩码;效果发生在内核之后的每一次"选核"决策里。
nohz_full是最重的一条:一次接管六类家务并隐含 RCU offload,但只在"单可运行任务"时真正停 tick。isolcpus=domain只管调度域,managed_irq只管 managed 中断,irqaffinity只管之后申请的普通中断——三者合起来才盖住全部中断与均衡路径。- 边界要记牢:CPU0 必留、两集合一致、运行时不可改、多任务恢复 tick、irqbalance 会改回去。