简介Linux 完全公平调度器CFS自 2.6.23 版本正式合入内核取代传统 O (1) 调度器成为系统中普通分时任务的核心调度实现。在单机多业务、容器集群、云主机、服务器混合部署等生产环境中单一进程维度的 nice 权重调节已无法满足资源划分需求例如一台服务器同时运行业务服务、日志采集、监控告警、定时任务等多类程序若不做分组管控高负载进程会抢占全部 CPU 资源导致核心业务响应延迟、服务雪崩。为此内核在 CFS 基础上延伸出组调度Group Scheduling能力结合 cgroup 实现进程分组管理而shares就是 CFS 组调度中定义分组权重、分配 CPU 时间的核心参数。shares采用相对权重机制在多个任务组同时竞争 CPU 时严格按照各组 shares 数值比例切割 CPU 时间片保障不同业务组的资源配额与运行稳定性。对于 Linux 运维工程师、内核开发人员、容器平台研发、云原生架构师而言吃透 CFS 组调度 shares 的底层原理、层级传递规则、源码逻辑与实战调优是排查 CPU 资源争抢、优化混合业务部署、定制容器资源策略、撰写内核相关论文与技术报告的必备技能。本文结合内核源码、实操命令、压测案例、问题排查全链路讲解从基础概念到内核实现再到线上落地最佳实践完整拆解 shares 权重如何控制 CPU 分配比例内容可直接用于工程实践、源码研读与学术调研。一、核心概念与术语解析1.1 CFS 基础核心知识CFS 全称 Completely Fair Scheduler完全公平调度器核心设计理念是模拟理想多任务 CPU让所有就绪任务按照权重公平分享 CPU 时间核心依赖两个关键要素调度权重每个调度实体拥有权重值默认普通进程权重为NICE_0_LOAD 1024nice 值越小优先级越高权重越大能获取更多 CPU 时间。虚拟运行时间 vruntimeCFS 不依赖固定时间片而是通过vruntime标记进程 “公平程度”。进程真实运行时权重越大vruntime增长越慢调度器永远选择vruntime最小的进程执行。 计算公式vruntime 真实运行时间 × (1024 / 进程权重)1.2 CFS 组调度 与 task_group普通 CFS 仅针对单进程做公平调度组调度将一组进程视为一个整体调度实体内核使用struct task_group描述一个任务组每个 cgroup 对应一个task_group。组内所有进程的负载、运行时间统一汇总参与全局调度组与组之间按照权重竞争 CPU组内再按照普通 CFS 规则分配资源。内核源码路径kernel/sched/sched.h、kernel/sched/fair.c1.3 shares 权重参数详解shares是task_group的核心权重字段对应 cgroup 下cpu.shares文件是组调度的核心默认值所有新建任务组默认shares 1024和普通进程基准权重一致作用范围仅在 CPU 存在竞争时生效。若系统 CPU 空闲单个组内进程可占用全部 CPUshares 限制失效分配规则多个任务组同时争抢 CPU 时各组获得的 CPU 占比 当前组 shares / 所有就绪组 shares 总和数据特性纯相对权重无绝对 CPU 百分比限制区别于cpu.cfs_quota_us硬配额限制。举个基础示例存在两个任务组 GroupA (shares2048)、GroupB (shares1024)两组满载竞争 CPU 时CPU 分配比例为 2:1GroupA 占用约 66.7%GroupB 占用约 33.3%。1.4 cgroup 层级传播规则CFS 组调度支持树形层级结构shares 权重会逐层传递计算这是实战中最容易踩坑的点子组的可用 CPU 上限由父组分配的资源决定同一父组下多个子组按照自身 shares 比例瓜分父组资源层级越深权重计算链路越长分配比例受多级父组共同约束。1.5 关键结构体释义1.5.1 task_group 结构体精简版// kernel/sched/sched.h struct task_group { /* 组调度权重对应 cgroup cpu.shares */ unsigned long shares; /* 每个CPU对应的组CFS运行队列 */ struct cfs_rq **cfs_rq; /* 每个CPU对应的组调度实体 */ struct sched_entity **se; /* cgroup 树形节点、带宽控制等扩展字段 */ struct cgroup_subsys_state css; struct cfs_bandwidth cfs_bandwidth; };字段说明shares当前任务组的权重值可通过 cgroup 文件动态修改cfs_rq组专属 CFS 运行队列存放组内所有就绪进程se任务组本身作为一个调度实体挂载到父组的运行队列中实现层级调度。1.5.2 cfs_rq 组运行队列每个 CPU 都会为每个 task_group 维护独立的struct cfs_rq负责管理组内进程的红黑树、总负载、累计运行时间是组内进程调度的容器。二、环境准备2.1 软硬件环境要求分类版本 / 配置要求操作系统Ubuntu 20.04 / 22.04、CentOS 7/8/9主流 Linux 发行版均可内核版本Linux 4.19、5.4、5.15、6.1主流 LTS 版本CFS 组调度逻辑完全兼容硬件x86_64 架构至少 2 核 4G 内存保证可以模拟 CPU 压测依赖工具stress-ng /stressCPU 压力测试、libcgroupcgroup 操作工具、gcc、make调试工具top、htop、perf、ftrace、gdb内核调试与观测2.2 环境初始化与依赖安装2.2.1 安装基础工具与压测软件以下命令全平台通用可直接复制执行# Ubuntu/Debian 系列 sudo apt update sudo apt install -y stress-ng libcgroup-tools htop perf # CentOS/RHEL 系列 sudo yum install -y stress-ng libcgroup-tools htop perf2.2.2 确认 cgroup v1 环境本文基于 cgroup v1生产环境主流现代 Linux 默认挂载 cgroup 文件系统执行以下命令校验# 查看cpu子系统挂载状态 mount | grep cgroup | grep cpu正常输出示例tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,relatime,size4096k) cgroup on /sys/fs/cgroup/cpu type cgroup (rw,relatime,cpu)若未自动挂载手动执行挂载命令sudo mkdir -p /sys/fs/cgroup/cpu sudo mount -t cgroup -o cpu cpu /sys/fs/cgroup/cpu2.2.3 内核源码准备源码阅读与调试使用如需跟踪内核源码下载长期支持版内核# 以 Linux 5.15 为例 wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.tar.xz tar -xf linux-5.15.tar.xz cd linux-5.15CFS 组调度核心源码路径结构体定义kernel/sched/sched.h组调度逻辑、shares 计算、入队出队kernel/sched/fair.c三、应用场景CFS 组调度与 shares 权重机制广泛应用在服务器混合部署、容器虚拟化、云主机、嵌入式集群等场景。在企业后端服务器中常将核心交易服务、日志采集、监控脚本、离线计算划分为不同 cgroup 组通过调整 shares 权重保障核心业务优先占用 CPU避免离线任务拖垮在线服务。在 Docker、Kubernetes 容器平台中cpu.shares是容器默认的 CPU 资源相对限制方案集群内不同业务容器通过权重划分资源实现节点资源错峰复用。在云计算公有云场景云厂商利用组调度为不同租户划分 CPU 权重保证多租户之间资源隔离与公平竞争。此外大数据集群、边缘计算网关等多进程常驻场景也依靠 shares 分组调度简化资源管控提升整机资源利用率与系统稳定性。四、实际案例、源码与实操步骤本章节分为三部分内核核心源码解析、命令行实操分组压测、自定义代码验证调度逻辑所有代码、命令均可直接复制运行。4.1 内核源码shares 初始化与权重计算逻辑4.1.1 task_group 初始化默认 shares 赋值内核创建新任务组时会默认将shares设置为 1024源码位于fair.c// kernel/sched/fair.c #define NICE_0_LOAD 1024UL /* 创建新任务组时初始化调度相关参数 */ int sched_create_group(struct task_group *tg) { int cpu; /* 组调度权重默认赋值为标准值 1024 */ tg-shares NICE_0_LOAD; /* 遍历所有CPU初始化每个CPU对应的组调度队列与调度实体 */ for_each_possible_cpu(cpu) { struct cfs_rq *cfs_rq tg-cfs_rq[cpu]; struct sched_entity *se tg-se[cpu]; /* 初始化组调度实体组本身作为一个调度单元参与上层调度 */ se-load.weight tg-shares; se-vruntime 0; } return 0; }代码说明NICE_0_LOAD是基准权重 1024所有新建分组默认继承该值每个 CPU 独立维护组队列与调度实体适配多核架构组的se-load.weight直接绑定shares这是组权重参与调度的核心关联点。4.1.2 修改 cpu.shares 时的内核更新逻辑当用户通过echo命令修改 cgroup 的cpu.shares文件时内核触发回调函数实时更新组权重// kernel/sched/fair.c static int cpu_shares_write_u64(struct cgroup_subsys_state *css, struct cftype *cft, u64 val) { struct task_group *tg css_tg(css); unsigned long new_shares (unsigned long)val; int cpu; /* 合法性校验shares 最小值限制防止权重为0 */ if (new_shares 2) return -EINVAL; /* 更新任务组的 shares 字段 */ tg-shares new_shares; /* 遍历所有CPU刷新该组在各个CPU上的调度实体权重 */ for_each_possible_cpu(cpu) { struct sched_entity *se tg-se[cpu]; /* 更新调度实体权重立刻生效 */ se-load.weight new_shares; /* 触发负载重新计算重新参与调度排序 */ update_load_avg(se, 0); } return 0; }代码作用限制 shares 最小为 2避免权重失效修改cpu.shares后全 CPU 刷新调度实体权重实时生效无需重启进程update_load_avg刷新负载统计让组权重变化立刻体现在调度逻辑中。4.1.3 组间 CPU 分配核心计算逻辑CFS 在选择调度组时会根据各组 shares 计算负载占比简化核心逻辑// 伪代码组调度CPU占比计算逻辑 unsigned long total_shares 0; struct task_group *tg; /* 1. 统计当前CPU上所有就绪任务组的shares总和 */ for (遍历当前CPU所有就绪 task_group) { total_shares tg-shares; } /* 2. 单个组理论CPU占比 本组shares / 总shares */ double cpu_ratio (double)tg-shares / total_shares;这就是 shares 相对权重分配的底层数学依据。4.2 实操案例一命令行创建分组 压测验证权重比例目标创建两个 cgroup 分组分别设置 shares2048、shares1024模拟 CPU 满负载验证 CPU 占比为 2:1。步骤 1创建两个任务组# 进入cpu cgroup根目录 cd /sys/fs/cgroup/cpu # 创建分组 group1、group2 sudo mkdir group1 sudo mkdir group2步骤 2配置两组的 shares 权重# group1 设置为 2048 echo 2048 | sudo tee group1/cpu.shares # group2 设置为 1024默认值可省略 echo 1024 | sudo tee group2/cpu.shares查看配置是否生效cat group1/cpu.shares cat group2/cpu.shares步骤 3后台启动 CPU 压测进程并加入对应分组新开两个终端窗口分别执行# 终端1在 group1 中启动4核CPU压测 sudo cgexec -g cpu:group1 stress-ng --cpu 4 --timeout 120 # 终端2在 group2 中启动4核CPU压测 sudo cgexec -g cpu:group2 stress-ng --cpu 4 --timeout 120 参数说明--cpu 4模拟 4 个死循环进程打满 CPU--timeout 120压测持续 120 秒。步骤 4使用 htop /top 观测 CPU 占用比例执行htop可以清晰看到group1 内进程总 CPU 占用约 66%group2 内进程总 CPU 占用约 33% 严格符合 2048:1024 2:1 的权重比例。步骤 5动态修改 shares验证热生效在压测运行过程中动态修改 group2 的 shares 为 2048echo 2048 | sudo tee group2/cpu.shares再次观察 htop两组 CPU 占用迅速变为 1:1证明 shares 修改实时生效。步骤 6清理测试环境# 终止压测进程 pkill stress-ng # 删除测试分组 sudo rmdir /sys/fs/cgroup/cpu/group1 sudo rmdir /sys/fs/cgroup/cpu/group24.3 实操案例二层级 cgroup 验证 shares 传递规则本案例验证父子分组的权重传递架构根分组 ├─ parent_group (shares1024) │ ├─ child1 (shares1024) │ └─ child2 (shares1024)理论结果父组占用整机 50% CPU两个子组平分父组资源各占整机 25%。# 1. 创建父子层级分组 sudo mkdir -p /sys/fs/cgroup/cpu/parent_group/child1 sudo mkdir -p /sys/fs/cgroup/cpu/parent_group/child2 # 2. 设置父组权重 echo 1024 | sudo tee /sys/fs/cgroup/cpu/parent_group/cpu.shares # 3. 设置两个子组权重 echo 1024 | sudo tee /sys/fs/cgroup/cpu/parent_group/child1/cpu.shares echo 1024 | sudo tee /sys/fs/cgroup/cpu/parent_group/child2/cpu.shares # 4. 根目录启动一组压测对比组shares1024 sudo cgexec -g cpu:/ stress-ng --cpu 2 --timeout 120 # 5. 两个子组分别启动压测 sudo cgexec -g cpu:parent_group/child1 stress-ng --cpu 2 --timeout 120 sudo cgexec -g cpu:parent_group/child2 stress-ng --cpu 2 --timeout 120 观测结果根分组、父分组整体各占 50% CPU父分组内部两个子组各占 25%完全匹配层级权重计算规则。4.4 实操案例三C 语言代码实现进程加入 cgroup编写简易 C 程序创建死循环进程并主动将自身加入指定 cgroup验证组调度效果代码可直接编译运行。4.4.1 测试代码 cgroup_demo.c#include stdio.h #include stdlib.h #include unistd.h #include string.h #define CGROUP_PATH /sys/fs/cgroup/cpu/test_group /* 将当前进程PID加入指定cgroup */ int add_proc_to_cgroup(const char *cgroup_path) { char tasks_path[256]; FILE *fp; pid_t pid getpid(); // 拼接 tasks 文件路径 snprintf(tasks_path, sizeof(tasks_path), %s/tasks, cgroup_path); // 写入当前进程PID到cgroup任务列表 fp fopen(tasks_path, w); if (!fp) { perror(fopen failed); return -1; } fprintf(fp, %d, pid); fclose(fp); return 0; } int main(int argc, char *argv[]) { // 1. 创建cgroup目录命令行提前执行代码仅做演示 printf(Process PID: %d, join cgroup...\n, getpid()); // 2. 将当前进程加入 test_group if (add_proc_to_cgroup(CGROUP_PATH) 0) { return -1; } // 3. 死循环模拟CPU密集型任务 while (1) { // 空循环持续占用CPU } return 0; }代码说明Linux cgroup 通过tasks文件管理组内进程写入 PID 即可完成分组绑定死循环模拟 CPU 密集业务用于压测代码不依赖第三方库标准 C 编译即可。4.4.2 编译与运行# 1. 提前创建分组并设置shares sudo mkdir /sys/fs/cgroup/cpu/test_group echo 1024 | sudo tee /sys/fs/cgroup/cpu/test_group/cpu.shares # 2. 编译代码 gcc cgroup_demo.c -o cgroup_demo # 3. 后台运行程序 sudo ./cgroup_demo 执行htop查看进程归属与 CPU 占用修改test_group/cpu.shares可实时观察 CPU 占比变化。4.5 使用 ftrace 跟踪内核组调度函数通过内核跟踪工具观测 shares 变更、组入队等内核函数用于深度调试# 挂载debugfs sudo mount -t debugfs none /sys/kernel/debug # 清空跟踪缓存 echo /sys/kernel/debug/tracing/trace # 过滤跟踪组调度核心函数 echo cpu_shares_write_u64 /sys/kernel/debug/tracing/set_ftrace_filter echo sched_create_group /sys/kernel/debug/tracing/set_ftrace_filter # 开启跟踪 echo function /sys/kernel/debug/tracing/current_tracer echo 1 /sys/kernel/debug/tracing/tracing_on此时修改cpu.shares再执行以下命令查看调用栈cat /sys/kernel/debug/tracing/trace可直观看到cpu_shares_write_u64函数被触发验证内核执行链路。五、常见问题与解答Q1设置了 cpu.shares512为什么进程依然能占满 100% CPU解答cpu.shares是相对权重仅在多个分组 / 进程同时竞争 CPU时生效。如果系统内只有当前组进程运行CPU 无争抢shares 限制会失效。若需要硬性限制 CPU 使用率需搭配cpu.cfs_quota_us做绝对配额。Q2父子层级 cgroup 中子组 shares 总和大于父组资源会溢出吗解答不会。子组的所有资源上限由父组分配的 CPU 比例决定子组之间仅在父组分配的资源池内按照自身 shares 比例划分不会突破父组限制。层级调度是自上而下逐层分配。Q3修改 cpu.shares 后为什么不需要重启进程就能生效解答内核在cpu_shares_write_u64回调中会立刻刷新该任务组所有 CPU 调度实体的权重并更新负载统计。CFS 调度是动态轮询机制权重变更会在下一次调度周期生效无需重启进程。Q4shares 可以设置为 0 或者负数吗解答不可以。内核源码中做了合法性校验shares最小值限制为 2写入 0、负数会直接返回参数错误配置不生效。Q5同一个 CPU 上多个进程属于同一个组组内进程如何分配 CPU解答组内部不再参考 shares完全遵循标准 CFS 调度规则根据进程 nice 值、虚拟运行时间 vruntime 做公平调度shares 只作用于组与组之间的资源划分。Q6多核 CPU 场景下shares 是按整机计算还是按单个核心计算解答CFS 组调度以单个 CPU 核心为单位独立计算权重。多核环境下每个核心单独执行组调度逻辑整机 CPU 占用是所有核心的累加结果。六、实践建议与最佳实践6.1 线上业务分组规划建议核心业务独立分组将交易、支付、网关等核心服务单独划分 cgroup调高 shares如 2048、4096优先保障资源离线计算、日志解析、备份任务降低 shares避免抢占。分组粒度不宜过细一台服务器分组数量建议控制在 10 个以内过多分组会增加内核组调度、红黑树遍历开销轻微影响系统性能。区分相对权重与硬配额高并发核心业务cpu.shares做优先级划分同时搭配cpu.cfs_quota_us做兜底硬限制防止异常进程打满 CPU。6.2 调优与调试技巧压测验证标准流程上线前必做压测多组满载场景下观测 CPU 占比是否符合预期权重提前发现层级配置错误。问题排查顺序CPU 争抢异常 → 查看 cgroup 层级结构 → 核对各级 shares 数值 → 用 ftrace 跟踪内核调度函数 → 检查进程是否正确加入分组。权限管理生产环境禁止普通用户修改cpu.shares防止人为篡改导致资源失衡。6.3 容器场景最佳实践Docker/K8s 中--cpu-shares本质就是修改 cgroupcpu.shares适合集群内容器优先级划分容器密集节点优先使用层级 cgroup按业务租户划分父组再按服务划分子组简化管理。6.4 内核定制与二次开发建议二次开发调度策略时不要修改shares基础计算逻辑会破坏 CFS 公平性可基于现有组调度增加业务标签、优先级扩展。高并发嵌入式设备中若分组数量固定可简化层级调度逻辑减少负载计算开销降低调度时延。七、总结与应用延伸本文从 CFS 基础原理、task_group 结构体、shares 内核源码、命令行实操、代码开发、层级规则、问题排查等维度完整讲解了 Linux CFS 组调度中shares权重的工作机制与 CPU 分配逻辑。核心要点总结shares是相对权重默认值 1024仅在 CPU 竞争时生效按各组权重比例分配 CPU 时间CFS 组调度支持树形层级结构资源自上而下逐层分配子组无法突破父组资源上限修改cpu.shares实时生效内核通过刷新调度实体权重、负载统计完成更新shares 用于优先级划分无法做绝对 CPU 限制需结合cfs_quota实现软硬双重管控。CFS 组调度与 shares 机制是现代 Linux 资源隔离的基石当前几乎所有服务器混合部署、容器虚拟化、云主机、边缘计算、大数据集群都依赖这套能力。掌握其底层原理不仅可以解决线上 CPU 资源争抢、业务卡顿等故障还能支撑容器平台调度策略开发、内核裁剪、实时系统优化等深度工作。建议读者结合本文源码与实操命令在测试机中反复验证单组、多组、层级分组三种场景尝试修改内核源码观察调度行为变化真正做到从理论落地到实战。在实际项目中根据业务重要程度合理规划分组与权重让 Linux CPU 资源发挥最大利用率同时保障核心业务的稳定性。