Linux Housekeeping 机制全解析:CPU 隔离、RCU 同步与 cpumask 管理
2026/9/15 11:08:39 网站建设 项目流程

Linux Housekeeping 机制全解析:CPU 隔离、RCU 同步与 cpumask 管理

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

Housekeeping 是 Linux 内核 CPU 隔离(CPU Isolation)体系的"反向"概念:它描述那些未被隔离、专门承接内核例行工作的 CPU。本文以内核文档 Documentation/core-api/housekeeping.rst 为主线,结合 kernel/sched/isolation.c、include/linux/sched/isolation.h、kernel/cgroup/cpuset.c 与 kernel/workqueue.c 等源码,系统讲解四种 housekeeping 类型的语义、cpumask 查询接口、与 cpuset 的 RCU 同步方案,以及isolcpus=nohz_full=等启动参数的解析细节。读完本文,你将掌握 housekeeping cpumask 的获取与更新时机、读写两侧的同步约束,以及如何在 DPDK 等对抖动敏感的负载中正确配置 CPU 隔离。

什么是 Housekeeping:隔离 CPU 的"反向"

CPU 隔离(CPU Isolation)把内核工作从某些 CPU 上移走,以降低操作系统抖动(OS jitter)。这类抖动是 DPDK 等极端负载无法容忍的。被移走的内核工作通常被称为"housekeeping"(内务处理),因为它包含大量例行事务:清理(cleanups)、统计维护(statistics maintenance)及依赖统计的动作、内存释放、各种延迟处理(deferrals)等。

从 Documentation/core-api/housekeeping.rst 的定义看:

  • 有些 housekeeping 只是非绑定工作(unbound workqueues、unbound timers),可以轻松地被指派到非隔离 CPU 上执行;
  • 有些 housekeeping绑定在特定 CPU上,需要精巧的技巧才能卸载到非隔离 CPU(例如 RCU_NOCB、远程调度 tick 等)。

因此,housekeeping CPU 就是隔离 CPU 的反面:它是可以执行 housekeeping 工作的 CPU。系统在任何时刻都必须至少有一个在线的 housekeeping CPU;未被隔离的 CPU 自动成为 housekeeping CPU。

四种 Housekeeping 类型:enum hk_type

Housekeeping 当前划分为四种特性,由 include/linux/sched/isolation.h 中的enum hk_type描述:

类型对应隔离手段移走的内核工作
HK_TYPE_DOMAINisolcpus=domain启动参数或 cgroup v2 的隔离 cpuset 分区调度器负载均衡、非绑定 workqueue 与定时器
HK_TYPE_KERNEL_NOISEnohz_full=isolcpus=nohz远程调度 tick、vmstat、lockup watchdog
HK_TYPE_MANAGED_IRQisolcpus=managed_irqmanaged IRQ 的中断处理
HK_TYPE_DOMAIN_BOOTisolcpus=domainHK_TYPE_DOMAIN,但忽略 cpuset 的隔离

源码中HK_TYPE_DOMAINHK_TYPE_DOMAIN_BOOT的区别被明确注释(include/linux/sched/isolation.h):HK_TYPE_DOMAIN_BOOT是启动时isolcpus=参数的反集,而HK_TYPE_DOMAIN在它的基础上还包含 cpuset 隔离分区的反集,因此始终是HK_TYPE_DOMAIN_BOOT的子集

此外,头文件还定义了若干别名(include/linux/sched/isolation.h):

  • HK_TYPE_KTHREAD = HK_TYPE_DOMAIN(kthread 类型已成为 domain 的别名);
  • HK_TYPE_TICKHK_TYPE_TIMERHK_TYPE_RCUHK_TYPE_MISCHK_TYPE_WQ全部共享HK_TYPE_KERNEL_NOISE的值,因为这几类工作只由nohz_full启动参数设置。

对应的标志位定义在 kernel/sched/isolation.c 的enum hk_flags中:HK_FLAG_DOMAIN_BOOTHK_FLAG_DOMAINHK_FLAG_MANAGED_IRQHK_FLAG_KERNEL_NOISE,以位图方式记录当前启用了哪些类型。

启动参数解析:isolcpus= 与 nohz_full=

enum hk_type的四种类型与内核启动参数一一对应,解析逻辑全部位于 kernel/sched/isolation.c。

nohz_full=

nohz_full=的解析入口是housekeeping_nohz_full_setup()(kernel/sched/isolation.c),通过__setup("nohz_full=", ...)注册:

static int __init housekeeping_nohz_full_setup(char *str) { unsigned long flags; flags = HK_FLAG_KERNEL_NOISE; return housekeeping_setup(str, flags); } __setup("nohz_full=", housekeeping_nohz_full_setup);

