news 2026/6/2 15:44:01

Linux内核学习轨迹第四部: 多核调度与负载均衡机制(第六小节)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux内核学习轨迹第四部: 多核调度与负载均衡机制(第六小节)

8. 多核调度与负载均衡机制

现代服务器几乎都是多核SMP(对称多处理)架构,甚至是多NUMA节点架构,Linux调度器不仅要保证单CPU上的进程调度公平性,还要解决多核场景下的核心挑战:如何把进程均衡地分配到各个CPU核心上,最大化CPU利用率,最小化跨CPU调度的开销,同时保证调度的公平性和实时性
很多工程师对多核调度的认知停留在「进程会在多个CPU之间自动均衡」,却不理解负载均衡的底层实现、调度域与调度组的层级结构、唤醒亲和性、NUMA调度等核心机制,最终导致系统CPU负载不均衡、性能上不去、调度延迟过高等问题。本章节基于Linux 6.6 LTS内核,完整拆解多核调度与负载均衡的全链路实现、工程调优与避坑指南。

8.1 SMP架构下的调度核心挑战

在单核时代,调度器只需要管理单个CPU的就绪队列,选择下一个要运行的进程即可。但在多核SMP架构下,调度器面临着多个相互冲突的核心挑战:
  1. CPU负载均衡:要把进程均衡地分配到各个CPU核心上,避免出现「部分CPU核心占满,其他核心空闲」的情况,最大化CPU利用率;
  2. 缓存亲和性:进程在同一个CPU上运行时,它的代码和数据会被缓存到该CPU的L1/L2/L3缓存中,访问速度极快。如果进程频繁在多个CPU之间迁移,会导致缓存失效,内存访问延迟大幅增加,性能下降;
  3. 调度延迟:负载均衡的操作需要加锁,遍历多个CPU的就绪队列,会带来一定的调度开销,不能影响进程的调度延迟;
  4. 功耗优化:在移动端、嵌入式场景下,需要尽量让进程集中在部分CPU核心上运行,让其他核心进入低功耗状态,降低系统功耗;
  5. NUMA架构的挑战:多NUMA节点架构下,跨NUMA节点的内存访问延迟是本地访问的2~3倍,调度器需要尽量让进程在同一个NUMA节点内运行,避免跨NUMA节点的内存访问。
Linux调度器的解决方案是:per-CPU就绪队列 + 层级化负载均衡机制。每个CPU核心有独立的就绪队列,调度器只在特定的时机,在CPU之间迁移进程,实现负载均衡,同时最小化进程迁移的次数,保证缓存亲和性。

8.2 调度域与调度组:负载均衡的层级结构

Linux负载均衡的核心是**调度域(sched_domain)调度组(sched_group)**的层级结构,它把CPU按共享缓存、NUMA节点、物理CPU的层级,划分为不同的调度域,每个调度域内的CPU之间做负载均衡,层级化的设计既保证了负载均衡的效果,又最小化了均衡的开销。

8.2.1 调度域与调度组的层级划分

调度域的层级是根据硬件的拓扑结构自动生成的,典型的双路NUMA服务器的调度域层级如下:
┌─────────────────────────────────────────────────────────────────────────────┐ │ 系统级调度域 (SD_SYSTEM) │ │ 最高层级 | 跨所有NUMA节点 | 负载均衡周期: 100-200ms | 仅极端情况触发 │ └───────────────────────────────────┬───────────────────────────────────────┘ │ ┌───────────────────────────────┴───────────────────────────────┐ │ NUMA调度域 (SD_NUMA) - 节点0 │ │ 跨CPU插槽 | 共享本地内存 | 负载均衡周期: 50-100ms │ └───────────────────────────────┬───────────────────────────────┘ │ ┌───────────────────────────────┴───────────────────────────────┐ │ Package调度域 (SD_PACKAGE) - 插槽0 │ │ 单CPU插槽 | 共享L3缓存 | 负载均衡周期: 10-20ms │ └───────────────────────────────┬───────────────────────────────┘ │ ┌───────────────────────────────┴───────────────────────────────┐ │ Core调度域 (SD_CORE) - 物理核心0 │ │ 单物理核心 | 共享L1/L2缓存 | 负载均衡周期: 1-2ms │ └───────────────────────────────┬───────────────────────────────┘ │ ┌───────────────┴───────────────┐ │ 调度组1: 超线程0 (CPU0) │ 调度组2: 超线程1 (CPU1) └───────────────┬───────────────┘ │ ┌───────┴───────┐ │ CPU调度域(SD_CPU) │ │ 最底层 | 单逻辑CPU | 无负载均衡 │ └───────────────┘

