第三讲:CFS调度器深度解析

大家好,我是你们的讲师。今天我们来聊聊CFS调度器——Linux内核里最核心的调度算法。说实话,我在车载项目里跟它打交道最多。有一次调试一个多媒体卡顿问题,查来查去,最后发现是CFS的调度延迟没调好。嗯,从那以后我对这块就格外上心了。

一、完全公平调度算法:到底怎么个“公平”法?

CFS的全称是Completely Fair Scheduler,完全公平调度器。你可能会问:什么叫完全公平?

说白了,就是让每个进程都能公平地获得CPU时间。但这里的“公平”不是平均主义,而是按权重分配。我打个比方:

假设CPU是一块蛋糕,有10个进程等着吃。如果大家权重一样,每人吃1/10。但如果某个进程权重高,它就能多吃点。这就像公司里,普通员工和项目经理分任务,项目经理权重高,自然分到的资源多。

CFS的核心思想是:让每个进程的运行时间与它的权重成正比。它不像O(1)调度器那样用时间片,而是用虚拟时钟来模拟“理想多任务处理器”。

关键点:CFS追求的是“每个进程的运行时间比例公平”,而不是“每个进程运行时间绝对相等”。

二、虚拟时钟与权重:CFS的数学基础

这里有个概念很重要——虚拟时钟(virtual runtime)。我刚开始学的时候也觉得抽象,后来在项目中调试过一次优先级反转,才真正理解了它的意义。

虚拟时钟的计算公式很简单:

vruntime += 实际运行时间 * (1024 / 进程权重)

为什么是1024?这是NICE_0_LOAD,也就是nice值为0时的基准权重。你想想看:

  • 权重高的进程(比如nice值低),vruntime增长慢
  • 权重低的进程(比如nice值高),vruntime增长快

CFS每次调度时,就选vruntime最小的进程来运行。这样权重高的进程虽然跑得快,但vruntime增长慢,所以能多跑一会儿。权重低的进程跑得慢,vruntime增长快,很快就轮到别人了。

我在项目中遇到过一个问题:一个后台服务进程的nice值设得太高,结果它几乎抢不到CPU,导致日志写不出去。后来把nice值调低,问题就解决了。这就是权重的作用。

nice值 权重 vruntime增长速率
-20 88761 极慢
-10 11051
0 1024 基准
10 95
19 15 极快

避坑指南:我曾经在车载项目中,把音频处理线程的nice值设成-15,结果它占用了太多CPU,导致导航系统卡顿。后来我意识到,权重太高反而会影响其他关键任务。建议车载场景下,nice值不要低于-10,除非你非常确定。

三、调度延迟与最小粒度:别让进程等太久

调度延迟(sched_latency)和最小粒度(min_granularity)是CFS的两个关键参数。它们决定了进程能跑多久、多久被切换一次。

调度延迟:所有可运行进程在一个周期内至少被调度一次的时间。默认是6ms(在桌面系统)或10ms(在服务器系统)。

最小粒度:进程至少能运行的时间,默认是1ms。为什么要有这个?因为进程切换是有开销的——保存上下文、加载新进程的页表、刷新TLB,这些都要时间。如果切换太频繁,CPU全花在切换上了,实际干活的时间就少了。

计算公式:

每个进程的时间片 = 调度延迟 * (进程权重 / 所有进程总权重)

举个例子:假设有4个进程,权重都是1024,调度延迟是6ms。那么每个进程的时间片 = 6ms * (1024 / 4096) = 1.5ms。

但如果进程数很多呢?比如有100个进程,每个进程的时间片 = 6ms * (1024 / 102400) ≈ 0.06ms。这比最小粒度1ms还小!

这时候CFS会怎么做?它会用最小粒度来保证每个进程至少运行1ms。但这样一来,调度延迟就变成了100 * 1ms = 100ms。也就是说,一个进程可能要等100ms才能再次被调度。

注意:在车载系统中,100ms的调度延迟是致命的。比如转向控制、刹车控制这些实时任务,延迟超过10ms就可能出问题。所以车载Linux通常会调整这两个参数:

  • 降低调度延迟到2-3ms
  • 降低最小粒度到0.5ms
  • 或者干脆把实时任务绑定到独立CPU上,用SCHED_FIFO调度

四、CFS在车载多核场景下的实战经验

好了,理论讲完了,咱们聊聊实战。我在车载项目中总结了几条经验:

  1. 别让所有进程都跑CFS。实时任务(如CAN通信、传感器采集)应该用SCHED_FIFO或SCHED_RR,绑定到独立CPU上。
  2. 调整调度延迟要谨慎。我见过有人把调度延迟设成1ms,结果进程切换太频繁,CPU利用率反而下降了。建议从3ms开始调,逐步降低。
  3. 利用cgroup做分组调度。比如把多媒体应用分到一组,导航系统分到另一组,每组内部再用CFS调度。这样即使某个应用出问题,也不会影响其他组。
  4. 监控vruntime的分布。如果发现某个进程的vruntime远大于其他进程,说明它可能被饿死了。这时候要检查它的权重设置是否合理。

一个小技巧:我曾经在调试车载娱乐系统时,发现视频播放偶尔卡顿。用perf一看,原来是CFS调度器在切换进程时,把视频解码线程的上下文给丢了。后来我把视频解码线程的nice值从0改成-5,卡顿就消失了。嗯,有时候一个小改动就能解决大问题。

五、总结

CFS调度器是Linux内核的基石,理解它对于车载多核系统至关重要。记住三个核心点:

  • 虚拟时钟是公平性的数学保证
  • 权重决定了进程能分到多少CPU时间
  • 调度延迟和最小粒度是性能与实时性的平衡点

下一讲,我们会深入探讨CFS在SMP(对称多处理)系统中的实现,以及如何利用CPU亲和性来优化性能。到时候我会分享一个我在项目中遇到的真实案例——一个因为CPU亲和性没设好,导致系统崩溃的故事。敬请期待!