Linux 性能实战 | 第 7 篇 CPU 核心负载与调度器概念

第七章|CPU 核心负载与调度器概念 🧠

🔗 从"系统负载"到"核心负载"

在第一部分中,我们已经学会了如何解读系统的整体性能指标。无论是 top 命令中的 load average(第二章),还是 mpstat 揭示的 CPU 总体使用率(第三章),它们都为我们提供了系统级别的宏观视图。

然而,现代服务器都是多核(Multi-Core)、多处理器(Multi-Processor)的。一个笼统的系统负载高,并不能告诉我们问题到底出在哪里。是所有 CPU 核心都在"雨露均沾"地忙碌,还是"旱的旱死,涝的涝死"------只有个别核心在拼命干活,而其他核心却在"摸鱼"?

这种负载不均衡的现象,在高性能计算、高并发网络服务和自动驾驶等场景中尤为致命。它不仅会导致系统资源浪费,更会造成服务响应时间剧烈抖动。

从本章开始,我们将深入 CPU 的微观世界,从 核心负载 (Per-Core Load)调度器 (Scheduler) 的视角,来理解 CPU 是如何管理和分配任务的。这将是我们从"看懂"性能到"调优"性能的关键一步。


🤔 核心概念:SMP、CFS 与 CPU 负载均衡

在深入案例之前,我们需要了解现代 Linux 内核管理 CPU 的三大基石。

1. SMP (Symmetric Multi-Processing, 对称多处理)

想象一个团队,如果只有一个项目经理能分配任务,其他人只能被动接受,这就是"非对称"的。而 对称多处理(SMP) 意味着团队里的每个成员(CPU 核心)都是平等的。它们共享同一份工作清单(内存、总线和 I/O),并且每个核心都有能力去处理任何任务。

  • 优点:极大地提高了系统的并行处理能力。
  • 挑战 :需要一个聪明的"项目管理系统"来确保任务被公平、高效地分配到每个核心上,避免"有的人忙死,有的人闲死"。这个系统就是 调度器

2. CFS (Completely Fair Scheduler, 完全公平调度器)

CFS 是自 Linux 2.6.23 内核以来默认的调度器。它的核心思想非常优雅:力求让每个进程都获得"完全公平"的 CPU 时间

它不再使用传统的时间片(timeslice)概念,而是为每个进程维护一个"虚拟运行时间"(vruntime)。哪个进程的 vruntime 最小,就说明它被"亏待"得最久,下一个就轮到它上 CPU 运行。

💡 vruntime 是如何计算的?
vruntime 的增长速度会根据进程的优先级(nice 值)进行加权。nice 值越低(优先级越高),vruntime 增长得越慢,从而获得更多的 CPU 时间。CFS 通过这种方式,巧妙地将"公平"与"优先级"结合在了一起。

3. CPU 负载均衡 (Load Balancing)

虽然 CFS 保证了单个 CPU 核心上运行队列的公平性,但它无法解决核心与核心之间的负载不均问题。这时,内核的 负载均衡机制 就登场了。

内核会周期性地检查所有 CPU 核心的负载情况(比如运行队列的长度、任务的总权重等)。如果发现某个核心("忙碌核")的负载远高于另一个核心("空闲核"),它就会触发 进程迁移 (Process Migration),将"忙碌核"上的一些进程"搬家"到"空闲核"上。
迁移后
迁移前
CPU 1 · 负载均衡 🟡
CPU 0 · 负载高 🔴
⚡ 触发迁移
搬家到 CPU 1
CPU 0 · 负载降低 🟡
进程1
进程2
进程3
CPU 1 · 负载低 🟢
进程5
进程1
进程2
进程3
进程4
负载均衡器
进程4
进程5

这个过程就像超市里,一个聪明的经理看到 1 号收银台排起了长队,而 2 号收银台却没人,于是引导几位顾客去 2 号结账。

💡 迁移的成本

