这篇文章上次修改于 899 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

1 CPU 上下文切换

多进程竞争 CPU 时没有真正运行,但仍然会导致系统负载升高,原因是过多的 CPU 上下文切换会把 CPU 时间消耗在寄存器、内核栈及虚拟内存等数据等保存和恢复上,从而缩短进程正真运行的时间,导致系统等整体性能大幅度下降。

CPU 上下文切换:把前一个任务的 CPU 寄存器和程序计数器保存起来,然后加载新任务的上下文到这些寄存器和程序计数器。

程序计数器:用了存储 CPU 正在执行的指令位置。

CPU 上下文切换场景:

  • 系统调用。用户态到内核态时,需要通过系统调用完成,会切换特权模式。系统调用过程中是在同一个进程中的。一次系统调用会发生两次 CPU 上下文切换:CPU 寄存器更新为内核态指令的新位置,系统调用结束后,CPU 寄存器需要恢复原来保存的用户态。
  • 进程上下文切换。进程的切换只发生在内核态。进程上下文包括了虚拟内存、栈、全局变量等用户空间的资源,还包括内核堆栈、寄存器等内核空间的状态。切换过程:将本进程等虚拟内存、栈等保存下来;加载新进程的内核态,并刷新新进程等虚拟内存和用户栈。当虚拟内存更新后,TLB 也会刷新(管理虚拟内存到物理内存到映射关系),内存访问随之变慢。只有进程调度到时候,才需要切换上下文。
  • 线程上下文切换。当进程拥有多个线程时,这些线程会共享虚拟内存和全局变量等资源,上下文切换时无需修改,但需要修改线程自己的私有数据,如栈和寄存器等。
  • 中断上下文切换。为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。中断上下文切换不涉及进程的用户态。中断上下文只包括内核中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。

2 查看 CPU 上下文切换

vmstat 用来分析系统的内存使用情况、CPU 上下文切换和中断次数。

# 5 表示每隔 5s 输出一组数据
$ vmstat 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0 152576 6399908 4478320 52046368    0    0    21    45    1    0  6  0 94  0  0
  • r Running or Runnable 就绪队列长度。
  • b Blocked 处于不可中断睡眠状态等进程数。
  • in Interrupt 每秒中断的次数
  • cs Context switch 每秒上下文切换次数

查看每个进程的上下文切换情况:

$ pidstat -w 5
Linux 4.15.0-142-generic (ubuntu)     2021年09月25日     _x86_64_    (16 CPU)

17时28分14秒   UID       PID   cswch/s nvcswch/s  Command
17时28分19秒     0         1      0.20      0.00  systemd
17时28分19秒     0         8     14.97      0.00  rcu_sched
17时28分19秒     0        11      0.40      0.00  watchdog/0
17时28分19秒     0        14      0.40      0.00  watchdog/
  • cswch 每秒自愿上下文切换的次数。指进程无法获取所需的资源,导致的上下文切换。比如 I/O、内存等系统资源不足。
  • nvcswch 每秒非自愿上下文切换的次数。进程由于时间片到期等原因,被系统强制调度,进而发生的上下文切换。比如大量进程争抢 CPU 时。

3 案例分析

模拟系统多线程调度瓶颈:

$ sysbench --num-threads=20 --max-time=300 --max-requests=1000000 --test=threads run

系统 cs 骤增到 290 万,in 也增加到了 1 万:

$ 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
11  0 152576 6388820 4479568 52056116    0    0    21    45    1    0  6  0 94  0  0
14  0 152576 6388564 4479568 52056116    0    0     0     0 9900 2909247 13 46 42  0  0
 8  0 152576 6388564 4479568 52056116    0    0     0     0 9641 2906662 13 44 43  0  0
13  0 152576 6388564 4479568 52056116    0    0     0     0 9759 2909695 13 44 43  0  0

查看进程切换和 CPU 使用:

# -w 表示输出进程切换指标,-u 表示输出 CPU 使用指标
$ pidstat -w -u 1
$ pidstat -w -u 1
Linux 4.15.0-142-generic (ubuntu)     2021年09月25日     _x86_64_    (16 CPU)

17时56分39秒   UID       PID    %usr %system  %guest    %CPU   CPU  Command
17时56分40秒  1000     13311  212.87  762.38    0.00  975.25     6  sysbench

17时56分39秒   UID       PID   cswch/s nvcswch/s  Command
17时56分40秒     0         7     12.87      0.00  ksoftirqd/0
17时56分40秒     0         8     45.54      0.00  rcu_sched
17时56分40秒     0        16      1.98      0.00  ksoftirqd/1

虽然 CPU 利用率最高的是 sysbench,但 cs 最高的并不是 sysbench,且数量较小。需要加上参数 -t 显示线程的情况:

$ pidstat -wt 5
Linux 4.15.0-142-generic (ubuntu)     2021年09月25日     _x86_64_    (16 CPU)

18时01分16秒   UID      TGID       TID   cswch/s nvcswch/s  Command
18时01分21秒  1000         -     13404  75772.31   3438.45  |__sysbench
18时01分21秒  1000         -     13405  76021.71   3480.08  |__sysbench
18时01分21秒  1000         -     13406  76695.82   3514.34  |__sysbench
18时01分21秒  1000         -     13407  76080.28   3487.45  |__sysbench
18时01分21秒  1000         -     13408  76257.37   3513.75  |__sysbench
18时01分21秒  1000         -     13409  76097.61   3487.65  |__sysbench
18时01分21秒  1000         -     13410  76530.28   3555.98  |__sysbench
18时01分21秒  1000         -     13411  75208.37   3452.79  |__sysbench
18时01分21秒  1000         -     13412  76037.25   3507.57  |__sysbench
18时01分21秒  1000         -     13413  76456.77   3538.05  |__sysbench
18时01分21秒  1000         -     13414  75866.14   3533.47  |__sysbench
18时01分21秒  1000         -     13415  75675.50   3423.71  |__sysbench
18时01分21秒  1000         -     13416  75554.78   3496.22  |__sysbench
18时01分21秒  1000         -     13417  75558.17   3468.13  |__sysbench
18时01分21秒  1000         -     13418  75677.49   3436.85  |__sysbench
18时01分21秒  1000         -     13419  75785.06   3496.41  |__sysbench
18时01分21秒  1000         -     13420  76172.31   3557.77  |__sysbench
18时01分21秒  1000         -     13421  74532.87   3437.25  |__sysbench
18时01分21秒  1000         -     13422  75826.29   3452.19  |__sysbench
18时01分21秒  1000         -     13423  77366.53   3623.90  |__sysbench

果然,sysbench 线程的上下文切换次数最高,说明上下文切换的原因是 sysbench 中过多的线程数导致的。

参考

《Linux性能优化实战》倪鹏飞.