linux操作系统是将CPU轮流分配给任务,分时执行的。而每次执行任务时,CPU需要知道CPU寄存器(CPU内置的内存)和程序计数器PC(CPU正在执行指令和下一条指令的位置)值,这些值是CPU执行任务所依赖的环境,也就是CPU上下文。
CPU上下文切换,就是把前一个任务的CPU上下文(CPU寄存器和程序计数器)保存起来,然后加载入新任务的上下文到CPU寄存器和程序计数器中,最后跳转到程序计数器所指的位置,运行新任务。
保存下来的上下文会在系统内核中,并在任务重新调度执行时再次加载进来,让任务看起来是连续执行的。
根据任务的不同,CPU上下文切换分为进程上下文切换、线程上下文切换、中断上下文切换。
进程上下文切换
Linux把进程的运行空间分为内核空间和用户空间,并对进程按照特权等级划分,从内核空间Ring 0至用户空间Ring 3:Ring 0具有最高权限,可以直接访问所有资源;Ring 3只能访问受限资源,不能直接访问内存等硬件设备,需要通过系统调用陷入到内核中,才能访问这些特权资源。
进程运行在用户空间为进程的用户态,陷入内核空间为进程的内核态。从用户态到内核态的转变,需要通过系统调用来完成,期间涉及CPU上下文的切换:先保存原来用户态指令位置,然后更新内核态指令新位置,最后执行内核态代码;系统调用结束后,恢复用户态数据,继续执行进程。一次系统调用发生二次CPU上下文切换。
进程上下文切换 VS 系统调用:(1)进程上下文切换是一个进程切换到另一个进程;系统调用是同一个进程。(2)都需要进行上下文切换。(3)进程是由内核管理和调度的,切换只发生在内核态,所以进程上下文切换包括虚拟内存、栈、全局变量等用户资源,还包括内核堆栈、寄存器等内核资源;而系统调用只包括内核堆栈、寄存器等内核资源。
每次上下文切换需要几十纳秒到数微妙的CPU时间。切换次数较多,容易导致平均负载升高。
Linux通过TLB(Translation Lookaside Buffer)来管理虚拟内存到物理内存的映射关系。当虚拟内存更新后,TLB 也需要刷新,内存的访问也会随之变慢。特别是在多处理器系统上,缓存是被多个处理器共享的,刷新缓存不仅会影响当前处理器的进程,还会影响共享缓存的其他处理器的进程。
Linux为每个CPU都维护一个就绪队列,将活跃进程(正在运行和正在等待)按照优先级和等待CPU时间进行排序,优先级最高和等待时间最长的进程优先运行。所以触发进程调度的情况:
(1)按照时间片轮转执行
(2)进程执行的资源不足时,该进程被挂起,系统调用其他进程
(3)进程通过睡眠函数sleep将自己挂起
(4)挂起当前进程,由优先级更高的进程运行
(5)硬件中断时,挂起CPU上的进程,转而执行内核中断服务程序。
线程上下文切换
线程是调度的基本单位,而进程是资源拥有的基本单位。(1)当进程只有一个线程时,进程等于线程(2)进程拥有多个线程时,线程共享相同的虚拟内存和全局变量等资源,这些资源在上下文切换时不需要修改(3)线程也有私有的数据(栈和寄存器),上下文切换时需要保存。
不同进程中的两个线程切换,与进程的上下文切换是一样的。同一进程中两个线程,共享资源不动,切换线程中私有数据。所以同进程中线程切换,比多进程切换消耗更少资源,多线程替代多进程更有优势。
中断上下文切换
中断事件会打断进程的正常调度和执行,转而调用中断处理程序,打断期间,保存当前进程状态,中断结束后,恢复原来的状态执行。
中断上下文,其实只包括内核态中断服务程序执行所必需的状态(CPU寄存器、内核堆栈、硬件中断参数等),不需要保存和恢复被打断进程的虚拟内存、全局变量等用户态资源。
中断处理比进程拥有更高的优先级,所以中断上下文切换与进程上下文切换不会同时发生。
中断次数过多,也会消耗大量CPU,降低系统整体性能。
例程
vmstat用于分析系统的内存使用情况、CPU上下文切换和中断次数。
$ vmstat
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 285444 290716 2682728 0 0 2 146 77 80 1 0 99 1 0
// cs (context switch) 每秒上下文切换次数
// in (interrupt) 每秒中断次数
// r (Running or Runable) 正在运行和等待CPU的进程数
// b (Blocked) 处于不可中断睡眠状态的进程数
pidstat查看每个进程的详细情况,-w选项,查看每个进程上下文切换情况,-wt选项,查看每个线程的上下文切换情况。
$ pidstat -w
21时57分43秒 UID PID cswch/s nvcswch/s Command
21时57分43秒 0 1 0.05 0.15 systemd
21时57分43秒 0 2 0.00 0.00 kthreadd
...
// cswch 每秒自愿上下文切换的次数(voluntary context switches):进程无法获取所需资源(I/O、内存不足等),导致上下文切换
// nvcswch 每秒非自愿上下文切换的次数 (non voluntary context switches):时间片已到等原因,系统强制调度,导致上下文切换,如大量进程争抢CPU
通过sysbench模拟系统多线程调度
$ sudo apt install sysbench sysstat
$ sysbench --num-threads=10 --max-time=300 --test=threads run // first terminal
$ vmstat 1 // second terminal
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 278256 291152 2688492 0 0 0 0 1080 2375 1 1 98 0 0
0 0 0 278256 291152 2688492 0 0 0 12 506 2520 3 2 95 0 0
9 0 0 277736 291152 2688492 0 0 0 20 319 9189 1 1 98 0 0
8 0 0 277612 291152 2688492 0 0 0 0 53149 2069616 8 92 1 0 0
8 0 0 277636 291152 2688492 0 0 0 0 53289 2129835 9 92 0 0 0
8 0 0 277612 291156 2688488 0 0 0 12 51352 2134779 10 90 0 0 0
9 0 0 277604 291156 2688492 0 0 0 0 53214 2079199 10 89 1 0 0
9 0 0 277604 291156 2688492 0 0 0 0 51975 2106476 7 93 0 0 0
0 0 0 278348 291156 2688492 0 0 0 0 3978 221384 3 12 86 0 0
0 0 0 278348 291156 2688492 0 0 0 0 433 1017 1 1 99 0 0
0 0 0 278348 291156 2688492 0 0 0 0 249 789 2 0 98 0 0
// r 列就绪队列长度远远大于CPU数量
// in cs 两列陡然上升
// us sy 两列升高,sy列高表示CPU被内核占用的多
$ pidstat -w -u 1 // -w 显示进程切换指标, -u 显示CPU使用指标
22时36分03秒 UID PID %usr %system %guest %CPU CPU Command
22时36分04秒 0 921 0.00 2.00 0.00 2.00 1 Xorg
22时36分04秒 1000 8703 1.00 1.00 0.00 2.00 1 pidstat
22时36分04秒 1000 8704 18.00 174.00 0.00 192.00 0 sysbench
22时36分03秒 UID PID cswch/s nvcswch/s Command
22时36分04秒 0 921 250.00 2.00 Xorg
22时36分04秒 1000 1459 84.00 0.00 compiz
22时36分04秒 1000 8703 1.00 314.00 pidstat
22时36分04秒 1000 15616 360.00 4.00 gnome-terminal-
22时36分04秒 0 27490 315.00 0.00 kworker/u4:0
// CPU使用率 是sysbench进程拉高
// 上下文切换 由pidstat、kworker等共同拉高,并不到vmstat中上万的次数,因为更多的是sysbench线程的上下文切换
$ pidstat -wt 1 // -wt 显示线程切换指标
22时44分18秒 1000 8751 - 4.00 0.00 sysbench
22时44分18秒 1000 - 8751 4.00 0.00 |__sysbench
22时44分19秒 1000 - 8761 14719.00 173756.00 |__sysbench
22时44分20秒 1000 - 8752 15012.00 142064.00 |__sysbench
$ watch -d cat /proc/interrupts // 中断次数过高,查看该文件分析具体的中断类型
CPU0 CPU1
RES: 2418433 2089523 Rescheduling interrupts
// RES 重调度中断,唤醒空闲状态的CPU来调度新任务运行。中断值过高,说明过多任务的调度问题。
最合适的上下文切换次数:每秒上下文切换次数取决于系统本身的CPU性能,通过数百到一万以内是正常;超过一万或次数出现数量级增长,则有性能问题。
vmstat 、 pidstat 、 /proc/interrupts