什么是CPU上下文?
每个任务运行前, CPU都需要知道任务从哪里加载, 又从哪里开始运行, 需要系统事先设置好CPU寄存器和程序计数器, 也就是所谓的CPU上下文.寄存器是CPU内置的容量小,但速度即可的内存, 用于存储指令,数据, 地址等信息; 程序计数器是其中的一类特殊的寄存器, 主要存储着下一条要执行的指令, 工作模式可参考下图:
上下文切换过程
- 记录当前任务的上下文(CPU寄存器和程序计数器)
- 加载新任务的上下文到这些寄存器和程序计数器
- 跳转到程序计数器所指的新位置, 运行新任务
进程上下文切换
-
进程上下文切换: 是指从一个进程切换到另一个进程
(1)进程运行状态分为内核运行态和用户运行态。内核空间态资源包括内核的堆栈、寄存器等;用户空间态资源包括虚拟内存、栈、变量、正文、数据等;
(2)系统调用(软中断)在内核态完成的,需要进行2次CPU上下文切换(用户空间–>内核空间–>用户空间),不涉及用户态资源,也不会切换进程;
(3)进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以进程的上下文不仅包括了用户空间的资源,也包括内核空间资源。 -
触发进程上下文切换的场景
(1) 根据调度策略,将CPU时间划片为对应的时间片,当时间片耗尽,当前进程必须挂起;
(2) 资源不足的,在获取到足够资源之前进程挂起;
(3) 当进程通过睡眠函数 sleep 这样的方法将自己主动挂起;
(4) 当有更高优先级的进程需要运行时, 当前进程会被挂起;
(5) 发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序.
线程上下文切换
- 线程是调度的基本单位,而进程则是资源拥有的基本单位;
- 不通进程之间的线程上下文切换,其过程和进程上下文切换大致相同;
- 进程内部的线程进上下文切换,不需要切换进程的用户资源,只需要切换线程私有的数据和寄存器等。这会比进程上下文进程切换消耗的资源少,所以多线程相比多进程的优势。
中断上下文切换
- 快速响应硬件的事件,中断处理会打断进程的正常调度和执行。同一CPU内,硬件中断优先级高于进程。切换过程类似于系统调用的时候,不涉及到用户运行态资源。但大量的中断上下文切换同样可能引发性能问题。
示例
-
sysbench 是一个多线程的基准测试工具, 使用sysbench模拟系统多线程调度的瓶颈, 如图:
-
使用vmstat命令, 查看系统的整体上下文切换情况, 如图:
(1) 指标介绍:
cs(context switch)是每秒上下文切换的次数
in(interrupt)则是每秒中断的次数
r(Running or Runnable)是就绪队列的长度, 也就是正在运行和等待CPU的进程数
b(Blocked)则是处于不可中断睡眠状态的进程数
(2) 数据分析
a. cs列的上下文切换次数从之前的16突然上升到167万
b. r列: 就绪队列最高达到了11, 远超过CPU个数4, 所以会有大量的CPU竞争
c. us和sy列: 这两列的CPU使用率加起来上升到85%左右, 系统CPU使用率达到73%, 说明主要是系统内核在占用
综合说明: 系统的就绪队列过长, CPU竞争的进程过多, 导致了大量的上下文切换, 导致了CPU使用率升高 -
使用pidstat -wt命令, 查看具体是哪个应用程序导致了CPU上下文切换的情况
(1) 指标说明:
cswch/s: 自愿上下文切换, 是指进程无法获取所需资源, 导致的上下文切换,e.g. I/O, 内存等系统资源不足时,就会发生自愿上下文切换;
nvcswch/s: 非自愿上下文切换, 是指进程由于时间片已到等原因,被系统强制调度, e.g. 大量的进程都在争抢CPU就会发生非自愿上下文切换. -
查看系统中断情况的数据 可以看到变化速度最快的是重调度中断(RES),这个中断类型表示,唤醒空闲状态的CPU来调度新的任务执行, 这里的中断升高还是因为过多任务的调度问题,跟前面上下文切换分析结果一致.
分析思路参考
- 如果系统的上下文切换次数比较稳定,那么从数百到一万以内,都应该算正常的; 当上下文切换次数超过一万次或者切换次数出现数量级的增长时, 就很有可能出现了性能问题;
- 自愿上下文切换变多了, 说明进程都在等待资源, 有可能发生了I/O等其他问题;
- 非自愿上下文切换变多了, 说明进程都在被强制调度, 都在争抢CPU, 说明CPU的确成了瓶颈;
- 中断次数变多了, 说明CPU被中断处理程序占用, 需要查看具体的中断类型.