CPU上下文切换

  |   0浏览

多任务的实施依赖于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次;运行或等待运行的进程r0,不可中断的睡眠进程b0

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 voluntary context switch occurs when  a  task blocks because it requires a resource that is unavailable.

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 time slice and then is forced to relinquish the processor.

输出结果中重点关注cswch/snvcswch/s 的次数,因为它们意味着不同的性能问题。

自愿上下文切换(cswch):当进程无法获得所需要的资源时,就会导致上下文切换,比如I/O、内存资源不足。

非自愿上下文切换(nvcswch):进程由于时间片已到等原因,被强制调度,发生上下文切换,比如大量进程争夺CPU

在进行案例模拟前,首先看下空闲系统的上下文切换

root@andy:~# vmstat  1 3

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 2484140  95048 919748    0    0    45    11   17   20  0  0 98  1  0

 0  0      0 2484148  95048 919748    0    0     0     0   27   23  0  0 100  0  0

 0  0      0 2484148  95048 919748    0    0     0     0   29   30  0  0 100  0  0

机器提前准备安装好sysbenchsysstat

在第一个终端里,执行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飙升到一万多,这明显影响系统性能;r5,系统逻辑CPU4,存在使用CPU的竞争,在CPUussy相加为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)  20200520日  _x86_64_        (4 CPU)

 

220420秒   UID       PID    %usr %system  %guest   %wait    %CPU   CPU  Command

220421秒     0      3142    0.00    1.00    0.00    0.00    1.00     0  sshd

220421秒     0      4181  100.00  100.00    0.00    0.00  100.00     3  sysbench

220421秒     0      4188    1.00    2.00    0.00    0.00    3.00     0  pidstat

 

220420秒   UID       PID   cswch/s nvcswch/s  Command

220421秒     0         9      2.00      0.00  ksoftirqd/0

220421秒     0        10     20.00      0.00  rcu_sched

220421秒     0        30      2.00      0.00  ksoftirqd/3

220421秒     0        37      1.00      0.00  kworker/0:1-mpt_poll_0

220421秒     0      3099     52.00      0.00  kworker/u256:0-events_freezable_power_

220421秒     0      3142     47.00      0.00  sshd

220421秒     0      4128     48.00      0.00  kworker/u256:2-events_unbound

220421秒     0      4180      1.00      0.00  vmstat

220421秒     0      4188      1.00     37.00  pidstat

.............

可以看出CPU使用率升高是sysbench导致的,但上下文切换是由其他进程导致的,比如自愿上下文切换的rcu_schedkworker等,非自愿上下文切换的pidstat.

我们看到这些切换总共加起来也就两百左右,怎么cs能飙到三十多万,中断飙到一万多呢?使用pidstat -wt 1显示线程指标,如下:

root@andy:~# pidstat -wt  1  #-wt参数输出线程的上下文切换指标

Linux 4.18.0-12-generic (andy)  20200520日  _x86_64_        (4 CPU)

 

225158秒   UID      TGID       TID   cswch/s nvcswch/s  Command

225159秒     0         9         -      8.00      0.00  ksoftirqd/0

225159秒     0         -         9      8.00      0.00  |__ksoftirqd/0

225159秒     0        10         -     20.00      0.00  rcu_sched

225159秒     0         -        10     20.00      0.00  |__rcu_sched

225159秒     0        18         -      1.00      0.00  ksoftirqd/1

225159秒     0         -        18      1.00      0.00  |__ksoftirqd/1

225159秒     0        37         -      2.00      0.00  kworker/0:1-events

225159秒     0         -        37      2.00      0.00  |__kworker/0:1-events

225159秒     0        39         -      1.00      0.00  kworker/1:1-mm_percpu_wq

225159秒     0         -        39      1.00      0.00  |__kworker/1:1-mm_percpu_wq

225159秒     0        55         -      1.00      0.00  kworker/2:1-mm_percpu_wq

225159秒     0         -        55      1.00      0.00  |__kworker/2:1-mm_percpu_wq

225159秒     0         -      1298      1.00      0.00  |__gmain

225159秒   125         -      1606      2.00      0.00  |__mysqld

225159秒   125         -      1607      2.00      0.00  |__mysqld

225159秒   125         -      1608      2.00      0.00  |__mysqld

225159秒   125         -      1609      2.00      0.00  |__mysqld

225159秒   125         -      1610      2.00      0.00  |__mysqld

225159秒   125         -      1611      2.00      0.00  |__mysqld

225159秒   125         -      1612      2.00      0.00  |__mysqld

225159秒   125         -      1613      2.00      0.00  |__mysqld

225159秒   125         -      1614      2.00      0.00  |__mysqld

225159秒   125         -      1615      2.00      0.00  |__mysqld

225159秒   125         -      1616      1.00      0.00  |__mysqld

225159秒   125         -      1848      1.00      0.00  |__mysqld

225159秒   125         -      1849      1.00      0.00  |__mysqld

225159秒   125         -      1851      1.00      0.00  |__mysqld

225159秒   110         -      2100      1.00      0.00  |__rtkit-daemon

225159秒   110         -      2101      1.00      0.00  |__rtkit-daemon

225159秒     0      3142         -     18.00      3.00  sshd

225159秒     0         -      3142     18.00      3.00  |__sshd

225159秒     0      3162         -      2.00      0.00  kworker/3:1-mm_percpu_wq

225159秒     0         -      3162      2.00      0.00  |__kworker/3:1-mm_percpu_wq

225159秒     0      4196         -     16.00      0.00  kworker/u256:2-events_unbound

225159秒     0         -      4196     16.00      0.00  |__kworker/u256:2-events_unbound

225159秒     0      4259         -     21.00      0.00  kworker/u256:1-events_unbound

225159秒     0         -      4259     21.00      0.00  |__kworker/u256:1-events_unbound

225159秒     0         -      4269   9886.00  41982.00  |__sysbench

225159秒     0         -      4270  10853.00  85926.00  |__sysbench

225159秒     0         -      4271   9830.00  41249.00  |__sysbench

225159秒     0         -      4272   9130.00  48229.00  |__sysbench

225159秒     0         -      4273   9306.00  58599.00  |__sysbench

225159秒     0      4274         -      1.00      9.00  pidstat

225159秒     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文件,查看中断类型

原文地址:https://blog.51cto.com/13162375/2507719