CPU上下文切换 CPU的调度策略

CPU上下文切换

就是先把前一个任务的CPU上下文(也就是CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文,到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

根据任务的不同,CPU的上下文切换就可以分为几个不同的,常见也就是进程上下文切换,线程上下文切换以及中断上下文切换。

Linux 按照特权等级,把进程的运行空间分为内核空间和用户空间。


内核空间(Ring 0)具有最高权限,可以直接访问所有资源
用户空间(Ring 3)只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。

进程上下文切换,是指从一个进程切换到另一个进程运行。
而系统调用过程中一直是同一个进程在运行。
所以,系统调用过程通常称为特权模式切换,而不是上下文切换。但实际上系统调用过程中,CPU的上下文切换还是无法避免的。

进程上下文切换

是指从一个进程切换到另一个进程运行。
而系统调用过程中一直是同一个进程在运行。
所以,系统调用过程通常称为特权模式切换,而不是上下文切换。但实际上系统调用过程中,CPU的上下文切换还是无法避免的。

线程上下文切换

线程与进程最大的区别在与,线程是调度的基本单位,而进程则是资源拥有的基本单位

所谓内核中的任务调用,实际上的调度对象是线程;而进程只是给线程提供了虚拟内存、全局变量等资源。所以,对于现场和进程。

当进程只有一个线程时,可以认为进程就等于线程。
当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。
另外,线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。
这么一来,线程的上下文切换其实就可以分为两种情况:
第一种,前后俩个线程属于不同进程,此时,由于资源不共享,所以切换过程就跟进程上下文切换是一样的。
第二种,前后两个线程属于同一个进程,此时,应为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据,寄存器等不共享的数据。

虽然同为上下文切换,但同进程内的线程切换,要比多进程间切换消耗更少的资源,而这,也正是多线程代替多进程的一个优势。

中断上下文切换

中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。

硬件中断

由与系统相连的外设(比如网卡 硬盘 键盘等)自动产生的. 每个设备或设备集都有他自己的IRQ(中断请求), 基于IRQ, CPU可以将相应的请求分发到相应的硬件驱动上(注: 硬件驱动通常是内核中的一个子程序, 而不是一个独立的进程).处理中断的驱动是需要运行在CPU上的, 因此, 当中断产生时, CPU会暂时停止当前程序的程序转而执行中断请求.

软件中断

是一条CPU指令,用以自陷一个中断。由于软中断指令通常要运行一个切换CPU至内核态(Kernel Mode/Ring 0)的子例程,它常被用作实现系统调用(System call)。

1. 通过INT 0x80中断方式进入系统调用
在 2.6以前的 Linux 2.4 内核中,用户态 Ring3 代码请求内核态 Ring0 代码完成某些功能是通过系统调用完成的,而系统调用的是通过软中断指令(int 0x80) 实现的。在 x86 保护模式中,处理 INT 中断指令时
    1) CPU 首先从中断描述表 IDT 取出对应的门描述符
    2) 判断门描述符的种类
    3) 检查门描述符的级别 DPL 和 INT 指令调用者的级别 CPL,当 CPL<=DPL 也就是说 INT 调用者级别高于描述符指定级别时,才能成功调用
    4) 根据描述符的内容,进行压栈、跳转、权限级别提升
    5) 内核代码执行完毕之后,调用 IRET 指令返回,IRET 指令恢复用户栈,并跳转会低级别的代码 
/*
在发生系统调用,由 Ring3 进入 Ring0 的这个过程浪费了不少的 CPU 周期,例如,系统调用必然需要由 Ring3 进入 Ring0,权限提升之前和之后的级别是固定的,CPL 肯定是 3,而 INT 80 的 DPL 肯定也是 3,这样 CPU 检查门描述符的 DPL 和调用者的 CPL 就是完全没必要。正是由于如此,Intel x86 CPU 从 PII 300(Family 6,Model 3,Stepping 3)之后,开始支持新的系统调用指令 sysenter/sysexit
*/

2. 通过sysenter指令方式直接进入系统调用
sysenter 指令用于由 Ring3 进入 Ring0,SYSEXIT 指令用于由 Ring0 返回 Ring3。由于没有特权级别检查的处理,也没有压栈的操作,所以执行速度比 INT n/IRET 快了不少。
sysenter和sysexit都是CPU原生支持的指令集


 

CPU的调度策略 

先进先出算法FIFO((First In First Out)

按照进程就绪的先后顺序来使用CPU,处理器被分配给最先进入就绪队列的进程,进程一单分到CPU的使用权,就一直执行到晋城结束或阻塞时才结束。这种进程按照时间顺序来使用,非抢占内存的方式,公平公正,实现起来也相对简单;但是算法的实际效果不佳,比如在长进程后的短进程,必须等到长进程执行完毕后才能执行,不利于有效的提升用户体验。再形象化一点,有3个进程p1,p2,p3,执行期分别为30,20,10,当他们分别进入就绪队列时,对于p1,p2,p3的周转时间分别为30,50,60,平均的周转时间是46.7,可见效率确实不高。

短进程优先算法SCBF(Shortest CPU Burst First)

先给每个进程都设置一个优先级,根据比较优先级来确定下一个执行的进程;将一些相对短的进程优先级适当提高,该算法虽然可获得较好的调度性能,用户体验也有所提升,但是难以准确的知道下一个CPU的执行期,只能根据每一个进程的执行历史来预测;不知道进程的具体执行时间,他的优先级还有可能继续改变,所以仍然有所缺陷。

时间片轮转调度算法RR(Round Robin)

分配给调度上CPU的进程,确定了允许该进程运行的时间长度。每个进程会被分配一个时间片,在这个时间片的时间段内,允许进程运行;如果在时间片结束时该进程还在运行,就会剥夺该进程得而CPU并分配给另一个进程;如果该进程在时间片结束前终止或者阻塞,则CPU会立即完成任务并进行切换。这种算法有利于交互式计算,响应的速度快,但是由于进程的切换,时间片轮转法要花费较多的内存开销,而且对于彼此进程间相差较大的有利,而对于进程大小相似或相同的则不利。

虚拟轮转法(Virtual RR)

这种算法基于时间片轮转法进行改进,能够解决在CPU调度中对于进程大小相似的进程集的不利性,主要特点就是设置了一个辅助的队列,每一个进程在执行完毕一个时间片后,就进入辅助队列,CPU在进行调度是总是先来检查辅助队列是否为空队列,若不为空则优先执行辅助队列中的进程,直到辅助队列为空,再调度就绪队列中的进程。

优先级算法PSA(Priority Scheduling Algorithm)

给每一个进程一个优先级,优先级越高的事件越紧急,应该先执行,一般来说,系统进程优先级高于用户进程,前台进程优先级高于后台进程,操作系统更偏向于I/O类型的进程。这种算法实现起来简单,但是缺乏公平性,很可能导致优先级低的进程产生饥饿现象,会造成优先级反转的问题,就是一个低优先级进程持有一个高优先级进程所需要的资源,使得高优先级进程等待低优先级进程运行完毕后再运行。

 

参考1:https://blog.csdn.net/weixin_44390145/article/details/88562778
参考2:https://www.jianshu.com/p/4393a4537eca
参考3:https://www.cnblogs.com/LittleHann/p/4111692.html

 

posted @ 2021-10-30 17:34  天天代码码天天  阅读(26)  评论(0编辑  收藏  举报  来源