Linux 性能优化实战(倪朋飞)---CPU 上下文切换

CPU 上下文:CPU 寄存器和程序计数器中的内容。
CPU 寄存器:CPU 内置的容量小、但速度极快的内存。
程序计数器:存储 CPU 正在执行的指令位置、或即将执行的下一条指令位置。

CPU 上下文切换:先把前一个任务的 CPU 上下文保存在系统内核中,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

根据任务的不同,CPU 的上下文可分为进程上下文切换、线程上下文切换和中断上下文切换。

系统调用:在同一个进程中进行,称为特权模式切换,而不是上下文切换,但 CPU 的上下文切换无法避免。先保存 CPU 寄存器里原来用户态的指令位置,接着,为执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置,最后跳转到内核态运行内核任务。系统调用结束后,CPU 寄存器恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以,一次系统调用过程共发生两次 CPU 上下文切换。

进程上下文切换:从一个进程切换到另一个进程运行。因为进程由内核态管理和调度,所以进程切换只能发生在内核态。进程的上下文切换比系统调用多一步:在保存当前进程的内核状态和 CPU 寄存器前,需要先把该进程的虚拟内存、栈等保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。

线程上下文切换分两种情况:

  1. 前后两个线程属于不同进程,切换过程同进程上下文切换。
  2. 前后两个线程属于同一个进程,切换中虚拟内存中的资源保持不动,只需要切换线程的私有数据,如寄存器、栈等不共享的数据。

中断上下文切换:中断会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。在打断其它进程时,需要将进程的当前状态保存下来,这样在中断结束后,进程仍然可以从原来的状态恢复运行。与进程上下文不同,中断上下文切换不涉及到进程的用户态。中断上下文只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。对于同一个 CPU,中断处理比进程拥有更高的优先级。

查看系统上下文切换情况

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

# 每隔 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  1      0 457696 104524 653672    0    0  1013   665  658 1388  6  6 63 25  0
 1  1      0 451644 105400 654112    0    0    19  1402 1613 3205  6  5 81  9  0
 1  0      0 402504 106272 654636    0    0    18  1452 1664 2957 28  6 57 10  0

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

查看每个进程的详细情况:

# 每隔 5 秒输出一组数据
# -w 表示输出进程切换指标
$ pidstat -w 5
Linux 4.13.0-45-generic (yjp-VirtualBox) 	2019年04月24日 	_x86_64_	(2 CPU)

21时40分24秒   UID       PID   cswch/s nvcswch/s  Command
21时40分29秒     0         1      2.00      0.00  systemd
21时40分29秒     0         5      1.40      0.00  kworker/u4:0
21时40分29秒     0         7      2.59      0.20  ksoftirqd/0
21时40分29秒     0         8     62.87      0.00  rcu_sched
21时40分29秒     0        11      0.20      0.00  watchdog/0
21时40分29秒     0        14      0.20      0.00  watchdog/1
21时40分29秒     0        16      8.78      1.20  ksoftirqd/1

cswch(voluntary context switches):每秒自愿上下文切换的次数。
nvcsswch(non voluntary context switches):每秒非自愿上下文切换的次数。

自愿上下文切换:进程无法获取所需资源导致,如 I/O、内存等系统系统资源不足。
非自愿上下文切换:进程由于时间片已到等原因,被系统强制调度导致。

案例分析

准备

sysbench 一般用来评估不同系统参数下的数据库负载情况。本例中用来模拟上下文切换过多的问题。

操作和分析

当前空闲系统的上下文切换次数:

# 间隔 1 秒后输出 1 组数据
$ vmstat 1 1
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   2048 189020 298080 692304    0    0   143   607  569 1176  4  3 84  9  0

在第一个终端运行 sysbench,模拟系统多线程调度的瓶颈:

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

在第二个终端运行 vmstat,观察上下文切换情况:

# 每隔 1 秒输出 1 组数据(需要 Ctrl+C 才结束)
$ 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
 4  0      0 101840 187396 852660    0    0   114    56  307 4111 11  2 84  2  0
12  0      0 101220 187396 852660    0    0     0     0 9091 223694 20 71  9  0  0

