CPU 上下文:CPU 寄存器和程序计数器中的内容。
CPU 寄存器:CPU 内置的容量小、但速度极快的内存。
程序计数器:存储 CPU 正在执行的指令位置、或即将执行的下一条指令位置。
CPU 上下文切换:先把前一个任务的 CPU 上下文保存在系统内核中,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
根据任务的不同,CPU 的上下文可分为进程上下文切换、线程上下文切换和中断上下文切换。
系统调用:在同一个进程中进行,称为特权模式切换,而不是上下文切换,但 CPU 的上下文切换无法避免。先保存 CPU 寄存器里原来用户态的指令位置,接着,为执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置,最后跳转到内核态运行内核任务。系统调用结束后,CPU 寄存器恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以,一次系统调用过程共发生两次 CPU 上下文切换。
进程上下文切换:从一个进程切换到另一个进程运行。因为进程由内核态管理和调度,所以进程切换只能发生在内核态。进程的上下文切换比系统调用多一步:在保存当前进程的内核状态和 CPU 寄存器前,需要先把该进程的虚拟内存、栈等保存下来;而加载了下一进程的内核态后,还需要刷新进程的虚拟内存和用户栈。
线程上下文切换分两种情况:
中断上下文切换:中断会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。在打断其它进程时,需要将进程的当前状态保存下来,这样在中断结束后,进程仍然可以从原来的状态恢复运行。与进程上下文不同,中断上下文切换不涉及到进程的用户态。中断上下文只包括内核态中断服务程序执行所必需的状态,包括 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 的机制,通常也被称为处理器间中断。
可见,中断的升高还是因为过多的任务调度问题。
当上下文次数切换过多时,做具体分析:
参考
倪朋飞. Linux 性能优化实战.