Linux 调度器深度解析:CFS 完全公平调度,原来如此简单
一、旧调度器的问题:什么叫“不公平”?
面试时被问到Linux进程调度,如果回答还停留在“O(1)调度器”和“固定时间片”,那就有点跟不上时代了。这套在Linux 2.6.23之前主力的调度算法,名字听着很高效,实际用起来却埋了不少坑。
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
当时的设计思路很直接:给每个进程分配一个固定的时间片,比如100毫秒,然后按照优先级轮着跑。听上去很公平,对吧?但实际操作中,几个根本性的问题就暴露出来了。
首当其冲的就是交互体验。想象一下,你正在用文本编辑器码字,后台同时跑着一个编译任务。如果两者优先级一样,各拿100毫秒的时间片轮流上CPU。那么当你敲下键盘,编辑器可能得等完编译进程那100毫秒才能响应,这种卡顿感对用户来说是实实在在的。
更深层的问题在于,当时的优先级系统有点像“黑魔法”。怎么决定一个nice值为-5的进程该拿多少时间片?并没有一个清晰的数学模型,全靠经验和一些启发式规则来拍脑袋。一旦优先级层次复杂起来,调整和维护就变成了一场噩梦。
更麻烦的是,CPU使用率的统计也不够准。O(1)调度器需要自己去猜测一个进程到底是“交互型”还是“计算密集型”,猜错了,调度就会出问题,该响应的不响应,该让出的不让出。
正是这些“不公平”和“不优雅”,催生了CFS的诞生。它干脆抛弃了“时间片”这个传统概念,用一种更精妙的思路来重新定义公平。
二、CFS的核心思想:虚拟时钟
CFS,全称完全公平调度器,它的核心秘密武器是一个叫vruntime(虚拟运行时间)的东西。你可以把它理解为每个进程在“公平裁判”的时钟下,已经消耗的时间。
它的目标用一句话就能说清:让所有进程的vruntime尽可能趋于相等。谁在这个虚拟时钟里跑得少,谁就排到前面去。这就像一场长跑,裁判不看谁实际反赌,而是看谁的“加权耗时”少,以此来调整下一段的出发顺序。
调度器每次要做的决策异常简单:从所有准备就绪的进程里,选出那个vruntime最小的来运行。进程在真实CPU上每运行一刻,它的vruntime就会增加一点。这样一来,跑得多的进程vruntime上去得就快,自然就往后排,让位给跑得少的。
但问题来了,如果所有进程的vruntime增速都一样,那优先级(nice值)还有什么意义?高优先级进程凭什么获得更多CPU时间呢?这就引出了下一个关键设计。
三、nice值与权重:vruntime的增速不一样
CFS实现优先级的秘诀,不在于给谁“插队”,而在于巧妙地控制每个进程vruntime的“流速”。这里的关键是权重。
每个nice值都对应一个预设的权重,以nice=0为基准,权重是1024。优先级提高(nice值降低),权重按比例增加(约25%);优先级降低(nice值升高),权重则减少(约20%)。
核心公式是这样的:
vruntime增量 = 实际运行时间 × (基准权重 / 该进程权重)
这意味着什么呢?高优先级进程(权重高)干同样的活儿,“虚拟耗时”却增加得慢。举个直观的例子:同样在物理CPU上跑了10毫秒,一个高优先级进程的vruntime可能只涨了3毫秒,而一个低优先级进程的vruntime可能涨了30毫秒。

如此一来,在调度器的排序队列里,高优先级进程就能更频繁地被选中,实际上也就获得了更多的CPU资源。优先级不再是生硬的“时间片加倍”,而是通过数学计算融入到了公平的度量体系里,非常优雅。
四、红黑树:CFS的“选人”数据结构
道理讲明白了,那具体怎么高效地“选出vruntime最小的那一个”呢?总不能每次都遍历所有进程吧?Linux内核的选择是使用红黑树。
所有可运行的进程都被组织成一颗红黑树,而排序的“键”正是它们的vruntime值。这样一来,vruntime最小的进程,自然就在树的最左边。