nohz_full=只设置HK_FLAG_KERNEL_NOISE。从 Documentation/admin-guide/kernel-parameters.txt 可知,该参数要求内核构建时启用CONFIG_NO_HZ_FULL=y,指定列表中的 CPU 会尽可能停止 tick,其 RCU 回调也会被卸载(等价于同时指定了rcu_nocbs=);引导 CPU(boot CPU)会被强制排除在范围之外以维持 timekeeping。当CONFIG_NO_HZ_FULL未启用时,housekeeping_setup()会打印Housekeeping: nohz unsupported. Build with CONFIG_NO_HZ_FULL并直接返回(kernel/sched/isolation.c)。

isolcpus=

isolcpus=的解析入口是housekeeping_isolcpus_setup()(kernel/sched/isolation.c),它支持子参数前缀匹配:

  • nohz,HK_FLAG_KERNEL_NOISE(等价于nohz_full);
  • domain,HK_FLAG_DOMAIN | HK_FLAG_DOMAIN_BOOT
  • managed_irq,HK_FLAG_MANAGED_IRQ
  • 未知子参数会被跳过并打印isolcpus: Skipped unknown flag ...;若包含非法字符则打印isolcpus: Invalid flag ...并返回。

如果isolcpus=未带任何子参数,则默认使用domain行为(flags |= HK_FLAG_DOMAIN | HK_FLAG_DOMAIN_BOOT,见 kernel/sched/isolation.c),这与 Documentation/admin-guide/kernel-parameters.txt 中 "default: domain" 的说明一致。

housekeeping_setup() 的核心校验

两种参数最终都进入housekeeping_setup()(kernel/sched/isolation.c),其中有两处关键校验:

  1. 必须保留至少一个在线 housekeeping CPUcpumask_andnot(housekeeping_staging, cpu_possible_mask, non_housekeeping_mask)计算非隔离 CPU 集合后,检查cpumask_first_and(cpu_present_mask, housekeeping_staging),若不存在则强制把引导 CPU(smp_processor_id())加回 housekeeping 集合,并打印Housekeeping: must include one present CPU, using boot CPU:%d(kernel/sched/isolation.c)。

  2. nohz_full=isolcpus=的组合一致性:第二次调用(先nohz_full=isolcpus=,或相反)时,会校验两个参数的非隔离 CPU 集合必须一致,否则打印Housekeeping: nohz_full= must match isolcpus=(kernel/sched/isolation.c);同时还会校验至少存在一个既不在nohz_full=也不在isolcpus=domain中的 present CPU,以避免定时器迁移层级(timer migration hierarchy)出现问题——managed_irq不参与该校验(kernel/sched/isolation.c)。

首次调用时通过housekeeping_setup_type()为每个启用的类型分配并保存初始 cpumask(使用memblock_alloc_or_panic,kernel/sched/isolation.c);若设置了HK_FLAG_KERNEL_NOISE且尚未设置,还会调用tick_nohz_full_setup()(kernel/sched/isolation.c)。

Housekeeping cpumask:核心查询接口

Housekeeping cpumask 包含可以执行被隔离特性移走工作的 CPU。核心查询函数为(kernel/sched/isolation.c):

const struct cpumask *housekeeping_cpumask(enum hk_type type)

其行为逻辑如下:

  • 默认情况下(既不使用nohz_full=,也不使用isolcpus=,也不使用 cpuset 隔离分区——这覆盖了绝大多数使用场景),函数返回cpu_possible_mask
  • 否则返回对应隔离特性的 cpumask补集。例如:
    • 使用isolcpus=domain,7时,housekeeping_cpumask(HK_TYPE_DOMAIN)返回除 CPU 7 外所有可能 CPU 的掩码;
    • 使用nohz_full=5,6时,housekeeping_cpumask(HK_TYPE_KERNEL_NOISE)返回除 CPU 5、6 外所有可能 CPU 的掩码。

实现上,函数首先通过static_branch_unlikely(&housekeeping_overridden)判断是否覆盖了默认行为,再检查housekeeping.flags中对应类型位是否置位,命中则通过rcu_dereference读取housekeeping.cpumasks[type](这是一个struct cpumask __rcu *数组,见 kernel/sched/isolation.c)。housekeeping_overridden是导出的静态键(EXPORT_SYMBOL_GPL),当CONFIG_CPU_ISOLATION未启用时,include/linux/sched/isolation.h 提供了一组内联空实现:housekeeping_cpumask()直接返回cpu_possible_maskhousekeeping_any_cpu()返回当前 CPU。