8.2.2 核心数据结构

1.调度域struct sched_domain

每个调度域对应一个层级的CPU集合,定义了该层级的负载均衡参数、规则、状态,每个CPU在每个层级都有一个对应的调度域实例。
struct sched_domain { // 调度域的CPU掩码,包含该调度域内的所有CPU struct cpumask span; // 父调度域,层级结构的上一级 struct sched_domain *parent; // 该调度域内的调度组链表 struct sched_group *groups; // 负载均衡的参数 unsigned int min_interval; // 最小均衡间隔 unsigned int max_interval; // 最大均衡间隔 unsigned int busy_factor; // 繁忙时的均衡间隔因子 unsigned int imbalance_pct; // 负载不均衡的阈值 // 负载均衡的状态 unsigned long next_balance; // 下一次均衡的时间 struct lb_env *env; // 负载均衡的环境变量 };

2.调度组struct sched_group

每个调度组是调度域内的一个CPU子集,是负载均衡的基本单位,负载均衡的本质是在同一个调度域内的不同调度组之间,均衡CPU负载。
struct sched_group { // 调度组的CPU掩码 struct cpumask span; // 调度组链表的下一个节点 struct sched_group *next; // 调度组的负载统计信息 struct sg_lb_stats *stats; // 调度组的权重 unsigned int group_weight; };
核心设计思想
  1. 负载均衡是层级化执行的,从最底层的SMT调度域开始,依次向上到MC调度域、NUMA调度域;
  2. 每个调度域只负责自己范围内的CPU负载均衡,只有当下层调度域无法解决负载不均衡问题时,才会触发上层调度域的均衡;
  3. 越底层的调度域,均衡间隔越短,不均衡阈值越低,因为进程迁移的开销小;越上层的调度域,均衡间隔越长,不均衡阈值越高,因为进程迁移的开销大,尽量避免跨NUMA节点的进程迁移。

8.3 负载均衡的三大触发场景

Linux负载均衡不是实时执行的,而是在三个特定的时机触发,既保证了负载均衡的效果,又最小化了均衡的开销。

8.3.1 周期性负载均衡(Periodic Load Balancing)

周期性负载均衡是最核心的负载均衡机制,每个时钟tick到来时,调度器会检查当前CPU是否需要执行负载均衡,如果到达了调度域设置的next_balance时间,就会触发负载均衡。
核心执行流程
  1. 每个时钟tick,内核调用update_process_times(),最终调用rebalance_domains();
  2. rebalance_domains()从最底层的调度域开始,依次向上遍历所有调度域;
  3. 对于每个调度域,检查是否到达了next_balance时间,如果到达了,就调用load_balance()执行负载均衡;
  4. load_balance()计算该调度域内各个调度组的负载,找到最繁忙的调度组和最空闲的调度组;
  5. 如果两个调度组的负载差超过了imbalance_pct设置的阈值,就从繁忙的调度组中选择合适的进程,迁移到空闲的调度组的CPU上;
  6. 更新调度域的next_balance时间,设置下一次均衡的时间。
进程选择规则
负载均衡时,不是随机选择进程迁移,而是有严格的选择规则,尽量减少迁移带来的性能损失:
  1. 优先选择正在睡眠的进程,而不是正在运行的进程,因为睡眠进程的缓存已经冷了,迁移的开销小;
  2. 优先选择在当前CPU上运行时间最短的进程,缓存热度最低;
  3. 优先选择优先级低的进程,避免影响高优先级/实时进程;
  4. 绝对不能迁移设置了CPU亲和性的进程,只能在允许的CPU范围内迁移。

8.3.2 唤醒时的负载均衡(Wake-up Balancing)

当一个进程被唤醒时,内核会触发唤醒时的负载均衡,为进程选择一个最合适的CPU来运行,这是实现唤醒亲和性的核心机制,也是交互式进程响应性的关键保证。
核心执行流程
  1. 进程被唤醒时,内核调用select_task_rq()接口,由调度类实现选核逻辑;
  2. CFS调度器的select_task_rq_fair()会执行唤醒亲和性算法,优先选择以下CPU:
    1. 进程上一次运行的CPU(缓存亲和性优先),如果该CPU空闲,直接选择;
    2. 进程上一次运行的CPU所在的调度域内的空闲CPU,保证缓存亲和性的同时,利用空闲CPU;
    3. 系统中负载最低的CPU,保证负载均衡。
  3. 选择好目标CPU后,把进程加入目标CPU的就绪队列,如果目标CPU的当前进程优先级更低,触发抢占。
核心优化:唤醒抢占与快速路径
对于交互式进程,唤醒后需要快速得到调度,内核会优先选择进程上一次运行的CPU,哪怕该CPU有进程在运行,只要唤醒进程的vruntime更小,就会触发抢占,保证交互式响应性。

8.3.3 空闲CPU的负载均衡(Idle Load Balancing)

当一个CPU进入空闲状态(没有可运行的进程)时,会立即触发空闲负载均衡,从其他繁忙的CPU上拉取进程来运行,避免CPU资源浪费,这是解决「CPU空闲但其他CPU繁忙」问题的核心机制。
核心执行流程
  1. CPU进入idle空闲进程时,调用schedule_idle(),触发空闲负载均衡newidle_balance();
  2. ewidle_balance()从最底层的调度域开始,遍历所有调度域,寻找繁忙的调度组;
  3. 从繁忙的调度组中拉取合适的进程,迁移到当前空闲的CPU上;
  4. 如果拉取到了进程,就调度该进程运行,否则继续执行idle进程,进入低功耗状态。
核心特点
  1. 空闲负载均衡的优先级最高,只要CPU空闲,就会立即触发,不需要等待周期性均衡;
  2. 空闲负载均衡只会拉取进程,不会推送进程,避免影响繁忙CPU的运行;
  3. 对于功耗敏感的场景,可以通过调优参数关闭空闲负载均衡,让CPU保持空闲,进入低功耗状态。

8.4 CPU亲和性与调度控制

Linux提供了CPU亲和性机制,允许用户手动设置进程可以在哪些CPU上运行,这是多核调度优化最常用的手段,能极大提升进程的性能和实时性。

8.4.1 CPU亲和性的系统调用

CPU亲和性通过sched_setaffinity()和sched_getaffinity()系统调用设置和获取,原型如下:
#include<sched.h>
// 设置进程pid的CPU亲和性,mask是CPU掩码
int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *mask);
// 获取进程pid的CPU亲和性
int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
使用示例:把进程绑定到0号和1号CPU上运行
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(0, &mask); // 加入0号CPU
CPU_SET(1, &mask); // 加入1号CPU
// 设置当前进程的CPU亲和性
if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) == -1) {
perror("sched_setaffinity failed");
exit(1);
}

