首页 > 其他资讯 > Linux 调度器深度解析:CFS 完全公平调度,原来如此简单

Linux 调度器深度解析:CFS 完全公平调度,原来如此简单

时间:26-04-25

一、旧调度器的问题:什么叫“不公平”?

面试时被问到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资源的分配比例。

七、实战:如何调整进程优先级?

理论懂了,动手试试才是关键。调整进程优先级,最常用的命令就是nicerenice

启动时指定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内核能够在瞬息之间,在上百个进程间做出精准的调度决策,既保证高优先级任务获得所需资源,又确保低优先级任务不会被彻底饿死。这才是现代操作系统调度艺术背后的精密工程。


这就是Linux 调度器深度解析:CFS 完全公平调度,原来如此简单的全部内容了,希望以上内容对小伙伴们有所帮助,更多详情可以关注我们的菜鸟游戏和软件相关专区,更多攻略和教程等你发现!

热搜     |     排行     |     热点     |     话题     |     标签

手机版 | 电脑版 | 客户端

湘ICP备2022003375号-1

本站所有软件,来自于互联网或网友上传,版权属原著所有,如有需要请购买正版。如有侵权,敬请来信联系我们,cn486com@outlook.com 我们立刻删除。