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的负载均衡机制,我总结为三步走:
- 周期性检查:每个调度域会定期(比如每1ms)检查各组之间的负载差异
- 寻找不平衡:如果某个调度组的负载比另一个组高出一定阈值(默认25%),就触发迁移
- 任务迁移:从最忙的组里挑一个合适的任务,迁移到最闲的组里
这里有个细节:不是所有任务都能随便迁移。比如绑定了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 知识体系图
下面这张图,是我梳理的多核调度核心逻辑。你看一眼,基本就明白整个框架了。
嗯,这张图把整个流程串起来了。从调度域到调度组,再到负载均衡,最后落到三种策略上。你写代码时,脑子里要有这个层次感。
一句话总结:多核调度不是把单核的调度器复制几份,而是要解决“谁在哪个核上跑、什么时候迁移、迁移代价多大”这三个问题。
好了,这一章的内容就到这儿。下一章我们会深入核间通信,讲讲IPI、共享内存、消息队列这些硬核玩意儿。到时候我会分享一个我调试过的核间通信死锁案例,保证让你印象深刻。
可可精品资料库