除查询函数外,头文件还导出了其他常用接口(include/linux/sched/isolation.h):

  • housekeeping_any_cpu(type):返回一个可用于该类型 housekeeping 工作的 CPU;
  • housekeeping_enabled(type):判断该类型是否已启用;
  • housekeeping_affine(t, type):把任务的 CPU 亲和性限制到 housekeeping cpumask;
  • housekeeping_test_cpu(cpu, type):判断指定 CPU 是否属于该类型的 housekeeping 集合;
  • housekeeping_update(isol_mask):供 cpuset 在运行时更新HK_TYPE_DOMAIN掩码;
  • housekeeping_init():启动早期初始化。

housekeeping_any_cpu()的实现(kernel/sched/isolation.c)值得注意:它先尝试sched_numa_find_closest()在 NUMA 上找距离当前 CPU 最近的 housekeeping CPU,失败则退回到cpumask_any_and_distribute()在 housekeeping 与在线 CPU 的交集中分布式选取;仅当没有任何候选(例如引导阶段第一个 housekeeping CPU 尚未上线)时才回退到smp_processor_id()。而housekeeping_affine()则直接调用set_cpus_allowed_ptr()收紧任务的 cpuset(kernel/sched/isolation.c)。

与 cpuset 的同步:基于 RCU 的方案

cpuset 在创建、修改或删除隔离分区时,会修改HK_TYPE_DOMAIN的 housekeeping cpumask。因此HK_TYPE_DOMAIN的用户必须与 cpuset 正确同步,以满足三个要求(Documentation/core-api/housekeeping.rst):

  1. cpumask 快照保持一致;
  2. 不允许把 housekeeping 工作排队到新近变成隔离的 CPU 上;
  3. 在创建/修改的隔离分区对用户空间可见之前,必须冲刷掉那些排队到"刚刚变成隔离"的 CPU 上的待处理 housekeeping 工作。

该同步由一套RCU 方案维护:

  • 更新侧:cpuset 在更新HK_TYPE_DOMAINcpumask 之后、冲刷待处理工作之前,等待一个 RCU 宽限期(grace period);
  • 读侧:必须在同一个 RCU 读侧临界区内完成"housekeeping 目标选取"和"工作入队"两个操作。

更新侧:housekeeping_update()

housekeeping_update()是更新侧的典型实现(kernel/sched/isolation.c),其核心骨架与文档示例一致:

rcu_assign_pointer(housekeeping.cpumasks[type], trial); synchronize_rcu(); flush_workqueue(example_workqueue);

完整流程是:

  1. 分配一个临时 cpumasktrial,用cpumask_andnot(trial, housekeeping_cpumask(HK_TYPE_DOMAIN_BOOT), isol_mask)计算新的 domain housekeeping 掩码;
  2. 校验新掩码与在线 CPU 集合有交集,否则返回-EINVAL
  3. 首次启用时通过static_branch_enable(&housekeeping_overridden)打开覆盖开关;
  4. rcu_assign_pointer()发布新的 cpumask 指针;
  5. 调用synchronize_rcu()等待读侧退出临界区;
  6. 依次冲刷受影响的 workqueue:pci_probe_flush_workqueue()mem_cgroup_flush_workqueue()vmstat_flush_workqueue()
  7. 调用workqueue_unbound_housekeeping_update()把新的 housekeeping 掩码传播给非绑定 workqueue;
  8. 调用tmigr_isolated_exclude_cpumask()从定时器迁移层级中排除隔离 CPU;
  9. 调用kthreads_update_housekeeping()更新相关 kthread 的亲和性;
  10. 释放旧掩码。

从源码结构看,第 5、6 步正是文档所述"等待 RCU 宽限期后再冲刷待处理工作"的落地实现。

读侧:选取与入队必须在同一 RCU 临界区

读侧的典型布局如下(见 Documentation/core-api/housekeeping.rst):

rcu_read_lock(); cpu = housekeeping_any_cpu(HK_TYPE_DOMAIN); queue_work_on(cpu, example_workqueue, work); rcu_read_unlock();

即"选取 housekeeping CPU"与"按该 CPU 入队 work"必须处于同一个rcu_read_lock()/rcu_read_unlock()之间。这样更新侧synchronize_rcu()就能保证:在它返回时,任何先前的入队操作都已经完成,从而可以安全地冲刷 workqueue,而不会把工作遗留在刚被隔离的 CPU 上。

cpuset 侧的调用链

cpuset 侧的同步由 kernel/cgroup/cpuset.c 完成。关键调用路径是cpuset_update_sd_hk_unlock()(kernel/cgroup/cpuset.c):当update_housekeeping标志被置位时,它把isolated_cpus拷贝到isolated_hk_cpus,然后在不持有cpus_read_lockcpuset_mutex(仅保留cpuset_top_mutex做互斥)的情况下调用housekeeping_update(isolated_hk_cpus)

