cpu 上下文切换

什么是 cpu 上下文切换

- CPU 上下文切换,就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行。

上下文切换场景有哪些?

- 进程上下文切换
- 线程上下文切换
- 中断上下文切换

进程上下文切换(进程间切换)

- 进程的运行空间有 内核空间、用户空间 两种;其中用户空间,不能直接访问内存等其他硬件设备,必须通过系统调用载入到内核空间中才能访问内核空间的资源。
- 也就是说,进程既可以在用户空间运行,称之为进程的用户态,又可以在内核空间运行,称之为进程的内核态,两者的转变,是需要通过系统调用来完成。

- 比如查看文件,首先调用 open() 函数打开文件,其次调用 read() 函数读取文件内容,最后调用 write() 函数将内容打印到 stdout,最最后再调用 close() 函数关闭文件。
- 以上系统调用,均是在一个进程运行,所以系统的调用我们成为特权模式切换,而不是上下文切换,但实际上上下文切换也是无法避免的,也就是说在整个调用的过程中,必然会发生 CPU 的上下文切换。

上下文切换、系统调用的区别?

- 系统调用只是会调用不同函数完成进程的需求,而进程上下文切换需要保存当前进程内核状态和 cpu 寄存器,也会把该进程的虚拟内存,栈等其它保存下来,从而加载下一进程的内核态后,而且也要刷新进程的虚拟内存和用户栈,所以保存上下文以及恢复上下文的过程是有资源消耗的,在大量的进程上下文切换过程,肯定会大量的消耗系统资源,好在进程的切换大部分都是几十纳秒和数微秒级别,所以切换时间还是相当可观的。

- 既然知道了上下文切换可能会导致系统负载较高,那我们需要知道什么时候会发生上下文切换,进程的切换就会有 cpu 上下文的切换,那么只有在进程调度时才需要上下文切换,而 linux为 每个 cpu 维护了一个就绪队列,其中主要是正在运行和正在等待 cpu 的进程(活跃进程),它们会按照优先级和等待cpu的时间进行排序,然后选择最需要 cpu 的进程,也就是优先级高的,和等待cpu时间最长的进程来运行。

哪些场景触发进程调度呢?

- cpu 是时间片,会被划分多个时间片,轮流的分配给进程,当某个进程时间片耗尽时,进程会被挂起,切换到其他正在等待 cpu 的进程运行
- 进程在系统资源不足时,需要等到资源满足后才可以运行进程,此时进程也会被挂起,并由系统调度其他进程运行。
- 优先级搞得进程,较低的优先级进程会被挂起。
- 主动挂起的进程 如 sleep,时间到了之后也会重新调度。
- 硬件中断时,cpu 上的进程也会被中断挂起,转而执行内核中的中断服务进程。

线程上下文切换

- 线程是调度基本单位,进程是拥有资源的基本单位,切换过程与进程上下文件切换一致。
- 内核态的任务调度,实际上调度的对象就是线程,而进程只是给线程提供了虚拟内存、全局变量等资源。
- 进程只有一个线程时,可以理解两者没有什么区别。
- 当进程有多个线程时,进程就比线程多了虚拟内存,全局变量等资源,而多个线程之间,这些资源他们是共享的,且上下文切换时它们是不需要被修改的,而线程也是有自己的私有资源的,比如栈和寄存器,这些独有的资源,在上下文切换时也是要被保存的,那进程间多线程切换就比进程内线程间切换资源消耗的多。

中断上下文切换

- 为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。而在打断其他进程时,就需要将进程当前的状态保存下来,这样在中断结束后,进程仍然可以从原来的状态恢复运行。
- 跟进程上下文不同,中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。
- 中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。对同一个 CPU 来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生。
- 同理,由于中断会打断正常进程的调度和执行,所以大部分中断处理程序都短小精悍,以便尽可能快的执行结束。
- 另外,跟进程上下文切换一样,中断上下文切换也需要消耗 CPU,切换次数过多也会耗费大量的CPU,甚至严重降低系统的整体性能。所以,当你发现中断次数过多时,就需要注意去排查它是否会给你的系统带来严重的性能问题。

总结

- CPU 上下文切换,是保证 Linux 系统正常工作的核心功能之一,一般情况下不需要我们特别关注,但过多的上下文切换,会把 CPU 时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,从而缩短进程真正运行的时间,导致系统的整体性能大幅下降

分析工具

vmstat (分析内存/disk/中断)

- vmstat -d -w -t 2 4 #查看当前系统disk的状态

disk- -------------------reads------------------- -------------------writes------------------ ------IO------- -----timestamp-----
          total    merged     sectors          ms     total    merged     sectors          ms     cur     sec                 CST
sda       44106        44     4820857     4332196  11668585     62776   114526946     7925754       0    4724 2020-11-23 13:48:02
sdb      300269        26   250656146   140945580    226256     10519     2601600     1145455       0    1033 2020-11-23 13:48:02
sr0           0         0           0           0         0         0           0           0       0       0 2020-11-23 13:48:02
dm-0      41684         0     4756993     4361968  11731339         0   114522729     8059180       0    4784 2020-11-23 13:48:02
dm-1        131         0        6480         103         0         0           0           0       0       0 2020-11-23 13:48:02
dm-2      58058         0     4818750    20286135    236775         0     2601600     2394758       0     386 2020-11-23 13:48:02
sda       44106        44     4820857     4332196  11668612     62776   114527234     7925763       0    4724 2020-11-23 13:48:04

- vmstat 2 10 -w   #查看当前系统内存的状态

