Linux 性能实战 | 第 8 篇 上下文切换、内核线程与调度延迟

Linux 性能实战 | 第 8 篇 上下文切换、内核线程与调度延迟 🐢

🔗 上下文切换:CPU 的"换挡"开销

在上一章中,我们探讨了调度器如何通过进程迁移来实现 CPU 负载均衡。我们提到了迁移是有成本的,这个成本的核心就是 上下文切换 (Context Switch)

想象一下你正在专注地写一份复杂的代码(执行一个进程),这时突然来了一个紧急电话(中断),你不得不放下手头的工作,记录下当前的思路(保存现场),然后去处理电话(切换到内核态处理中断)。打完电话后,你还需要回忆刚才写到哪里了(恢复现场),才能继续编程。这个"放下-切换-恢复"的过程,就是一次上下文切换。

对 CPU 而言,上下文切换意味着:

  1. 保存当前进程/线程的"快照":包括 CPU 寄存器(程序计数器、栈指针等)、进程状态、内存管理信息等。
  2. 加载下一个进程/线程的"快照" 到 CPU 寄存器中。
  3. 刷新 TLB (Translation Lookaside Buffer):导致虚拟地址到物理地址的缓存失效。
  4. CPU 缓存失效:新进程的代码和数据很可能不在 CPU 的 L1/L2/L3 缓存中,需要从更慢的内存中重新加载。

一句话总结:上下文切换是必要的"恶"。没有它,多任务系统无法工作。但过于频繁的切换,就像一个员工不停地在多个任务间来回切换,大部分时间都浪费在了"切换"本身,而不是真正地"执行"任务,导致系统整体效率大幅下降。


🤔 什么时候会发生上下文切换?

上下文切换主要分为三类:

1. 进程上下文切换 (Process Context Switch)

这是开销最大的一种切换。它不仅涉及到内核态和用户态的切换,还涉及到虚拟内存空间的切换。

  • 时间片用完 :CFS 调度器为了"公平",会中断当前进程,让给 vruntime 更小的进程。
  • 进程阻塞 :当进程需要等待某个资源时,如等待磁盘 I/O、等待网络数据、或者通过 sleep 主动挂起,它会被置为 INTERRUPTIBLEUNINTERRUPTIBLE 状态,从而触发调度,让出 CPU。
  • 更高优先级的进程就绪 :一个实时进程(如 SCHED_RR)变为可运行状态,会立即抢占当前正在运行的普通进程。

2. 线程上下文切换 (Thread Context Switch)

当同一进程内的两个线程之间发生切换时,因为它们共享同一个虚拟内存空间,所以切换时 不需要更换页表,TLB 也不会被完全刷新。因此,它的开销比进程上下文切换要小得多。

3. 中断上下文切换 (Interrupt Context Switch)

为了快速响应硬件事件(如网卡收到数据包、硬盘完成读写),CPU 会暂停当前运行的进程,切换到内核态去执行一个 中断服务程序 (Interrupt Service Routine, ISR)

中断上下文的切换非常快,因为它只涉及少量内核信息的保存和恢复,不涉及用户进程。但它会打断正常进程的执行,如果中断过于频繁,也会严重影响系统性能。


🛠️ 实战:定位上下文切换元凶

场景 :一个高并发的 Web 服务器,在压力测试期间,top 显示系统 CPU 使用率并不高(例如,us+sy 只有 30%),但 load average 却异常地高,并且服务响应延迟(RT)剧增。

1. 初步诊断:vmstat

vmstat 是快速发现上下文切换问题的利器。

bash 复制代码
# 每秒输出一次报告
vmstat 1

输出

复制代码
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 123456  10240 567890    0    0     0    20 1024 200000  15 15 70  0  0
 2  0      0 123450  10240 567892    0    0     0    30 1280 250000  18 17 65  0  0
 1  0      0 123440  10240 567898    0    0     0    25 1150 220000  16 16 68  0  0

关注两列:

  • cs (context switch) :每秒上下文切换的次数。这个值通常在几千到几万是正常的。但如果飙升到 几十万甚至上百万,就表明系统存在严重的调度问题。
  • in (interrupt):每秒中断的次数。

在这个案例中,cs 高达 20 多万,显然是问题的根源。CPU 的大部分时间都花在了"换挡"上,而不是在执行应用代码(us)或内核代码(sy)。

2. 深入分析:pidstat

找到了问题,下一步就是定位 是哪个进程 导致的。pidstatsysstat 工具包中的一员,专门用于分析进程级别的统计信息。

bash 复制代码
# -w: 显示上下文切换信息
# -p ALL: 监控所有进程
# 1: 每秒输出一次
pidstat -w -p ALL 1

输出

复制代码
Linux 5.4.0-100-generic (hostname)  01/31/26  _x86_64_ (8 CPU)

10:30:01      UID       PID    cswch/s nvcswch/s  Command
10:30:02     1000     12345   150000.00      5.00  nginx
10:30:02        0     54321      200.00     10.00  kworker/u16:0
10:30:02     1001      9876       10.00      0.00  redis-server
...

