进程调度与Linux操作系统的调度机制

进程调度

CPU调度

通常来说,操作系统是对计算机软硬件资源进行管理。

典型的资源有内存和物理设备。CPU也可以认为是一个资源,调度器可以临时分配一个任务在上面执行(单位是时间片)。

内核必须提供一种方法, 在各个进程之间尽可能公平地共享CPU时间, 而同时又要考虑不同的任务优先级.

调度器的一个重要目标是有效地分配 CPU 时间片,同时提供很好的用户体验。调度器还需要面对一些互相冲突的目标,例如既要为关键实时任务最小化响应时间, 又要最大限度地提高 CPU 的总体利用率.

调度器的一般原理是, 按所需分配的计算能力, 向系统中每个进程提供最大的公正性, 或者从另外一个角度上说, 他试图确保没有进程被亏待.

当我们将一个进程从就绪态切换到运行态,这个进程就可以获取CPU去执行,当我们将一个进程从运行态切换成为其他态,该进程便无法继续向下执行。那么我们什么时候将进程切换到运行态,什么时候将进程切换到其他态,这便是CPU调度需要解决的事情

  • 为什么需要调度:CPU的资源是有限的,我们需要通过调度进程对CPU的使用来达到一些可以量化的指标(EG:响应速率,吞吐量,公平性…)

调度策略

  • 进程响应时间尽可能快
  • 后台作业吞吐量尽可能高
  • 尽可能避免进程饥饿
  • 低优先级和高优先级进程需要尽可能调和

当然很显然我们很难同时最优的满足这些目标(EG:响应时间和吞吐量),因此很多时候需要操作系统进行权衡取舍,是选择平衡的对待这些目标,使得这些目标的完成度都在一个可以被接受的水准,还是当有特殊情况时需要尽可能的完成某一目标,这都是操作系统在进行CPU调度的时候需要关心的事情

涉及调度问题时进程的分类方式

  1. 传统的分类方式:
    • IO密集型:频繁的使用IO设备,并花费很多的时间等待IO的完成,
    • 计算密集型:花费大量的CPU时间进逻辑运算
  2. 另一种分类方法
    • 交互式进程:此类进程经常与用户交互,因此花费很多时间等待键盘和鼠标的操作,当接受到了用户的输入后,需要立即的将进程唤醒,给予用户尽可能快的响应速度
    • 批处理进程:此类进程不必与用户交互,通常都在后台运行,因此对于这样的进程其实不需要很快的响应时间,因此在调度上通常优先级都比较低
    • 实时进程:此类进程具有很强的调度需求,所谓实时就是必须或者竟可能要在规定的时间内完成既定的目标,他们的响应要求比较的高

抢占式执行与非抢占式执行

​ 所谓抢占式执行即当有高优先级进程就绪后是否能剥夺正在使用CPU资源的进程使用CPU的权利,而非抢占式执行则是当一个进程在获取到CPU之后便会一直占用CPU直到进程的结束,当然显然非抢占执行有一个不可忽视的弊端:当获取CPU的进程陷入阻塞时,其依旧会占用CPU,因此CPU的使用率就很低,所在现代操作系统基本都是支持抢占式执行的

  • 按照抢占时发生的位置不同主要分为两种类型的抢占式执行【具体的内容在LinuxCPU调度更加深入的解析】:
    1. 位于用户态时发生的抢占式执行
    2. 当进程通过处于内核态中(系统调用),时发生的抢占式执行,当然在内核中也由于某些原因可能会是不能被进行抢占执行(具体之后分析)

基础调度算法