每次需要调度时,直接取最左节点就行了。红黑树查找的时间复杂度是O(log n),但内核做了优化,它会缓存这个最左节点的指针,使得挑选下一个进程的操作在常数时间内就能完成。
所以,整个CFS的调度循环可以浓缩成三步:
- 从红黑树摘下最左节点(vruntime最小)投入运行。
- 进程运行时,根据其权重更新它的vruntime(注意,权重高的涨得慢)。
- 进程运行一段时间后(或因等待I/O而放弃CPU),根据更新后的vruntime,重新插入红黑树,等待下一次被选中。
这个循环周而复始,驱动着所有进程的vruntime向一个共同的平衡点靠拢,这就是“完全公平”的动态实现。
五、调度周期与时间片:CFS怎么决定一次跑多久?
虽然CFS没有固定时间片,但它引入了调度周期的概念。你可以把它理解成一次“公平分配回合”。默认情况下,这个周期在低负载时约为6毫秒,高负载时会动态增加到48毫秒。
在一个调度周期内,调度器的目标是让所有就绪进程都能至少运行一次。每个进程能分到的时间,是根据它的权重按比例计算的:
进程分得时间 = 调度周期 × (该进程权重 / 所有进程权重之和)
举个例子,假设三个进程A、B、C的权重分别是1024、512、512,调度周期是6毫秒:
- A的时间 = 6ms × (1024/2048) = 3ms
- B的时间 = 6ms × (512/2048) = 1.5ms
- C的时间 = 6ms × (512/2048) = 1.5ms
当然,如果进程太多,按比例算下来每个进程分到的时间可能非常短,频繁的进程切换会造成大量开销。因此CFS设置了一个底线——min_granularity(最小粒度,默认0.75毫秒)。如果算出来的时间低于这个值,就按这个最小值来,避免无意义的频繁切换。
六、Linux完整调度层次:CFS不是唯一的
需要明确的是,CFS并非Linux调度世界的全部。Linux的调度体系是分层的,不同类型的任务走不同的通道。

