跳转至

频繁上下文切换导致sys高

前言

CPU 上下文切换是保证 Linux 系统正常工作的一个核心功能,按照不同场景,可以分为进程上下文切换、线程上下文切换和中断上下文切换。

vmstat 是一个常用的系统性能分析工具,主要用来分析系统的内存使用情况,也常用来分析 CPU 上下文切换和中断的次数。

比如,下面就是一个 vmstat 的使用示例:

# 每隔5秒输出1组数据
$ 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      0 7005360  91564 818900    0    0     0     0   25   33  0  0 100  0  0

我们一起来看这个结果,你可以先试着自己解读每列的含义。在这里,我重点强调下,需要特别关注的四列内容:

  • cs(context switch)是每秒上下文切换的次数。
  • in(interrupt)则是每秒中断的次数。
  • r(Running or Runnable)是就绪队列的长度,也就是正在运行和等待 CPU 的进程数。
  • b(Blocked)则是处于不可中断睡眠状态的进程数。

可以看到,这个例子中的上下文切换次数 cs 是 33 次,而系统中断次数 in 则是 25 次,而就绪队列长度 r 和不可中断状态进程数 b 都是 0。

vmstat 只给出了系统总体的上下文切换情况,要想查看每个进程的详细情况,就需要使用我们前面提到过的 pidstat 了。给它加上 -w 选项,你就可以查看每个进程上下文切换的情况了。

比如说:

# 每隔5秒输出1组数据
$ pidstat -w 5
Linux 4.15.0 (ubuntu)  09/23/18  _x86_64_  (2 CPU)

08:18:26      UID       PID   cswch/s nvcswch/s  Command
08:18:31        0         1      0.20      0.00  systemd
08:18:31        0         8      5.40      0.00  rcu_sched
...

这个结果中有两列内容是重点关注对象。一个是 cswch ,表示每秒自愿上下文切换(voluntary context switches)的次数,另一个则是 nvcswch ,表示每秒非自愿上下文切换(non voluntary context switches)的次数。

这两个概念一定要牢牢记住,因为它们意味着不同的性能问题:

  • 所谓自愿上下文切换,是指进程无法获取所需资源,导致的上下文切换。比如说, I/O、内存等系统资源不足时,就会发生自愿上下文切换。
  • 而非自愿上下文切换,则是指进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。比如说,大量进程都在争抢 CPU 时,就容易发生非自愿上下文切换。

使用 sysbench 来模拟系统多线程调度切换的情况

  • 机器配置:4 CPU,8GB 内存
  • 预先安装 sysbench 和 sysstat 包, yum install sysbench sysstat

终端1:

# 以10个线程运行5分钟的基准测试,模拟多线程切换的问题
[root@centos ~]# sysbench --threads=10 --max-time=300 threads run

终端2:

[root@centos ~]# vmstat -wt 1
procs -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu-------- -----timestamp-----
 r  b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st                 CST
 0  0            0      5903872       257528      1504408    0    0     0    12 1588 3094   0   0 100   0   0 2022-05-20 03:24:36
 0  0            0      5903872       257528      1504408    0    0     0     0 1543 2985   0   0 100   0   0 2022-05-20 03:24:37
 5  0            0      5902564       257528      1504408    0    0     0     0 15689 229995   6  12  82   0   0 2022-05-20 03:24:38
 4  0            0      5902200       257528      1504408    0    0     0     0 39276 959684  35  57   8   0   0 2022-05-20 03:24:39
 4  0            0      5902200       257528      1504408    0    0     0     0 40576 1106457  34  57   9   0   0 2022-05-20 03:24:40
 4  0            0      5902200       257528      1504408    0    0     0     0 39332 1016539  35  57   9   0   0 2022-05-20 03:24:41
 6  0            0      5902200       257528      1504408    0    0     0    12 40197 980060  35  56   9   0   0 2022-05-20 03:24:42
 7  0            0      5902200       257528      1504408    0    0     0     0 39096 1054514  36  56   9   0   0 2022-05-20 03:24:43
 6  0            0      5902200       257528      1504408    0    0     0     0 38769 1049458  35  57   9   0   0 2022-05-20 03:24:44
 6  0            0      5902200       257528      1504408    0    0     0     0 34921 972992  36  56   8   0   0 2022-05-20 03:24:45

你应该可以发现,cs 列的上下文切换次数从之前的 2000多 骤然上升到了 110 万。

同时,注意观察其他几个指标:

r 列:就绪队列的长度已经到了 7,远远超过了系统 CPU 的个数 4,所以肯定会有大量的 CPU 竞争。

us(user)和 sy(system)列:这两列的 CPU 使用率加起来上升到了 99%,其中系统 CPU 使用率,也就是 sy 列高达 57%,说明 CPU 主要是被内核占用了。

