2. 多核调度基础:调度域与调度组、负载均衡原理、调度策略(CFS/RT/DL)在多核下的表现

好,咱们进入正题。多核调度这事儿,说白了就是让多个CPU核心别闲着,也别打架。我刚开始接触多核OS时,总觉得不就是把单核的调度器复制几份嘛?结果一跑起来,系统直接卡死。嗯,这里面的门道,比想象中深得多。

2.1 调度域与调度组:CPU的“朋友圈”

你想想看,一个系统里可能有8个核、16个核,甚至更多。如果每个核都各自为政,那负载均衡就乱套了。所以Linux内核引入了调度域(Scheduling Domain)的概念。

调度域,其实就是一组CPU的集合。它们共享某些资源,比如L2缓存或者内存控制器。内核把CPU按层级组织起来:

  • 基础域(Base Domain):通常是一个物理CPU包里的所有核心
  • NUMA域:跨内存节点的CPU集合
  • 系统域:所有CPU的顶层集合

每个调度域里,又分成若干个调度组(Scheduling Group)。调度组是负载均衡的最小单元。我习惯把调度组想象成“工位小组”——组内的核心可以快速迁移任务,组间迁移代价就大一些。

核心要点:调度域决定了“谁和谁可以互相借力”,调度组决定了“借力的粒度”。

我在项目中遇到过一个问题:一个16核的ARM服务器,跑数据库时性能始终上不去。后来发现是调度域配置不对,把两个物理CPU包划到了同一个域里,导致跨片迁移频繁,缓存命中率暴跌。调整之后,性能直接提升了30%。

2.2 负载均衡原理:别让一个核累死,其他核闲死

多核调度的核心矛盾是什么?就是任务分布不均。有的核忙得冒烟,有的核在摸鱼。负载均衡就是解决这个问题的。

Linux的负载均衡机制,我总结为三步走:

  1. 周期性检查:每个调度域会定期(比如每1ms)检查各组之间的负载差异
  2. 寻找不平衡:如果某个调度组的负载比另一个组高出一定阈值(默认25%),就触发迁移
  3. 任务迁移:从最忙的组里挑一个合适的任务,迁移到最闲的组里

这里有个细节:不是所有任务都能随便迁移。比如绑定了CPU亲和性的任务、正在执行实时任务、或者有缓存热度的任务,迁移代价可能大于收益。内核会计算一个“迁移代价”,只有收益大于代价时才会动手。

我的经验:我曾经调试过一个视频编码系统,发现负载均衡频繁触发,导致画面卡顿。后来把阈值从25%调到了40%,迁移次数减少了80%,画面流畅多了。记住:负载均衡不是越频繁越好,它本身也有开销。

负载均衡的触发时机主要有三种:

  • tick触发:每个时钟中断都会检查当前CPU是否需要拉取任务
  • 任务唤醒时:新任务创建或唤醒时,选择最空闲的CPU
  • 空闲时:CPU空闲时会主动去其他核“偷”任务(这叫idle pull)

2.3 调度策略在多核下的表现:CFS、RT、DL各显神通

Linux有三种主流调度策略:CFS(完全公平调度)、RT(实时调度)、DL(截止时间调度)。它们在多核环境下的表现,差异很大。

2.3.1 CFS:公平但不一定高效

CFS的目标是让每个任务获得公平的CPU时间。在多核下,CFS会尽量把任务分散到各个核上。但问题来了:公平不等于高效。

举个例子:两个任务A和B,A需要大量L1缓存,B需要大量内存带宽。如果CFS把A和B放在同一个核上,它们会互相干扰;如果放在不同核上,反而能并行跑得更快。但CFS不会考虑这些,它只看“谁该用CPU了”。

我建议在CFS场景下,手动设置CPU亲和性,把关联性强的任务绑定在一起,把干扰性强的任务分开。

2.3.2 RT:优先级至上,但小心死锁

RT调度(SCHED_FIFO和SCHED_RR)给每个任务一个固定的优先级(0-99,数字越小优先级越高)。在多核下,RT任务会抢占所有可用的CPU。

这里有个坑:优先级反转。我曾经在一个机器人控制系统中,一个高优先级的RT任务等待一个低优先级任务释放锁,结果低优先级任务被其他RT任务抢占了,导致高优先级任务活活饿死。解决方案是使用优先级继承协议,让持有锁的任务临时提升优先级。

警告:RT任务在多核下要特别小心CPU亲和性。如果一个RT任务绑定了某个核,而另一个核上的RT任务优先级更高,它可能永远得不到执行。我曾经见过一个系统,两个RT任务互相等待对方释放CPU,直接死锁。

2.3.3 DL:精确但脆弱

截止时间调度(SCHED_DEADLINE)是Linux 3.14引入的,它给每个任务指定一个运行周期和截止时间。内核保证在截止时间前完成任务。

DL在多核下的表现,说实话,有点“娇气”。它依赖全局EDF(最早截止时间优先)算法,但EDF在过载时会直接崩溃——所有任务都错过截止时间。我建议只在硬实时场景下使用DL,并且要留出至少20%的余量。

DL的另一个问题是任务迁移。如果一个DL任务从一个核迁移到另一个核,它的截止时间信息需要同步,这本身就有开销。我在一个音频处理项目里试过DL,结果迁移开销导致音频断断续续,最后还是换回了RT。

2.4 三种策略的对比

特性 CFS RT DL
调度目标 公平分配CPU时间 优先级最高的先执行 保证截止时间
多核友好度 高(自动负载均衡) 中(需手动亲和性) 低(迁移开销大)
适用场景 通用计算、服务器 实时控制、多媒体 硬实时、周期任务
常见问题 缓存干扰 优先级反转 过载崩溃

2.5 知识体系图

下面这张图,是我梳理的多核调度核心逻辑。你看一眼,基本就明白整个框架了。

多核调度核心逻辑 调度域(Scheduling Domain) 调度组(Scheduling Group) 负载均衡(Load Balance) CFS(完全公平) RT(实时) DL(截止时间) 调度域决定范围 → 调度组决定粒度 → 负载均衡动态调整 → 策略决定行为

嗯,这张图把整个流程串起来了。从调度域到调度组,再到负载均衡,最后落到三种策略上。你写代码时,脑子里要有这个层次感。

一句话总结:多核调度不是把单核的调度器复制几份,而是要解决“谁在哪个核上跑、什么时候迁移、迁移代价多大”这三个问题。

好了,这一章的内容就到这儿。下一章我们会深入核间通信,讲讲IPI、共享内存、消息队列这些硬核玩意儿。到时候我会分享一个我调试过的核间通信死锁案例,保证让你印象深刻。


可可精品资料库