尽管进程迁移是实现负载均衡的利器,但它并非"免费"的。当一个进程从 CPU 0 迁移到 CPU 1 时,它在 CPU 0 的高速缓存(L1/L2 Cache)中建立的热数据会全部失效,需要在 CPU 1 上重新建立。这个过程会带来性能开销,这也是我们下一章将要深入探讨的"上下文切换"成本的一部分。


� 特权阶级:实时进程与 RR 调度

到目前为止,我们讨论的 CFS 调度器主要服务于普通进程(SCHED_NORMALSCHED_OTHER)。它的目标是"公平",保证大家都能分到 CPU 时间,适合于 Web 服务器、数据库等高吞吐量场景。

但在自动驾驶、工业控制、高频交易等实时系统(Real-Time System)中,"公平"不是最重要的,"及时响应" 才是。一个控制车辆刹车的进程,必须在毫秒之内得到响应,绝不能因为要"公平"地让一个日志进程先运行而延误。

为了满足这种严苛的需求,Linux 提供了 实时调度策略 (Real-Time Scheduling Policies) 。被设置为实时策略的进程,如同拥有了"特权",它们的调度优先级 远高于 所有普通进程。

实时调度策略:FIFO 与 RR

Linux 主要提供两种实时调度策略:

  1. SCHED_FIFO (First-In, First-Out) :先进先出。一个 FIFO 进程一旦占用 CPU,就会一直运行,直到它自愿放弃(如等待 I/O)或被更高优先级的实时进程抢占。它没有时间片的概念。
  2. SCHED_RR (Round-Robin) :轮询。RRFIFO 的一个变种。它同样是高优先级抢占,但增加了 时间片 的概念。当一个 RR 进程用完了它的时间片,但队列中还有其他同优先级的 RR 进程时,它会被放到队列末尾,让其他同级进程运行。这保证了同优先级的实时任务之间也能"轮流"使用 CPU。

一句话总结 :实时进程(无论 FIFO 还是 RR)就像 VIP,普通 CFS 进程就像普通乘客。只有当所有 VIP 都不需要服务(运行)时,CPU 才会去服务普通乘客。

如何查看和设置实时进程?

我们可以使用 chrt (change real-time attributes) 命令来操作进程的调度策略和优先级。

查看进程的调度策略

bash 复制代码
# 查看 PID 为 12345 的进程的调度策略
chrt -p 12345

输出示例

复制代码
pid 12345's current scheduling policy: SCHED_OTHER
pid 12345's current scheduling priority: 0

设置进程为 RR 调度

假设在自动驾驶系统中,planning_process (PID 34567) 是一个路径规划的关键进程,对延迟极度敏感。我们可以将其设置为 SCHED_RR 策略,并赋予较高的实时优先级(范围 1-99)。

bash 复制代码
# -r: 设置为 SCHED_RR 策略
# -p 80: 设置实时优先级为 80
# 34567: 目标进程 PID
sudo chrt -r -p 80 34567

设置后,planning_process 就成了一个高优先级的实时进程。只要它处于可运行状态,调度器就会 立即抢占 正在运行的普通进程,让它插队先跑。


⚖️ 实时与非实时的平衡艺术:混合系统调优

引入实时进程后,一个核心的挑战出现了:如何平衡实时任务的"及时性"与非实时任务的"公平性"?

如果实时进程设置不当(例如,优先级过高且长时间占用 CPU),会导致普通进程"饿死" (Starvation),系统可能会失去响应,连 SSH 登录都变得困难。

场景:某自动驾驶计算单元,既要运行决策规划、控制等高优先级实时任务,也要运行日志记录、数据上传等低优先级后台任务。工程师发现,在压力场景下,日志偶尔会丢失,数据上传也经常中断。

分析

通过 topps -eo pid,pri,rtprio,cls,cmd 发现,控制进程以 RR 策略、优先级 90 运行,而日志和上传进程以普通 TS (CFS) 策略运行。当控制任务繁忙时,它会持续抢占 CPU,导致日志进程根本没有机会运行,数据来不及写入磁盘。