in 列:中断次数也上升到了 4 万左右,说明中断处理也是个潜在的问题。综合这几个指标,系统的就绪队列过长,也就是正在运行和等待 CPU 的进程数过多,导致了大量的上下文切换,而上下文切换又导致了系统 CPU 的占用率升高。那么到底是什么进程导致了这些问题呢?

终端3:

$ pidstat -w -u -t 1
Average:      UID      TGID       TID    %usr %system  %guest    %CPU   CPU  Command
Average:        0      1188         -    0.30    0.60    0.00    0.90     -  rshim
Average:        0         -      1188    0.30    0.60    0.00    0.90     -  |__rshim
Average:        0      8916         -    0.20    0.30    0.00    0.50     -  YDService
Average:        0         -      8919    0.00    0.10    0.00    0.10     -  |__YDService
Average:        0         -     24019    0.10    0.00    0.00    0.10     -  |__YDService
Average:        0      8926         -    0.00    0.10    0.00    0.10     -  YDLive
Average:        0     28048         -    0.00    0.10    0.00    0.10     -  kworker/3:1
Average:        0         -     28048    0.00    0.10    0.00    0.10     -  |__kworker/3:1
Average:        0     28408         -  100.00  100.00    0.00  100.00     -  sysbench
Average:        0         -     28409   14.49   24.28    0.00   38.76     -  |__sysbench
Average:        0         -     28410   14.79   23.88    0.00   38.66     -  |__sysbench
Average:        0         -     28411   14.99   23.78    0.00   38.76     -  |__sysbench
Average:        0         -     28412   14.39   22.48    0.00   36.86     -  |__sysbench
Average:        0         -     28413   14.39   23.58    0.00   37.96     -  |__sysbench
Average:        0         -     28414   14.29   23.88    0.00   38.16     -  |__sysbench
Average:        0         -     28415   14.89   23.88    0.00   38.76     -  |__sysbench
Average:        0         -     28416   14.29   24.38    0.00   38.66     -  |__sysbench
Average:        0         -     28417   13.99   23.28    0.00   37.26     -  |__sysbench
Average:        0         -     28418   15.18   24.18    0.00   39.36     -  |__sysbench
Average:        0     28432         -    0.40    0.90    0.00    1.30     -  pidstat
Average:        0         -     28432    0.40    0.90    0.00    1.30     -  |__pidstat

Average:      UID      TGID       TID   cswch/s nvcswch/s  Command
Average:        0         6         -      4.70      0.00  ksoftirqd/0
Average:        0         -         6      4.70      0.00  |__ksoftirqd/0
Average:        0         9         -     15.08      0.00  rcu_sched
Average:        0         -         9     15.08      0.00  |__rcu_sched
Average:        0         -     28048      3.40      0.00  |__kworker/3:1
Average:        0     28184         -      0.10      0.00  bash
Average:        0         -     28184      0.10      0.00  |__bash
Average:       89     28341         -      0.10      0.00  pickup
Average:       89         -     28341      0.10      0.00  |__pickup
Average:        0     28405         -      0.50      0.00  kworker/3:0
Average:        0         -     28405      0.50      0.00  |__kworker/3:0
Average:        0         -     28409  25990.21  65447.15  |__sysbench
Average:        0         -     28410  25781.02  66814.89  |__sysbench
Average:        0         -     28411  25511.19  64601.10  |__sysbench
Average:        0         -     28412  26991.11  62141.86  |__sysbench
Average:        0         -     28413  26430.07  65891.11  |__sysbench
Average:        0         -     28414  26558.14  64489.21  |__sysbench
Average:        0         -     28415  26396.90  63810.69  |__sysbench
Average:        0         -     28416  26074.23  63148.05  |__sysbench
Average:        0         -     28417  26524.78  67813.79  |__sysbench
Average:        0         -     28418  25621.38  69440.06  |__sysbench
Average:        0     28432         -      1.00      0.90  pidstat
Average:        0         -     28432      1.00      0.90  |__pidstat

从 pidstat 的输出你可以发现,CPU 使用率的升高果然是 sysbench 导致的,它的 CPU 使用率已经达到了 100%。上下文切换罪魁祸首,还是过多的 sysbench 线程。

终端4:

