CPU上下文切换
? 我们经常说的平均负载和cpu升高没有直接的关系,在不同的场景cpu升高会导致系统负载,但是系统负载不一定是cpu升高导致的。
一、系统负载过高的三种场景
- cpu密集型进程,使用大量cpu会导致平均负载升高,此时这两者是一致的。
- io密集型进程,等待io也会导致平均负载升高,但cpu不一定很高。
- 大量等待cpu的进程调度也会导致平均负载升高,此时cpu使用率也会比较高。
? 大量进程竞争cpu(也就是上面的第三个场景),往往是被忽略的,cpu虽然没有使用,只是在竞争,也会发生负载吗?
? 我们都知道linux是一个多任务操作系统,它支持远大于cpu数量的任务同时运行,当然这些任务不是同时运行,而是系统在很短时间内,将cpu轮流分配给它们,造成多任务同时运行的错觉。而每个任务运行前,cpu需要知道任务从哪里加载、又从哪里开始运行,也就是说,需要系统事先帮它设置好cpu寄存器和程序计数器。
二、CPU寄存器和程序计数器
1. cpu寄存器:
? cpu内置的容量小、但速度极快的内存。
2. 程序计数器:
? 是用来存储cpu正在执行的指令位置、或者即将执行的下一条指令位置。他们都是cpu在运行任何任务前,必须的依赖环境,因此也被叫做cpu上下文。
3. cpu上下文切换
? cpu上下文切换:就是先前一个任务的cpu上下文(也就是CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
? 而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就保证原来任务的状态不受影响,让任务看起来还是连续运行。
? 根据任务不同,cpu的上下文切换就可以分为不同的几个场景,进程上下文切换,线程上下文切换以及中断上下文切换。
3.1. 进程上下文切换
? linux安装特权等级,把进程的运行空间分为内核空间和用户空间
![]
- 内核空间(Ring0)具有最高权限,可以直接访问所有资源;
- 用户空间(Ring3)只能访问受限权限,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问特权资源。
? 所以,进程既可以在用户空间运行又可以在内核空间运行,在用户空间运行时,称为用户态。在内核空间运行时,称为内核态。
3.1.1. 系统调用是否发生cpu上下文切换
- 系统调用:cpu寄存器里面原来的用户态的指令位置,需要先保存起来。接着,为了执行内核态代码,cpu寄存器需要更新为内核态指令的新位置。最后,才跳转到内核态运行内核任务。
- 系统调用结束后:cpu寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以,一次系统调用,其实发送了两次cpu上下文切换。
- 系统调用过程中,并不会涉及到虚拟内存等进程用户态的资源,也不会切换进程。所以,系统调用通常称为特权模式切换,但实际上,系统调用也无法避免cpu上下文切换。
3.1.2. 进程上下文切换跟系统调用区别
? 进程是由内核来管理和调度的,进程的切换只能发生在内核态,所以,进程的上下文不仅包括了用户态的虚拟内存,用户栈,全局变量等资源。还包括了内核栈、寄存器等内核空间的状态。
? 因此,进程的切换比系统调用多了一步:在保存当前进程的内核状态和cpu寄存器之前,需要先把用户态的虚拟内存,用户栈保存下来;在加载了下一进程的内核态后,需要刷新进程的虚拟内核和用户栈。
? 保存上下文和恢复上下文的进程并不是免费的,需要内存在cpu上运行才能完成。
? 每次上下文切换都需要几十纳秒到微妙的cpu时间。这个时间还是相当可观。在进程上下文切换次数较多的情况下,很容易导致cpu将大量时间耗费在寄存器、内核栈以及虚拟内存等资源的保存和恢复上,进而大大缩短了真正运行进程的时间。这也是导致平均负载升高的重要因素。
? linux通过(tlb)来管理虚拟内存和物理内存直接的映射关系,当虚拟内存更新后,tlb也要更新,内存访问随之变慢。特别是在多处理器系统上,缓存被多个处理器共享,刷新缓存不仅影响当前处理器的进程,还会影响共享缓存的其他处理器的进程。
3.1.3. 进程什么时候切换
? 进程执行完终止了,会释放cpu,这个时候cpu从就绪队列里面拿一个新的进程运行,还有其他的一些非正常场景也会导致进程切换。
- 时间片耗尽:为了保证所以进程可以得到公平调度,CPU时间被划分成一段段的时间片,这些时间片轮流分配给各个进程。当某个时间片耗尽了,就会被系统挂起,切换到其他等待cpu的进程运行。
- 系统资源不足(如:内存不足):这个时候进程会被挂起,并由系统调度其他进程运行。
- 进程主动挂起: 当进程通过睡眠函数sleep这样的方式将自己主动挂起时。
- 高优先级进程:当有优先级高的进程出现时,为了保证优先级高的进程运行,当前进程会被系统挂起
- 硬件中断时:当发生硬件中断时,Cpu上的进程会被中断挂起,转而执行内核中的中断服务程序。
3.2. 线程上下文切换
? 线程和进程最大的区别在于,线程是调度的基本单位,而进程则是资源拥有的基本单位。所谓内核中的任务调度,实际上的调度对象是线程;而进程只是给线程提供了虚拟内存、全局变量等资源。所以,对于线程和进程可以理解为:
- 当进程只有一个线程时,可以任务进程就是线程。
- 当进程拥有多个线程时,这些线程共享相同的虚拟内存、全局变量等资源。这些资源在上下文切换时是不需要修改的。
- 另外,线程也有自己的私有数据,如:栈和寄存器等,这些在上下文切换时也是需要保存的。
? 所以说:
? 当两个线程不属于同一进程时,线程上下文切换时。因为资源不共享,所以切换就相当于时进程间切换。
? 当两个线程属于同一进程时,线程上下文切换时。因为资源共享,只需要切换线程私有数据。
? 进程间切换消耗的资源多于线程间切换,所以就出现了多线程代替多进程的优势。
3.3. 中断上下文切换
? 为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。在打断其他进程时,就需要将进程当前状态保存起来。
? 跟进程上下文切换不同是,中断上下文切换不会涉及到进程的用户态。所以,即便中断过程打断了一个用户态的进程,也不需要保存和恢复进程的虚拟内存。全局变量等用户态资源。中断上下文,其实只包括内核态中断服务程序执行必须的状态,包括cpu寄存器、内核堆栈、硬件中断参数等。
? 对同一个cup来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上线文切换同时发生。所以大部分中断处理程序都短小精悍,以便尽快的执行结束。
? 总结
- cpu上下文切换,时保持Linux系统正常运行的核心功能之一,一般情况不需要我们特别关注。
- 过多的上下文切换,会把cpu时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,从而缩短了进程真正运行的时间,导致系统性能下降。
原文地址:https://blog.51cto.com/12924846/2406421