procs -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------
 r  b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st
 1  0            0      4850040       182236      1479240    0    0    38    17    1   11  11   6  82   0   0
 0  0            0      4850184       182236      1479276    0    0     0    91 5993 8727  10   5  84   0   0
 1  0            0      4849300       182236      1479312    0    0     0    79 8565 13358  10   7  83   0   0
 2  0            0      4849192       182236      1479312    0    0     0    43 6021 9128   8   4  88   0   0
 0  0            0      4847916       182236      1479332    0    0     0   107 8270 12498  17   8  75   0   0


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

通过 us、sys、in 可以判断系统当前是否有大量的中断以及从中得出系统的就绪队列是否过长,也就是正在运行和等待 CPU 的进程数过多,导致了大量的上下文切换,
而上下文切换又导致了系统 CPU 的占用率升高

pidstat 一探究竟(上下文切换的真凶是谁)

- pidstat -w -t 1 1   #-w 显示进程上下文切换信息,自愿切换(cswch)和非自愿切换(nvcswch)/s,pid,uid,进程名、 -t 显示线程指标,默认仅显示进程

pidstat -w -t   | awk '{if($6>40) print $0}'   
02:13:29 PM   UID      TGID       TID   cswch/s nvcswch/s  Command
02:13:29 PM    81       892         -     69.73      7.84  dbus-daemon
02:13:29 PM    81         -       892     69.73      7.84  |__dbus-daemon
02:13:29 PM     0         -      1543    502.17      0.79  |__kubelet
02:13:29 PM     0         -      1570    196.74      0.42  |__dockerd
02:13:29 PM     0      2877         -     68.78      0.33  kube-apiserver
02:13:29 PM     0         -      2877     68.78      0.33  |__kube-apiserver
02:13:29 PM     0         -      3013    782.50      0.18  |__kube-apiserver
02:13:29 PM     0         -      3014     68.41      0.32  |__kube-apiserver
02:13:29 PM     0         -      3015     73.18      0.35  |__kube-apiserver
02:13:29 PM     0         -      3016     41.45      0.19  |__kube-apiserver
02:13:29 PM     0         -      3068     68.00      0.32  |__kube-apiserver
02:13:29 PM     0         -      3009    261.39      0.35  |__etcd
02:13:29 PM     0         -      4592    321.63      0.11  |__calico-node
02:13:29 PM     0         -      5069     44.32      0.01  |__coredns
02:13:29 PM     0         -      5776     44.45      0.01  |__coredns
02:13:29 PM     0         -      6177    223.16      0.26  |__promtail
02:13:29 PM     0         -     16975     77.53      0.02  |__kube-controller

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

- 通过pidstat 查看发现,进程+线程的上下文切换数量等于 vmstat 看到的cs总数,也就是说某个进程cpu冲高,是因为有大量的上下文切换引起的

- 再仔细看下 vmstat 输出可以发现也有大量的 in 中断,那么中断如何查看呢?

- pidstat 是查看进程级别,也就是用户态的信息,中断的话要看,/proc/interrupts 这个只读文件。/proc 实际上是 Linux 的一个虚拟文件系统,用于内核空间与用户空间之间的通信。/proc/interrupts 就是这种通信机制的一部分,提供了一个只读的中断使用情况

- cat /proc/interrupts

 56:    1111142    3538019     167594    5684931   PCI-MSI-edge      vmw_pvscsi
 57:        115          0   61338897          0   PCI-MSI-edge      ens192-rxtx-0
 58:    7466477       2487      19776   18644726   PCI-MSI-edge      ens192-rxtx-1
 59:     178574   30670451       1453    3161194   PCI-MSI-edge      ens192-rxtx-2
 60:   15560739    2658091       5120    6380376   PCI-MSI-edge      ens192-rxtx-3
 61:          0          0          0          0   PCI-MSI-edge      ens192-event-4
 62:         20          0     115918          0   PCI-MSI-edge      vmw_vmci
 63:          0          0          0          0   PCI-MSI-edge      vmw_vmci
NMI:          0          0          0          0   Non-maskable interrupts
LOC: 1110114780 1148914792 1159639791 1150435420   Local timer interrupts
SPU:          0          0          0          0   Spurious interrupts
PMI:          0          0          0          0   Performance monitoring interrupts
IWI:    5708696    6109423    6135541    6016581   IRQ work interrupts
RTR:          0          0          0          0   APIC ICR read retries
RES:  452734633  446248773  437210738  426982733   Rescheduling interrupts
CAL:    2322698    1821284    2640630    1284817   Function call interrupts
TLB:   27516878   27649410   27662689   27530045   TLB shootdowns

- RES 列对应的重新调度的中断,中断类型表示,唤醒空闲状态的 CPU 来调度新的任务运行,所以由此得知,vmstat 看到的in 过高,也是因为有大量的中断进程在重新调度,所以系统cpu冲高,
是因为有大量的上下文切换与中断在重新调度引起的

结论

- pidstat: 
	-w 上下文切换 -u cpu状态
	-t 显示线程级别信息如,cswch、nvcswch
- vmstat   
	-d disk信息, 默认仅输出内存相关信息如: cs、in
- cat /proc/interrupts 
	- 中断相关信息
- 自愿上下文切换变多了(cswch),说明进程都在等待资源,有可能发生了 I/O 等其他问题
- 非自愿上下文切换变多了(nvcswch),说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 的确成了瓶颈
- 中断次数变多了(interrupts),说明 CPU 被中断处理程序占用,还需要通过查看 /proc/interrupts 文件来分析具体的中断类型