调度算法的一些量化指标:

  1. CPU使用率
  2. 吞度量
  3. 周转时间:一个进程从初始化到结束所花费的时间
  4. 等待时间:进程在就绪队列中的总时间
  5. 响应时间:一个请求从提交到响应的时间
  6. 公平性:现在操作系统上会运行非常多的进程,那么是否能很好的让这些进程都获取到CPU资源去运行自己的程序,不造成进程饥饿
  • 进程饥饿:进程饥饿指进程等待时间给进程的推进和响应带来明显的影响,这样的情况被称为进程饥饿,当饥饿的进程达到一定的程度,即使在所赋予的任务在完成时已经不具备意义的时候称为饥饿死亡

  • 先来先服务(FIFO):对于将被调度的进程来说,按照谁先来,先调度

    • 实现:链表,按照时间顺序排布

    • 优点:实现简单

    • 平均等待时间长,可能会出现进程饥饿的问题

    • 该算法的实现没有考虑抢占,因此响应时间也会收到很大影响

  • 短作业优先:将执行时间短的进程优先调度,短作业优先的调度算法有抢占式的也有非抢占式的

    • 实现:首先会根据进程的执行时间进行排序,优先调度执行时间最小的进程
    • 优点:最优的平均等待时间
    • 缺点:连续的短作业会导致长作业进程饥饿,需要预知进程的执行时间
      • 如何预知进程执行时间?预估执行时间
    • 抢占式短作业优先:也被称为最少剩余时间优先,当正在运行的进程的执行时间 > 某个后来的进程所需的执行时间,则会发生抢占,将运行的进程从运行态变为就绪态挂回就绪等待队列,让执行时间更少的进程获取CPU资源
  • 最高响应比优先:之前的调度算法都没有考虑进程等待时间的问题,因此之前的调度算法都可能出现进程饥饿的问题,最高响应比优先算法是在短作业优先的基础上进行改良,其同时兼顾等待时间和执行性时间两个方面

    • 实现:R = (w + s)/ s,w为进程的等待时间,s为进程执行时间,调度时选择R值最大的进行调度,因此随着一个进程的等待时间变长其被调度的优先级也会提高
    • 优点:饥饿现象能得到有效的缓解
    • 缺点:不支持抢占式执行,需要预估进程执行时间
  • 轮询调度算法:按照时间片让各个进程轮流的获取CPU资源

    • 按照时间片进行上下文切换
    • 优点:公平,每一个进程都有机会获取CPU资源
    • 缺点:如果划分的时间片太长,则会使得平均等待时间变长,极限情况下能退化成为FIFS算法。如果划分的时间片太小,效率会由于频繁的上下文切换收到影响
    • 时间片的选择:选择一个合适的时间片是影响轮询调度算法性能的关键,根据经验规则:维持上下文切换的开销为总开销的1%
  • 多级队列:将就绪的进程分为许多不同的队列,根据不同的队列采取不同的调度算法(EG:对于优先级高的队列采取短作业优先,对于优先级低的进程采用FIFS)

    • 优点:能根据不同进程的特点执行不同的调度算法,能够基本实现让需要高响应的进程优先响应,让批处理进程在一个低的优先级队列中
    • 缺点:1. 优先级被固定,随着程序的动态运行,其可能在刚开始作为交互式进程需要高的响应速率,但随着程序的运行,此时该进程处于计算密集的状态,此时 就并不迫切的需要高的响应速率因此就应当降低被调度的优先级。2. 可能导致低优先级的进程饥饿,解决方案:时间切片:让每个队列都能得到一个确切的能够调度其进程的CPU的总时间(EG:70%的时间分配给交互式进程与实时进程。30%的时间分配给批处理进程)
  • 多级反馈队列:多级反馈队列是对多级队列的优化,其考虑了程序运行的动态性,一个进程被调度的优先级是可能随着程序运行的阶段动态进行调整的,例如:程序在刚开始交互性特征比较明显,在IO等待结束后能在短时内完成事件处理,因此其优先级应当较高,在执行一段时间后程序的特征变为计算密集型,此时就应当降低进程的优先级

    • 实现:考虑IO密集型和计算密集型的特征区别,IO密集型通常在IO等待时间发生后能很快完成事件的响应,而计算密集型通常需要更多的CPU时间进行逻辑运算。因此我们为了能有效的区分他们可以设置一个时间阈值,如果进程任务在规定阈值内没有完成,则表明其可能大量的占用CPU进行运算,因此其会被下降到下一个优先级队列,通过这样的设计就能将计算密集型进程随着执行的时间逐渐被放在低级队列中,使得那些交互性较好的进程随着时间推移被放在优先级较高的就绪队列中去
  • 公平共享型调度算法(EG:CFS,FFS):当多人多用户共享一台计算机时需要对不同用户之间进行一个公平的CPU资源分配

    • CFS:完全公平调度算法,是目前LInux的调度算法,其能在不同级别都保证公平的CPU资源分配,具体的在LinuxCPU调度进一步讲解

