cpu-上下文切换
前奏
在每个进程任务运行之前,CPU需要知道任务从哪加载,又从哪里开始运行,也就是说,系统要实现帮进程任务设置好CPU寄存器和程序计数器
-
CPU寄存器
- 容量小,速度极快的内存
-
程序计数器
- 存储CPU正在执行指令的位置
所以,CPU寄存器和程序计数器,是CPU在运行任何任务之前必须依赖的环境,因此也被称作CPU上下文
CPU上下文切换也就是把前一个任务的CPU上下文保存起来,然后加载新任务的向下文到这些寄存器和程序计数器
CPU上下文切换场景分类
根据任务的不同,CPU上下文切换分为如下的几类:
-
进程上下文切换
-
线程上下文切换
-
中断上下文切换
进程上下文切换
Linux按照特权等级,把进程分为内核空间
和用户空间
内核空间:
- Ring0:具有最高权限,可以直接访问所有资源
用户空间
- Ring3: 只能访问受限的资源,不能直接访问内存等硬件设备,必须用过系统调用陷入内核,才能访问这些特权资源
image.png
用户态到内核态的转变需要系统调用
来完成,
系统调用结束后,CPU 寄存器需要恢复原来保存的用户态,然后在切换到用户空间,继续运行进程。一次系统调用的过程其实发生了两次上下文切换。
进程上下文切换和 系统调用的上下文切换
进程上下文切换: 是从一个进程切换到另一个进程运行
系统调用的上下文切换: 一直是同一个进程在运行,系统调用过程通常称为特权模式切换,而不是上下文切换
区别如下:
- 进程由内核来管理和调度的,进程的切换只能发生在内核态
- 进程上下文切换比系统调用多了一个过程
Linux通过TLB来管理虚拟内存到物理内存的映射关系,当虚拟内存更新之后TLB也需要刷新,内存的访问也会随之变慢。
进程切换时才需要上下文切换,Linux为每个CPU都维护了一个就绪队列,将活跃进程按照优先级和等待CPU的时间进行排序,然后选择最需要CPU的进程,也就是最高优先级和等菜CPU时间最长的进程来运行
进程在什么时候会调度到CPU上运行
-
为了保证所有的进程可以公平调度,CPU时间被划分成一段段的时间片,这些时间片灾备轮流分配给各个进程,这样,当某个进程的时间片被耗尽,就会被系统挂起,切换到其他正在等待CPU的进程运行
-
进程在系统资源不足时,要等到资源买足后才可以运行,这个时候进程也会被挂起,并有系统调度大其他进程运行
-
当进程通过睡眠函数sleep这样的方法将自己主动挂起,自然也会重新调度
-当优先级更高的进程运行时,为了保证高优先级的进程运行,当前的进程会被挂起,由高优先级的进程来运行
- 当发生硬件中断时,CPU上的进程会被中断挂起,转而执行内核中的中断服务程序
线程上下文切换
线程是调度的基本单位,晋城市资源拥有的基本单位。
所谓的内核中的任务调度,实际上调度对象是线程,而进程只是给线程提供了虚拟内存,全局变量等资源。
-
当进程只有一个线程时,可以认为进程就等于线程
-
当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源。这些资源在上下文切换的时候是不需要修改的
-
线程也有自己的私有数据,比如栈和寄存器等,浙西在上下文切换时也是需要保存的
线程上下文切换的情况:
- 前后两个线程属于不同的进程
- 前后两个线程属于同一个进程,切换的数据只有线程的不共享的数据
- 多线程对于多进程会消耗更少的资源,所以多线程是替代多进程的一个优势
中断上下文切换
为了快速响应硬件时间,中断会打断进程的正常调度和执行,转而调度中断程序,响应设备事件,而中断执行过程中会挂起当前正常的进程。
-
为了快速响应硬件事件,中断会打断进程的正常调度和执行
-
对于同一个CPU来说,中断处理比进程拥有更高的优先级
小结
-
CPU上下文切换是保证Linux正常工作的核心功能之一,一般不用特别关注
-
如果上下文切换过多,会将CPU时间消耗在
寄存器
,内核栈
以及虚拟内存
等数据的保存和恢复上
实战上下文切换
查看整体的CPU上下文切换
[root@www ~]# vmstat 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
3 0 0 80144 140508 1143972 0 0 16 25 1 1 1 0 99 0 0
其中:
-
cs:每秒上下文切换次数
-
in:每秒中断次数
-
r:就绪队列长度
-
b: 不可睡眠状态的进程数
查看每个进程的CPU详细上下文切换
[root@www ~]# pidstat -w 5
Linux 3.10.0-862.el7.x86_64 (www.cropy.cn) 10/16/19 _x86_64_ (1 CPU)
16:11:34 UID PID cswch/s nvcswch/s Command
16:11:39 0 1 0.80 0.00 systemd
16:11:39 0 3 1.40 0.00 ksoftirqd/0
16:11:39 0 9 28.74 0.00 rcu_sched
16:11:39 0 11 0.40 0.00 watchdog/0
16:11:39 0 29 0.20 0.00 khugepaged
需要记住的参数如下:
-
cswch:自愿上下文切换,这个参数是指进程无法获取所需资源导致的上下文切换,比如:
IO,内存等资源不足
的时候会发生自愿上下文切换 -
nvcswch: 非自愿上下文切换,这个参数表示进程时间片已到等原因被系统强制调度,进而发生上下文切换。比如:
大量进程抢占CPU
的时候会发生非自愿上下文切换
案例分析
环境需求
-
centos7
-
sysstat: 1.15以上
-
sysbench
-
root权限
实战测试
- 第一个终端执行如下操作
[root@www ~]# sysbench --threads=10 --max-time=300 threads run #开启10个进程的五分钟的基准测试,模拟多线程切换
- 第二个终端执行如下
[root@www ~]# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
10 0 0 180128 122484 1054324 0 0 12 0 1112 1249403 17 83 0 0 0
7 0 0 180128 122484 1054336 0 0 4 0 1097 1262657 16 84 0 0 0
8 0 0 180128 122484 1054340 0 0 0 0 1089 1224114 18 82 0 0 0
8 0 0 180112 122484 1054340 0 0 0 0 1116 1255382 19 81 0 0 0
8 0 0 180096 122488 1054340 0 0 36 16 1149 1249025 18 82 0 0 0
发现如下的问题
-
r:就绪队列在长度达到8
-
us和sy:两列加起来到了100,sy在84左右,证明CPU被内核占用了
-
in: 中断也达到了1000以上
综合上面的三个点,可以发现CPU的使用率很高
- 然后再开一个终端,执行如下命令
[root@www ~]# pidstat -w -u 1
Linux 3.10.0-862.el7.x86_64 (www.cropy.cn) 10/16/19 _x86_64_ (1 CPU)
16:42:08 UID PID %usr %system %guest %wait %CPU CPU Command
16:42:09 0 22937 0.00 1.00 0.00 0.00 1.00 0 barad_agent
16:42:09 0 31092 17.00 83.00 0.00 0.00 100.00 0 sysbench
16:42:08 UID PID cswch/s nvcswch/s Command
16:42:09 0 1 1.00 0.00 systemd
16:42:09 0 3 1.00 0.00 ksoftirqd/0
16:42:09 0 9 32.00 0.00 rcu_sched
16:42:09 0 9921 1.00 0.00 supervisord
16:42:09 0 10617 1.00 0.00 YDLive
16:42:09 0 16286 2.00 0.00 IntelliJIDEALic
16:42:09 1000 17350 2.00 0.00 sshd
16:42:09 0 22936 3.00 0.00 barad_agent
16:42:09 0 28412 1.00 0.00 kworker/0:1
16:42:09 1003 28963 3.00 0.00 tmux
16:42:09 0 30007 11.00 0.00 kworker/0:0
16:42:09 0 30560 1.00 0.00 vmstat
16:42:09 0 31225 6.00 0.00 ntpdate
16:42:09 0 31226 2.00 0.00 grep
16:42:09 0 31241 1.00 0.00 pidstat
可以发现CPU使用过高是sysbench导致的,pidstat -wt
可以分析到线程级别
也可以如下的方式去查看:
**[root@www ~]# watch -d cat /proc/interrupts **
网友评论