8.4.2 CPU亲和性的工程最佳实践

1.关键业务进程绑定固定CPU

对于CPU密集型、延迟敏感的业务进程,绑定到固定的CPU核心上,避免进程跨CPU迁移,提升缓存命中率,降低调度延迟。

最佳实践:每个核心业务进程绑定一个独立的CPU核心,不要多个进程共享同一个核心;把进程绑定到同一个NUMA节点的CPU上,避免跨NUMA节点访问。

2.中断亲和性与进程亲和性绑定

对于网络、IO密集型进程,把中断亲和性设置到和进程相同的CPU核心上,让中断处理和进程处理在同一个CPU上,提升缓存命中率,降低延迟。

示例:把网卡中断绑定到0号CPU,把网络处理进程也绑定到0号CPU。

3.隔离CPU核心

对于实时进程、延迟敏感的进程,可以通过内核启动参数isolcpus隔离CPU核心,隔离后的CPU核心不会被普通进程使用,只能通过CPU亲和性手动绑定进程,同时不会触发负载均衡,避免其他进程的干扰。

内核启动参数:isolcpus=2,3 隔离2号和3号CPU核心;

最佳实践:实时进程必须绑定到隔离的CPU核心上,避免其他进程的抢占和干扰,保证实时性。

4.NUMA架构的亲和性设置

多NUMA节点架构下,进程必须绑定到同一个NUMA节点的CPU核心上,同时内存分配也绑定到该NUMA节点,避免跨NUMA节点的内存访问,延迟降低2~3倍。

最佳实践:用numactl工具启动进程,绑定CPU和内存节点:

# 把进程绑定到NUMA节点0的CPU上,内存分配也在节点0
numactl --cpunodebind=0 --membind=0 ./your_program

8.5 能量感知调度EAS

在移动端、嵌入式等功耗敏感的场景,Linux内核提供了能量感知调度(Energy Aware Scheduling, EAS),它在调度进程时,不仅考虑CPU的负载,还考虑CPU的功耗,在保证性能的同时,最小化系统功耗。