优先级反转

​ 优先级反转现象最早发现与美国NASA火星探路者的操作系统总是会发生重启的问题,后来经过研究发现,其原因是一个优先级很高的进程在等待一个低优先进程的完成,而导致高优先级的进程没有在规定时间内完成,由于那个高优先级的关键进程没有被即使响应,因此操作系统认为此时处于不稳定状态,因此发生重启。而对于一个高优先级进程等待低优先级进程的完成这样的过程就被称为优先级反转

  • 导致优先级反转的一个例子:

    image-20230517110804947

    • 图中T1,T2,T3分别代表了三个优先级不同的进程,越靠上则代表其优先级越高,图中蓝色部分表示为一段临界区,T1和T3都共享这一块临界区
    • 最开始T3获取CPU资源运行值到t3时间,此时T3已经进入临界区访问,在T1出现后T3被抢占,T1开始运行直到t4时T1也想访问临界区,但此时临界区已被T3访问,因此T1开始等待,T3开始运行到t5时T2出现,T2优先级比T3高因此抢占了T3的cpu资源直到t6时刻,T3才可以接着运行临界区到t7离开临界区此时T1才能被唤醒进入临界区访问。这样就出现了优先级反转的现象,导致了一个高优先级的进程需要等待低优先级进程的结束
    • 如上我们可以发现优先级反转的持续时间取决于其他不相关任务的不可预测行为
  • 优先级反转的解决方案:

    1. 在高优先级需要低优先的临界区资源时,会将低优先级的优先级进行临时提升,来让低优先级尽快将临界区访问完毕
    2. 优先级天花板协议:操作系统在一开始便统计进程所需要的资源,然后将这些资源也顶了一个优先级:资源的优先级等于可以锁定资源的进程中优先级最高的那个进程。因此当有低优先级进程在访问到临界区后便会被提升到资源优先级,其他优先级低于资源优先级的进程就无法进行CPU的抢占,这样就可以确保一旦有进程在访问共享资源后能够快速的完成对共享资源的操作,从而确保高优先级进程不会大量的出现优先级反转
Linux的进程调度

​ 在有了基础的对于调度的认识后,我们来看看Linux具体是如何进行调度的

Linux对于进程的区分

​ Linux在对进程调度时主要将进程分为两个大类:

  1. 实时进程:实时进程需要很强的调度需求,实时进程若在规定时间未能满足则可能导致严重后果,因此实时进程需要更高的响应速率。Linux针对实时进程的调度策略比较简单且基本没有变过:将实时进程,根据每个进程的重要程度被分为不同的优先级,在进行调度时总是选取优先级最高的进程开始调度,对于实时进程通常使用FIFO或者轮询的调度策略即可

  2. 非实时进程(普通进程):Linux操作系统针对于普通进程的调度策略则是有过非常多的版本迭代,我们在之后会一一讲解。最主要的是,Linux将普通进程也划分成为了两类

    • 交互式进程:交互式进程通常是需要花费大量的时间等待某个IO事件的产生,一旦这个IO事件产生后,能够在很快的时间内完成事件的响应,通常对于交互式进程其优先级较高,因为需要对响应时间有一定的要求,不能让用户明显的感觉到系统的延迟
    • 批处理进程:此类进程通常是后台进程,一般是长期想要占据CPU进程逻辑运算,对于这种进程通常会被放在一个较低的优先级进行调度

    传统的调度器(2.6版本以前的内核),一般通过动态的调整进程的优先级,来使得交互式进程被放在高优先级的调度序列中。而在2,6版本的Linux操作系统则使用CFS(完全公平调度器)进行普通进程的调度,CFS的设计对各种调度的需求都有着更完美的支持,在之后会对CFS进行重点讲解