cs 从 1176 上升到 223694。同时观察其它指标:
r 列:就绪队列的长度达到了 12,远超 CPU 的个数 2。
us 和 sy 列:加起来高达 91%,其中 sy 高达71%,说明 CPU 主要被内核占用了。
in 列:中断上升到了 1 万左右,说明中断处理也是个潜在问题。

可见,系统的就绪队列过长导致了大量的上下文切换,进而导致了系统 CPU 的占用率升高。

查找导致这些问题的进程:

# 每隔 1 秒输出 1 组数据(需要 Ctrl+C 才结束)
# -w 参数表示输出进程切换指标,而 -u 参数则表示输出 CPU 使用指标
$ pidstat -w -u 1
Linux 4.13.0-45-generic (yjp-VirtualBox) 	2019年04月25日 	_x86_64_	(2 CPU)

20时42分05秒   UID       PID    %usr %system  %guest    %CPU   CPU  Command
20时42分06秒     0      1407    0.91    0.00    0.00    0.91     1  Xorg
20时42分06秒  1000      4781   30.91  131.82    0.00  162.73     0  sysbench
20时42分06秒  1000      4792    0.91    0.91    0.00    1.82     1  pidstat

20时42分06秒   UID       PID   cswch/s nvcswch/s  Command
20时42分07秒     0      1407    166.35      1.92  Xorg
20时42分07秒  1000      4471     55.77     26.92  gnome-terminal-
20时42分07秒     0      4719    230.77      0.00  kworker/u4:0
20时42分07秒  1000      4792      0.96    282.69  pidstat

可见 CPU 使用率的升高是由 sysbench 导致的。
但上下文的切换来自其它进程,包括自愿上下文切换频率最高的 kworker 和非自愿上下文切换最高的 pidstat。
但 pidstat 输出的上下文切换次数远小于 vmstat 中的 22 万,是因为 pidstat 默认显示进程的指标,加上 -t 可显示线程的指标:

# 每隔 1 秒输出一组数据(需要 Ctrl+C 才结束)
# -wt 参数表示输出线程的上下文切换指标
$ pidstat -wt 1
Linux 4.13.0-45-generic (yjp-VirtualBox) 	2019年04月25日 	_x86_64_	(2 CPU)

20时54分13秒   UID      TGID       TID   cswch/s nvcswch/s  Command
Average:     1000         -      4853   3561.02  16435.59  |__sysbench
Average:     1000         -      4854   2583.05  16700.85  |__sysbench
Average:     1000         -      4855   3406.78  13905.51  |__sysbench
Average:     1000         -      4856   1715.25  18775.00  |__sysbench
Average:     1000         -      4857   2139.41  19266.10  |__sysbench
Average:     1000         -      4858   2279.66  26106.78  |__sysbench
Average:     1000         -      4859   2393.22  26106.78  |__sysbench
Average:     1000         -      4860   1585.59  27501.27  |__sysbench
Average:     1000         -      4861   3755.93  29716.10  |__sysbench
Average:     1000         -      4862   2482.63  20950.42  |__sysbench

可见上下切换次数较多是因为过多的 sysbench 线程。

但是,之前在 vmstat 中看到中断次数也很多,而 pidstat 无法提供中断信息,需查看 /proc/interrupts 这个只读文件。

# -d 参数表示高亮显示变化的区域
$ watch -d cat /proc/interrupts
           CPU0       CPU1
...
RES:    1630464    1517947   Rescheduling interrupts
...

变化最快的是重调度中断(RES),该中断类型表示,唤醒空闲状态的 CPU 来调度新的任务运行。
这是多处理器系统中,调度器用来分散任务到不同 CPU 的机制,通常也被称为处理器间中断。
可见,中断的升高还是因为过多的任务调度问题。

当上下文次数切换过多时,做具体分析:

  • 自愿上下文切换过多,说明进程都在等待自愿,有可能发生了 I/O 等其它问题。
  • 非自愿上下文切换过多,说明进程都在被强制调度,说明 CPU 为瓶颈。
  • 中断次数较多,需通过查看 /proc/interrupts 分析具体中断类型。

参考
倪朋飞. Linux 性能优化实战.

你可能感兴趣的:(Linux,性能优化实战(倪朋飞)笔记)