解决方案:CPU 隔离与精细化绑定

这是一种在混合实时系统中非常经典且有效的调优策略:将 CPU 核心进行物理隔离

  1. 预留核心 (CPU Isolation) :通过修改内核启动参数(isolcpus),告诉内核不要将任何普通进程调度到某些特定的 CPU 核心上。这些核心被专门"预留"出来给实时任务使用。

    例如,在一个 8 核系统中,可以在 /etc/default/grubGRUB_CMDLINE_LINUX_DEFAULT 中加入 isolcpus=6,7。然后更新 grub 并重启。

    复制代码
    GRUB_CMDLINE_LINUX_DEFAULT="quiet splash isolcpus=6,7"

    重启后,内核的负载均衡器会自动忽略 CPU 6CPU 7

  2. 绑定任务 (CPU Affinity)

    • 将最关键的实时任务(如车辆控制)绑定到被隔离的 CPU 6CPU 7 上。
    bash 复制代码
    sudo taskset -pc 6,7 <control_process_pid>
    • 将所有非实时、吞吐量优先的任务(如日志、数据上传、Web 服务等)明确地限制在 CPU 0-5 上。

效果

  • 实时任务 :在 CPU 6,7 上独享资源,不受任何其他进程的干扰,保证了最低的调度延迟和最可靠的响应时间。
  • 非实时任务 :在 CPU 0-5 上由 CFS 进行公平调度和负载均衡,保证了系统整体的吞吐量和响应性。

通过这种"物理隔离"+"任务绑定"的方式,我们成功地构建了一个"楚河汉界",让实时任务的确定性与非实时任务的公平性互不干扰,实现了混合系统性能的最大化。


🛠️ 实战:从诊断到调优的完整路径

现在,让我们把前面讨论的所有概念串联起来,走一遍从发现问题到最终调优的完整流程。

场景:一个自动驾驶计算单元,在集成测试中表现不稳定。系统平均 CPU 使用率不高,但偶尔出现控制指令延迟超标,同时后台的日志记录不完整。

1. 初步诊断:发现"冰火两重天"

运维工程师首先使用 top 并按 1,看到了熟悉的"负载不均衡"现象:

复制代码
%Cpu0  : 99.8 us,  0.2 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  2.1 us,  1.0 sy,  0.0 ni, 96.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  3.4 us,  1.5 sy,  0.0 ni, 95.1 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
... (其他核心都很空闲)

CPU0 几乎被跑满,而其他核心却在"围观"。这解释了为何系统平均负载不高,但性能却出了问题------系统的有效算力被浪费了

2. 根因分析:不当的 CPU 亲和性

通过 ps 找到在 CPU0 上最繁忙的进程 PID(假设为 12345),使用 taskset 检查其绑定情况:

bash 复制代码
taskset -pc 12345
# 输出: pid 12345's current affinity list: 0

原因找到了:这个关键进程被死死地绑在了 CPU0 上。

3. 进阶分析:实时与非实时任务的冲突

工程师进一步使用 ps -eo pid,pri,rtprio,cls,cmd 查看所有相关进程的调度策略,发现了更深层次的问题:

PID CLS RTPRIO PRI CMD
12345 RR 80 120 ./control_process
12346 TS - 80 ./planning_process
12347 TS - 80 ./log_process
  • 控制进程 (12345) 是一个高优先级(80)的实时进程(RR),它对延迟要求最高。
  • 规划进程 (12346)日志进程 (12347) 都是普通进程(TS,即 SCHED_OTHER)。

现在问题清晰了:

  1. 单核瓶颈 :所有这些进程(或它们的父进程)都被错误地绑定在了 CPU0 上。
  2. 任务饿死 :当 control_process 繁忙时,作为实时进程,它会持续抢占 CPU0。这导致在 CPU0 上的 planning_processlog_process 几乎没有机会运行,从而引发指令延迟和日志丢失。

