多任务的实施依赖于CPU上下文的切换,其中就要理解CPU寄存器(CPU Register)和程序计数器(Program Counter)。前者是CPU内置的容量小、速度极快的内存;程序计数器保存正在执行的指令位置,或即将执行的下一条指令的位置,它们都是CPU运行任何任务前,必须的依赖环境,因此被叫做CPU上下文切换。过程大致如下:
首先,保存前一任务的CPU上下文(CPU寄存器和程序计数器)
然后,加载更新新任务的CPU上下文到CPU寄存器和程序计数器
最后,跳到程序计数器所指的新位置,运行新的任务
根据任务不同,可以分为进程上下文切换、线程上下文切换、中断上下文切换
进程上下文切换,是指从一个进程切换到另一个进程,进程由内核管理和调度,进程切换只能发生在内核态。进程在用户态运行称为进程的用户态,陷入内核运行时,称为进程的内核态。但是从用户态转变为内核态,需要系统调用(system call ).一次系统调用发生两次CPU上下文的切换:
CPU寄存器和程序计数器先保存用户态的CPU上下文,接着为了执行内核态代码,更新加载内核态指令的位置,之后跳到内核态执行内核任务;
系统调用结束后,CPU寄存器需要恢复到原来保存的用户态,之后切换到用户空间,继续执行进程。
注意的是,系统调用不涉及到虚拟内存等用户态的资源,也不切换进程。
触发进程上下文切换的场景:
(a)CPU时间分为一段段的时间片,公平分配给各进程,当某进程的时间片耗尽,就会被系统挂起,切换到另一进程
(b)进程在资源不足时(内存不足),要等到资源满足时才能运行,这时进程也会被挂起,并由系统调度其他进程运行
(c)当进程遇到睡眠函数sleep这样的方法时,会主动挂起,切换到其他进程
(d)当遇到高优先级进程时,为保证高优先级的进程运行,当前进程会被挂起,由高优先级进程运行
(e)发生硬件中断时,CPU上的进程会被中断挂起,转而执行内核中的中断服务程序
线程上下文切换
线程是任务调度的最小单位,进程是资源分配的最小单位,实质是:内核中的任务调度,实际上的调度对象是线程;进程则是为线程提供虚拟内存、全局变量等资源。也可以这样理解进程和线程:
(1)当进程只有一个线程时,可以认为进程就等于线程
(2)当进程有多个线程时,这些线程共享相同的虚拟内存、全局变量等资源,这些资源在上下文切换时不需要修改
(3)线程也有自己私有数据,如栈和寄存器等,这些私有数据在上下文切换时是需要保存的
线程上下文切换分为两种情况:(1)前后两个线程属于不同的进程,由于资源不共享,此时的线程切换就和进程切换一样
(2)前后两个线程属于相同的进程,由于虚拟内存等资源共享,切换时,只切换线程的私有数据等不共享数据。
可以看到同进程的不同线程切换消耗的资源较小,效率高
中断上下文切换
为了快速响应硬件事件,中断处理会打断进程的正常调度和执行,转而调用中断服务程序,响应设备事件。
和进程上下文切换不同,中断上下文切换不涉及进程的用户态,它只包括内核态中断服务程序执行所必需的状态,包括CPU寄存器、内核堆栈、硬件中断参数等。
对同一CPU来说,中断处理比进程拥有更高的优先级;和进程切换一样,中断切换也消耗CPU,切换次数过多也消耗大量的CPU,甚至会影响系统的整体性能。
小结
不管哪种场景导致的上下文切换,我们都应该知道:
(1)CPU上下文切换,是保证Linux系统多任务处理的核心功能之一,一般情况下不用我们关注
(2)过多的上下文切换,会把CPU时间消耗在寄存器、虚拟内存等数据的保存和恢复上,缩短了进程真正运行时间,导致系统整体性能下降。
案例分析:
vmstat主要用来分析系统的内存情况,也常用来分析CPU上下文切换和中断的次数
root@andy:~# 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 0 2437540 97108 956224 0 0 161 72 54 58 1 2 92 5 0
注释
Procs
r: The number of runnable processes (running or waiting for run time).
b: The number of processes in uninterruptible sleep.
IO
bi: Blocks received from a block device (blocks/s).
bo: Blocks sent to a block device (blocks/s).
System
in: The number of interrupts per second, including the clock.
cs: The number of context switches per second.
从上面的例子可以看到系统上下文切换为58次,系统中断为54次;运行或等待运行的进程r为0,不可中断的睡眠进程b为0
vmstat只能看到系统总体上下文切换情况,查看各进程的详细情况,需要前面使用的pidstat命令-w就能详细查看
root@andy:~# pidstat -w
Linux 4.18.0-12-generic (andy) 05/20/20 _x86_64_ (4 CPU)
14:32:52 UID PID cswch/s nvcswch/s Command
14:32:52 0 1 5.15 5.67 systemd
14:32:52 0 2 0.64 0.00 kthreadd
14:32:52 0 3 0.00 0.00 rcu_gp
14:32:52 0 9 1.37 0.02 ksoftirqd/0
14:32:52 0 10 33.00 0.03 rcu_sched
....................................
-w Report task switching activity (kernels 2.6.23 and later only)
cswch/s
Total number of voluntary context switches the task made per second.
A voluntarycontext switch occurs whenatask blocks because it requires a resource that isunavailable.
nvcswch/s
Total number of non voluntary context switches the task made per second. A involuntary context switch takes place when a task executes for the duration of its timeslice and then is forced to relinquish the processor.
输出结果中重点关注cswch/s和nvcswch/s的次数,因为它们意味着不同的性能问题。
自愿上下文切换(cswch):当进程无法获得所需要的资源时,就会导致上下文切换,比如I/O、内存资源不足。
非自愿上下文切换(nvcswch):进程由于时间片已到等原因,被强制调度,发生上下文切换,比如大量进程争夺CPU。
在进行案例模拟前,首先看下空闲系统的上下文切换
root@andy:~# vmstat 1 3
procs -----------memory---------------------------- ---swap-- ----------io--------- -system-- ------cpu-----
r bswpdfreebuff cachesi so bi boin csussy idwa st
1 0 0 2484140 95048 919748 0 0 45 1117200 0 98 1 0
0 0 0 2484148 95048 919748 0 0 0 027230 0 100 0 0
0 0 0 2484148 95048 919748 0 0 0 029300 0 100 0 0
机器提前准备安装好sysbench和sysstat
在第一个终端里,执行sysbench模拟系统多线程调度的瓶颈
root@andy:~# sysbench --threads=5 --max-time=60 threads run
以5个线程运行60秒为基准测试,模拟多线程切换的问题
第二个终端,执行vmstat观察上下文切换
root@andy:~# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b 交换 空闲 缓冲 缓存si so bi bo in cs us sy id wa st
1 0 780 218488 112644 981264 0 0 111 50 41 49 1 1 95 3 0
0 0 780 218448 112644 981264 0 0 0 0 71 94 0 0 100 0 0
0 0 780 218448 112644 981264 0 0 0 0 64 90 0 0 100 0 0
5 0 780 215968 112644 982552 0 0 1224 0 2649 129107 7 19 70 4 0
5 0 780 215960 112644 982488 0 0 0 0 10481 367900 32 61 7 0 0
5 0 780 215960 112644 982488 0 0 0 0 11893 347654 35 58 6 0 0
5 0 780 215960 112644 982488 0 0 0 0 10051 335603 30 63 7 0 0
能明显看到cs切换飙升到三十多万,中断in飙升到一万多,这明显影响系统性能;r为5,系统逻辑CPU为4,存在使用CPU的竞争,在CPU中us与sy相加为93左右,且sy在六十上下,说明内核占用CPU较多。
综合可以得出:运行或等待运行的系统就绪队列(进程)过多,导致上下文切换的飙升,上下文的切换又导致CPU的使用率上升。
第三个终端,用pidstat查看CPU和进程上下文切换的情况
root@andy:~# pidstat -w -u 1 #-w表示进程切换指标-u表示CPU使用指标1表示每个1秒输出1次(需Ctrl+c结束)
Linux 4.18.0-12-generic (andy) 2020年05月20日_x86_64_ (4 CPU)
22时04分20秒UID PID %usr %system %guest %wait %CPU CPU Command
22时04分21秒0 3142 0.00 1.00 0.00 0.00 1.00 0 sshd
22时04分21秒0 4181 100.00 100.00 0.00 0.00100.003sysbench
22时04分21秒0 4188 1.00 2.00 0.00 0.00 3.00 0 pidstat
22时04分20秒UID PID cswch/s nvcswch/s Command
22时04分21秒0 9 2.00 0.00 ksoftirqd/0
22时04分21秒0 1020.000.00rcu_sched
22时04分21秒0 30 2.00 0.00 ksoftirqd/3
22时04分21秒0 37 1.00 0.00 kworker/0:1-mpt_poll_0
22时04分21秒0 309952.000.00kworker/u256:0-events_freezable_power_
22时04分21秒0 314247.000.00sshd
22时04分21秒0 412848.000.00kworker/u256:2-events_unbound
22时04分21秒0 4180 1.00 0.00 vmstat
22时04分21秒0 4188 1.0037.00pidstat
.............
可以看出CPU使用率升高是sysbench导致的,但上下文切换是由其他进程导致的,比如自愿上下文切换的rcu_sched、kworker等,非自愿上下文切换的pidstat.
我们看到这些切换总共加起来也就两百左右,怎么cs能飙到三十多万,中断飙到一万多呢?使用pidstat -wt 1显示线程指标,如下:
root@andy:~# pidstat -wt 1 #-wt参数输出线程的上下文切换指标
Linux 4.18.0-12-generic (andy) 2020年05月20日_x86_64_ (4 CPU)
22时51分58秒UID TGID TID cswch/s nvcswch/s Command
22时51分59秒0 9 - 8.00 0.00 ksoftirqd/0
22时51分59秒0 - 9 8.00 0.00 |__ksoftirqd/0
22时51分59秒0 10 - 20.00 0.00 rcu_sched
22时51分59秒0 - 10 20.00 0.00 |__rcu_sched
22时51分59秒0 18 - 1.00 0.00 ksoftirqd/1
22时51分59秒0 - 18 1.00 0.00 |__ksoftirqd/1
22时51分59秒0 37 - 2.00 0.00 kworker/0:1-events
22时51分59秒0 - 37 2.00 0.00 |__kworker/0:1-events
22时51分59秒0 39 - 1.00 0.00 kworker/1:1-mm_percpu_wq
22时51分59秒0 - 39 1.00 0.00 |__kworker/1:1-mm_percpu_wq
22时51分59秒0 55 - 1.00 0.00 kworker/2:1-mm_percpu_wq
22时51分59秒0 - 55 1.00 0.00 |__kworker/2:1-mm_percpu_wq
22时51分59秒0 - 1298 1.00 0.00 |__gmain
22时51分59秒125 - 1606 2.00 0.00 |__mysqld
22时51分59秒125 - 1607 2.00 0.00 |__mysqld
22时51分59秒125 - 1608 2.00 0.00 |__mysqld
22时51分59秒125 - 1609 2.00 0.00 |__mysqld
22时51分59秒125 - 1610 2.00 0.00 |__mysqld
22时51分59秒125 - 1611 2.00 0.00 |__mysqld
22时51分59秒125 - 1612 2.00 0.00 |__mysqld
22时51分59秒125 - 1613 2.00 0.00 |__mysqld
22时51分59秒125 - 1614 2.00 0.00 |__mysqld
22时51分59秒125 - 1615 2.00 0.00 |__mysqld
22时51分59秒125 - 1616 1.00 0.00 |__mysqld
22时51分59秒125 - 1848 1.00 0.00 |__mysqld
22时51分59秒125 - 1849 1.00 0.00 |__mysqld
22时51分59秒125 - 1851 1.00 0.00 |__mysqld
22时51分59秒110 - 2100 1.00 0.00 |__rtkit-daemon
22时51分59秒110 - 2101 1.00 0.00 |__rtkit-daemon
22时51分59秒0 3142 - 18.00 3.00 sshd
22时51分59秒0 - 3142 18.00 3.00 |__sshd
22时51分59秒0 3162 - 2.00 0.00 kworker/3:1-mm_percpu_wq
22时51分59秒0 - 3162 2.00 0.00 |__kworker/3:1-mm_percpu_wq
22时51分59秒0 4196 - 16.00 0.00 kworker/u256:2-events_unbound
22时51分59秒0 - 4196 16.00 0.00 |__kworker/u256:2-events_unbound
22时51分59秒0 4259 - 21.00 0.00 kworker/u256:1-events_unbound
22时51分59秒0 - 4259 21.00 0.00 |__kworker/u256:1-events_unbound
22时51分59秒0 - 4269 9886.00 41982.00 |__sysbench
22时51分59秒0 - 4270 10853.00 85926.00 |__sysbench
22时51分59秒0 - 4271 9830.00 41249.00 |__sysbench
22时51分59秒0 - 4272 9130.00 48229.00 |__sysbench
22时51分59秒0 - 4273 9306.00 58599.00 |__sysbench
22时51分59秒0 4274 - 1.00 9.00 pidstat
22时51分59秒0 - 4274 1.00 9.00 |__pidstat
.............
可以看到虽然sysbench进程(也就是主线程)的上下文切换不多,但是它的子线程切换较多,结论就是上下文切换的罪魁祸首就是sysbench的子线程。
中断的类型可以通过cat /proc/interrupts查看,如下:root@andy:~# watch -d cat /proc/interrupts
Every 2.0s: cat /proc/interrupts andy: Wed May 20 23:08:42 2020
CPU0 CPU1 CPU2 CPU3
0: 17 0 0 0 IO-APIC 2-edge timer
1: 0 0 0 9 IO-APIC 1-edge i8042
8: 1 0 0 0 IO-APIC 8-edge rtc0
9: 0 0 0 0 IO-APIC 9-fasteoi acpi
12: 125 0 16 0 IO-APIC 12-edge i8042
14: 0 0 0 0 IO-APIC 14-edge ata_piix
15: 0 0 0 0 IO-APIC 15-edge ata_piix
16: 46 0 0 17031 IO-APIC 16-fasteoi vmwgfx, snd_ens1371, ens38
17: 5202 37960 0 0 IO-APIC 17-fasteoi ehci_hcd:usb1, ioc0, ens39
18: 0 217 0 0 IO-APIC 18-fasteoi uhci_hcd:usb2
24: 0 0 0 0 PCI-MSI 344064-edge PCIe PME, pciehp
25: 0 0 0 0 PCI-MSI 346112-edge PCIe PME, pciehp
通过这个案例可以看到多工具、多个指标对比的好处,可以发现进程内部的变化及细节。
再回到CPU上下文切换多少才算正常?这个值其实取决于CPU本身的性能,从数百到一万都可以算正常,但是当上下文切换超多一万的时候,就可能要分析系统,特别是当切换次数出现数量级的增长,就很可能出现性能问题。
可以根据上下文切换的类型,做进一步的分析
自愿上下文切换增多,说明进程等待资源,有可能出现内存、IO等其他问题
非自愿上下文切换增多,说明进程被强制调度,也就是在争夺CPU资源,CPU成了系统性能瓶颈
中断次数变多,说明CPU被中断处理程序占用,可以通过cat /proc/interrupts文件,查看中断类型