简介Linux 2.6.23 版本正式引入 CFS 完全公平调度器替代传统 O (1) 调度器成为默认普通任务调度策略。早期 CFS 仅针对单个进程做时间片公平分配在单机多用户、多业务隔离、容器虚拟化、云主机集群等复杂场景下暴露明显短板所有进程在全局队列中竞争 CPU 资源无法按业务、用户、租户做资源配额隔离高负载业务极易挤占其他进程算力系统稳定性与资源可控性大幅下降。为解决这一问题Linux 内核基于cgroup管控体系推出组调度Group Scheduling核心依托task_group结构体实现层次化调度模型。它打破了 “进程直接参与全局调度” 的单一模式构建出组间公平竞争、组内进程再公平竞争的双层调度架构。task_group本身被抽象为独立调度实体和普通进程一样参与 CFS 调度结合树形层级结构可灵活实现多级资源划分、权重分配、带宽限流。目前该机制是 Linux 虚拟化、容器技术的底层基石Docker、Kubernetes、LXC 等容器平台的 CPU 资源限制、租户配额、业务优先级划分全部依赖task_group组调度同时在服务器多用户运维、嵌入式多业务分区、后台服务优先级管控等场景广泛落地。对于内核研发、运维工程师、云原生开发、嵌入式 Linux 从业者而言吃透task_group层次化调度原理、内核源码、实操配置与问题排查不仅能理解容器资源限制的底层逻辑还可自主定制 CPU 资源分配策略、优化高并发服务器负载、排查 CPU 抢占异常问题也是撰写内核相关论文、技术报告、做内核二次开发的必备知识。本文从基础概念、环境搭建、源码解析、实操案例、排错优化全维度讲解兼顾理论深度与工程实战。一、核心概念与术语解析1.1 CFS 基础回顾CFS 完全公平调度器放弃了固定时间片设计以虚拟运行时间 (vruntime)作为调度依据核心规则优先选择 vruntime 最小的调度实体运行保证所有可运行实体获得相对公平的 CPU 占用比例。普通进程task_struct内嵌struct sched_entity调度实体这是进程参与 CFS 调度的基础单元。1.2 组调度与 task_group 核心定义组调度基于CONFIG_FAIR_GROUP_SCHED内核配置开启依赖 cgroup CPU 子系统。task_group即任务组是组调度的核心载体每一个 CPU cgroup 都会对应一个独立task_group多个任务组可构成父子层级的树形结构。和普通进程不同task_group具备两套调度资源组调度实体 (se)每个 CPU 核心上任务组拥有独立的struct sched_entity让任务组作为整体参与上层 CFS 队列调度组运行队列 (cfs_rq)每个 CPU 核心上任务组维护专属 CFS 就绪队列组内所有进程挂载到此队列中组内进程在此队列内竞争 CPU。简单来说上层看组下层看进程。父组分配 CPU 时间给子任务组子任务组再将时间分配给组内进程形成层次化调度。1.3 关键数据结构与字段说明1.3.1 task_group 结构体该结构体定义在include/linux/sched.h是组调度顶层结构以下为内核精简源码适配 5.4~6.6 主流 LTS 版本struct task_group { /* cgroup基础控制结构关联cgroup体系 */ struct cgroup_subsys_state css; #ifdef CONFIG_FAIR_GROUP_SCHED /* 每个CPU对应的组调度实体数组下标为CPU编号 */ struct sched_entity **se; /* 每个CPU对应的组内CFS运行队列 */ struct cfs_rq **cfs_rq; /* 组权重决定该组在同级组中分得CPU的比例默认值1024 */ unsigned long shares; /* 组负载统计用于负载均衡计算 */ atomic_long_t load_avg ____cacheline_aligned; /* CPU带宽限流配置配额、周期、定时器 */ struct cfs_bandwidth cfs_bandwidth; #endif #ifdef CONFIG_RT_GROUP_SCHED /* 实时任务组调度相关成员本文不展开 */ struct rt_rq **rt_rq; struct sched_rt_entity **rt_se; #endif /* 父子组链表构建树形层级 */ struct task_group *parent; struct list_head siblings; struct list_head children; };字段解读shares核心权重字段同级任务组之间按照shares数值比例瓜分父组分配的 CPU 时间se/cfs_rq按 CPU 维度拆分多核环境下每个核心独立维护组队列与调度实体避免跨核锁竞争parent/children构建层级树支持多级分组比如根组→业务组→容器组→容器内进程。1.3.2 cfs_rq 与 sched_entity 关系全局rqCPU 物理运行队列顶层挂载各级 task_group 的调度实体任务组cfs_rq组内私有队列挂载普通进程的调度实体层级流转进程唤醒 → 加入所属 task_group 的cfs_rq→ 组se参与上层rq调度 → 组获得 CPU 后调度组内进程。1.4 核心调度规则同级组公平原则同一父组下的多个子任务组CPU 占用比例等于各自shares的比值例如 A 组 shares2048B 组 shares1024则 A:B CPU 占比为 2:1。层级传递原则资源自上而下逐层分配子组无法超越父组的 CPU 配额多级嵌套依然遵循公平规则。组内调度原则任务组拿到 CPU 时间后组内进程按照标准 CFS 规则基于 nice 值、vruntime 竞争执行。带宽限流规则可通过cfs_quota_us/cfs_period_us限制任务组最大 CPU 使用率实现硬资源隔离。1.5 常用工具与术语cgroup v1/v2Linux 资源分组管控接口CPU 子系统用于配置 task_group 参数cgcreate/cgsetcgroup 命令行管理工具perf/ftrace跟踪组调度内核函数、观测 vruntime 与队列状态cpu.sharescgroup 文件对应 task_group-shares 权重cpu.cfs_quota_us/cpu.cfs_period_usCPU 硬限频配置文件。二、环境准备2.1 软硬件环境清单环境分类版本 / 配置要求用途说明操作系统Ubuntu 20.04 / 22.0464 位、CentOS 7/8主流服务端发行版默认开启 cgroup 与组调度内核版本Linux 5.4、5.15、6.1 LTS组调度逻辑稳定源码一致适配生产环境硬件x86_64 多核 CPU≥4 核、4G 以上内存多核可直观观测 per-CPU 组队列编译工具gcc、make、libncurses-dev、bison、flex内核编译、模块调试运维工具cgroup-tools、libcgroup、perf、trace-cmdcgroup 配置、调度跟踪、性能观测调试工具gdb、kgdb内核源码断点调试2.2 系统环境检查与依赖安装2.2.1 检查内核配置关键组调度依赖三大内核开关执行以下命令验证是否开启# 检查Cgroup总开关、公平组调度 zcat /proc/config.gz | grep -E CGROUP|FAIR_GROUP_SCHED正常输出必须包含CONFIG_CGROUPSy CONFIG_CGROUP_SCHEDy CONFIG_FAIR_GROUP_SCHEDy如果未开启需要重新编译内核并勾选以上配置项。2.2.2 安装 cgroup 管理工具# Ubuntu/Debian 系列 sudo apt update sudo apt install cgroup-tools libcgroup-dev -y # CentOS/RHEL 系列 sudo yum install libcgroup libcgroup-tools -y2.2.3 挂载 cgroup 文件系统cgroup v1主流系统默认已挂载手动挂载命令故障恢复使用# 创建挂载目录 sudo mkdir -p /sys/fs/cgroup/cpu # 挂载CPU子系统 sudo mount -t cgroup -o cpu none /sys/fs/cgroup/cpu2.3 内核源码路径说明组调度核心源码目录后续代码分析均基于以下路径kernel/sched/fair.c # CFS组调度核心逻辑、队列操作、调度选择 kernel/sched/sched.h # task_group、cfs_rq、sched_entity 结构体定义 kernel/cgroup/cgroup.c # cgroup与task_group绑定、组创建/销毁逻辑 kernel/sched/task_group.c # task_group初始化、层级管理、shares更新三、应用场景task_group 实现的层次化组调度是现代 Linux 资源隔离的核心在生产环境中落地极广。在云服务器与容器场景中K8s、Docker 利用 task_group 为每个 Pod / 容器创建独立任务组通过 shares 权重和 CPU 配额限制不同租户、业务的算力占用避免单容器耗尽整机 CPU。在多用户服务器场景运维人员可为不同登录用户创建专属任务组按部门重要性分配 CPU 权重保障核心业务优先运行。在嵌入式车载、工业网关设备中系统将前台交互、后台采集、日志服务划分为多级任务组分层限制资源保证高优先级任务稳定运行。同时在大数据集群、编译集群中借助层级组调度实现任务队列优先级划分合理调度算力资源全面提升系统稳定性与资源利用率。四、实际案例与步骤源码 实操代码本章分为内核源码解析、cgroup 实操配置、调度跟踪验证三部分所有代码、命令均可直接复制运行。4.1 内核源码深度解析task_group 工作全流程4.1.1 任务组创建与初始化流程当用户通过 cgroup 创建新 CPU 分组时内核会调用alloc_task_group分配task_group并为每个 CPU 初始化se调度实体与cfs_rq队列核心源码片段// kernel/sched/task_group.c struct task_group *alloc_task_group(struct task_group *parent) { struct task_group *tg; int cpu; unsigned int nr_cpu_ids nr_cpu_ids; // 分配task_group主结构体 tg kzalloc(sizeof(*tg), GFP_KERNEL); if (!tg) return NULL; // 绑定父组构建层级树 tg-parent parent; INIT_LIST_HEAD(tg-siblings); INIT_LIST_HEAD(tg-children); list_add(tg-siblings, parent-children); // 默认权重 1024与系统默认进程权重一致 tg-shares 1024; #ifdef CONFIG_FAIR_GROUP_SCHED // 按CPU数量分配 se 数组每个CPU一个组调度实体 tg-se kcalloc(nr_cpu_ids, sizeof(struct sched_entity *), GFP_KERNEL); // 按CPU数量分配 cfs_rq 数组每个CPU一个组运行队列 tg-cfs_rq kcalloc(nr_cpu_ids, sizeof(struct cfs_rq *), GFP_KERNEL); // 遍历所有CPU逐个初始化组调度实体与队列 for_each_possible_cpu(cpu) { struct sched_entity *se; struct cfs_rq *cfs_rq; // 分配并初始化组调度实体 se kzalloc(sizeof(*se), GFP_KERNEL); init_sched_entity(se); tg-se[cpu] se; // 分配并初始化组内CFS运行队列 cfs_rq kzalloc(sizeof(*cfs_rq), GFP_KERNEL); init_cfs_rq(cfs_rq); tg-cfs_rq[cpu] cfs_rq; // 建立组队列与父队列的关联完成层级绑定 link_cfs_rq(cfs_rq, cpu_rq(cpu)-cfs); } #endif return tg; }代码说明新任务组默认继承父组层级加入父子链表形成树形结构全局默认shares1024同级组无配置时均分 CPU采用per-CPU设计每个核心独立一套secfs_rq是多核调度性能优化的关键。4.1.2 进程加入任务组逻辑将进程移入 cgroup 时内核会把进程调度实体从原组cfs_rq移除加入新组cfs_rq核心函数move_task_to_cgroup调用链路下的队列迁移代码// kernel/sched/fair.c static void detach_task_cfs_rq(struct task_struct *p) { struct sched_entity *se p-se; struct cfs_rq *cfs_rq task_cfs_rq(p); // 从原组CFS队列删除进程调度实体 if (se-on_rq) { dequeue_entity(cfs_rq, se, DEQUEUE_SLEEP); cfs_rq-nr_running--; } } static void attach_task_cfs_rq(struct task_struct *p) { struct sched_entity *se p-se; struct cfs_rq *cfs_rq task_cfs_rq(p); // 加入新任务组的CFS队列 if (se-on_rq) { cfs_rq-nr_running; enqueue_entity(cfs_rq, se, ENQUEUE_WAKEUP); } }代码说明进程的归属本质是调度实体所属 cfs_rq 的切换进程本身不直接参与全局调度始终挂靠在某个task_group的私有队列中。4.1.3 层次化调度选择逻辑CFS 选任务时先选顶层 task_group再选组内进程核心函数pick_next_task_fair精简源码// kernel/sched/fair.c static struct task_struct *pick_next_task_fair(struct rq *rq) { struct cfs_rq *cfs_rq rq-cfs; struct sched_entity *se; // 循环遍历层级队列从顶层组逐级向下查找 do { // 从当前cfs_rq选出vruntime最小的调度实体 se pick_next_entity(cfs_rq, NULL); // 如果选中的是任务组调度实体进入下一层组队列 cfs_rq group_cfs_rq(se); } while (cfs_rq); // 最终se指向普通进程调度实体返回进程 return se-my_task; }核心逻辑解读顶层cfs_rq存放所有任务组的 se选出最优组进入该组的私有cfs_rq继续选出组内最优进程多级 task_group 嵌套时循环逐层向下遍历实现完整层次化调度。4.1.4 shares 权重更新函数修改 cgroup 的cpu.shares文件时内核更新task_group-shares并重新计算权重// kernel/sched/task_group.c void sched_group_set_shares(struct task_group *tg, unsigned long shares) { int cpu; tg-shares shares; // 遍历所有CPU更新该组在每个核心上的调度实体权重 for_each_possible_cpu(cpu) { struct sched_entity *se tg-se[cpu]; if (se) { // 将shares转换为CFS调度权重 se-load.weight calc_weight(shares); } } }代码说明shares仅对同级兄弟组生效父组权重不会被修改严格保证层级隔离。4.2 实操案例 1创建任务组、配置权重并测试 CPU 分配本案例基于 cgroup v1 CPU 子系统创建两个任务组配置不同cpu.shares压测验证 CPU 占比。步骤 1创建两级任务组模拟层级架构# 进入CPU cgroup根目录 cd /sys/fs/cgroup/cpu # 创建一级父组 parent_group sudo mkdir parent_group # 在父组内创建两个子组 group_a、group_b形成层级父→子 sudo mkdir parent_group/group_a sudo mkdir parent_group/group_b步骤 2配置组权重shares规则父组下 group_a2048group_b1024理论 CPU 占比 2:1# 配置group_a权重 echo 2048 | sudo tee parent_group/group_a/cpu.shares # 配置group_b权重 echo 1024 | sudo tee parent_group/group_b/cpu.shares # 查看配置结果 cat parent_group/group_a/cpu.shares cat parent_group/group_b/cpu.shares步骤 3编写 CPU 压测程序无限循环占用 CPU新建文件cpu_stress.c代码如下#include stdio.h // 简单死循环持续占用CPU int main(void) { while(1) { ; } return 0; }编译运行gcc cpu_stress.c -o cpu_stress步骤 4将压测进程加入不同任务组# 后台启动两个压测进程 ./cpu_stress PID1$! ./cpu_stress PID2$! # 将PID1加入 group_a echo $PID1 | sudo tee parent_group/group_a/cgroup.procs # 将PID2加入 group_b echo $PID2 | sudo tee parent_group/group_b/cgroup.procs步骤 5观测 CPU 占用比例打开新终端使用top命令观测top预期结果两个进程 CPU 使用率近似 66% : 33%严格匹配 2048:1024 的权重比例验证组调度公平性。4.3 实操案例 2配置 CPU 硬配额带宽限流基于cfs_quota_us限制任务组最大 CPU 使用率单 CPU 核心上限为 100000us100%。# 限制group_a 最多使用单个核心的50% CPU50000us / 100000us echo 50000 | sudo tee parent_group/group_a/cpu.cfs_quota_us echo 100000 | sudo tee parent_group/group_a/cpu.cfs_period_us重新运行压测程序可观察到group_a内进程 CPU 被限制在 50% 以内不受系统总负载影响实现硬隔离。4.4 实操案例 3ftrace 跟踪组调度内核函数通过内核跟踪工具观测task_group创建、队列入队、调度选择等函数调用验证源码执行流程# 挂载debugfs sudo mount -t debugfs none /sys/kernel/debug # 清空跟踪缓冲区 echo /sys/kernel/debug/tracing/trace # 过滤需要跟踪的组调度核心函数 echo pick_next_task_fair /sys/kernel/debug/tracing/set_ftrace_filter echo enqueue_entity /sys/kernel/debug/tracing/set_ftrace_filter echo sched_group_set_shares /sys/kernel/debug/tracing/set_ftrace_filter # 开启跟踪 echo function /sys/kernel/debug/tracing/current_tracer echo 1 /sys/kernel/debug/tracing/tracing_on # 执行前面的cgroup、压测操作完成后停止跟踪 echo 0 /sys/kernel/debug/tracing/tracing_on # 查看跟踪日志 cat /sys/kernel/debug/tracing/trace日志作用可清晰看到层级调度的函数调用顺序对应前文内核源码逻辑。五、常见问题与解答Q1修改 cpu.shares 后CPU 占比没有变化是什么原因解答1.cpu.shares仅在同级任务组同时处于就绪状态时生效若某组无活跃进程资源会全部分配给其他组2. 检查进程是否真正移入对应 cgroup查看cgroup.procs文件确认 PID3. shares 是比例权重不是绝对值整机 CPU 空闲时所有进程都会占满资源压力满载时才会体现比例。Q2多级 task_group 嵌套后权重计算规则是怎样的解答资源逐层分配。例如根组下父组 A (shares1024)、父组 B (shares1024)A 下有子组 A1 (1024)、A2 (1024)。整机压力满载时A、B 各分 50% CPUA 的 50% 再平分给 A1、A2最终 A1/A2 各占整机 25%。Q3cfs_quota_us 配置不生效无法限制 CPU解答1.cfs_quota_us-1代表不限制需设置为正数2. 该配额是单 CPU 核心上限多核场景下进程漂移到其他核心会突破限制可配合cpuset绑定 CPU 核心3. 检查内核是否开启CONFIG_CFS_BANDWIDTH。Q4进程迁移到新 task_group 后出现短暂调度卡顿解答进程迁移时会执行dequeue/enqueue队列操作重新计算 vruntime高频率迁移会产生开销。生产环境应避免频繁将进程在不同 cgroup 间切换。Q5如何区分普通 CFS 调度和组调度在队列上的差异解答开启CONFIG_FAIR_GROUP_SCHED后CPU 顶层队列存放 task_group 的调度实体组内队列存放进程关闭该配置后所有进程直接挂载在全局 CFS 队列无分层结构。可通过内核配置与 ftrace 函数调用区分。六、实践建议与最佳实践6.1 资源划分最佳实践层级不宜过深task_group 树形层级建议控制在 3 层以内根→业务组→容器组层级越多调度逐层遍历开销越大会轻微增加调度时延。shares 统一规划同层级任务组shares尽量使用 1024 的整数倍便于比例计算核心业务组设置更高权重非核心业务降低权重。权重与配额结合使用对弹性业务使用cpu.shares做软隔离压力大时按比例分配对核心租户、容器使用cfs_quota_us做硬限制防止资源耗尽。6.2 性能优化技巧CPU 核心绑定对延迟敏感的业务使用cpuset将 task_group 内进程绑定固定 CPU 核心减少进程跨核迁移、组队列重建的开销。减少组数量单 CPU 上不要创建大量空闲 task_group空组依然会占用队列与调度实体资源增加调度遍历耗时。避免动态修改权重运行中频繁修改cpu.shares会调用sched_group_set_shares刷新权重引发队列重计算尽量在业务启动前配置完成。6.3 问题排查规范CPU 抢占异常排查顺序先检查进程所属 cgroup → 查看shares/quota配置 → ftrace 跟踪调度函数 → 分析层级队列状态。高调度时延排查使用perf sched统计调度延迟重点排查层级过深、组数量过多、进程频繁迁移三类问题。容器资源异常Docker/K8s 本质基于 cgrouptask_group排查容器 CPU 限制问题时直接进入对应 cgroup 目录查看配置文件。6.4 内核定制开发建议若需要二次开发组调度逻辑尽量基于现有task_group层级架构扩展不要重构分层模型新增调度规则时在enqueue_entity/pick_next_entity钩子函数中扩展减少对主调度流程的侵入。七、总结与应用延伸本文完整讲解了 Linux CFS 组调度的核心设计思想、task_group层次化架构、关键内核数据结构、源码逻辑、cgroup 实操配置、压测验证与排错方案。task_group的核心创新是将任务组抽象为调度实体把单一进程调度升级为多层级树形调度实现了 CPU 资源的分层、分租户、分业务精细化隔离。从技术本质来看组调度分为两大能力基于shares的软权重分配保障同级组公平竞争基于cfs_quota的硬带宽限流实现资源强隔离。两者结合构成了 Linux 虚拟化、容器、多用户服务器的资源管控底座。在工程落地层面除了 Docker、K8s 容器场景该机制还广泛用于云主机租户隔离、大数据集群任务调度、嵌入式系统业务分区、服务器多部门资源划分等场景。对于开发者而言掌握task_group原理不仅能理解容器资源限制的底层逻辑还能自主编写资源管控脚本、优化服务器负载、排查 CPU 调度类疑难问题对于学术研究本文的源码、实操案例、运行机制可直接用于 Linux 调度子系统相关论文、技术报告的撰写。建议读者基于本文代码尝试创建多级嵌套任务组、组合cpusetcgroup做 CPU 绑定与限流、修改内核源码观察调度行为变化在实操中加深对层次化公平调度的理解将理论知识落地到实际项目中。
Linux 组调度核心原理:task_group 的层次化公平调度
简介Linux 2.6.23 版本正式引入 CFS 完全公平调度器替代传统 O (1) 调度器成为默认普通任务调度策略。早期 CFS 仅针对单个进程做时间片公平分配在单机多用户、多业务隔离、容器虚拟化、云主机集群等复杂场景下暴露明显短板所有进程在全局队列中竞争 CPU 资源无法按业务、用户、租户做资源配额隔离高负载业务极易挤占其他进程算力系统稳定性与资源可控性大幅下降。为解决这一问题Linux 内核基于cgroup管控体系推出组调度Group Scheduling核心依托task_group结构体实现层次化调度模型。它打破了 “进程直接参与全局调度” 的单一模式构建出组间公平竞争、组内进程再公平竞争的双层调度架构。task_group本身被抽象为独立调度实体和普通进程一样参与 CFS 调度结合树形层级结构可灵活实现多级资源划分、权重分配、带宽限流。目前该机制是 Linux 虚拟化、容器技术的底层基石Docker、Kubernetes、LXC 等容器平台的 CPU 资源限制、租户配额、业务优先级划分全部依赖task_group组调度同时在服务器多用户运维、嵌入式多业务分区、后台服务优先级管控等场景广泛落地。对于内核研发、运维工程师、云原生开发、嵌入式 Linux 从业者而言吃透task_group层次化调度原理、内核源码、实操配置与问题排查不仅能理解容器资源限制的底层逻辑还可自主定制 CPU 资源分配策略、优化高并发服务器负载、排查 CPU 抢占异常问题也是撰写内核相关论文、技术报告、做内核二次开发的必备知识。本文从基础概念、环境搭建、源码解析、实操案例、排错优化全维度讲解兼顾理论深度与工程实战。一、核心概念与术语解析1.1 CFS 基础回顾CFS 完全公平调度器放弃了固定时间片设计以虚拟运行时间 (vruntime)作为调度依据核心规则优先选择 vruntime 最小的调度实体运行保证所有可运行实体获得相对公平的 CPU 占用比例。普通进程task_struct内嵌struct sched_entity调度实体这是进程参与 CFS 调度的基础单元。1.2 组调度与 task_group 核心定义组调度基于CONFIG_FAIR_GROUP_SCHED内核配置开启依赖 cgroup CPU 子系统。task_group即任务组是组调度的核心载体每一个 CPU cgroup 都会对应一个独立task_group多个任务组可构成父子层级的树形结构。和普通进程不同task_group具备两套调度资源组调度实体 (se)每个 CPU 核心上任务组拥有独立的struct sched_entity让任务组作为整体参与上层 CFS 队列调度组运行队列 (cfs_rq)每个 CPU 核心上任务组维护专属 CFS 就绪队列组内所有进程挂载到此队列中组内进程在此队列内竞争 CPU。简单来说上层看组下层看进程。父组分配 CPU 时间给子任务组子任务组再将时间分配给组内进程形成层次化调度。1.3 关键数据结构与字段说明1.3.1 task_group 结构体该结构体定义在include/linux/sched.h是组调度顶层结构以下为内核精简源码适配 5.4~6.6 主流 LTS 版本struct task_group { /* cgroup基础控制结构关联cgroup体系 */ struct cgroup_subsys_state css; #ifdef CONFIG_FAIR_GROUP_SCHED /* 每个CPU对应的组调度实体数组下标为CPU编号 */ struct sched_entity **se; /* 每个CPU对应的组内CFS运行队列 */ struct cfs_rq **cfs_rq; /* 组权重决定该组在同级组中分得CPU的比例默认值1024 */ unsigned long shares; /* 组负载统计用于负载均衡计算 */ atomic_long_t load_avg ____cacheline_aligned; /* CPU带宽限流配置配额、周期、定时器 */ struct cfs_bandwidth cfs_bandwidth; #endif #ifdef CONFIG_RT_GROUP_SCHED /* 实时任务组调度相关成员本文不展开 */ struct rt_rq **rt_rq; struct sched_rt_entity **rt_se; #endif /* 父子组链表构建树形层级 */ struct task_group *parent; struct list_head siblings; struct list_head children; };字段解读shares核心权重字段同级任务组之间按照shares数值比例瓜分父组分配的 CPU 时间se/cfs_rq按 CPU 维度拆分多核环境下每个核心独立维护组队列与调度实体避免跨核锁竞争parent/children构建层级树支持多级分组比如根组→业务组→容器组→容器内进程。1.3.2 cfs_rq 与 sched_entity 关系全局rqCPU 物理运行队列顶层挂载各级 task_group 的调度实体任务组cfs_rq组内私有队列挂载普通进程的调度实体层级流转进程唤醒 → 加入所属 task_group 的cfs_rq→ 组se参与上层rq调度 → 组获得 CPU 后调度组内进程。1.4 核心调度规则同级组公平原则同一父组下的多个子任务组CPU 占用比例等于各自shares的比值例如 A 组 shares2048B 组 shares1024则 A:B CPU 占比为 2:1。层级传递原则资源自上而下逐层分配子组无法超越父组的 CPU 配额多级嵌套依然遵循公平规则。组内调度原则任务组拿到 CPU 时间后组内进程按照标准 CFS 规则基于 nice 值、vruntime 竞争执行。带宽限流规则可通过cfs_quota_us/cfs_period_us限制任务组最大 CPU 使用率实现硬资源隔离。1.5 常用工具与术语cgroup v1/v2Linux 资源分组管控接口CPU 子系统用于配置 task_group 参数cgcreate/cgsetcgroup 命令行管理工具perf/ftrace跟踪组调度内核函数、观测 vruntime 与队列状态cpu.sharescgroup 文件对应 task_group-shares 权重cpu.cfs_quota_us/cpu.cfs_period_usCPU 硬限频配置文件。二、环境准备2.1 软硬件环境清单环境分类版本 / 配置要求用途说明操作系统Ubuntu 20.04 / 22.0464 位、CentOS 7/8主流服务端发行版默认开启 cgroup 与组调度内核版本Linux 5.4、5.15、6.1 LTS组调度逻辑稳定源码一致适配生产环境硬件x86_64 多核 CPU≥4 核、4G 以上内存多核可直观观测 per-CPU 组队列编译工具gcc、make、libncurses-dev、bison、flex内核编译、模块调试运维工具cgroup-tools、libcgroup、perf、trace-cmdcgroup 配置、调度跟踪、性能观测调试工具gdb、kgdb内核源码断点调试2.2 系统环境检查与依赖安装2.2.1 检查内核配置关键组调度依赖三大内核开关执行以下命令验证是否开启# 检查Cgroup总开关、公平组调度 zcat /proc/config.gz | grep -E CGROUP|FAIR_GROUP_SCHED正常输出必须包含CONFIG_CGROUPSy CONFIG_CGROUP_SCHEDy CONFIG_FAIR_GROUP_SCHEDy如果未开启需要重新编译内核并勾选以上配置项。2.2.2 安装 cgroup 管理工具# Ubuntu/Debian 系列 sudo apt update sudo apt install cgroup-tools libcgroup-dev -y # CentOS/RHEL 系列 sudo yum install libcgroup libcgroup-tools -y2.2.3 挂载 cgroup 文件系统cgroup v1主流系统默认已挂载手动挂载命令故障恢复使用# 创建挂载目录 sudo mkdir -p /sys/fs/cgroup/cpu # 挂载CPU子系统 sudo mount -t cgroup -o cpu none /sys/fs/cgroup/cpu2.3 内核源码路径说明组调度核心源码目录后续代码分析均基于以下路径kernel/sched/fair.c # CFS组调度核心逻辑、队列操作、调度选择 kernel/sched/sched.h # task_group、cfs_rq、sched_entity 结构体定义 kernel/cgroup/cgroup.c # cgroup与task_group绑定、组创建/销毁逻辑 kernel/sched/task_group.c # task_group初始化、层级管理、shares更新三、应用场景task_group 实现的层次化组调度是现代 Linux 资源隔离的核心在生产环境中落地极广。在云服务器与容器场景中K8s、Docker 利用 task_group 为每个 Pod / 容器创建独立任务组通过 shares 权重和 CPU 配额限制不同租户、业务的算力占用避免单容器耗尽整机 CPU。在多用户服务器场景运维人员可为不同登录用户创建专属任务组按部门重要性分配 CPU 权重保障核心业务优先运行。在嵌入式车载、工业网关设备中系统将前台交互、后台采集、日志服务划分为多级任务组分层限制资源保证高优先级任务稳定运行。同时在大数据集群、编译集群中借助层级组调度实现任务队列优先级划分合理调度算力资源全面提升系统稳定性与资源利用率。四、实际案例与步骤源码 实操代码本章分为内核源码解析、cgroup 实操配置、调度跟踪验证三部分所有代码、命令均可直接复制运行。4.1 内核源码深度解析task_group 工作全流程4.1.1 任务组创建与初始化流程当用户通过 cgroup 创建新 CPU 分组时内核会调用alloc_task_group分配task_group并为每个 CPU 初始化se调度实体与cfs_rq队列核心源码片段// kernel/sched/task_group.c struct task_group *alloc_task_group(struct task_group *parent) { struct task_group *tg; int cpu; unsigned int nr_cpu_ids nr_cpu_ids; // 分配task_group主结构体 tg kzalloc(sizeof(*tg), GFP_KERNEL); if (!tg) return NULL; // 绑定父组构建层级树 tg-parent parent; INIT_LIST_HEAD(tg-siblings); INIT_LIST_HEAD(tg-children); list_add(tg-siblings, parent-children); // 默认权重 1024与系统默认进程权重一致 tg-shares 1024; #ifdef CONFIG_FAIR_GROUP_SCHED // 按CPU数量分配 se 数组每个CPU一个组调度实体 tg-se kcalloc(nr_cpu_ids, sizeof(struct sched_entity *), GFP_KERNEL); // 按CPU数量分配 cfs_rq 数组每个CPU一个组运行队列 tg-cfs_rq kcalloc(nr_cpu_ids, sizeof(struct cfs_rq *), GFP_KERNEL); // 遍历所有CPU逐个初始化组调度实体与队列 for_each_possible_cpu(cpu) { struct sched_entity *se; struct cfs_rq *cfs_rq; // 分配并初始化组调度实体 se kzalloc(sizeof(*se), GFP_KERNEL); init_sched_entity(se); tg-se[cpu] se; // 分配并初始化组内CFS运行队列 cfs_rq kzalloc(sizeof(*cfs_rq), GFP_KERNEL); init_cfs_rq(cfs_rq); tg-cfs_rq[cpu] cfs_rq; // 建立组队列与父队列的关联完成层级绑定 link_cfs_rq(cfs_rq, cpu_rq(cpu)-cfs); } #endif return tg; }代码说明新任务组默认继承父组层级加入父子链表形成树形结构全局默认shares1024同级组无配置时均分 CPU采用per-CPU设计每个核心独立一套secfs_rq是多核调度性能优化的关键。4.1.2 进程加入任务组逻辑将进程移入 cgroup 时内核会把进程调度实体从原组cfs_rq移除加入新组cfs_rq核心函数move_task_to_cgroup调用链路下的队列迁移代码// kernel/sched/fair.c static void detach_task_cfs_rq(struct task_struct *p) { struct sched_entity *se p-se; struct cfs_rq *cfs_rq task_cfs_rq(p); // 从原组CFS队列删除进程调度实体 if (se-on_rq) { dequeue_entity(cfs_rq, se, DEQUEUE_SLEEP); cfs_rq-nr_running--; } } static void attach_task_cfs_rq(struct task_struct *p) { struct sched_entity *se p-se; struct cfs_rq *cfs_rq task_cfs_rq(p); // 加入新任务组的CFS队列 if (se-on_rq) { cfs_rq-nr_running; enqueue_entity(cfs_rq, se, ENQUEUE_WAKEUP); } }代码说明进程的归属本质是调度实体所属 cfs_rq 的切换进程本身不直接参与全局调度始终挂靠在某个task_group的私有队列中。4.1.3 层次化调度选择逻辑CFS 选任务时先选顶层 task_group再选组内进程核心函数pick_next_task_fair精简源码// kernel/sched/fair.c static struct task_struct *pick_next_task_fair(struct rq *rq) { struct cfs_rq *cfs_rq rq-cfs; struct sched_entity *se; // 循环遍历层级队列从顶层组逐级向下查找 do { // 从当前cfs_rq选出vruntime最小的调度实体 se pick_next_entity(cfs_rq, NULL); // 如果选中的是任务组调度实体进入下一层组队列 cfs_rq group_cfs_rq(se); } while (cfs_rq); // 最终se指向普通进程调度实体返回进程 return se-my_task; }核心逻辑解读顶层cfs_rq存放所有任务组的 se选出最优组进入该组的私有cfs_rq继续选出组内最优进程多级 task_group 嵌套时循环逐层向下遍历实现完整层次化调度。4.1.4 shares 权重更新函数修改 cgroup 的cpu.shares文件时内核更新task_group-shares并重新计算权重// kernel/sched/task_group.c void sched_group_set_shares(struct task_group *tg, unsigned long shares) { int cpu; tg-shares shares; // 遍历所有CPU更新该组在每个核心上的调度实体权重 for_each_possible_cpu(cpu) { struct sched_entity *se tg-se[cpu]; if (se) { // 将shares转换为CFS调度权重 se-load.weight calc_weight(shares); } } }代码说明shares仅对同级兄弟组生效父组权重不会被修改严格保证层级隔离。4.2 实操案例 1创建任务组、配置权重并测试 CPU 分配本案例基于 cgroup v1 CPU 子系统创建两个任务组配置不同cpu.shares压测验证 CPU 占比。步骤 1创建两级任务组模拟层级架构# 进入CPU cgroup根目录 cd /sys/fs/cgroup/cpu # 创建一级父组 parent_group sudo mkdir parent_group # 在父组内创建两个子组 group_a、group_b形成层级父→子 sudo mkdir parent_group/group_a sudo mkdir parent_group/group_b步骤 2配置组权重shares规则父组下 group_a2048group_b1024理论 CPU 占比 2:1# 配置group_a权重 echo 2048 | sudo tee parent_group/group_a/cpu.shares # 配置group_b权重 echo 1024 | sudo tee parent_group/group_b/cpu.shares # 查看配置结果 cat parent_group/group_a/cpu.shares cat parent_group/group_b/cpu.shares步骤 3编写 CPU 压测程序无限循环占用 CPU新建文件cpu_stress.c代码如下#include stdio.h // 简单死循环持续占用CPU int main(void) { while(1) { ; } return 0; }编译运行gcc cpu_stress.c -o cpu_stress步骤 4将压测进程加入不同任务组# 后台启动两个压测进程 ./cpu_stress PID1$! ./cpu_stress PID2$! # 将PID1加入 group_a echo $PID1 | sudo tee parent_group/group_a/cgroup.procs # 将PID2加入 group_b echo $PID2 | sudo tee parent_group/group_b/cgroup.procs步骤 5观测 CPU 占用比例打开新终端使用top命令观测top预期结果两个进程 CPU 使用率近似 66% : 33%严格匹配 2048:1024 的权重比例验证组调度公平性。4.3 实操案例 2配置 CPU 硬配额带宽限流基于cfs_quota_us限制任务组最大 CPU 使用率单 CPU 核心上限为 100000us100%。# 限制group_a 最多使用单个核心的50% CPU50000us / 100000us echo 50000 | sudo tee parent_group/group_a/cpu.cfs_quota_us echo 100000 | sudo tee parent_group/group_a/cpu.cfs_period_us重新运行压测程序可观察到group_a内进程 CPU 被限制在 50% 以内不受系统总负载影响实现硬隔离。4.4 实操案例 3ftrace 跟踪组调度内核函数通过内核跟踪工具观测task_group创建、队列入队、调度选择等函数调用验证源码执行流程# 挂载debugfs sudo mount -t debugfs none /sys/kernel/debug # 清空跟踪缓冲区 echo /sys/kernel/debug/tracing/trace # 过滤需要跟踪的组调度核心函数 echo pick_next_task_fair /sys/kernel/debug/tracing/set_ftrace_filter echo enqueue_entity /sys/kernel/debug/tracing/set_ftrace_filter echo sched_group_set_shares /sys/kernel/debug/tracing/set_ftrace_filter # 开启跟踪 echo function /sys/kernel/debug/tracing/current_tracer echo 1 /sys/kernel/debug/tracing/tracing_on # 执行前面的cgroup、压测操作完成后停止跟踪 echo 0 /sys/kernel/debug/tracing/tracing_on # 查看跟踪日志 cat /sys/kernel/debug/tracing/trace日志作用可清晰看到层级调度的函数调用顺序对应前文内核源码逻辑。五、常见问题与解答Q1修改 cpu.shares 后CPU 占比没有变化是什么原因解答1.cpu.shares仅在同级任务组同时处于就绪状态时生效若某组无活跃进程资源会全部分配给其他组2. 检查进程是否真正移入对应 cgroup查看cgroup.procs文件确认 PID3. shares 是比例权重不是绝对值整机 CPU 空闲时所有进程都会占满资源压力满载时才会体现比例。Q2多级 task_group 嵌套后权重计算规则是怎样的解答资源逐层分配。例如根组下父组 A (shares1024)、父组 B (shares1024)A 下有子组 A1 (1024)、A2 (1024)。整机压力满载时A、B 各分 50% CPUA 的 50% 再平分给 A1、A2最终 A1/A2 各占整机 25%。Q3cfs_quota_us 配置不生效无法限制 CPU解答1.cfs_quota_us-1代表不限制需设置为正数2. 该配额是单 CPU 核心上限多核场景下进程漂移到其他核心会突破限制可配合cpuset绑定 CPU 核心3. 检查内核是否开启CONFIG_CFS_BANDWIDTH。Q4进程迁移到新 task_group 后出现短暂调度卡顿解答进程迁移时会执行dequeue/enqueue队列操作重新计算 vruntime高频率迁移会产生开销。生产环境应避免频繁将进程在不同 cgroup 间切换。Q5如何区分普通 CFS 调度和组调度在队列上的差异解答开启CONFIG_FAIR_GROUP_SCHED后CPU 顶层队列存放 task_group 的调度实体组内队列存放进程关闭该配置后所有进程直接挂载在全局 CFS 队列无分层结构。可通过内核配置与 ftrace 函数调用区分。六、实践建议与最佳实践6.1 资源划分最佳实践层级不宜过深task_group 树形层级建议控制在 3 层以内根→业务组→容器组层级越多调度逐层遍历开销越大会轻微增加调度时延。shares 统一规划同层级任务组shares尽量使用 1024 的整数倍便于比例计算核心业务组设置更高权重非核心业务降低权重。权重与配额结合使用对弹性业务使用cpu.shares做软隔离压力大时按比例分配对核心租户、容器使用cfs_quota_us做硬限制防止资源耗尽。6.2 性能优化技巧CPU 核心绑定对延迟敏感的业务使用cpuset将 task_group 内进程绑定固定 CPU 核心减少进程跨核迁移、组队列重建的开销。减少组数量单 CPU 上不要创建大量空闲 task_group空组依然会占用队列与调度实体资源增加调度遍历耗时。避免动态修改权重运行中频繁修改cpu.shares会调用sched_group_set_shares刷新权重引发队列重计算尽量在业务启动前配置完成。6.3 问题排查规范CPU 抢占异常排查顺序先检查进程所属 cgroup → 查看shares/quota配置 → ftrace 跟踪调度函数 → 分析层级队列状态。高调度时延排查使用perf sched统计调度延迟重点排查层级过深、组数量过多、进程频繁迁移三类问题。容器资源异常Docker/K8s 本质基于 cgrouptask_group排查容器 CPU 限制问题时直接进入对应 cgroup 目录查看配置文件。6.4 内核定制开发建议若需要二次开发组调度逻辑尽量基于现有task_group层级架构扩展不要重构分层模型新增调度规则时在enqueue_entity/pick_next_entity钩子函数中扩展减少对主调度流程的侵入。七、总结与应用延伸本文完整讲解了 Linux CFS 组调度的核心设计思想、task_group层次化架构、关键内核数据结构、源码逻辑、cgroup 实操配置、压测验证与排错方案。task_group的核心创新是将任务组抽象为调度实体把单一进程调度升级为多层级树形调度实现了 CPU 资源的分层、分租户、分业务精细化隔离。从技术本质来看组调度分为两大能力基于shares的软权重分配保障同级组公平竞争基于cfs_quota的硬带宽限流实现资源强隔离。两者结合构成了 Linux 虚拟化、容器、多用户服务器的资源管控底座。在工程落地层面除了 Docker、K8s 容器场景该机制还广泛用于云主机租户隔离、大数据集群任务调度、嵌入式系统业务分区、服务器多部门资源划分等场景。对于开发者而言掌握task_group原理不仅能理解容器资源限制的底层逻辑还能自主编写资源管控脚本、优化服务器负载、排查 CPU 调度类疑难问题对于学术研究本文的源码、实操案例、运行机制可直接用于 Linux 调度子系统相关论文、技术报告的撰写。建议读者基于本文代码尝试创建多级嵌套任务组、组合cpusetcgroup做 CPU 绑定与限流、修改内核源码观察调度行为变化在实操中加深对层次化公平调度的理解将理论知识落地到实际项目中。