Linux调度器的演变历程

  • Linux2.4版本的调度器(O(N)调度器):每个进程在被创建时都赋予某个权重的时间片,时钟中断时递减当前运行进程的时间片,当前进程的时间片被用完后,他需要等待所有RUNNING态的进程将其时间片耗尽,再所有RUNNING态进程的时间片被耗尽后会重新给所有进程分配时间片

    • 对于普通进程:如何动态调整优先级来使得交互进程能被更快响应?普通进程的优先级主要由PCB块中的counter字段决定(此外还需要加上nice设定的静态优先级值),进程被创建时子进程的counter值为父进程的一半,这样就避免了,一个进程通过大量的fork去获得更多的执行机会。当所有的RUNNING进程的时间片都耗尽后,调度器将重新计算所有进程的counter值,所有的进程也包括处于睡眠态的进程。由于处于睡眠态的进程本就没有消耗完上一轮的counter值,在这一轮重新计算contuer值的时候会加上上一轮没有使用完的值,因此对于交互式进程来说,由于他进程处于IO等待的状态,且在IO事件出现后本就可以快速执行完任务,所有时间片本就剩余更多,因此其很快conter值就会被提升,从而提高进程的优先级

    • 对于实时进程的调度:实时进程的优先级是由操作系统静态设置的,其一定被普通进程优先级高,调度算法为FIFO或者时间片轮询算法

    • 缺陷:1.当系统中活跃的进程数量较多时,效率较低(O(N)的conter值修改 + O(N)的pick_next进程

      1. 对严格实时进程的支持不够好:Linux2.4版本不支持内核抢占(用户抢占和内核抢占会在后面详细分析
  • O(1调度器) :进程优先级的范围为:0 - 139,具体的对于实时进程使用0 - 99,对于普通进程使用100 - 139.因此o(1)算法为每一个优先级都设置了一个可执行队列,每一个优先级队列中的进程具有相同的优先级,此外还包括一个优先级位图,用一个位来表示一个优先级,当某个优先级的进程就绪了,则将对应的bit置1

    • O(1)调度器大体实现思路和O(N)调度器相同,对于普通进程都是动态的对进程的优先级进行修改。O(1)调度器在O(N)调度器的基础上修改了进程优先级的计算方法和pick_next的算法(选择下一个进程的算法),对于实时算法依旧和之前相同

    • 新的优先级计算方法:每次进程与生俱来(从父进程继承优先级)都有一个优先级,这个优先级被称为静态优先级,静态优先级越高(值越小),进程所获取的时间片就越长,动态优先级的改变其实是基于静态优先级为基础,在其上进行按照某种规则进行奖惩值,这个奖惩值的决定性因素体现为进程的平均睡眠时间。

      所谓平均睡眠时间(sleep_avg,位于task_struct结构中):就是为了反应一个进程他的交互性的一个重要指标。平均睡眠时间随着进程的睡眠而增长,随着进程运行而减少。如果一个进程的平均睡眠时间很大,则说明其可能长时间处于睡眠等待某个事件的到来,且在事件到来之后能快速的完成事件的响应,因此其平均睡眠事件会比较大。如果一个进程的平均睡眠时间很短,则说明其大部分时间都在占用CPU,其可能是计算密集型进程。因此平均睡眠时间是操纵系统去衡量一个进程交互性好坏的重要指标。且平均睡眠时间能很好的满足一些会进行动态变换的进程进行调度,假如进程在某个时间段交互性很强,则其平均睡眠时间就会猛涨,之后一直处于执行状态则会使得平均睡眠时间递减。因此调度器会对交互性强的进程给予奖励,对于交互性差的进程给予惩罚

    • 实时进程的优先级:实时进程的优先级由操作系统按照其重要程度静态决定,实时进程的优先级不会动态改变,且一定高于普通进程

    • 新的pick_next算法:O(N)调度在进行pick_next时会轮询一遍就绪队列,从中寻找到优先级最高的进程,这样的算法复杂度在就绪进程数量很多的时候效率不高,而O(1)调度器其O(1)体现则体现在选择下一个获取CPU进程时其效率是常数级别,不受就绪进程数量的影响。

      O(1)调度器的大体框架和O(N)类似,其思想都是在所有活跃进程的时间片都被耗尽后在重新进行新的一轮调度,因此O(1)调度器为每个CPU都维护了两个数组:还未有时间片剩余的活跃队列active数组,过期的运行队列ecpire数组数组中的元素为某一优先级队列的头指针。系统一共有140个级别,因此这两个数组的大小都为140,前100个用于实时进程,后40个用于普通进程。对于每一个优先级队列来说都是按照FIFO进行服务,对于时间片耗尽的进程则会非放在expire数组的对应优先级队列中去

      image-20230517213320445

      当我们要进行pick_next的时候,O(1)调度器不需要轮询所有的就绪队列,而是去轮询优先级位图,找到优先级最高且具有就绪的进程的队列,然后直接将进行FIFO服务

      为了提高交互式响应速度,O(1)调度器不仅通过睡眠平均值动态提高交互式进程的优先级,还有以下方法:当一个进程时间片为0时,调度器会去判断进程的类型,若是批处理进程则被直接放入expire数组,若是实时进程或者交互式进程则重置时间片并重新插入active数组进行调度,从而提高响应速度。但这样的插入也并不是无限的,为了保证expire数组中的进程不会出现进程饥饿,调度器会设置一个阈值,当实时进程和交互进程使用CPU的时间超过了这个阈值也会被放入expire数组中,当active中所有进程全部被放入expire数组中后,调度器交换expire数组和active数组开始新的一轮调度

    • O(1)调度器之所以能完成O(1)调度取决于两个因素:

      1. pick_next算法不需要便利所有的就绪队列
      2. 不需要定期的改变counter值,平均睡眠时间的修改发生在上下文切换,或者时钟中断
    • 优点:调度效率得到了提升

    • 缺点:为了达到这样的效率,一大堆难以阅读和难以维护的代码被引入内核。O(1)调度器为了区分交互进程和批处理进程,使用了很多难以理解和维护的经验公式来修正进程的优先级。

  • 完全公平的调度器(CFS)

    • 之前的调度器虽然或多或少都有考虑进程的公平性的问题,但都没有作为最重要的指标。之前的调度器最核心的理念还是动态修改优先级。而完全公平调度器则摒弃了这一理念,其将公平性作为调度时最重要的衡量指标,其不在追踪进程的平均睡眠时间,不在试图区分交互式进程和批处理进程,其将所有的进程都统一对待,这便是公平的含义。且在众多实践和测试中表明其性能也非常卓越

      CFS考虑理想状态:对于每个进程都有一个CPU对应,在同一个时刻,每一个进程都可以并发的进行执行。但事实上很难为每一个进程分配一个CPU同事进行使用,因此当一个进程获取到CPU后,其他进程就需要进行等待,这就造成了不公平。假如可运行进程有2个,其中一个进程占据CPU运行了10秒,而按照完全公平考虑,这10秒应当均匀的分配给两个进程,也就是说在公平的情况下,每个进程应当获得5秒的执行时间,而这个进程缺使用了10秒,因此CFS应当惩罚这个进程,使得下次调度竟可能取代他。当然如果每一个进程能获取CPU运行的时间都相同其实这也是一种不公平,对于那些重要的进程来说不公平,CFS弱化了优先级的概念,CFS认为那些优先级高的进程了理应获取更多的CPU使用权,所以CFS的调度策略其实是按照进程的重要程度给予每个进程运行的时间

    • CFS的就绪队列:

      与之前的Linux调度器不同,CFS完全摒弃了active,expire数组,还有链式就绪队列。CFS为每一个CPU都维护了一个以时间为键值的红黑树

      • 红黑树是一颗相对平衡二叉树,不会出现高度差大于两倍的情况
      • 红黑树在插入删除等多方面时间复杂度都较好,且不会发生大量的平衡调整

      红黑树的键值作为调度的关键,每一次调度都选择最左那个进程,也就是键值最小的那个进程

    • 红黑树的键值:红黑树的键值是虚拟运行时间(vruntime),虚拟运行时间与进程实际运行时间和进程的权重相关,在CFS中弱化了进程优先级的概念,而是强调一个进程的权重,即进程相对重要程度,一个进程权重越大则意味着器越应当被运行,则器虚拟运行时间就越小,则其就越靠近红黑树左部,就越容易被调度。那么设置的nice值又如何影响vruntime呢?在内核中通过prio_to_weight数组进行nice值和权重的转换,CFS希望nice值每低一级则进程能多获取CPU10%的运行时间

      权重的大小理论上代表着一个进程在一段时间应当获取CPU的时间,假如现有进程1:权重为1,进程2:权重2,进程3:权重3.CPU运行了6秒,则理论上进程1应该获取1秒的CPU使用时间,进程2应获取2秒的CPU使用时间,进程3应获取3秒的运行时间

      image-20230518183338806

    • vruntime的计算:虚拟运行时间是进程被调度的核心因素,那么vruntime如何被维护呢?对于nice值为0的进程,让其虚拟时钟和物理相等。每一个进程都维护了一个vruntime,每次调度时选择vruntime值最小的进行调度,vruntime在时钟中断时进行更新,每次时钟中断都会更新获取CPU资源的进程的vruntime,对于nice值为0的进程来说其更新方式为直接加上物理时间,而对于其他的则会通过某个权重公式进行计算,总之权重越高的进程其vruntime的增长速度越慢,其就能越多的被调度

    • 时钟中断:时钟中断是一种非常重要的硬件中断,其可被称为系统的脉搏,其保证了现代操作系统多进程并发执行的基础。我们的程序在编写时就没有考虑过需要主动的让出CPU资源,但是如果这样的程序需要提供24小时不间断的服务,若其一直占用CPU资源机会导致其他进程无法执行,也就没有并发可谈,因此时钟中断保证了操作系统能够进行调度的基础,且时钟中断可以作为计时使用,每一次中断就去调整vruntime和时间片,然后调度器便开始进行调度,确保了CPU不被一个进程独占

    • CFS的调度机制:每一次选择红黑树最左的结点,同时维护好vruntime与每个就绪队列的min_vruntime(队列目前最小的vruntime)

      • 具体的对于运行的进程:其vruntime的值总是稳定的增加,因此他总是在像红黑树右边移动

        对于权重越大的进程其vruntime的增长速率越慢,其越多的被调度

      • 对于阻塞睡眠的进程:其vruntime不变,而其他的可运行进程vruntime是在逐渐增大的,因此对于阻塞睡眠的进程来说,其在唤醒后在红黑树的位置就会靠左,而进程陷入阻塞的进程陷入其vruntime的增加就更加缓慢,因此其在事件到来后就能更快的被调度响应,也就满足了交互式进程的需求,因此完全公平调度器对于交互式进程也做到了公平

        一个问题:如果阻塞进程的vruntime保持不变, 而其他运行进程的 vruntime一直在推进, 那么等到休眠进程终于唤醒的时候, 它的vruntime比别人小很多, 会使它获得长时间抢占CPU的优势, 其他进程就要饿死了. 这显然是另一种形式的不公平,因此CFS是这样做的:在休眠进程被唤醒时重新设置vruntime值,以min_vruntime值为基础,给予一定的补偿,但不能补偿太多.

    • CFS对CPU的负载均衡:目前大部分机器都是多核处理机器,也就是说有多个CPU,在多核处理器中一个最重要的核心问题就在于如何进行CPU的负载均衡,所谓负载均衡就是希望每个CPU的负荷量都比较接近,不出现有的CPU特别忙,而有的CPU又特别闲,这样的情况被认为是没有合理的资源利用

      负载均衡的时机:

      1. 当一个进程要加入到就绪队列时需要选择一个负载量较轻的CPU
      2. 周期性的计算各个CPU的负载情况,有必要时进行进程的迁移
      3. 当前CPU的就绪队列为空,主动获取别的CPU的就绪队列的进程来运行

      在多核CPU的系统上,不同的CPU的负载不一样,有的CPU更忙一些,而每个CPU都有自己的运行队列,每个队列中的进程的vruntime也走得有快有慢,比如我们对比每个运行队列的min_vruntime值,都会有不同, 如果一个进程从min_vruntime更小的CPU (A) 上迁移到min_vruntime更大的CPU (B) 上,可能就会占便宜了,因为CPU (B) 的运行队列中进程的vruntime普遍比较大,迁移过来的进程就会获得更多的CPU时间片。这显然不太公平

      同样的问题出现在刚创建的进程上, 还没有投入运行, 没有加入到某个就绪队列中, 它以某个就绪队列的min_vruntime为基准设置了虚拟运行时间, 但是进程不一定在当前CPU上运行, 即新创建的进程应该是可以被迁移的.

      CFS是这样做的:

      • 当进程从一个CPU的运行队列中出来 的时候,它的vruntime要减去队列的min_vruntime值

      • 而当进程加入另一个CPU的运行队列) 时,它的vruntime要加上该队列的min_vruntime值

      • 当进程刚刚创建以某个CPU的某个就绪队列的min_vruntime为基准设置其虚拟运行时间后,也要减去队列的min_vruntime值

抢占式调度

​ 为什么需要抢占式调度呢?其实本质原因是为了使得CPU资源的公平分配,对于那些权重高的进程,他们可能需要更多的CPU资源,而如果我们只期望进程主动放弃CPU资源那么他们可能就会永远的被执行下去直到结束

为了预防这种情况,内核提供了一个need_resched标志来标识进程是否需要重新执行一次调度。当调度器认为当前占用CPU的进程需要被调度(EG:vruntime较大),则会给进程打上,这样在下一次从内核态回到用户态时(EG:时钟中断),调度器就会检查当前使用CPU资源的进程是否需要调度如果设置了TIF_NEED_RESCHED标识则进行调度

  • 设置TIF_NEED_RESCHED标识的时机:
    1. 周期性调度处理时(产生时钟中断)
    2. 唤醒一个进程时

用户抢占

​ 当内核即将返回用户空间时, 内核会检查need_resched是否设置,如果设置, 则调用schedule(),此时,发生用户抢占.

  • 用户抢占发生的时机:
    1. 完成系统调用即将返回用户态时
    2. 从中断处理返回用户态时
  • 如上我们可以知晓用户抢占发生在内核已经完成了其既定的功能现在准备返回用户空间时发生,其具体的可能被抢占的情况有:
    1. 时钟中断处理例程检查当前任务的时间片,当进程设置用户进程的nice值时,可能会使高优先级的任务进入就绪状态;
    2. 改变任务的优先级时,可能会使高优先级的任务进入就绪状态;
    3. 新建一个任务时,可能会使高优先级的任务进入就绪状态;
    4. 对CPU进行负载均衡时,当前任务可能需要放到另外一个CPU上运行的时间片消耗完时

内核抢占

​ 内核抢占与用户抢占的一个核心区别在于,发生用户抢占时内核一定已经完成了其既定的功能,而内核抢占的含义则是:当一个进程通过系统调用进入内核运行后,由于有更高优先级的进程出现,内核在还没有完成系统调用的功能时,发生了进程切换

  • 为什么需要内核抢占?在早期的2.6版本以前的Linux操作系统并不支持内核抢占,这意味着,当一个进程通过系统调用陷入内核态以后,只要其没有阻塞睡眠,则不能被抢占,则可能使得一个很高优先级的进程需要等待低优先级进程从内核中返回时才能抢夺,但如果某些进程的实时性要求非常高(EG:优先级反转的例子),其若在规定时间内没有完成既定的功能,则可能会导致一个灾难性事件产生。但由于不支持内核抢夺,这个实时进程需要等待进程从内核返回时才能抢夺,如果恰好内核正在执行一些耗时的操作,例如:磁盘IO,那么就可能会导致这样的实时进程无法得到满足。因此在2.6以后的LInux操作系统都支持内核抢夺
  • 内核抢占的时机:
    1. 从硬件中断返回内核时
    2. 当内核再一次可以被抢占时
    3. 如果内核中的任务,即通过系统调用进入内核后被阻塞,则也会发生内核抢占
  • 不可发生内核抢占的情况:并非所有的时候都能进行内核抢占
    1. 内核正在进行硬件中断的处理
    2. 有进程通过加锁访问临界区,需要保证并发的安全性
    3. 内核正在执行进程调度,内核已经在进行进程调度了,抢占就是想进行调度,不必多此一举
  • Linux操作系统如何支持内核抢占?主要通过一个计数器也就是抢占锁preempt_count
    1. 在中断入口处加锁,preempt_count + 1,代表此时内核不可抢占,在中断返回处 - 1,若preempt_count为0表示当前内核可以抢夺
    2. 当通过自旋锁,读写锁等进行加锁时preemptcount变量加1,以禁止内核抢占;在释放锁时preemptcount变量减1
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值