[root@VM-0-16-centos ~]# watch -n 1 -d cat /proc/interrupts
Every 1.0s: cat /proc/interrupts                                                                                                                                                    Fri May 20 04:03:32 2022

           CPU0       CPU1   CPU2       CPU3
  0:         88          0          0          0   IO-APIC-edge      timer
  1:         10          0          0          0   IO-APIC-edge      i8042
  4:        387          0          0          0   IO-APIC-edge      serial
  6:          3          0          0          0   IO-APIC-edge      floppy
  8:          0          0          0          0   IO-APIC-edge      rtc0
  9:          0          0          0          0   IO-APIC-fasteoi   acpi
 11:          0          0          0          0   IO-APIC-fasteoi   uhci_hcd:usb1, virtio3
 12:         15          0          0          0   IO-APIC-edge      i8042
 14:    5016380          0          0          0   IO-APIC-edge      ata_piix
 15:          0          0          0          0   IO-APIC-edge      ata_piix
 24:          0          0          0          0   PCI-MSI-edge      virtio1-config
 25:    2810303          0          0          0   PCI-MSI-edge      virtio1-req.0
 26:         10          0          0          0   PCI-MSI-edge      virtio0-config
 27:    4356739          0          0          0   PCI-MSI-edge      virtio0-input.0
 28:          1          0          0          0   PCI-MSI-edge      virtio0-output.0
 29:          1    1136553          0          0   PCI-MSI-edge      virtio0-input.1
 30:          1          0          0          0   PCI-MSI-edge      virtio0-output.1
 31:          0          0          0          0   PCI-MSI-edge      virtio2-config
 32:        343          0          0          0   PCI-MSI-edge      virtio2-req.0
NMI:          0          0          0          0   Non-maskable interrupts
LOC: 1782951044 1786075743 1867019728 1796389760   Local timer interrupts
SPU:          0          0          0          0   Spurious interrupts
PMI:          0          0          0          0   Performance monitoring interrupts
IWI:    1957371    1944591    1726299    1928079   IRQ work interrupts
RTR:          0          0          0          0   APIC ICR read retries
RES:  201178492  201387163  198141978  204827265   Rescheduling interrupts
CAL:     157144     725105    2071436    2165417   Function call interrupts
TLB:    2732341    2720775    2684270    2657260   TLB shootdowns
TRM:          0          0          0          0   Thermal event interrupts
THR:          0          0          0          0   Threshold APIC interrupts
DFR:          0          0          0          0   Deferred Error APIC interrupts
MCE:          0          0          0          0   Machine check exceptions
MCP:      17122      17122  17122      17122   Machine check polls
ERR:          0
MIS:          0
PIN:          0          0          0          0   Posted-interrupt notification event
NPI:          0          0          0          0   Nested posted-interrupt event
PIW:          0          0          0          0   Posted-interrupt wakeup event

前面在观察系统指标时,除了上下文切换频率骤然升高,还有一个指标也有很大的变化。是的,正是中断次数。中断次数也上升到了 4 万,但到底是什么类型的中断上升了。既然是中断,它只发生在内核态,而 pidstat 只是一个进程的性能分析工具,并不提供任何关于中断的详细信息,怎样才能知道中断发生的类型呢?那就是从 /proc/interrupts 这个只读文件中读取。/proc 实际上是 Linux 的一个虚拟文件系统,用于内核空间与用户空间之间的通信。/proc/interrupts 就是这种通信机制的一部分,提供了一个只读的中断使用情况。

观察一段时间,你可以发现,变化速度最快的是重调度中断(RES),这个中断类型表示,唤醒空闲状态的 CPU 来调度新的任务运行。这是多处理器系统(SMP)中,调度器用来分散任务到不同 CPU 的机制,通常也被称为处理器间中断(Inter-Processor Interrupts,IPI)。所以,这里的中断升高还是因为过多任务的调度问题,跟前面上下文切换次数的分析结果是一致的。

终端5:

[root@VM-0-16-centos ~]# cat /proc/interrupts ; sleep 1s ;cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3       
  0:         88          0          0          0   IO-APIC-edge      timer
  1:         10          0          0          0   IO-APIC-edge      i8042
  4:        387          0          0          0   IO-APIC-edge      serial
  6:          3          0          0          0   IO-APIC-edge      floppy
  8:          0          0          0          0   IO-APIC-edge      rtc0
  9:          0          0          0          0   IO-APIC-fasteoi   acpi
 11:          0          0          0          0   IO-APIC-fasteoi   uhci_hcd:usb1, virtio3
 12:         15          0          0          0   IO-APIC-edge      i8042
 14:    5016252          0          0          0   IO-APIC-edge      ata_piix
 15:          0          0          0          0   IO-APIC-edge      ata_piix
 24:          0          0          0          0   PCI-MSI-edge      virtio1-config
 25:    2810206          0          0          0   PCI-MSI-edge      virtio1-req.0
 26:         10          0          0          0   PCI-MSI-edge      virtio0-config
 27:    4356352          0          0          0   PCI-MSI-edge      virtio0-input.0
 28:          1          0          0          0   PCI-MSI-edge      virtio0-output.0
 29:          1    1136531          0          0   PCI-MSI-edge      virtio0-input.1
 30:          1          0          0          0   PCI-MSI-edge      virtio0-output.1
 31:          0          0          0          0   PCI-MSI-edge      virtio2-config
 32:        343          0          0          0   PCI-MSI-edge      virtio2-req.0
