一、背景:
- 上下文切换
CPU 上下文切换,就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
我的理解是因为CPU在一个时刻只能执行一个任务(进程、线程),现代处理器(CPU)都是可支持多任务的,也是分时间片的,当一个长任务在CPU上的时间片完成后,他的信息(数据)其实是被放在寄存器和程序计数器(目的是为了记录任务执行到哪条语句(机器码)了)上的。这时我们需要把他的信息保存起来后把CPU让出去,让其他任务占用CPU。其他任务从内存中恢复上次保存下来的寄存器、程序计数器信息到寄存器、程序计数器里,然后继续执行。
二、分类
上下文切换从切换者的角度上可以分为三类:1. 进程上下文切换、2. 线程上下文切换、3. 中断上下文切换。
在介绍进程上下文切换前,首先要说一下特权切换(内核态->用户态的切换、用户态->内核态的切换),特权切换是通过系统调用来达到的。
在特权切换的过程中需要保存用户态的指令位置,然后陷入内核态,内核态运行完毕后再恢复上次保存的用户态指令位置。
所以在特权切换的同时需要做指令位置的保存和恢复操作(CPU切换)。
系统调用和上下问切换的不同:系统切换是在同一个进程内的,不需要保存/恢复寄存器信息等。
1. 进程上下文切换
每个进程都有他自己独立的虚拟内存(包涵堆、栈等)、寄存器、程序计数器等信息。在不同进程之间做上下文切换的代价是比较昂贵的。根据 Tsuna 的测试报告,每次上下文切换都需要几十纳秒到数微秒的 CPU 时间。这个时间还是相当可观的,特别是在进程上下文切换次数较多的情况下,很容易导致 CPU 将大量时间耗费在寄存器、内核栈以及虚拟内存等资源的保存和恢复上,进而大大缩短了真正运行进程的时间。对比昂贵的进程上下文切换,操作系统又引入了线程上下文切换的概念。
2. 线程上下文切换
线程会比进程轻量级很多,同一进程内的线程是共享虚拟内存等,仅需要切换线程的私有数据即可。比如寄存器、栈等不共享的数据即可。
3. 中断
为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。而在打断其他进程时,就需要将进程当前的状态保存下来,这样在中断结束后,进程仍然可以从原来的状态恢复运行。
为了达到尽量不影响程序运行,所以中断一般都是短小精悍的。占用CPU处理完中断立马让出CPU让用户程序执行。
三、上下文切换、中断的查看
请在你的terminal中键入vmstat
可以得到以下结果
bash-4.1# vmstat
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 129860136 0 1003608 0 0 278930 132239 214 2 1 0 99 0 0
这是一个系统信息快照的工具,第一行分别是进程状态、内存状态、交换分区状态、IO状态、系统状态、CPU状态。
我们这里主要想看的是第二行的:
状态 | 全名 | 含义 |
---|---|---|
r | running | 可以运行的进程的个数 |
b | block | 处于block状态的进程数 |
cs | context switch | 每秒上下文切换次数 |
in | interrupt | 每秒中断次数 |
那么如果你想看每个进程的详细信息,那么你需要:pidstat
这个工具,pidstat -w
可以看到进程的详细情况如下:
Average: UID PID cswch/s nvcswch/s Command
Average: 0 30438 0.91 0.00 python3
Average: 0 79828 0.91 0.00 python3
从上图可以看到pidstat -w 5
展示了两个数据分别了:
PID | 进程号 |
cswch | voluntary context switches 主动上下文切换次数 |
ncswch | non voluntary context switches 被动上下文切换次数 |
主动上下文切换:IOWait等就是主动上下文切换。
被动上下文切换:时间片用完等情况属于被动上下文切换。