关注两列:

  • cswch/s (voluntary context switches)自愿上下文切换。通常是因为进程等待资源(如 I/O)而主动放弃 CPU。如果这个值很高,说明应用可能存在大量的 I/O 等待或者同步锁竞争。
  • nvcswch/s (non-voluntary context switches)非自愿上下文切换。通常是因为时间片用完,或者被更高优先级的进程抢占。如果这个值很高,说明 CPU 正在被多个活跃进程激烈争抢。

pidstat 的输出中,我们一目了然地看到 nginx 进程(PID 12345)每秒产生了 15 万次自愿上下文切换。这说明 nginx 的 worker 进程在疯狂地等待某个资源,导致它们不断地被挂起和唤醒。

可能的原因

  • 后端服务瓶颈nginx 作为反向代理,后端应用(如 PHP、Java)处理缓慢,导致 nginx worker 大量阻塞在网络 I/O 上。
  • 锁竞争:应用代码中存在设计不当的全局锁,导致大量线程在等待同一个锁。
  • 连接数过多nginx 配置的 worker_connections 过高,而系统资源(如文件描述符)不足。

通过这个线索,开发和运维团队就可以进一步深入到 nginx 的配置和后端应用代码中,找到最终的性能瓶颈。


👽 神秘的内核线程:kworkerksoftirqd

在使用 topps 时,你经常会看到一些以 k 开头的、方括号括起来的进程,如 kworkerksoftirqd。它们是 内核线程 (Kernel Threads),在后台为操作系统执行各种管理任务。

kworker:内核的"临时工"

kworker 是内核工作队列(workqueue)的执行者。内核的各个子系统(如磁盘、网络、定时器)会把一些耗时较长、不能在中断上下文中完成的任务,打包成一个"工作项",扔到工作队列里,然后由 kworker 线程在未来的某个时刻异步地去执行。

命名格式kworker/u<cpu_id>:<worker_id>kworker/<cpu_id>:<worker_id>

  • u 表示这个 kworker 是非绑定的(unbound),可以在多个 CPU 核心间迁移。
  • <cpu_id> 表示它主要在哪个 CPU 上活动。

如果 kworker 的 CPU 使用率很高,通常意味着内核正在忙于处理某些后台任务。你可以通过 perf 工具来追踪 kworker 到底在忙什么。

ksoftirqd:软中断的"清道夫"

当硬件中断(硬中断)发生得过于频繁时,为了避免长时间关中断影响系统响应,内核会将一部分耗时较长的处理工作推迟,变成 软中断 (softirq)

ksoftirqd 就是专门用来处理软中断的内核线程,每个 CPU 核心都有一个。如果软中断的产生速度超过了处理速度,积压的软中断就会由 ksoftirqd 来"兜底"处理。

命名格式ksoftirqd/<cpu_id>

如果你在 top 中看到 si(softirq)或者 ksoftirqd 的 CPU 使用率很高,通常指向以下问题:

  • 网络风暴:网络收发包极其频繁,导致大量的网络软中断。
  • 内核锁竞争:内核中处理软中断的逻辑遇到了锁竞争,导致处理效率下降。

📝 总结与展望

  • 上下文切换是核心成本 :过高的 cs 值是系统"内耗"的明确信号。使用 vmstat 发现问题,再用 pidstat -w 定位到具体进程。
  • 区分自愿与非自愿切换cswch/s 高通常指向 I/O 或锁等待问题;nvcswch/s 高通常指向 CPU 资源不足或争抢激烈。
  • 关注内核线程kworkerksoftirqd 的高 CPU 占用率是内核层面存在瓶颈的线索,通常与 I/O 和网络活动密切相关。

下一篇预告

在本章中,我们学会了如何量化和定位上下文切换带来的性能损耗。我们不仅掌握了 vmstatpidstat 这两个强大的诊断工具,还认识了 kworkerksoftirqd 这两个重要的内核"打工人"。

在下一章,我们将继续深入 CPU 的世界,专门探讨 软中断和硬中断 。我们将揭示 top 命令中 hisi 的真正含义,并学习如何处理由它们引发的性能问题。敬请期待!

相关推荐
安科士andxe6 小时前
深入解析|安科士1.25G CWDM SFP光模块核心技术,破解中长距离传输痛点
服务器·网络·5g
小白同学_C9 小时前
Lab4-Lab: traps && MIT6.1810操作系统工程【持续更新】 _
linux·c/c++·操作系统os
今天只学一颗糖9 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
儒雅的晴天10 小时前
大模型幻觉问题
运维·服务器
通信大师11 小时前
深度解析PCC策略计费控制:核心网产品与应用价值
运维·服务器·网络·5g
不做无法实现的梦~11 小时前
ros2实现路径规划---nav2部分
linux·stm32·嵌入式硬件·机器人·自动驾驶
SQL必知必会12 小时前
SQL 窗口帧:ROWS vs RANGE 深度解析
数据库·sql·性能优化
默|笙13 小时前
【Linux】fd_重定向本质
linux·运维·服务器
叫我龙翔13 小时前
【计网】从零开始掌握序列化 --- JSON实现协议 + 设计 传输\会话\应用 三层结构
服务器·网络·c++·json
陈苏同学13 小时前
[已解决] Solving environment: failed with repodata from current_repodata.json (python其实已经被AutoDL装好了!)
linux·python·conda