prstate_housekeeping_conflict()(kernel/cgroup/cpuset.c)则体现了HK_TYPE_DOMAIN_BOOT的约束:当该类型启用时,非隔离分区(prstate != PRS_ISOLATED)的 CPU 集合必须是housekeeping_cpumask(HK_TYPE_DOMAIN_BOOT)的子集,也就是说isolcpus=domain圈定之外的 CPU 只能用于隔离分区。注释同时说明(kernel/cgroup/cpuset.c):调用housekeeping_update()时必须持有cpus_read_lock(CPU 热插拔锁),否则热插拔操作可能与之并发。

内核其他子系统的使用与传播

非绑定 workqueue 的掩码传播

workqueue_unbound_housekeeping_update()(kernel/workqueue.c)在housekeeping_update()中被调用,用于把新的 housekeeping cpumask 传播到非绑定 workqueue:有效非绑定亲和性是"新 housekeeping 掩码"与"通过 nohz_full=/isolcpus= 或 sysfs 请求的亲和性集合"的交集;若交集为空则回退到请求掩码。更新成功后,wq_isolated_cpumaskcpu_possible_mask与 housekeeping 掩码的差集)会通过 sysfs 导出。这也是nohz_full=场景中全局 workqueue 需要手动配置/sys/devices/virtual/workqueue/cpumask来保护个别 CPU 的原因(见 Documentation/admin-guide/kernel-parameters.txt)。

调度 tick 的卸载

housekeeping_init()(kernel/sched/isolation.c)在启动早期执行:若设置了HK_FLAG_KERNEL_NOISE则调用sched_tick_offload_init()(位于 kernel/time/tick-sched.c 附近的tick_nohz_full_setup()同文件)为远程调度 tick 卸载做准备;随后把所有启用的类型重新分配为可 kfree 的 cpumask,并检查每个掩码非空("至少需要一个 CPU 处理 housekeeping 工作"),旧掩码进入memblock_freelist,最终由housekeeping_late_init()pure_initcall)释放(kernel/sched/isolation.c)。

实践配置示例

综合上述机制,一个典型的 CPU 隔离配置(例如为 DPDK 预留 CPU 7)如下:

isolcpus=domain,managed_irq,7 nohz_full=7 rcu_nocbs=7
  • isolcpus=domain,7:把 CPU 7 移出调度域(负载均衡、非绑定 workqueue/定时器);
  • isolcpus=managed_irq,7:把 CPU 7 从 managed IRQ 的目标集合中移出;
  • nohz_full=7:CPU 7 上尽量停掉 tick(需CONFIG_NO_HZ_FULL=y),其 RCU 回调被卸载;
  • rcu_nocbs=7:显式把 CPU 7 的 RCU 回调卸载到 housekeeping CPU(nohz_full=已隐含此效果)。

需要注意的前提:

  • 必须保留至少一个在线 housekeeping CPU(通常引导 CPU 会被强制保留);
  • nohz_full=isolcpus=的非隔离集合必须一致,否则内核会告警并忽略;
  • isolcpus=domain的域隔离是不可逆的(见 Documentation/admin-guide/kernel-parameters.txt),需要运行时动态调整时应改用 cpuset 隔离分区,而housekeeping_update()正是 cpuset 分区变化时动态更新HK_TYPE_DOMAIN的桥梁。

小结

Housekeeping 机制是 Linux CPU 隔离体系的内务基础设施:enum hk_type定义了四类被移走的工作(调度域、内核噪声、managed IRQ 与纯启动域),housekeeping_cpumask()等接口以补集形式返回可执行这些工作的 CPU 集合,而基于 RCU 的同步方案确保了 cpuset 运行时调整隔离分区时,housekeeping 工作的选取与入队不会落到新隔离的 CPU 上。理解这一机制,是正确配置isolcpus=/nohz_full=、分析 DPDK 等低抖动负载抖动来源的基础。

参考文件

  • Documentation/core-api/housekeeping.rst(本文主线文档)
  • include/linux/sched/isolation.h(enum hk_type与接口声明)
  • kernel/sched/isolation.c(启动参数解析、cpumask 维护、RCU 更新)
  • kernel/cgroup/cpuset.c(cpuset 隔离分区与housekeeping_update()调用)
  • kernel/workqueue.c(非绑定 workqueue 掩码传播)
  • Documentation/admin-guide/kernel-parameters.txt(isolcpus=/nohz_full=参数语义)

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询