我们得知多进程争用CPU会导致平均负载升高.
可能你会觉得奇怪,虽然看起来有很多进程在争用cpu,但实际上在一个时间点只有一个进程在使用,那为什么平均负载还会升高呢?
CPU 上下文切换就是罪魁祸首 !
CPU上下文
Linux系统是一个多任务操作系统, 它支持远大于cpu数量的进程同时运行.
当然, 这些任务并不是真的同时在运行,而是将CPU时间轮流分配给它们,造成多任务同时运行的错觉.
在每个任务开始运行时,CPU需要知道从哪里加载, 从什么位置开始运行. 即:需要系统事先帮它设置好CPU寄存器和程序计数器(PC:Program Counter).
CPU寄存器: CPU 内置的容量小、但速度极快的内存
程序计数器: 用来存储 CPU 正在执行的指令位置, 或者即将执行的下一条指令位置 它们都是 CPU 在运行任何任务前,必须的依赖环境,因此被叫做CPU上下文
CPU上下文切换
先把前一个任务的CPU上下文保存起来, 然后加载新任务的上下文到这些寄存器和程序计数器,再跳转到程序计数器所在的新位置,运行新任务
稍微详细描述一下,上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行以下的活动: (1)挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处. (2)在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复 (3)跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程 除了进程切换会有上下文切换外, 线程的切换以及中断均会有上下文切换的发生我们分别称之为进程上下文切换, 线程上下文切换及中断上下文切换.
进程上下文切换: 从一个进程切换到另一个进程
1).进程运行态为内核态和用户态。内核空间态资源包括内核的堆栈、寄存器等;用户空间态资源包括虚拟内存、栈、变量、正文、数据等
2).系统调用(软中断)在内核态完成的,需要进行2次CPU上下文切换(用户空间–>内核空间–>用户空间),不涉及用户态资源,也不会切换进程。
3).进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以,进程的上下文不仅包括了用户空间的资源,也包括内核空间资源。
4).进程的上下文切换过程:
(a).接收到切换信号,挂起进程,记录当前进程的虚拟内存、栈等资源存储;
(b).将这个进程在 CPU 中的上下文状态存储于起来;
©.然后在内存中检索下一个进程的上下文;
(d).并将其加载到 CPU的寄存器中恢复;
(e).还需要刷新进程的虚拟内存和用户栈;
(f).最后跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程。
5).下列将会触发进程上下文切换的场景:
(a).根据调度策略,将CPU时间划片为对应的时间片,当时间片耗尽,当前进程必须挂起。
(b).资源不足的(如内存不足),在获取到足够资源之前进程挂起。
©.进程sleep挂起进程。
(d).高优先级进程导致当前进程挂起
(e).硬件中断,导致当前进程挂起
线程上下文切换
线程与进程的区别: 线程是调度的基本单位,而进程则是资源拥有的基本单位
内核中的任务调度,实际上调度的是线程, 而进程只是给线程提供了虚拟内存,全局变量等资源,所以对于进程和线程,我们可以这样理解:
1).当进程只有一个线程时,我们可以认为进程就是线程
2).当进程中有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源,这些资源在同一个进程中的线程上下文切换时是不需要修改的
3).线程也有自己的私有数据, 比如栈和寄存器等,这些在进行上下文切换时是需要保存的
由此,线程上下文切换分为两种情况:
1).两个线程不属于同一进程, 这种情况上下文切换和进程的上下文切换是一样的 2).同一进程中的线程间切换, 由于同属于一个进程, 所以切换时虚拟内存和全局变量等共享资源保持不动, 只需要切换线程的私有数据等不共享的数据
我们可以看出: 同一进程内的线程切换要比进程间切换消耗更少资源, 这也是多线程的优势所在
中断上下文切换 为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行, 转而调用中断处理程序,响应设备事件。 在打断其他进程时,就需要将进程当前的状态保存下来,这样在中断结束后,进程仍然可以从原来的状态恢复运行。
中断上下文切换和进程的上下文切换不太一样,它并不涉及到进程的用户态. 因此,即便是中断打断了一个正在用户态的进程,也无需保存和恢复用户态资源.
中断上下文其实只包括内核态中断服务程序执行所必需的状态,包括CPU寄存器,内核堆栈,硬件中断参数等.
由于中断会打断正常程序的执行和调度, 所以大部分中断处理程序都短小精悍, 以便尽可能快的执行结束.
===============================================================
软中断
Linux按照特权等级,把进程的运行空间分为内核空间和用户空间, 分别对应CPU的ring0和ring3.
• 内核空间(ring0)具有最高权限,可以直接访问所有资源
• 用户空间(ring3)只能访问有限资源, 不能直接访问硬件设备. 如果想要访问硬件, 必须通过系统调用陷入到内核中才可以.
换个角度看,进程既能运行在用户空间,也能运行在内核空间.
当进程运行在用户空间时,我们称之为进程的用户态, 当进程运行在内核空间时,则被称为进程的内核态.
从用户态到内核态的转变,需要通过系统调用来完成.如查看文件内容的过程如下:
打开文件 open() - 读取文件 read() - 输出内容 write() - 关闭文件 close()
在系统调用的过程中也有上下文切换(两次):
用户态 – 内核态 – 用户态
不过在系统调用的过程中并不会涉及到虚拟内存等用户态资源,也不会切换进程,一直是同一个进程在运行.
故我们通常把系统调用的过程称为特权模式切换,而不是上下文切换.实际上在系统调用的过程中上下文切换也是不可避免的.
总结:
CPU 上下文切换,是保证 Linux 系统正常工作的核心功能之一,一般情况下不需要我们特别关注
过多的上下文切换,会把 CPU 时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,从而导致系统的整体性能大幅下降