
Linux Housekeeping 机制全解析CPU 隔离、RCU 同步与 cpumask 管理【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linuxHousekeeping 是 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_typeHousekeeping 当前划分为四种特性由 include/linux/sched/isolation.h 中的enum hk_type描述类型对应隔离手段移走的内核工作HK_TYPE_DOMAINisolcpusdomain启动参数或 cgroup v2 的隔离 cpuset 分区调度器负载均衡、非绑定 workqueue 与定时器HK_TYPE_KERNEL_NOISEnohz_full或isolcpusnohz远程调度 tick、vmstat、lockup watchdogHK_TYPE_MANAGED_IRQisolcpusmanaged_irqmanaged IRQ 的中断处理HK_TYPE_DOMAIN_BOOT仅isolcpusdomain同HK_TYPE_DOMAIN但忽略 cpuset 的隔离源码中HK_TYPE_DOMAIN与HK_TYPE_DOMAIN_BOOT的区别被明确注释include/linux/sched/isolation.hHK_TYPE_DOMAIN_BOOT是启动时isolcpus参数的反集而HK_TYPE_DOMAIN在它的基础上还包含 cpuset 隔离分区的反集因此始终是HK_TYPE_DOMAIN_BOOT的子集。此外头文件还定义了若干别名include/linux/sched/isolation.hHK_TYPE_KTHREAD HK_TYPE_DOMAINkthread 类型已成为 domain 的别名HK_TYPE_TICK、HK_TYPE_TIMER、HK_TYPE_RCU、HK_TYPE_MISC、HK_TYPE_WQ全部共享HK_TYPE_KERNEL_NOISE的值因为这几类工作只由nohz_full启动参数设置。对应的标志位定义在 kernel/sched/isolation.c 的enum hk_flags中HK_FLAG_DOMAIN_BOOT、HK_FLAG_DOMAIN、HK_FLAG_MANAGED_IRQ、HK_FLAG_KERNEL_NOISE以位图方式记录当前启用了哪些类型。启动参数解析isolcpus 与 nohz_fullenum hk_type的四种类型与内核启动参数一一对应解析逻辑全部位于 kernel/sched/isolation.c。nohz_fullnohz_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_FULLy指定列表中的 CPU 会尽可能停止 tick其 RCU 回调也会被卸载等价于同时指定了rcu_nocbs引导 CPUboot CPU会被强制排除在范围之外以维持 timekeeping。当CONFIG_NO_HZ_FULL未启用时housekeeping_setup()会打印Housekeeping: nohz unsupported. Build with CONFIG_NO_HZ_FULL并直接返回kernel/sched/isolation.c。isolcpusisolcpus的解析入口是housekeeping_isolcpus_setup()kernel/sched/isolation.c它支持子参数前缀匹配nohz,→HK_FLAG_KERNEL_NOISE等价于nohz_fulldomain,→HK_FLAG_DOMAIN | HK_FLAG_DOMAIN_BOOTmanaged_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其中有两处关键校验必须保留至少一个在线 housekeeping CPUcpumask_andnot(housekeeping_staging, cpu_possible_mask, non_housekeeping_mask)计算非隔离 CPU 集合后检查cpumask_first_and(cpu_present_mask, housekeeping_staging)若不存在则强制把引导 CPUsmp_processor_id()加回 housekeeping 集合并打印Housekeeping: must include one present CPU, using boot CPU:%dkernel/sched/isolation.c。nohz_full与isolcpus的组合一致性第二次调用先nohz_full后isolcpus或相反时会校验两个参数的非隔离 CPU 集合必须一致否则打印Housekeeping: nohz_full must match isolcpuskernel/sched/isolation.c同时还会校验至少存在一个既不在nohz_full也不在isolcpusdomain中的 present CPU以避免定时器迁移层级timer migration hierarchy出现问题——managed_irq不参与该校验kernel/sched/isolation.c。首次调用时通过housekeeping_setup_type()为每个启用的类型分配并保存初始 cpumask使用memblock_alloc_or_panickernel/sched/isolation.c若设置了HK_FLAG_KERNEL_NOISE且尚未设置还会调用tick_nohz_full_setup()kernel/sched/isolation.c。Housekeeping cpumask核心查询接口Housekeeping cpumask 包含可以执行被隔离特性移走工作的 CPU。核心查询函数为kernel/sched/isolation.cconst struct cpumask *housekeeping_cpumask(enum hk_type type)其行为逻辑如下默认情况下既不使用nohz_full也不使用isolcpus也不使用 cpuset 隔离分区——这覆盖了绝大多数使用场景函数返回cpu_possible_mask否则返回对应隔离特性的 cpumask补集。例如使用isolcpusdomain,7时housekeeping_cpumask(HK_TYPE_DOMAIN)返回除 CPU 7 外所有可能 CPU 的掩码使用nohz_full5,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.hhousekeeping_any_cpu(type)返回一个可用于该类型 housekeeping 工作的 CPUhousekeeping_enabled(type)判断该类型是否已启用housekeeping_affine(t, type)把任务的 CPU 亲和性限制到 housekeeping cpumaskhousekeeping_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()收紧任务的 cpusetkernel/sched/isolation.c。与 cpuset 的同步基于 RCU 的方案cpuset 在创建、修改或删除隔离分区时会修改HK_TYPE_DOMAIN的 housekeeping cpumask。因此HK_TYPE_DOMAIN的用户必须与 cpuset 正确同步以满足三个要求Documentation/core-api/housekeeping.rstcpumask 快照保持一致不允许把 housekeeping 工作排队到新近变成隔离的 CPU 上在创建/修改的隔离分区对用户空间可见之前必须冲刷掉那些排队到刚刚变成隔离的 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);完整流程是分配一个临时 cpumasktrial用cpumask_andnot(trial, housekeeping_cpumask(HK_TYPE_DOMAIN_BOOT), isol_mask)计算新的 domain housekeeping 掩码校验新掩码与在线 CPU 集合有交集否则返回-EINVAL首次启用时通过static_branch_enable(housekeeping_overridden)打开覆盖开关用rcu_assign_pointer()发布新的 cpumask 指针调用synchronize_rcu()等待读侧退出临界区依次冲刷受影响的 workqueuepci_probe_flush_workqueue()、mem_cgroup_flush_workqueue()、vmstat_flush_workqueue()调用workqueue_unbound_housekeeping_update()把新的 housekeeping 掩码传播给非绑定 workqueue调用tmigr_isolated_exclude_cpumask()从定时器迁移层级中排除隔离 CPU调用kthreads_update_housekeeping()更新相关 kthread 的亲和性释放旧掩码。从源码结构看第 5、6 步正是文档所述等待 RCU 宽限期后再冲刷待处理工作的落地实现。读侧选取与入队必须在同一 RCU 临界区读侧的典型布局如下见 Documentation/core-api/housekeeping.rstrcu_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_lock和cpuset_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)的子集也就是说isolcpusdomain圈定之外的 CPU 只能用于隔离分区。注释同时说明kernel/cgroup/cpuset.c调用housekeeping_update()时必须持有cpus_read_lockCPU 热插拔锁否则热插拔操作可能与之并发。内核其他子系统的使用与传播非绑定 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如下isolcpusdomain,managed_irq,7 nohz_full7 rcu_nocbs7isolcpusdomain,7把 CPU 7 移出调度域负载均衡、非绑定 workqueue/定时器isolcpusmanaged_irq,7把 CPU 7 从 managed IRQ 的目标集合中移出nohz_full7CPU 7 上尽量停掉 tick需CONFIG_NO_HZ_FULLy其 RCU 回调被卸载rcu_nocbs7显式把 CPU 7 的 RCU 回调卸载到 housekeeping CPUnohz_full已隐含此效果。需要注意的前提必须保留至少一个在线 housekeeping CPU通常引导 CPU 会被强制保留nohz_full与isolcpus的非隔离集合必须一致否则内核会告警并忽略isolcpusdomain的域隔离是不可逆的见 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.henum hk_type与接口声明kernel/sched/isolation.c启动参数解析、cpumask 维护、RCU 更新kernel/cgroup/cpuset.ccpuset 隔离分区与housekeeping_update()调用kernel/workqueue.c非绑定 workqueue 掩码传播Documentation/admin-guide/kernel-parameters.txtisolcpus/nohz_full参数语义【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考