4. 终极解决方案:隔离核心,各行其道

针对这种混合系统的典型问题,最终的解决方案是采用我们前面讨论的"CPU 隔离"策略。

  1. 隔离核心 :修改 GRUB 配置,将 CPU6CPU7 隔离出来,专门用于实时任务。

    复制代码
    # /etc/default/grub
    GRUB_CMDLINE_LINUX_DEFAULT="... isolcpus=6,7"

    更新 GRUB 并重启后,CPU6CPU7 就成了实时任务的"专属领地"。

  2. 重新绑定任务

    • 将最关键的控制进程绑定到专属的 CPU7
    bash 复制代码
    sudo taskset -pc 7 12345
    • 将次重要的规划进程绑定到 CPU6
    bash 复制代码
    sudo taskset -pc 6 12346
    • 将所有非实时的后台任务(如日志)明确地限制在普通核心 0-5 上。
    bash 复制代码
    sudo taskset -pc 0-5 12347

通过这一系列操作,我们不仅解决了最初的"负载不均衡"问题,更从架构上保证了高优先级实时任务的确定性,避免了它对普通任务的冲击,完美地平衡了系统的实时性与吞吐量。


🛠️ 实战:诊断 CPU 核心负载不均衡

场景:一个自动驾驶的感知融合模块,它接收来自摄像头、激光雷达等多个传感器的数据,进行处理和融合。在压力测试中,团队发现系统的平均 CPU 使用率只有 50% 左右,但端到端的处理延迟却偶尔会飙升,远超预期。

1. 初步诊断:topmpstat

首先,运维同学执行 top 命令,然后按下 1,切换到显示所有 CPU 核心的状态。

复制代码
top - 14:25:01 up 10 days,  2:10,  1 user,  load average: 4.15, 3.98, 3.50
Tasks: 450 total,   1 running, 449 sleeping,   0 stopped,   0 zombie
%Cpu0  : 99.7 us,  0.3 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  5.2 us,  2.1 sy,  0.0 ni, 92.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  4.8 us,  2.5 sy,  0.0 ni, 92.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  6.1 us,  3.0 sy,  0.0 ni, 90.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
... (其他核心都很空闲)

现象 :问题立刻暴露出来!Cpu0 的用户态使用率 (us) 达到了惊人的 99.7%,几乎被完全占满,而其他核心则非常空闲,idle 普遍在 90% 以上。系统的 load average (4.15) 看着不低,但实际上是"虚假繁荣",压力完全集中在了一个核心上。

接着,使用 mpstat 进一步确认:

bash 复制代码
# -P ALL: 显示所有核心
# 1 3: 每秒刷新一次,共刷新 3 次
mpstat -P ALL 1 3

mpstat 的输出同样证实了 CPU 0 的负载远高于其他核心。

2. 根因分析:进程绑定 (CPU Affinity)

为什么内核的负载均衡机制"失灵"了?

最常见的原因是 进程被手动绑定到了某个特定的 CPU 核心上 ,这被称为 CPU 亲和性 (CPU Affinity)。进程绑定通常是出于性能优化的目的(例如,为了提高 CPU 缓存命中率),但如果配置不当,就会造成严重的负载瓶颈。

我们可以使用 taskset 命令来检查和设置进程的 CPU 亲和性。

假设通过 ps 查到那个繁忙的感知融合进程的 PID 是 12345

bash 复制代码
# -p: 指定 PID
# -c: 以列表形式显示核心
taskset -pc 12345

输出

复制代码
pid 12345's current affinity list: 0

果然,进程 12345 被死死地绑在了 CPU 0 上!这意味着,无论 CPU 0 多么繁忙,无论其他核心多么空闲,调度器都无权将这个进程或其子线程迁移到别的核心上。所有传感器数据处理的压力都压在了 CPU 0 这一个点上,延迟飙升也就不难理解了。

3. 解决方案与优化