8.5.1 EAS的核心设计思想

传统的调度器只关注性能和负载均衡,而EAS基于CPU的能耗模型,计算不同CPU调度进程的功耗开销,优先选择功耗最低的CPU来运行进程,同时保证进程的性能要求。
EAS的核心依赖:
  1. 能耗模型:内核通过设备树获取CPU的能耗模型,包括不同频率、不同负载下的CPU功耗;
  2. CPU调频子系统:和schedutil调频器深度配合,根据进程的负载动态调整CPU频率,平衡性能和功耗;
  3. 异构架构支持:原生支持ARM big.LITTLE异构架构(大核+小核),把性能要求高的进程调度到大核,性能要求低的进程调度到小核,最大化能效比。

8.5.2 EAS的核心调度规则

  1. 进程唤醒时,根据进程的负载计算性能需求,选择能满足性能需求的、功耗最低的CPU核心;
  2. 对于轻负载进程,优先调度到小核,降低功耗;
  3. 对于重负载、延迟敏感的进程,调度到大核,保证性能;
  4. 尽量让进程集中在部分核心上运行,让其他核心进入深度空闲状态,降低功耗;
  5. 只有当所有核心的负载超过阈值时,才会唤醒更多的核心,平衡负载。
EAS已经成为Android、嵌入式Linux系统的默认调度机制,能在保证性能的同时,大幅降低系统功耗,延长电池续航。

8.6 多核调度的工程实践与避坑指南

8.6.1 CPU负载不均衡的排查与解决

常见现象:系统中部分CPU核心使用率100%,其他核心使用率很低,整体CPU利用率上不去,业务吞吐量低。
排查流程
  1. 用top → 按1,查看每个CPU核心的使用率,确认负载不均衡的情况;
  2. 用ps -eLo pid,tid,psr,comm查看每个线程运行的CPU核心,找到占满CPU的线程;
  3. 检查这些线程是否设置了CPU亲和性,绑定到了固定的CPU核心;
  4. 检查系统的负载均衡参数是否被修改,比如sched_migration_cost_ns被设置得过大,导致进程无法迁移;
  5. 检查是否是NUMA架构,进程是否都集中在同一个NUMA节点;
  6. 用perf sched记录调度事件,查看进程的迁移情况,确认负载均衡是否正常工作。
解决方案
  1. 对于多线程业务,不要给线程设置固定的CPU亲和性,让调度器自动均衡负载;
  2. 调小sched_migration_cost_ns参数,降低进程迁移的阈值,提升负载均衡的灵敏度;
  3. 调大sched_nr_migrate参数,增加一次负载均衡最多迁移的进程数,加快负载均衡的速度;
  4. 对于NUMA架构,开启NUMA均衡,保证NUMA节点之间的负载均衡;
  5. 优化业务代码,把单线程的任务拆分为多线程,充分利用多核CPU。

8.6.2 进程频繁迁移的性能优化

常见现象:进程频繁在多个CPU之间迁移,缓存命中率低,内存访问延迟高,业务性能下降,上下文切换次数过多。
排查流程
  1. 用pidstat -w 1查看进程的上下文切换次数,cswch/s数值过高说明进程频繁被切换;
  2. 用ps -eLo pid,tid,psr,comm持续查看线程运行的CPU核心,确认线程是否频繁在多个CPU之间迁移;
  3. 用perf stat -e cache-misses ./your_program查看进程的缓存缺失率,确认是否缓存失效严重。
解决方案
  1. 给进程设置CPU亲和性,绑定到固定的CPU核心/NUMA节点,避免跨CPU迁移;
  2. 调大sched_migration_cost_ns参数,增加进程迁移的成本,减少进程迁移的频率;
  3. 调大sched_wakeup_granularity_ns参数,减少唤醒抢占的频率,避免进程频繁被切换;
  4. 对于多线程业务,使用线程池,绑定每个线程到固定的CPU核心,提升缓存命中率。

8.6.3 NUMA架构的性能优化

常见现象:多NUMA节点服务器上,业务性能远低于预期,内存访问延迟高,跨NUMA节点访问过多。
排查流程
  1. 用numactl -H查看系统的NUMA节点拓扑、每个节点的CPU和内存;
  2. 用numastat查看每个NUMA节点的内存分配情况,numa_miss数值过高说明跨节点内存访问过多;
  3. 用perf stat -e numa_hit,numa_miss,numa_foreign ./your_program查看进程的NUMA访问统计。
