简介
在 Linux 系统中,完全公平调度器(CFS)是面向普通分时任务的核心调度模块,依靠cpu.shares实现 CPU 资源的比例分配,但这种分配方式存在明显短板:它只能划分任务组之间的资源权重,无法对单个任务组设置硬上限。在服务器集群、云容器、嵌入式设备、多业务混部服务器等场景下,一旦某个业务进程出现死循环、内存泄漏、逻辑异常等问题,会持续抢占 CPU 资源,挤压其他正常业务的运行空间,最终引发整机负载过高、业务响应延迟、系统卡顿甚至宕机。
为解决这一问题,Linux 内核基于 Cgroup v1 的 CPU 子系统,扩展出CFS 带宽控制(CFS Bandwidth Control)功能,通过cpu.cfs_quota_us(CPU 配额)与cpu.cfs_period_us(调度周期)两个核心参数,为每一个任务组(cgroup)配置固定的 CPU 使用上限,实现 CPU 资源的硬限制。该机制不改变 CFS 本身的公平调度逻辑,而是在调度流程中增加带宽节流环节:当任务组在一个调度周期内耗尽配额时间后,组内所有 CFS 任务会被暂时节流挂起,直到下一个周期重置配额后才能继续运行。
目前这套带宽控制机制是 Docker、K8s 等容器平台 CPU 限流的底层实现,也是运维工程师、内核开发人员、云原生架构师、嵌入式 Linux 开发者必须掌握的核心技术。无论是线上故障排查、容器资源配额规划、混部服务器稳定性优化,还是内核源码研究、学术论文撰写,深入理解cfs_quota_us与cfs_period_us的工作原理、内核实现、实操配置与异常处理,都具备极高的工程价值。本文结合内核源码、实操命令、测试案例与排错经验,从原理、环境、实战、问题排查到最佳实践做完整拆解,内容可直接用于项目落地与技术文档编写。
一、核心概念与术语解析
1.1 CFS 调度与 Cgroup 组调度基础
CFS(Completely Fair Scheduler,完全公平调度器)是 Linux 2.6.23 版本之后默认的普通进程调度器,核心思想是按照任务权重均分 CPU 时间,保证所有就绪任务获得相对公平的运行时长。
而Cgroup(控制组)是 Linux 内核提供的资源隔离与限制技术,CONFIG_FAIR_GROUP_SCHED开启后,CFS 支持组调度:将多个进程 / 线程划入同一个任务组,以组为单位进行 CPU 调度、资源统计与限制。CFS 带宽控制是组调度的扩展功能,依赖内核配置CONFIG_CFS_BANDWIDTH。
1.2 核心参数定义(单位:微秒 us)
1.2.1 cpu.cfs_period_us
代表调度周期,定义带宽统计的时间窗口。
- 取值范围:内核规范为
1000 ~ 1000000(1ms ~ 1s),超出范围会写入失败; - 默认值:绝大多数发行版默认
100000(100ms); - 作用:所有配额统计、带宽消耗、节流判断,都以该周期为单位循环执行,周期结束后自动重置组内已使用 CPU 时长。
1.2.2 cpu.cfs_quota_us
代表CPU 时间配额,即当前任务组在一个cfs_period_us周期内,最多能使用的 CPU 总时长。
- 取值规则:支持正数、负数;
- 默认值:
-1,表示无配额限制,任务组可以耗尽整机 CPU 资源; - 计算规则:CPU 最大使用率 = cfs_quota_us /cfs_period_us × 100%;
- 特殊说明:配额可以大于周期值,例如周期 100ms、配额 200ms,代表该组可以独占2 个 CPU 核心的算力。
1.3 节流(Throttle)与解除节流
- 节流:当任务组在当前周期内消耗的 CPU 时长达到
cfs_quota_us配额时,内核将组内所有 CFS 任务标记为节流状态,不再参与 CPU 调度,任务被挂起。 - 解除节流:当前
cfs_period_us周期结束,内核自动重置该组的已消耗 CPU 时长,节流状态清除,组内任务恢复正常调度。
1.4 关键内核数据结构
CFS 带宽控制的核心统计与控制结构定义在kernel/sched/fair.c中,cfs_rq是每个 CPU 上的 CFS 运行队列,扩展了带宽相关成员:
// kernel/sched/fair.c 节选,CFS运行队列带宽控制相关字段 struct cfs_rq { /* 基础CFS调度成员,省略 */ struct sched_entity *curr; struct rb_root_cached tasks_timeline; #ifdef CONFIG_CFS_BANDWIDTH /* 带宽控制开关:1=开启配额限制,0=关闭 */ int runtime_enabled; /* 当前周期内已使用的CPU时长 */ u64 runtime_used; /* 周期定时器,用于周期重置、解除节流 */ struct timer_list cfs_bandwidth_timer; /* 节流计数与状态标记 */ unsigned int throttled; /* 任务组带宽配置:配额、周期 */ struct cfs_bandwidth *tg_cfs_bw; #endif };throttled字段是排查限流问题的核心,数值大于 0 代表当前队列存在被节流的任务。
1.5 相关工具说明
mount:查看 debugfs、cgroup 文件系统挂载状态;top / htop:观测进程 CPU 使用率,验证限流效果;perf:跟踪内核节流函数、统计调度耗时;ftrace:动态跟踪 CFS 带宽控制内核函数调用流程;cat/echo:原生文件接口,配置与读取 cgroup 配额参数。
二、环境准备
2.1 软硬件环境清单
| 分类 | 版本 / 配置要求 | 说明 |
|---|---|---|
| 操作系统 | Ubuntu 20.04 / 22.04、CentOS 7/8/9 | 主流服务器发行版,均默认开启 Cgroup v1 |
| 内核版本 | Linux 5.4、5.15、6.1 LTS | 全系列支持 CFS 带宽控制,源码逻辑一致 |
| 硬件 | x86_64 架构,2 核 4G 及以上 | 多核环境可验证配额大于周期的场景 |
| 依赖工具 | gcc、make、libncurses-dev、bison、flex | 内核编译、源码调试使用 |
| 调试工具 | perf、trace-cmd、gdb、htop | 观测限流状态、跟踪内核调用 |
2.2 环境检查与基础配置
2.2.1 检查 Cgroup 文件系统挂载
现代 Linux 发行版默认已挂载 cgroup,执行以下命令验证:
# 查看cgroup挂载点 mount | grep cgroup正常输出会包含/sys/fs/cgroup/cpu,代表 CPU 子系统已就绪。如果未挂载,手动执行挂载命令:
# 创建挂载目录并挂载cgroup cpu子系统 sudo mkdir -p /sys/fs/cgroup/cpu sudo mount -t cgroup -o cpu none /sys/fs/cgroup/cpu2.2.2 检查内核配置项
必须确保内核开启以下两个核心配置,否则带宽控制功能失效:
# 查看是否开启组调度与CFS带宽控制 zcat /proc/config.gz | grep -E "FAIR_GROUP_SCHED|CFS_BANDWIDTH"正常结果:
CONFIG_FAIR_GROUP_SCHED=y CONFIG_CFS_BANDWIDTH=y若为=m或=n,需要重新编译内核开启对应选项。
2.2.3 安装辅助工具
# Ubuntu/Debian 系列 sudo apt update && sudo apt install htop perf trace-cmd -y # CentOS/RHEL 系列 sudo yum install htop perf -y2.3 源码路径说明
后续源码阅读与分析均基于以下路径:
- CFS 带宽控制核心逻辑:
kernel/sched/fair.c - Cgroup CPU 子系统文件接口:
kernel/sched/cpuacct.c、kernel/cgroup/cgroup.c - 官方文档参考:
Documentation/scheduler/sched-bwc.rst
三、应用场景
CFS 带宽控制是线上服务稳定性保障的核心手段,落地场景十分广泛。在云服务器与容器集群中,K8s、Docker 依靠该机制限制单个容器 CPU 配额,避免容器之间相互抢占资源,实现租户资源隔离。在物理机多业务混部场景下,将日志采集、监控告警、定时任务等低优先级业务划入独立 cgroup,限制其 CPU 使用率,保障核心交易、接口服务等高优先级业务稳定运行。嵌入式 Linux 设备中,后台守护进程、日志服务会被设置 CPU 上限,防止后台进程耗尽算力导致前台交互卡顿。此外,压力测试、性能压测场景中,通过配额限制压测进程的 CPU 占用,精准控制压测强度;运维故障应急时,可快速限制异常高负载进程的 CPU 资源,临时止损。整套机制从云原生、服务器到嵌入式终端,全面覆盖资源隔离与限流需求。
四、实际案例与步骤(含命令、代码、源码解析)
本章从基础配置、压测验证、内核源码解析、自定义测试程序、函数跟踪五个维度逐步实操,所有命令与代码均可直接复制运行。
4.1 基础操作:创建 Cgroup 并配置 CPU 配额
步骤 1:新建自定义任务组
在 cgroup cpu 目录下创建目录,即生成一个独立任务组:
# 进入cpu子系统根目录 cd /sys/fs/cgroup/cpu # 创建名为 cpu_limit_demo 的任务组 sudo mkdir cpu_limit_demo cd cpu_limit_demo步骤 2:查看默认参数
# 查看默认周期与配额 cat cpu.cfs_period_us cat cpu.cfs_quota_us输出说明:
cpu.cfs_period_us默认100000(100ms);cpu.cfs_quota_us默认-1(无限制)。
步骤 3:配置配额与周期(限制 CPU 使用率)
案例 1:限制为单核 CPU 的 20%计算公式:100ms周期 × 20% = 20ms配额
# 设置周期为默认100000us(100ms) sudo echo 100000 > cpu.cfs_period_us # 设置配额为20000us(20ms) sudo echo 20000 > cpu.cfs_quota_us此时该组内所有进程最大 CPU 使用率被限制在 20%。
案例 2:限制使用 2 个完整 CPU 核心周期 100ms,配额设置为 200ms(200000us):
sudo echo 200000 > cpu.cfs_quota_us sudo echo 100000 > cpu.cfs_period_us案例 3:关闭配额限制恢复默认无限制状态:
sudo echo -1 > cpu.cfs_quota_us4.2 实战验证:压测进程 + 观测限流效果
步骤 1:编写 CPU 压测程序(无限消耗 CPU)
创建cpu_stress.c,模拟死循环高负载进程:
#include <stdio.h> /* 死循环消耗CPU,纯计算任务 */ int main(void) { unsigned long long i = 0; while (1) { i++; i--; } return 0; }代码说明:无 IO、无休眠,持续占用 CPU,用于验证限流是否生效。
编译运行:
gcc cpu_stress.c -o cpu_stress # 后台运行压测程序 ./cpu_stress & # 记录进程PID echo $! > stress_pid.txt步骤 2:将进程加入限流 Cgroup
把压测进程 PID 写入tasks文件,进程即被纳入当前任务组管控:
# 读取PID并加入cgroup sudo cat stress_pid.txt | sudo tee tasks步骤 3:使用 htop/top 观测 CPU 使用率
打开新终端执行:
htop现象验证: 配置20000/100000配额后,压测进程 CPU 使用率稳定在20% 左右,无法打满单核 CPU;修改为-1无限制后,进程会立即打满单核 CPU。
步骤 4:查看节流状态
cgroup 提供cpu.stat文件,统计带宽节流信息:
cat cpu.stat关键字段解释:
nr_periods : 已经经过的调度周期总数 nr_throttled : 触发节流的周期数 throttled_time : 累计被节流挂起的总时长(纳秒)当nr_throttled持续增长,代表配额耗尽、限流生效。
4.3 内核核心源码解析(CFS 带宽控制核心逻辑)
4.3.1 配额写入接口函数
当我们通过echo修改cfs_quota_us和cfs_period_us时,最终会调用内核写入函数,文件接口逻辑如下(kernel/sched/fair.c):
/* 写入 cfs_quota_us 回调函数 */ static ssize_t cpu_cfs_quota_write_s64(struct kernfs_file *kf, char __user *buf, size_t count, loff_t *ppos) { struct task_group *tg = kf->priv; s64 quota; // 从用户态读取写入的配额值 if (kstrtos64_from_user(buf, count, 10, "a)) return -EINVAL; // 调用核心接口,更新任务组带宽配置 tg_set_cfs_bandwidth(tg, quota, tg->cfs_bandwidth.period); return count; } /* 写入 cfs_period_us 回调函数 */ static ssize_t cpu_cfs_period_write_u64(struct kernfs_file *kf, char __user *buf, size_t count, loff_t *ppos) { struct task_group *tg = kf->priv; u64 period; if (kstrtou64_from_user(buf, count, 10, &period)) return -EINVAL; // 校验周期范围:1ms ~ 1s if (period < 1000 || period > 1000000) return -EINVAL; tg_set_cfs_bandwidth(tg, tg->cfs_bandwidth.quota, period); return count; }代码解读:
- 两个文件写入接口最终都会调用
tg_set_cfs_bandwidth统一更新带宽配置; - 内核强制校验
cfs_period_us范围,非法数值直接返回写入失败; - 配置更新后,内核会重新计算各 CPU 队列的可用配额。
4.3.2 配额消耗与节流判断逻辑
CFS 每次调度任务运行时,都会累加runtime_used(已使用时长),并做节流判断:
// 调度切片结束后,统计CPU耗时并判断是否需要节流 static void cfs_bandwidth_account(struct cfs_rq *cfs_rq, u64 delta_exec) { struct cfs_bandwidth *bw = cfs_rq->tg_cfs_bw; // 无配额限制,直接返回 if (bw->quota == -1) return; // 累加当前周期已使用CPU时长 cfs_rq->runtime_used += delta_exec; // 判断:已使用时长 >= 配额 → 触发节流 if (cfs_rq->runtime_used >= bw->quota) { cfs_rq->throttled++; // 标记队列节流,禁止新任务调度 cfs_rq->runtime_enabled = 0; // 启动周期定时器,到期后解除节流 cfs_bandwidth_start_timer(cfs_rq); } }核心流程:
- 任务每运行一个时间片,就累加消耗时长;
- 达到配额上限后,关闭队列调度能力,标记为节流;
- 启动周期定时器,等待当前
period结束。
4.3.3 周期定时器:重置配额、解除节流
周期到期后,定时器回调函数执行重置逻辑:
static void cfs_bandwidth_timer(struct timer_list *timer) { struct cfs_rq *cfs_rq = timer_container_of(timer, struct cfs_rq, cfs_bandwidth_timer); struct cfs_bandwidth *bw = cfs_rq->tg_cfs_bw; // 重置当前周期已使用时长 cfs_rq->runtime_used = 0; // 解除节流,恢复调度 cfs_rq->runtime_enabled = 1; // 唤醒队列中被节流的任务 check_cfs_rq_runtime(cfs_rq); }这是带宽控制循环执行的核心:计时到期 → 清零消耗 → 解除限流 → 重新开始统计。
4.4 Ftrace 跟踪内核调用流程
使用 ftrace 动态跟踪带宽控制相关函数,直观验证源码逻辑:
# 挂载debugfs(大部分系统已默认挂载) sudo mount -t debugfs none /sys/kernel/debug # 清空跟踪缓存 sudo echo > /sys/kernel/debug/tracing/trace # 设置需要跟踪的内核函数 sudo echo cfs_bandwidth_account >> /sys/kernel/debug/tracing/set_ftrace_filter sudo echo cfs_bandwidth_timer >> /sys/kernel/debug/tracing/set_ftrace_filter sudo echo tg_set_cfs_bandwidth >> /sys/kernel/debug/tracing/set_ftrace_filter # 开启跟踪 sudo echo function > /sys/kernel/debug/tracing/current_tracer sudo echo 1 > /sys/kernel/debug/tracing/tracing_on此时保持压测程序运行,等待 10 秒后停止跟踪并查看日志:
# 关闭跟踪 sudo echo 0 > /sys/kernel/debug/tracing/tracing_on # 查看调用日志 sudo cat /sys/kernel/debug/tracing/trace日志解读:可以看到cfs_bandwidth_account持续被调用(统计 CPU 消耗),配额耗尽后触发定时器cfs_bandwidth_timer,完成周期重置。
4.5 退出 Cgroup 与清理环境
实操完成后,恢复环境:
# 终止压测进程 sudo kill $(cat stress_pid.txt) # 将进程移出cgroup(写入根cgroup的tasks) sudo echo $$ | sudo tee /sys/fs/cgroup/cpu/tasks # 删除自定义cgroup目录 sudo rmdir /sys/fs/cgroup/cpu/cpu_limit_demo五、常见问题与解答
Q1:修改 cfs_period_us 写入失败,提示权限错误或参数非法?
解答:第一检查数值范围,cfs_period_us必须在1000~1000000(1ms~1s)之间,超出直接写入失败;第二必须使用root/sudo权限操作 cgroup 文件,普通用户无写入权限;第三检查内核是否开启CONFIG_CFS_BANDWIDTH。
Q2:设置了配额,但进程 CPU 使用率依然打满,限流不生效?
解答:1. 检查cfs_quota_us是否误写为-1(无限制);2. 确认进程 PID 已经正确写入当前 cgroup 的tasks文件;3. 区分 CFS 任务与实时任务:SCHED_FIFO/SCHED_RR实时任务不受 CFS 带宽控制,需要使用 RT 带宽限制;4. 多核场景下,配额大于周期代表允许多核运行,属于正常现象。
Q3:cpu.stat 中 nr_throttled 不断增长,业务响应变慢如何处理?
解答:nr_throttled上涨代表进程持续耗尽配额被节流。若为正常业务,说明配额设置过小,需要调高cfs_quota_us;若为异常进程,先排查代码死循环、逻辑 BUG,再临时收紧配额止损。
Q4:父子 Cgroup 的配额是否会叠加生效?
解答:Cgroup 是层级结构,子组配额不能超过父组配额。如果父组限制 50% CPU,子组即使设置 100% 配额,最大也只能使用 50%,资源限制遵循层级约束。
Q5:为什么不建议把 cfs_period_us 设置为 1s(最大值)?
解答:周期越大,带宽统计窗口越长,CPU 占用的抖动会越明显。1s 周期下,进程会先打满 CPU 长达 1s,随后被节流挂起,出现 “突高突低” 的负载波形。常规业务建议使用默认 100ms,兼顾限流精度与系统平滑性。
Q6:Docker/K8s 中的 --cpus 参数和这两个字段是什么关系?
解答:Docker 的--cpus 0.5本质就是封装了cfs_quota_us/cfs_period_us,默认周期 100ms,0.5 核对应quota=50000,底层实现完全一致。
六、实践建议与最佳实践
6.1 配额与周期选型规范
- 常规在线业务:保持
cfs_period_us=100000(默认 100ms),仅调整配额即可,调度抖动最小,是生产环境首选。 - 高抖动容忍业务(离线计算、日志处理):可适当放大周期至 200ms~500ms,减少定时器触发次数,降低内核开销。
- 低延迟业务(接口、交易服务):不建议修改默认周期,避免长时间节流导致请求超时。
6.2 生产环境资源划分最佳实践
- 核心业务独立分组:将数据库、接口服务、核心业务进程划入独立 cgroup,分配充足配额,优先保障运行。
- 边缘业务严格限流:日志采集、备份、监控、定时任务统一分组,设置低配额,避免抢占核心资源。
- 禁止全局使用 - 1 无限制:混部服务器中,除内核进程外,所有业务组都建议配置合理上限,作为故障兜底。
6.3 故障排查流程(CPU 限流异常)
- 第一步:查看
cpu.stat,确认nr_throttled是否增长,判断是否触发节流; - 第二步:核对
cfs_quota_us与cfs_period_us配置,计算理论最大使用率; - 第三步:检查进程是否正确加入对应 cgroup;
- 第四步:区分 CFS 普通任务与 RT 实时任务,排除实时任务不受限问题;
- 第五步:使用
perf/ftrace跟踪内核函数,定位内核层异常。
6.4 性能优化技巧
- 不要频繁动态修改配额与周期,每次修改都会调用
tg_set_cfs_bandwidth重构带宽配置,产生微小性能开销。 - 高并发场景下,单个 cgroup 内不要承载过多进程,进程拆分到多个子 cgroup,提升调度效率。
- 嵌入式设备中,可适当缩小周期(不低于 1ms),提升限流的实时性。
6.5 内核二次开发建议
若需要定制带宽控制逻辑,建议基于原有cfs_bandwidth框架扩展,不要重构定时器与统计逻辑;新增限流规则时,复用runtime_used与throttled字段,保证和原生调度逻辑兼容。
七、总结与应用延伸
本文完整讲解了 Linux CFS 带宽控制的核心原理、cfs_quota_us与cfs_period_us的参数规则、内核源码实现、端到端实操案例、问题排查与工程最佳实践。简单总结核心要点:
- 两个参数组合实现 CPU 硬限流,使用率 = 配额 / 周期,周期合法范围 1ms~1s;
- 内核通过
runtime_used统计 CPU 耗时,耗尽配额后触发节流,周期结束自动重置; - 该机制仅作用于 CFS 普通任务,Linux 实时调度任务不受此限制;
- 容器技术、服务器混部、嵌入式系统是该功能最主要的落地场景。
CFS 带宽控制是 Linux 资源管控体系中承上启下的关键模块,上接 CFS 调度核心,下接 Cgroup 资源隔离,也是云原生、运维、内核开发的必备知识点。对于研发人员来说,不仅要会配置参数,更要理解内核节流、定时器、统计计数的底层逻辑,才能在业务卡顿、CPU 限流、容器资源异常等问题中快速定位根因。
建议读者基于本文的压测代码、ftrace 命令、内核源码片段,在测试机上反复复现实验,尝试修改不同配额与周期观察负载变化,结合线上业务场景规划合理的资源配额策略,将理论知识落地到实际项目中。同时可以延伸学习 Cgroup v2、RT 实时带宽控制,形成完整的 Linux CPU 资源管控知识体系。