方案一:解除绑定(简单粗暴)

如果业务逻辑允许,最简单的办法就是解除绑定,让内核的负载均衡机制重新接管它。

bash 复制代码
# 将进程 12345 的亲和性设置为所有核心 (0-3)
sudo taskset -pc 0-3 12345

执行后,再观察 top -1,会发现原本集中在 CPU 0 的负载,被迅速地分散到了其他空闲核心上,系统的整体吞吐量得到提升。

方案二:精细化绑定(推荐)

在高性能场景下,完全不进行绑定也是不理想的。因为进程在核心之间频繁迁移会导致 CPU L1/L2 缓存失效,反而降低性能。

更优化的方法是 基于任务特性进行精细化绑定。例如,在这个案例中,可以将处理不同传感器(摄像头、激光雷达)的线程绑定到不同的 CPU 核心上,实现数据流的并行处理。

bash 复制代码
# 假设 PID 20001 是摄像头处理线程,绑定到 CPU 1
sudo taskset -pc 1 20001

# 假设 PID 20002 是激光雷达处理线程,绑定到 CPU 2
sudo taskset -pc 2 20002

通过这种方式,我们既避免了所有任务在单个核心上"内卷",又保证了每个任务能稳定地利用特定核心的缓存,实现了真正的并行化,从而达到降低延迟、提升吞吐的最终目的。


📝 总结与展望

  • 从宏观到微观 :分析 CPU 问题时,不能只看总体负载,必须使用 top -1mpstat -P ALL 下沉到每个核心的负载,这是发现负载不均衡问题的起点。
  • 调度器不是万能的 :Linux 的 CFS 调度器和负载均衡机制非常强大,但它们的工作会受到 CPU 亲和性(taskset)等人为设置的限制。
  • 绑定是双刃剑 ⚔️:CPU 绑定是重要的性能优化工具,但错误的绑定策略是导致单核瓶颈的常见元凶。优化的关键在于"并行化",将不同的任务流分散到不同的核心上,而不是将所有任务都压在一个核心上。
  • 实时优先,隔离保障 👑:对于自动驾驶等混合实时系统,必须认识到实时进程(SCHED_RR/FIFO)的"特权"。使用 chrt 设置实时优先级,并通过 CPU 隔离 (isolcpus) 为它们提供专属的运行环境,是保证关键任务延迟达标的最佳实践。

下一篇展望

在本章中,我们揭开了 CPU 核心负载不均衡的神秘面纱,并掌握了从诊断到利用 CPU 隔离进行架构调优的全路径。在下一章,我们将继续深入探讨一个与 CPU 性能密切相关的经典话题:上下文切换。我们将分析它发生的原因、带来的开销,以及如何定位那些导致系统"内耗"过高的元凶。敬请期待!

相关推荐
那就回到过去3 小时前
MPLS多协议标签交换
网络·网络协议·hcip·mpls·ensp
qq_297574673 小时前
Linux 服务器 Java 开发环境搭建保姆级教程
java·linux·服务器
70asunflower4 小时前
Emulation,Simulation,Virtualization,Imitation 的区别?
linux·docker
那就回到过去4 小时前
VRRP协议
网络·华为·智能路由器·ensp·vrrp协议·网络hcip
极客小云4 小时前
【ComfyUI API 自动化利器:comfyui_xy Python 库使用详解】
网络·python·自动化·comfyui
神梦流4 小时前
GE 引擎的内存优化终局:静态生命周期分析指导下的内存分配与复用策略
linux·运维·服务器
凡人叶枫4 小时前
C++中输入、输出和文件操作详解(Linux实战版)| 从基础到项目落地,避坑指南
linux·服务器·c语言·开发语言·c++
wdfk_prog4 小时前
[Linux]学习笔记系列 -- [drivers][input]serio
linux·笔记·学习
符哥20085 小时前
用Apollo + RxSwift + RxCocoa搭建一套网络请求框架
网络·ios·rxswift