解决方案
  1. 用numactl工具绑定进程到同一个NUMA节点的CPU和内存,避免跨节点访问;
  2. 开启内核NUMA均衡,echo 1 > /proc/sys/kernel/numa_balancing,让内核自动把进程和内存迁移到同一个NUMA节点;
  3. 业务代码优化,避免多个NUMA节点的进程共享同一块内存,减少跨节点内存访问;
  4. 对于数据库等大内存应用,开启大页,减少TLB缺失,同时绑定大页到对应的NUMA节点。

8.6.4 避坑指南

1.不要盲目绑定CPU亲和性

很多工程师为了提升性能,给所有进程都绑定CPU亲和性,反而导致负载不均衡,性能下降。

最佳实践:只有CPU密集型、延迟敏感的核心进程才需要绑定CPU亲和性;普通的后台进程、IO密集型进程,让调度器自动分配CPU即可,不需要手动绑定。

2.隔离CPU核心的正确用法

用isolcpus隔离的CPU核心,内核不会把普通进程调度到上面,也不会触发负载均衡,必须手动通过CPU亲和性绑定进程才能使用。很多工程师隔离了CPU核心,却没有给进程绑定亲和性,导致隔离的CPU核心完全空闲,浪费资源。

3.跨NUMA节点的进程迁移禁止

跨NUMA节点的进程迁移会导致缓存完全失效,内存访问延迟大幅增加,性能下降严重。内核默认只有当NUMA节点之间负载严重不均衡时,才会触发跨节点迁移,不要手动修改参数强制跨节点迁移。

4.实时进程的CPU绑定

实时进程必须绑定到隔离的CPU核心上,同时把中断亲和性设置到其他核心,避免中断和其他进程的干扰,保证实时性。绝对不要让实时进程在多个CPU之间迁移,否则会导致调度延迟大幅增加,无法满足实时性要求。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/2 15:41:58

山东英语学习者必备:高效背单词平台推荐与使用心得

引言在山东&#xff0c;无论是中小学生还是成人自学者&#xff0c;在提升英语词汇量的过程中都面临着记忆效率低、遗忘快的问题。为了帮助大家找到真正适合自己的背单词工具&#xff0c;本文将基于教育逻辑、技术内核及长期价值三个维度&#xff0c;深度解析启飞背单词这一优秀…

作者头像 李华
网站建设 2026/6/2 15:41:57

Fan Control:Windows平台终极风扇控制解决方案完全指南

Fan Control&#xff1a;Windows平台终极风扇控制解决方案完全指南 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trending/f…

作者头像 李华
网站建设 2026/6/2 15:41:01

可穿戴LED互动裙装开发:从NeoPixel灯带到PixelBlaze编程全流程解析

1. 项目概述&#xff1a;从一块黑纱裙到一片会呼吸的仲夏夜空几年前&#xff0c;当我第一次把一串NeoPixel灯带缝到衣服上&#xff0c;看着它随着代码指令亮起预设的颜色时&#xff0c;那种“造物”的兴奋感至今难忘。那不仅仅是让衣服发光&#xff0c;而是赋予了一件日常物品以…

作者头像 李华
网站建设 2026/6/2 15:40:04

如何彻底释放惠普OMEN游戏本性能:OmenSuperHub完整使用指南

如何彻底释放惠普OMEN游戏本性能&#xff1a;OmenSuperHub完整使用指南 【免费下载链接】OmenSuperHub Control Omen laptop performance, fan speeds, and keyboard lighting, and unlock power limits. 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub 想要…

作者头像 李华
网站建设 2026/6/2 15:39:56

2023年免费LLM应用开发指南:从开源模型到RAG实战

1. 从零开始的LLM应用入门&#xff1a;一份完全免费的2023年实践指南 如果你在2023年对“大语言模型”这个词感到既兴奋又困惑&#xff0c;不知道从哪里开始动手&#xff0c;那么你找对地方了。过去一年&#xff0c;我身边从产品经理、数据分析师到刚毕业的学生&#xff0c;都…

作者头像 李华
网站建设 2026/6/2 15:38:58

基于micro:bit的双人刷牙计时器:状态机与LED动画设计实践

1. 项目概述与设计思路最近在辅导孩子养成良好生活习惯时&#xff0c;发现让他们坚持刷满牙医推荐的三分钟是个老大难问题。口头计时不准&#xff0c;手机计时又容易分心&#xff0c;市面上专门的计时器要么功能单一&#xff0c;要么价格不菲。正好手头有几块闲置的micro:bit开…

作者头像 李华