处于顶端的是实时调度类(SCHED_FIFO或SCHED_RR)。这类进程(比如某些音视频流处理、工业实时控制)一旦就绪,就可以抢占所有普通CFS进程。对于它们,nice值毫无意义,它们有自己的实时优先级(1-99)。为了保证系统不被一个死循环的实时进程拖垮,通常需要非常谨慎地使用。
而我们日常运行的绝大多数应用程序、后台服务,都属于普通调度类,默认就在CFS的管理之下,通过调整nice值来影响其CPU资源的分配比例。
七、实战:如何调整进程优先级?
理论懂了,动手试试才是关键。调整进程优先级,最常用的命令就是nice和renice。
启动时指定nice值(范围-20到+19,越低优先级越高):
nice -n -5 ./my_program # 以nice=-5启动(通常需root权限)
nice -n 10 ./background_job # 以低优先级启动,普通用户只能调低(增大nice值)
调整一个已在运行进程的nice值:
renice -n 5 -p 1234 # 将PID为1234的进程nice值改为5
查看进程的调度信息:
ps -o pid,ni,cls,comm -p 1234
# 输出示例:
# PID NI CLS COMMAND
# 1234 5 TS my_program
# CLS列:TS代表SCHED_OTHER(即CFS),RR/FIFO代表实时调度
对于实时任务(需root权限):
chrt -f 50 ./realtime_task # 以SCHED_FIFO策略,实时优先级50运行
chrt -r 50 ./realtime_task # 以SCHED_RR策略,实时优先级50运行
在C程序中直接设置:
#include
// 设置为实时调度(需要CAP_SYS_NICE权限)
struct sched_param param = { .sched_priority = 50 };
sched_setscheduler(0, SCHED_FIFO, ¶m);
// 对于普通进程,调整nice值
nice(10); // 降低优先级(增大nice值)
八、CFS的边界情况:新进程和睡醒进程怎么处理
CFS的设计非常周全,它特别考虑了两个边界场景。
首先是新进程的加入。如果新进程的vruntime从0开始,而其他老进程的vruntime已经积累到几百毫秒了,那么这个新进程会因为vruntime极小而长时间霸占CPU,直到“追上”大家,这显然不合理。因此,新进程的vruntime会被初始化为当前CPU运行队列中的min_vruntime,这样它就站在了和大家差不多的起跑线上。
其次是睡眠进程的唤醒。进程在睡眠(比如等待I/O)期间,vruntime是不增加的。当它醒来时,自己的vruntime可能远小于那些一直在运行的兄弟,如果直接参与竞争,又会不公平地长期霸占CPU。CFS的做法是:唤醒时,将其vruntime设置为max(其原始vruntime, min_vruntime - sched_latency)。这相当于给了它一个合理的“补偿”,让它能尽快获得服务,但又不会过度补偿,保证了整体的公平性。
九、高频面试题精析
聊到这里,几个经典的面试题就很好回答了。
Q:CFS的“完全公平”到底指什么?
A:并非指每个进程获得绝对相同的物理CPU时间,而是指每个进程都能获得按其权重比例分配的、理想化的CPU时间。最终目标是让所有进程的vruntime(虚拟运行时间)趋向一致,这才是它衡量公平的尺子。
Q:nice值和priority(PRI)有什么区别?
A:我们用户能用nice命令设置的是nice值(-20到+19)。而ps等命令看到的PRI(优先级)是内核内部使用的值,它等于20加上nice值。实时进程有另一套完全独立的实时优先级(1-99),不在这个体系里。
Q:为什么像Redis这样的高性能服务,反而建议谨慎使用或降低实时优先级?
A:这是一个重要经验。实时进程一旦处于可运行状态,就会抢占所有普通进程。如果它陷入死循环或一个长耗时操作,可能导致整个系统无法响应。像Redis这类依赖事件循环和IO复用的服务,其高性能不依赖于独占CPU,使用默认的CFS调度,配合合理的nice值调整通常更为安全和稳定。
Q:如何让一个程序近乎“独占”一个CPU核心?
A:有几种层级的方法:1) 使用chrt设为最高实时优先级(风险高);2) 使用taskset绑定进程到特定CPU,并结合内核启动参数isolcpus将该CPU从通用调度器中隔离;3) 利用cgroup的cpuset子系统,在容器化环境中进行精细的CPU资源隔离和分配。
十、结语
理解了CFS,你就掌握了Linux公平调度的灵魂。记住这三个核心支柱:
- vruntime(虚拟运行时间):一把衡量公平的、统一的尺子。
- 红黑树(最左节点选择):一套高效、稳定地找出“最该运行进程”的机制。
- 权重(由nice值决定):一个将优先级差异巧妙转化为vruntime不同“流速”的数学桥梁。
正是这三者的精妙组合,让Linux内核能够在瞬息之间,在上百个进程间做出精准的调度决策,既保证高优先级任务获得所需资源,又确保低优先级任务不会被彻底饿死。这才是现代操作系统调度艺术背后的精密工程。
相关攻略
从 proc cpuinfo 到 proc sys:读懂 Linux 深处的“动态信息宝库” 想真正掌控 Linux 系统,就绕不开 proc 文件系统。它远不止一个普通的目录,而是内核与用户空间沟通的核心桥梁,更是解锁系统监控、调试与性能调优的关键钥匙。与那些躺在磁盘上的普通文件系统不同,p
一、旧调度器的问题:什么叫“不公平”? 面试时被问到Linux进程调度,如果回答还停留在“O(1)调度器”和“固定时间片”,那就有点跟不上时代了。这套在Linux 2 6 23之前主力的调度算法,名字听着很高效,实际用起来却埋了不少坑。 当时的设计思路很直接:给每个进程分配一个固定的时间片,比如10
Linux 内存管理:一场由“懒惰”驱动的效率革命 Linux 内存管理的精妙之处,在于它巧妙地将几种“懒惰”哲学叠加在一起。正是这套组合拳,让系统能够在有限的内存资源上,高效地运行成百上千个进程,同时还能牢牢守住进程间的隔离墙。 上一期我们探讨文件系统时,提到了Page Cache如何占用内存,以
一、从一个问题开始:fork() 复制了什么? 想象这样一个场景:一个进程已经加载了10GB的数据到内存中,然后它调用了fork()。如果这个系统调用需要完整复制这10GB数据,那么整个服务恐怕会卡顿数秒,这在像Redis这样的高性能系统中是完全不可接受的。 但现实是,fork()几乎在瞬间就完成了
彻底告别定时任务噩梦:一份来自生产环境的crontab避坑指南 凌晨三点,备份脚本在终端里跑得完美无缺,你信心满满地将它加入crontab,然后安心睡去。第二天一早,迎接你的却是磁盘告警的邮件——任务根本没执行,数据原地未动。这种场景,恐怕是每一位Linux运维新手都经历过的“乘人礼”。 事实上,9
热门专题
热门推荐
通过AirDrop功能,可在iPhone16之间快速传输已安装的App,无需重新下载。 省去重新下载的等待,直接在两部iPhone 16之间“搬运”已经安装好的App——这个用AirDrop传App的功能,确实方便。不过,想顺利操作,有几个关键前提得先摆正。 准备工作与条件确认 开始之前,最好花一分
修改iPhone17设备名称的核心步骤 想给你的iPhone17换个独具特色的名字吗?其实很简单,整个操作的核心路径就在「设置」>「通用」>「关于本机」>「名称」里,几步就能完成自定义。 为什么要修改iPhone17的设备名称? 给iPhone17改个名,可不仅仅是图个新鲜。它在蓝牙配对、使用Air
解除iPhone14隐藏ID的核心方法是联系原机主或提供购买凭证,通过官方渠道重置Apple ID 手里突然多出一台被锁的iPhone 14,用起来处处受限,这事儿确实头疼。好消息是,只要遵循官方路径,问题基本都能解决。关键在于,你得有耐心走完正规流程。 什么是iPhone隐藏ID? 简单来说,iP
通过“查找”应用或iCloud网站,登录Apple ID即可实时定位iPhone 17,即使设备离线也能显示最后已知位置。 使用“查找”应用定位iPhone 17 如果你手边还有别的苹果设备,比如iPad或者Mac,最省事的方法就是直接用上面的“查找”应用。打开应用,登录和iPhone 17同一个
iPhone 16通知权限设置与微信提示音修复指南 微信消息突然“静音”了?先别急着怀疑手机坏了。在iPhone 16上,通知体系和声音管理比以往更精细,有时只是某个开关没到位。接下来,咱们就把系统通知中心、应用权限、勿扰模式这几个关键环节捋清楚,帮你快速找回失联的提示音,避免错过重要信息。 iPh





