Linux 调度器中的任务组权重:shares 的分配与层级传播

张开发
2026/6/17 4:15:52 15 分钟阅读
Linux 调度器中的任务组权重:shares 的分配与层级传播
一、简介在现代云计算和容器化部署场景中多租户资源隔离与公平分配已成为操作系统调度的核心挑战。Linux内核的完全公平调度器Completely Fair Scheduler, CFS通过引入任务组task_group机制和shares权重系统实现了从单一进程级公平到多层级资源管理的跨越式演进。核心应用场景包括云原生容器平台Kubernetes通过cpu.shares实现Pod间的CPU资源按比例分配确保在资源争用时各租户获得承诺的计算份额多用户服务器环境防止单个用户的批量任务垄断CPU资源保障交互式任务的响应延迟混合负载数据中心在AI训练、大数据分析与Web服务共存的环境中实现QoS分级保障掌握任务组权重机制对于系统管理员、云架构师和内核开发者具有重要价值它不仅是调优容器密度的关键技术更是理解现代操作系统资源管理哲学的窗口。本文将从源码级深度解析shares权重的计算原理、层级传播算法及其实战应用。二、核心概念2.1 任务组task_group架构任务组是CFS实现分组调度的核心抽象其在内核中的定义体现了多CPU环境下的复杂需求// include/linux/sched.h struct task_group { struct cgroup_subsys_state css; /* 任务组在系统范围内的权重配置由cpu.shares/cpu.weight转换而来 */ unsigned long shares; /* 任务组的全局平均负载跨所有CPU的聚合 */ atomic_long_t load_avg; /* 每个CPU上的调度实体数组tg-se[i]表示该任务组在CPU i上的代表 */ struct sched_entity **se; /* 每个CPU上的CFS运行队列数组tg-cfs_rq[i]存储该任务组在CPU i上的可运行任务 */ struct cfs_rq **cfs_rq; /* 用于RT组调度的相关字段本文聚焦CFS暂不展开 */ // ... };关键设计洞察任务组通过在每个CPU上创建独立的调度实体sched_entity和运行队列cfs_rq解决了组内任务可能分布在多核上的并行调度难题。2.2 Shares权重与Nice值的统一映射CFS内部使用统一的权重体系load_weight来量化任务的CPU份额配置接口默认值内核内部表示说明cpu.shares (cgroup v1)1024NICE_0_LOAD (1024)相对份额无上限cpu.weight (cgroup v2)100经比例转换后与v1统一语义等价于sharesnice值0对应权重1024进程级优先级权重计算公式关键源码逻辑// kernel/sched/fair.c static long calc_group_shares(struct cfs_rq *cfs_rq) { long tg_weight, tg_shares, load, shares; struct task_group *tg cfs_rq-tg; /* 读取任务组配置的总shares值 */ tg_shares READ_ONCE(tg-shares); /* 获取该任务组在当前CPU上的实际负载取运行队列权重与平均负载的最大值 */ load max(scale_load_down(cfs_rq-load.weight), cfs_rq-avg.load_avg); /* 获取任务组的全局负载跨所有CPU的聚合 */ tg_weight atomic_long_read(tg-load_avg); /* 调整计算移除旧贡献加入当前负载 */ tg_weight - cfs_rq-tg_load_avg_contrib; tg_weight load; /* 核心公式按本地负载占全局负载的比例分配shares */ shares (tg_shares * load); if (tg_weight) shares / tg_weight; /* 限制在合理范围内防止极端值 */ return clamp_t(long, shares, MIN_SHARES, tg_shares); }2.3 虚拟运行时间vruntime与层级传播CFS通过vruntime实现公平调度其计算在任务组场景下呈现层级传播特性// kernel/sched/fair.c: __update_curr() static inline void __update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr, u64 now) { u64 delta_exec; /* 计算实际执行时间 */ delta_exec now - curr-exec_start; if (unlikely((s64)delta_exec 0)) return; curr-exec_start now; curr-sum_exec_runtime delta_exec; /* 关键vruntime增长速率与权重成反比 */ curr-vruntime calc_delta_fair(delta_exec, curr); /* 层级传播更新父任务组的调度实体 */ update_curr_cfs_rq(cfs_rq); } /* vruntime计算的核心公式 */ static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se) { /* delta_exec * (NICE_0_LOAD / se-load.weight) */ return delta * NICE_0_LOAD / se-load.weight; }层级传播原理当任务运行时其消耗的CPU时间不仅累加到自身vruntime还会按权重比例累加到所属任务组的调度实体进而可能累加到更高层级的祖先组。这种自底向上的时间记账机制确保了嵌套cgroup场景下的公平性。三、环境准备3.1 软硬件环境要求组件最低要求推荐配置说明操作系统Linux 4.xLinux 5.10cgroup v2完整支持需5.2架构x86_64/ARM64x86_64多核环境更利于观察调度行为CPU核心数2核4核验证跨CPU负载均衡内存2GB4GB运行压力测试工具内核配置CONFIG_CGROUPSyCONFIG_CGROUP_SCHEDy, CONFIG_FAIR_GROUP_SCHEDy必须开启组调度支持3.2 环境检查与配置步骤1验证内核支持# 检查cgroup和组调度配置 zgrep CONFIG_CGROUP /boot/config-$(uname -r) zgrep CONFIG_FAIR_GROUP_SCHED /boot/config-$(uname -r) zgrep CONFIG_CGROUP_SCHED /boot/config-$(uname -r) # 预期输出应包含 # CONFIG_CGROUPSy # CONFIG_CGROUP_SCHEDy # CONFIG_FAIR_GROUP_SCHEDy # CONFIG_CGROUP_PIDSy # CONFIG_CGROUP_RDMAy # CONFIG_CGROUP_FREEZERy # CONFIG_CGROUP_DEVICEy # CONFIG_CGROUP_CPUACCTy # CONFIG_CGROUP_PERFy # CONFIG_CGROUP_BPFy # CONFIG_CGROUP_MISCy步骤2挂载cgroup文件系统# 对于cgroup v1传统层级 sudo mkdir -p /sys/fs/cgroup/cpu sudo mount -t cgroup -o cpu none /sys/fs/cgroup/cpu # 对于cgroup v2统一层级推荐 sudo mkdir -p /sys/fs/cgroup/unified sudo mount -t cgroup2 none /sys/fs/cgroup/unified # 验证挂载 mount | grep cgroup步骤3安装监控工具# Debian/Ubuntu sudo apt-get install -y sysstat procps cgroup-tools stress-ng # RHEL/CentOS sudo yum install -y sysstat procps-ng libcgroup-tools stress-ng # 验证安装 which cgcreate cgexec stress-ng pidstat步骤4准备测试程序创建CPU密集型测试脚本cpu_burn.py#!/usr/bin/env python3 CPU密集型负载生成器 用于验证任务组shares权重分配效果 import os import sys import time def get_cpu_time(): 获取当前进程的CPU时间用户态内核态 with open(f/proc/{os.getpid()}/stat, r) as f: fields f.read().split() # 第14、15字段分别为utime和stime单位clock ticks utime int(fields[13]) stime int(fields[14]) return utime stime def main(): group_name sys.argv[1] if len(sys.argv) 1 else default duration int(sys.argv[2]) if len(sys.argv) 2 else 30 print(f[{group_name}] PID {os.getpid()} 开始CPU负载测试持续{duration}秒) start_time time.time() start_cpu get_cpu_time() # CPU密集型计算计算大数的质因数 counter 0 while time.time() - start_time duration: num 123456789012345 counter # 简单的因数分解模拟CPU负载 for i in range(2, int(num**0.5) 1): if num % i 0: break counter 1 if counter % 100000 0: elapsed time.time() - start_time current_cpu get_cpu_time() # 获取系统clock ticks每秒通常为100 ticks_per_sec os.sysconf(os.sysconf_names[SC_CLK_TCK]) cpu_seconds (current_cpu - start_cpu) / ticks_per_sec print(f[{group_name}] 已运行{elapsed:.1f}s, CPU时间{cpu_seconds:.1f}s, f迭代次数{counter}) total_time time.time() - start_time final_cpu get_cpu_time() ticks_per_sec os.sysconf(os.sysconf_names[SC_CLK_TCK]) total_cpu_sec (final_cpu - start_cpu) / ticks_per_sec print(f[{group_name}] 测试完成: 实际时间{total_time:.1f}s, fCPU时间{total_cpu_sec:.1f}s, fCPU利用率{(total_cpu_sec/total_time)*100:.1f}%) if __name__ __main__: main()四、应用场景多租户容器平台的CPU资源治理在Kubernetes集群中当多个命名空间的Pod共享节点资源时shares机制实现了弹性超售与硬隔离的混合策略。具体场景如下某电商平台的K8s集群运行三类工作负载实时交易服务高优先级、数据分析Job中优先级、日志处理DaemonSet低优先级。管理员为三个命名空间配置cpu.shares分别为4096、2048、1024比例为4:2:1。当节点CPU空闲时所有Pod可充分利用资源当资源争用发生时调度器确保交易服务获得约57%4/7的CPU时间数据分析获得29%日志处理获得14%。这种比例分配避免了传统先到先得导致的 starvation同时允许低优先级任务在空闲时蹭资源提升整体利用率。更复杂的场景涉及嵌套cgroup层级根cgroup下创建production和development两个组前者shares为8192后者为2048。在production组内再细分为payment4096和inventory4096。当payment组的Pod繁忙时它首先与inventory竞争production内的资源各占50%然后production作为整体与development竞争约80% vs 20%。这种层级化权重传播确保了资源治理策略的递归生效符合企业组织架构的权限模型。五、实际案例与步骤构建层级化CPU资源隔离环境5.1 案例一基础任务组创建与shares配置cgroup v1目标创建两个任务组A和B配置shares为2048和1024观察CPU分配比例。步骤1创建cgroup层级结构# 以root身份执行 sudo -i # 创建任务组A和B mkdir -p /sys/fs/cgroup/cpu/group_a mkdir -p /sys/fs/cgroup/cpu/group_b # 验证创建 ls -la /sys/fs/cgroup/cpu/步骤2配置shares权重# 设置group A的shares为2048获得2倍于默认的资源 echo 2048 /sys/fs/cgroup/cpu/group_a/cpu.shares # 设置group B的shares为1024默认值 echo 1024 /sys/fs/cgroup/cpu/group_b/cpu.shares # 验证配置 echo Group A shares: $(cat /sys/fs/cgroup/cpu/group_a/cpu.shares) echo Group B shares: $(cat /sys/fs/cgroup/cpu/group_b/cpu.shares)步骤3启动测试进程并绑定到cgroup# 在后台启动group A的CPU负载2个进程 python3 /path/to/cpu_burn.py GroupA 60 PID_A1$! echo $PID_A1 /sys/fs/cgroup/cpu/group_a/cgroup.procs python3 /path/to/cpu_burn.py GroupA2 60 PID_A2$! echo $PID_A2 /sys/fs/cgroup/cpu/group_a/cgroup.procs # 在后台启动group B的CPU负载1个进程 python3 /path/to/cpu_burn.py GroupB 60 PID_B$! echo $PID_B /sys/fs/cgroup/cpu/group_b/cgroup.procs echo Group A PIDs: $PID_A1, $PID_A2 echo Group B PID: $PID_B步骤4实时监控CPU分配# 使用pidstat监控各进程CPU使用率每秒输出共30次 pidstat 1 30 -u -p $PID_A1,$PID_A2,$PID_B # 或者使用top查看cgroup级别的统计 # 在另一个终端执行 watch -n 1 cat /sys/fs/cgroup/cpu/group_a/cpu.stat echo --- cat /sys/fs/cgroup/cpu/group_b/cpu.stat预期结果在CPU争用情况下Group Ashares20482个进程应获得约66.7%的CPU时间每个进程约33.3%Group Bshares10241个进程获得约33.3%。实际比例可能因系统负载和调度延迟略有偏差。5.2 案例二嵌套层级权重传播验证cgroup v2目标验证三层cgroup结构root → parent → child中的权重传播公式。步骤1挂载cgroup v2sudo mkdir -p /sys/fs/cgroup/unified sudo mount -t cgroup2 none /sys/fs/cgroup/unified # 启用CPU控制器 echo cpu /sys/fs/cgroup/unified/cgroup.subtree_control步骤2创建嵌套层级cd /sys/fs/cgroup/unified # 创建父组shares4096 mkdir parent echo 4096 parent/cpu.weight # v2使用cpu.weight语义与v1的shares相同但数值范围不同1-10000 # 创建子组shares2048和1024 mkdir parent/child_high mkdir parent/child_low echo 2048 parent/child_high/cpu.weight echo 1024 parent/child_low/cpu.weight # 验证层级 tree /sys/fs/cgroup/unified/parent步骤3内核级权重传播追踪通过ebpf或ftrace观察calc_group_shares的调用# 使用ftrace跟踪权重计算需要root权限 cd /sys/kernel/debug/tracing # 启用函数跟踪 echo 0 tracing_on echo trace echo calc_group_shares set_ftrace_filter echo function current_tracer # 启动测试进程 echo $$ /sys/fs/cgroup/unified/parent/child_high/cgroup.procs stress-ng --cpu 1 --timeout 30s echo 1 tracing_on sleep 5 echo 0 tracing_on # 查看跟踪结果 cat trace | head -505.3 案例三动态权重调整与负载均衡观察目标演示在运行时调整shares并观察调度器如何重新平衡资源。#!/bin/bash # dynamic_adjust.sh - 动态权重调整演示脚本 CGROUP_ROOT/sys/fs/cgroup/cpu GROUP1$CGROUP_ROOT/dynamic_1 GROUP2$CGROUP_ROOT/dynamic_2 # 创建cgroup mkdir -p $GROUP1 $GROUP2 # 初始配置各占102450%:50% echo 1024 $GROUP1/cpu.shares echo 1024 $GROUP2/cpu.shares # 启动两组CPU负载 stress-ng --cpu 2 --timeout 120s --pid $GROUP1/cgroup.procs PID1$! stress-ng --cpu 2 --timeout 120s --pid $GROUP2/cgroup.procs PID2$! echo 阶段1初始状态 1024:1024观察10秒... sleep 10 # 调整权重为3072:102475%:25% echo 3072 $GROUP1/cpu.shares echo 阶段2已调整权重为3072:1024观察10秒... sleep 10 # 再次调整为512:102433%:67% echo 512 $GROUP1/cpu.shares echo 阶段3已调整权重为512:1024观察10秒... sleep 10 # 清理 kill $PID1 $PID2 2/dev/null rmdir $GROUP1 $GROUP2 echo 测试完成监控方法# 使用mpstat观察各组CPU利用率变化 watch -n 1 mpstat -P ALL 1 1 | tail -n 4 # 或查看cgroup的CPU统计 watch -n 1 echo Group1: $(cat /sys/fs/cgroup/cpu/dynamic_1/cpuacct.usage) echo Group2: $(cat /sys/fs/cgroup/cpu/dynamic_2/cpuacct.usage)5.4 案例四与Kubernetes集成的实际配置场景为K8s Pod配置Burstable QoS类的cpu.shares。# pod-example.yaml apiVersion: v1 kind: Pod metadata: name: shares-demo spec: containers: - name: high-priority image: python:3.9 command: [python3, -c, while True: pass] resources: requests: cpu: 2 # 转换为 shares 2 * 1024 2048 limits: cpu: 4 - name: low-priority image: python:3.9 command: [python3, -c, while True: pass] resources: requests: cpu: 0.5 # 转换为 shares 0.5 * 1024 512 limits: cpu: 1底层验证# 在节点上查看Pod的cgroup配置 POD_ID$(crictl pods --name shares-demo -q) CGROUP_PATH/sys/fs/cgroup/kubepods/burstable/pod$POD_ID # 查看容器级shares cat $CGROUP_PATH/*/cpu.shares # 使用crictl exec进入容器查看当前cgroup crictl exec $CONTAINER_ID cat /proc/self/cgroup六、常见问题与解答Q1为什么设置了shares后任务组仍然没有获得预期的CPU比例原因分析非CPU密集型任务shares仅在CPU争用时生效若任务有大量I/O等待实际CPU利用率不足比例分配不明显多核并行在多核系统上任务组可能在多个CPU上并行运行每个CPU独立计算本地shares层级嵌套父组的shares限制了子组的总可用资源验证与解决# 检查任务是否真正在运行非睡眠状态 watch -n 1 cat /proc/$PID/stat | cut -d -f3 # 强制绑定到单核以简化观察 taskset -cp 0 $PID # 检查父组是否限制了资源 cat /sys/fs/cgroup/cpu/parent_group/cpu.statQ2shares与cpu.cfs_quota_us硬限制的区别是什么特性cpu.sharescpu.cfs_quota_us性质相对权重比例分配绝对限制硬上限生效时机仅在资源争用时始终生效超额使用允许空闲时禁止严格限制适用场景多租户公平共享计费、严格SLA保障内核参数CONFIG_FAIR_GROUP_SCHEDCONFIG_CFS_BANDWIDTH混合使用示例# 设置shares为2048高优先级 echo 2048 /sys/fs/cgroup/cpu/mixed_group/cpu.shares # 同时设置硬限制每100ms周期内最多使用50ms50%硬上限 echo 100000 /sys/fs/cgroup/cpu/mixed_group/cpu.cfs_period_us echo 50000 /sys/fs/cgroup/cpu/mixed_group/cpu.cfs_quota_usQ3如何调试任务组权重计算异常调试脚本#!/bin/bash # debug_shares.sh - 诊断任务组权重问题 TG_NAME$1 CPU${2:-0} echo 任务组 $TG_NAME 在CPU $CPU 上的调试信息 # 1. 查看配置shares echo 1. 配置的cpu.shares: $(cat /sys/fs/cgroup/cpu/$TG_NAME/cpu.shares) # 2. 查看当前负载 echo 2. 运行队列负载统计: cat /sys/fs/cgroup/cpu/$TG_NAME/cpu.stat # 3. 查看进程列表 echo 3. 组内进程: cat /sys/fs/cgroup/cpu/$TG_NAME/cgroup.procs # 4. 使用sched_debug如果内核支持 if [ -f /proc/sched_debug ]; then echo 4. 调度器调试信息查找cfs_rq相关: grep -A 20 cfs_rq\[$CPU\] /proc/sched_debug | grep -E (tg_|throttled|load) fi # 5. 计算理论权重占比简化模型 TOTAL_SHARES$(awk {sum$1} END {print sum} /sys/fs/cgroup/cpu/*/cpu.shares 2/dev/null) TG_SHARES$(cat /sys/fs/cgroup/cpu/$TG_NAME/cpu.shares) echo 5. 理论CPU占比: $TG_SHARES / $TOTAL_SHARES $(echo scale2; $TG_SHARES * 100 / $TOTAL_SHARES | bc)%Q4为什么cgroup v2的cpu.weight与v1的cpu.shares数值范围不同解答cgroup v2为了提升用户体验将weight范围调整为1-10000默认100而v1的shares范围是2-262144默认1024。内核内部通过线性映射将v2的weight转换为与v1统一的内部表示// kernel/sched/core.c 中的转换逻辑概念性 static inline unsigned long scale_from_cgroup_v2(unsigned long weight) { /* 将1-10000映射到内部权重体系 */ return (weight * 1024) / 100; // 简化示意实际更复杂 }配置对应关系cgroup v2 (cpu.weight)内核内部值等效cgroup v1 (cpu.shares)1~1010100102410241000~1024010240七、实践建议与最佳实践7.1 生产环境配置建议1. shares值的规划策略# 建议采用2的幂次方作为基准便于计算和记忆 # 层级1部门级划分 mkdir -p /sys/fs/cgroup/cpu/dept_{a,b,c} echo 4096 dept_a/cpu.shares # 高优先级部门如核心交易 echo 2048 dept_b/cpu.shares # 中优先级如数据分析 echo 1024 dept_c/cpu.shares # 低优先级如日志处理 # 层级2项目级划分继承父组限制 mkdir -p dept_a/project_{1,2} echo 2048 dept_a/project_1/cpu.shares # 在项目内占2/3 echo 1024 dept_a/project_2/cpu.shares # 在项目内占1/32. 与systemd集成# /etc/systemd/system/myapp.service [Unit] DescriptionMy Application with CPU Shares [Service] ExecStart/usr/bin/myapp CPUShares2048 # 或cgroup v2 CPUWeight200 # 创建层级结构 Slicedept_a.slice7.2 性能调优技巧1. 减少调度延迟# 对于延迟敏感型任务结合shares与cpu.cfs_quota_us # 确保任务组在需要时能获得足够CPU同时防止垄断 # 设置较高的shares保证优先级 echo 8192 /sys/fs/cgroup/cpu/latency_critical/cpu.shares # 设置合适的period和quota保证响应 echo 1000000 latency_critical/cpu.cfs_period_us # 1秒周期 echo 900000 latency_critical/cpu.cfs_quota_us # 允许使用900ms90%2. NUMA环境下的权重优化# 在NUMA系统上任务组可能在不同Node上分布不均 # 建议结合cpuset限制任务组在特定Node上运行减少跨Node调度开销 mkdir -p /sys/fs/cgroup/cpuset/numa_aware_group echo 0-3 numa_aware_group/cpuset.cpus # 绑定到NUMA Node 0的CPU echo 0 numa_aware_group/cpuset.mems # 绑定到NUMA Node 0的内存 # 同时设置CPU shares mkdir -p /sys/fs/cgroup/cpu/numa_aware_group echo 2048 numa_aware_group/cpu.shares7.3 监控与告警Prometheus监控指标采集脚本#!/bin/bash # cgroup_exporter.sh - 采集cgroup CPU指标 CGROUP_ROOT/sys/fs/cgroup/cpu OUTPUT/var/lib/node_exporter/textfile_collector/cgroup_stats.prom echo # HELP cgroup_cpu_shares Configured CPU shares $OUTPUT echo # TYPE cgroup_cpu_shares gauge $OUTPUT for group in $(find $CGROUP_ROOT -mindepth 1 -type d); do group_name$(echo $group | sed s|/|_|g) shares$(cat $group/cpu.shares 2/dev/null || echo 0) echo cgroup_cpu_shares{group\$group_name\} $shares $OUTPUT done echo # HELP cgroup_cpu_usage_seconds_total Total CPU time consumed $OUTPUT echo # TYPE cgroup_cpu_usage_seconds_total counter $OUTPUT for group in $(find $CGROUP_ROOT -mindepth 1 -type d); do group_name$(echo $group | sed s|/|_|g) usage$(cat $group/cpuacct.usage 2/dev/null || echo 0) # 转换为秒纳秒转秒 usage_sec$(echo scale9; $usage / 1000000000 | bc) echo cgroup_cpu_usage_seconds_total{group\$group_name\} $usage_sec $OUTPUT done八、总结与应用场景展望本文深入剖析了Linux CFS调度器中任务组权重shares的分配机制与层级传播原理。核心要点包括权重计算模型通过calc_group_shares实现基于本地负载占全局负载比例的动态分配确保多CPU环境下的公平性层级传播机制自底向上的vruntime累加与自顶向下的shares继承构建嵌套cgroup的递归资源治理模型与虚拟运行时间的协同权重通过影响vruntime增长速率间接决定任务在红黑树中的位置和调度优先级未来应用场景Serverless计算在函数计算场景中shares机制可实现毫秒级的容器启动与资源比例分配支持高密部署AI训练集群在GPU/CPU混合调度中结合shares与cpuset实现训练任务的拓扑感知资源分配边缘计算在资源受限的边缘节点通过精细化的shares配置保障关键物联网应用的实时性掌握任务组权重机制开发者不仅能够优化容器密度与资源利用率更能深入理解现代操作系统从进程中心向资源中心演进的架构哲学。建议读者在测试环境反复实验本文提供的案例结合ftrace和bpftrace工具观察内核实际行为将理论知识转化为运维与开发的实战能力。

更多文章