Linux2.6 O(1)调度器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rikeyone/article/details/87366809

O(1)调度器存在于2.4-2.6版本的内核中。

普通进程和实时进程

Linux中把进程分为普通进程和实时进程两种,实时进程对于系统的响应时间要求很高,因此实时进程的优先级都是要高于普通进程的。

静态和动态优先级

静态和动态优先级的范围都是[100~139],这两个值对于普通进程来说非常重要。
静态优先级(值越小)越高,基本时间片越长,静态优先级越低(值越大),基本时间片越短。这是有一个固定公式进行换算出来的。
动态优先级的生成是以静态优先级为基础,再加上相应的惩罚或奖励(bonus)。这个bonus并不是随机的产生,而是对进程过去的平均睡眠时间做出的相应的惩罚或奖励。

bonus值来表示根据平均睡眠时间对动态优先级进行调整的趋势,比如是增加还是降低动态优先级。bonus值大于5表示要提高动态优先级,bonus值小于5表示要降低动态优先级。
bonus值和平均睡眠时间之间的关系是固定的,因此最终影响动态优先级的因素有两个:平均睡眠时间和静态优先级。
在这里插入图片描述
静态优先级决定了进程的时间片长度,动态优先级决定了进程调度运行时的优先顺序。笼统的说调度程序在选择一个进程运行时总是会优先运行动态优先级高的进程。

实时优先级

实时进程的运行优先级都要高于普通进程,对于实时进程,内核专门定义了的实时优先级来表示实时进程的运行优先顺序。它的范围是[0-99]。可以看到它的范围和上面的静态、动态优先级刚好区分开了,而且始终大于普通进程的优先级。因此只有当runqueue中没有实时进程的情况下,普通进程才能够获得调度。

时间片

一个操作系统要支持多进程、多线程的话,必须要保证进程可以同时进行执行,那么做到这种功能就必须要求CPU时间能够被多个进程共享。每个进程分配一小段时间,这段时间就叫时间片,进程的时间片执行完了就需要选择一个其他的进程去运行,这种方式就可以保证进程得到并行处理,不同的进程都能得到很好的响应。时间片就是tick的数量,每个tick到来都要执行一个时钟中断处理程序去更新使用的时间片,当时间片用完后就执行进程切换。

runqueue运行队列

运行队列中存放的是所有属于running状态的进程,而其中还区分为两组,一个为活动链表active,存放要被执行的进程,一个为过期链表expired,存放时间片用尽的链表。当一个进程的时间片用完,那么是把它存放到active链表还是过期链表,这是由调度器算法决定的,它会根据一系列规则判断是否要放入过期链表中。
之所以这样区分两个链表的目的,是为了保证所有处于运行队列的进程都有机会得到执行,如果仅仅简单按照优先级去执行,那么很可能会导致低优先级的进程长时间得不到执行从而响应延迟。当然这种runqueue运行队列的组织方式也是跟调度器有关,新版的其他调度器已经不在按照这两种链表的方式管理进程了。

进程调度类型

Linux中定义了多种调度类型,比如如下的3种:

  • SCHED_FIFO:先入先出的实时进程
  • SCHED_RR:时间片轮转的实时进程
  • SCHED_NORMAL:普通的分时进程
    对于SCHED_FIFO类型的进程,是不用关心时间片的,因为它的执行是顺序的,如果没有更高实时优先级的进程,当前进程会执行完毕后才会顺序执行后面的进程,不会分时复用。
    对于SCHED_RR类型的进程,首先也是按照实时优先级进行区分,对于同一优先级的进程,是按照时间片轮转执行的。当然时间片使用完后的进程依然存在于active活动链表中,而不会涉及expired过期链表。

调度算法

调度算法的设计一定要平衡如下两个因素:一个是要保证高优先级的进程能够得到快速的响应,另一个也要保证低优先级的进程能够得到执行,不能出现进程饥饿的现象。调度算法的目的就是管理runqueue中的进程,以及寻找下一个被运行的进程,调度算法需要考虑很多类型的进程。

如何快速的查找到下一个要运行的进程?
如何计算和更新各个进程的优先级参量?

这两个是调度算法核心要处理的问题,调度器的发展和优化也大都是根据这两个问题点去做的。比如后来出现的CFS调度器等。
对于这种早期的调度器,选择一个进程的依据就是优先级,把进程按照两大类去管理,一个是active活动队列,一个是expired过期队列,前面已经有所介绍,在每个队列中又按照优先级分为很多个不同的链表中,在O(1)调度器中,一共分为140个链表,因为优先级(实时+动态)一共有0-139这140个。每次查找进程时,就从优先级最高的非空链表中挑出一个去运行,这样就是一个简单的挑选逻辑了。此时还有一个问题,如果每次都挑选高优先级的进程运行,那么低优先级的进程岂不是都要等到高优先级进程运行结束才能运行。其实不然,这也是为什么加入活动队列和过期队列的原因,为了保证所有优先级的进程都有机会得到运行,当一个高优先级的进程时间片运行完后,可能会被放置到expired队列中,这样后面的低优先级进程就可以被选择运行了。当active活动队列中的进程都运行完了,在翻转active队列和expired队列,重新去选择运行。

O(1)调度器相对于更早期的调度器,在查找下一个运行进程时做了一些优化,最早起的进程查找需要轮流查看每个优先级的进程,这随着进程的增加效率变得很低,而现在除了有140的不同优先级的链表,还包含了一个bitmap,这个bitmap表示哪个优先级链表中有可运行进程,然后直接找到对应优先级链表的第一个进程选为运行进程。这样查找过程最大也就是优先级的个数,而不会随着进程个数增加而变得效率更低。

  • 针对交互式的特殊处理
    除了把进程分为实时进程和普通进程以外,还有一种按照业务划分进程的方式,比如交互式进程,它也同时可能是一个普通进程,那么对于这类进程,如果时间片用完后不会立马放入过期队列,而是重新放回活动队列,因为该算法倾向于提升交互进程的运行优先级。

  • 针对进程饥饿的特殊处理
    当活动队列里的进程时间片用完后,实际上可能并不会立刻放到过期队列,而是会判断过期队列中的第一个进程等待时间是否超过一定值,如果超过了那么就认为已经处于饥饿状态,需要把活动队列中时间片用的进程都放入到过期队列。此时包括交互进程也一视同仁,当所有活动队列中的进程都运行完了,再翻转两个队列,从而重新进行选择运行。

  • 动态优先级的更新
    在每个tick到来是,会运行scheduler_tick函数,在该函数中会对普通进程的动态优先级进行调整,按照一个固定的公式重新计算,依据静态优先级以及平均睡眠时间。

  • 平均睡眠时间的计算
    每次在进程唤醒时,都会重新计算平均睡眠时间。

O(1)调度器关键特点

1.区分处理实时进程和普通进程
2.动态优先级的调整
3.平均睡眠时间的计算和更新
4.根据睡眠时间长短区分交互式进程和批处理进程。

猜你喜欢

转载自blog.csdn.net/rikeyone/article/details/87366809
今日推荐