NMI:          0          0          0          0   Non-maskable interrupts
LOC: 1782787996 1785909076 1866856961 1796226528   Local timer interrupts
SPU:          0          0          0          0   Spurious interrupts
PMI:          0          0          0          0   Performance monitoring interrupts
IWI:    1957329    1944532    1726246    1928026   IRQ work interrupts
RTR:          0          0          0          0   APIC ICR read retries
RES:  199987693  200087749  197045111  203640135   Rescheduling interrupts
CAL:     157140     725022    2071329    2165308   Function call interrupts
TLB:    2732319    2720756    2684264    2657244   TLB shootdowns
TRM:          0          0          0          0   Thermal event interrupts
THR:          0          0          0          0   Threshold APIC interrupts
DFR:          0          0          0          0   Deferred Error APIC interrupts
MCE:          0          0          0          0   Machine check exceptions
MCP:      17122      17122      17122      17122   Machine check polls
ERR:          0
MIS:          0
PIN:          0          0          0          0   Posted-interrupt notification event
NPI:          0          0          0          0   Nested posted-interrupt event
PIW:          0          0          0          0   Posted-interrupt wakeup event
           CPU0       CPU1       CPU2       CPU3       
  0:         88          0          0          0   IO-APIC-edge      timer
  1:         10          0          0          0   IO-APIC-edge      i8042
  4:        387          0          0          0   IO-APIC-edge      serial
  6:          3          0          0          0   IO-APIC-edge      floppy
  8:          0          0          0          0   IO-APIC-edge      rtc0
  9:          0          0          0          0   IO-APIC-fasteoi   acpi
 11:          0          0          0          0   IO-APIC-fasteoi   uhci_hcd:usb1, virtio3
 12:         15          0          0          0   IO-APIC-edge      i8042
 14:    5016254          0          0          0   IO-APIC-edge      ata_piix
 15:          0          0          0          0   IO-APIC-edge      ata_piix
 24:          0          0          0          0   PCI-MSI-edge      virtio1-config
 25:    2810206          0          0          0   PCI-MSI-edge      virtio1-req.0
 26:         10          0          0          0   PCI-MSI-edge      virtio0-config
 27:    4356355          0          0          0   PCI-MSI-edge      virtio0-input.0
 28:          1          0          0          0   PCI-MSI-edge      virtio0-output.0
 29:          1    1136531          0          0   PCI-MSI-edge      virtio0-input.1
 30:          1          0          0          0   PCI-MSI-edge      virtio0-output.1
 31:          0          0          0          0   PCI-MSI-edge      virtio2-config
 32:        343          0          0          0   PCI-MSI-edge      virtio2-req.0
NMI:          0          0          0          0   Non-maskable interrupts
LOC: 1782789324 1785910399 1866858200 1796227706   Local timer interrupts
SPU:          0          0          0          0   Spurious interrupts
PMI:          0          0          0          0   Performance monitoring interrupts
IWI:    1957329    1944533    1726247    1928026   IRQ work interrupts
RTR:          0          0          0          0   APIC ICR read retries
RES:  199997549  200099900  197051402  203648151   Rescheduling interrupts
CAL:     157140     725022    2071329    2165308   Function call interrupts
TLB:    2732319    2720756    2684264    2657244   TLB shootdowns
TRM:          0          0          0          0   Thermal event interrupts
THR:          0          0          0          0   Threshold APIC interrupts
DFR:          0          0          0          0   Deferred Error APIC interrupts
MCE:          0          0          0          0   Machine check exceptions
MCP:      17122      17122      17122      17122   Machine check polls
ERR:          0
MIS:          0
PIN:          0          0          0          0   Posted-interrupt notification event
NPI:          0          0          0          0   Nested posted-interrupt event
PIW:          0          0          0          0   Posted-interrupt wakeup event

从1秒钟的数量变化来计算,RES在4个CPU中断次数变化差 的总和 差不多是 4W

所以,这里的中断升高还是因为过多任务的调度问题,跟前面上下文切换次数的分析结果是一致的。

  • 自愿上下文切换变多了,说明进程都在等待资源,有可能发生了 I/O 等其他问题;
  • 非自愿上下文切换变多了,说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 的确成了瓶颈;
  • 中断次数变多了,说明 CPU 被中断处理程序占用,还需要通过查看 /proc/interrupts 文件来分析具体的中断类型。