进程调度

  1. 在多进程、多线程并发的环境里,从概念上看,有多个进程或者多个线程在同时执行,具体到单个CPU级别,实际上任何时刻只能有一个进程或者线程处于执行状态;因此OS需要决定哪个进程执行,哪些进程等待,也就是进程的调度。
    一、调度的目标
    1、首先要区分程序使用CPU的三种模式:IO密集型、计算密集型和平衡型。对于IO密集型程序来说,响应时间非常重要;对于CPU密集型来说,CPU的周转时间就比较重要;对于平衡型程序来说,响应和周转之间的平衡是最重要的。
    2、CPU的调度就是要达到极小化平均响应时间、极大化系统吞吐率、保持系统各个功能部件均处于繁忙状态和提供某种公平的机制。
    3、对于实时系统来说,调度的目标就是要达到截止时间前完成所应该完成的任务和提供性能的可预测性。

    二、调度算法

1、FCFS(First come first serve),或者称为FIFO算法,先来先处理。这个算法的优点是简单,实现容易,并且似乎公平;缺点在于短的任务有可能变的非常慢,因为其前面的任务占用很长时间,造成了平均响应时间非常慢。

2、时间片轮询算法,这是对FIFO算法的改进,目的是改善短程序(运行时间短)的响应时间,其方法就是周期性地进行进程切换。这个算法的关键点在于时间片的选择,时间片过大,那么轮转就越接近FIFO,如果太小,进程切换的开销大于执行程序的开销,从而降低了系统效率。因此选择合适的时间片就非常重要。选择时间片的两个需要考虑的因素:一次进程切换所使用的系统消耗以及我们能接受的整个系统消耗、系统运行的进程数。
时间片轮询看上起非常公平,并且响应时间非常好,然而时间片轮转并不能保证系统的响应时间总是比FIFO短,这很大程度上取决于时间片大小的选择,以及这个大小与进程运行时间的相互关系。

3、STCF算法(Short time to complete first),顾名思义就是短任务优先算法。这种算法的核心就是所有的程序都有一个优先级,短任务的优先级比长任务的高,而OS总是安排优先级高的进程运行。
STCF又分为两类:非抢占式和抢占式。非抢占式STCF就是让已经在CPU上运行的程序执行到结束或者阻塞,然后在所有的就绪进程中选择执行时间最短的来执行;而抢占式STCF就不是这样,在每进来一个新的进程时,就对所有进程(包括正在CPU上执行的进程)进行检查,谁的执行时间短,就运行谁。

STCF总是能提供最优的响应时间,然而它也有缺点,第一可能造成长任务的程序无法得到CPU时间而饥饿,因为OS总是优先执行短任务;其次,关键问题在于我们怎么知道程序的运行时间,怎么预测某个进程需要的执行时间?通常有两个办法:使用启发式方法估算(例如根据程序大小估算),或者将程序执行一遍后记录其所用的CPU时间,在以后的执行过程中就可以根据这个测量数据来进行STCF调度。

4、优先级调度,STCF遇到的问题是长任务的程序可能饥饿,那么优先级调度算法可以通过给长任务的进程更高的优先级来解决这个问题;优先级调度遇到的问题可能是短任务的进程饥饿,这个可以通过动态调整优先级来解决。实际上动态调整优先级(称为权值)+时间片轮询的策略正是linux的进程调度策略之一的 SCHED_OTHER分时调度策略,它的调度过程如下:

(1)创建任务指定采用分时调度策略,并指定优先级nice值(-20~19)。

(2)将根据每个任务的nice值确定在cpu上的执行时间(counter)。

(3)如果没有等待资源,则将该任务加入到就绪队列中。

(4)调度程序遍历就绪队列中的任务,通过对每个任务动态优先级的计算(counter+20-nice)结果,选择计算结果最大的一个去运行,当这个时间片用完后(counter减至0)或者主动放弃cpu时,该任务将被放在就绪队列末尾(时间片用完)或等待队列(因等待资源而放弃cpu)中。

(5)此时调度程序重复上面计算过程,转到第4步。

(6)当调度程序发现所有就绪任务计算所得的权值都为不大于0时,重复第2步。

linux还有两个实时进程的调度策略:FIFO和RR,实时进程会立即抢占非实时进程。

5、显然,没有什么调度算法是毫无缺点的,因此现代OS通常都会采用混合调度算法。例如将不同的进程分为几个大类,每个大类有不同的优先级,不同大类的进程的调度取决于大类的优先级,同一个大类的进程采用时间片轮询来保证公平性。

6、其他调度算法,保证调度算法保证每个进程享用的CPU时间完全一样;彩票调度算法是一种概率调度算法,通过给进程“发彩票”的多少,来赋予不同进程不同的调用时间,彩票调度算法的优点是非常灵活,如果你给短任务发更多“彩票”,那么就类似STCF调度,如果给每个进程一样多的“彩票”,那么就类似保证调度;用户公平调度算法,是按照每个用户,而不是按照每个进程来进行公平分配CPU时间,这是为了防止贪婪用户启用了过多进程导致系统效率降低甚至停顿。

7、实时系统的调度算法,实时系统需要考虑每个具体任务的响应时间必须符合要求,在截止时间前完成。
(1)EDF调度算法,就是最早截止任务优先(Earliest deadline first)算法,也就是让最早截止的任务先做。当新的任务过来时,如果它的截止时间更靠前,那么就让新任务抢占正在执行的任务。EDF算法其实是贪心算法的一种体现。如果一组任务可以被调度(也就是所有任务的截止时间在理论上都可以得到满足),那么EDF可以满足。如果一批任务不能全部满足(全部在各自的截止时间前完成),那EDF满足的任务数最多,这就是它最优的体现。EDF其实就是抢占式的STCF,只不过将程序的执行时间换成了截止时间。EDF的缺点在于需要对每个任务的截止时间做计算并动态调整优先级,并且抢占任务也需要消耗系统资源。因此它的实际效果比理论效果差一点。

(2)RMS调度算法,EDF是动态调度算法,而RMS(rate monotonic scheduling)算法是一种静态最优算法;该算法在进行调度前先计算出所有任务的优先级,然后按照计算出来的优先级进行调度,任务执行中间既不接收新任务,也不进行优先级调整或者CPU抢占。因此它的优点是系统消耗小,缺点就是不灵活了。对于RMS算法,关键点在于判断一个任务组是否能被调度,这里有一个定律,如果一个系统的所有任务的CPU利用率都低于ln2,那么这些任务的截止时间均可以得到满足,ln2约等于0.693147,也就是此时系统还剩下有30%的CPU时间。这个证明是Liu和Kayland在1973年给出的。

三、优先级反转
1、什么是优先级反转?
优先级反转是指一个低优先级的任务持有一个被高优先级任务所需要的共享资源。高优先任务由于因资源缺乏而处于受阻状态,一直等到低优先级任务释放资源为止。而低优先级获得的CPU时间少,如果此时有优先级处于两者之间的任务,并且不需要那个共享资源,则该中优先级的任务反而超过这两个任务而获得CPU时间。如果高优先级等待资源时不是阻塞等待,而是忙循环,则可能永远无法获得资源,因为此时低优先级进程无法与高优先级进程争夺CPU时间,从而无法执行,进而无法释放资源,造成的后果就是高优先级任务无法获得资源而继续推进。

2、解决方案:
(1)设置优先级上限,给临界区一个高优先级,进入临界区的进程都将获得这个高优先级,如果其他试图进入临界区的进程的优先级都低于这个高优先级,那么优先级反转就不会发生。

(2)优先级继承,当一个高优先级进程等待一个低优先级进程持有的资源时,低优先级进程将暂时获得高优先级进程的优先级别,在释放共享资源后,低优先级进程回到原来的优先级别。嵌入式系统VxWorks就是采用这种策略。
这里还有一个八卦,1997年的美国的火星探测器(使用的就是vxworks)就遇到一个优先级反转问题引起的故障。简单说下,火星探测器有一个信息总线,有一个高优先级的总线任务负责总线数据的存取,访问总线都需要通过一个互斥锁(共享资源出现了);还有一个低优先级的,运行不是很频繁的气象搜集任务,它需要对总线写数据,也就同样需要访问互斥锁;最后还有一个中优先级的通信任务,它的运行时间比较长。平常这个系统运行毫无问题,但是有一天,在气象任务获得互斥锁往总线写数据的时候,一个中断发生导致通信任务被调度就绪,通信任务抢占了低优先级的气象任务,而无巧不成书的是,此时高优先级的总线任务正在等待气象任务写完数据归还互斥锁,但是由于通信任务抢占了CPU并且运行时间比较长,导致气象任务得不到CPU时间也无法释放互斥锁,本来是高优先级的总线任务也无法执行,总线任务无法及时执行的后果被探路者认为是一个严重错误,最后就是整个系统被重启。Vxworks允许优先级继承,然而遗憾的工程师们将这个选项关闭了。

(3)第三种方法就是使用中断禁止,通过禁止中断来保护临界区,采用此种策略的系统只有两种优先级:可抢占优先级和中断禁止优先级。前者为一般进程运行时的优先级,后者为运行于临界区的优先级。火星探路者正是由于在临界区中运行的气象任务被中断发生的通信任务所抢占才导致故障,如果有临界区的禁止中断保护,此一问题也不会发生

515876337a
采纳率:43% 11级 2013.06.19 .高级调度:又称作业调度。其主要功能是根据一定的算法,从输人的一批作业中选出若干个作业,分配必要的资源,如内存、外设等,为它建立相应的用户作业进程和为其服务的系统进程(如输人、输出进程),最后把它们的程序和数据调人内存,等待进程调度程序对其执行调度,并在作业完成后作善后处理工作。

低级调度:又称进程调度。其主要功能是根据一定的算法将CPU分派给就绪队列中的一个进程。执行低级调度功能的程序称做进程调度程序,由它实现 CPU在进程间的切换。进程调度的运行频率很高,在分时系统中往往几十毫秒就要运行一次。进程调度是操作系统中最基本的一种调度。在一般类型的操作系统中都必须有进程调度,而且它的策略的优劣直接影响整个系统的计能。

中级调度:又称交换调度。为了使内存中同时存放的进程数目不至于太多,有时就需要把某些进程从内存中移到外存上,以减少多道程序的数目,为此设立了中级调度。特别在采用虚拟存储技术的系统或分时系统中,往往增加中级调度这一级。所以中级调度的功能是在内存使用情况紧张时,将一些暂时不能运行的讲程从内存对换到外存上等待。当以后内存有足够的空闲空间时,再将合适的进程重新换人内存,等待进程调度。引人中级调度的主要目的是为了提高内存的利用率和系统吞吐量。它实际上就是存储器管理中的对换功能

作业调度和进程调度属于处理机管理。 处理机调度是操作系统的主要功能之一,它的实现策略决定了操作系统的类型,其调度算法的优劣直接影响整个系统的性能。处理机调度的任务是选出待分派的作业或进程,为之分配处理机。 一般来说,处理机调度可分为三个级别,分别是高级调度、中级调度和低级调度。 高级调度又称作业调度,作业就是用户程序及其所需的数据和命令的集合,作业管理就是对作业的执行情况进行系统管理的程序的集合。作业调度程序的主要功能是审查系统是否能满足用户作业的资源要求以及按照一定的算法来选取作业。 引入中级调度的主要目的是为了提高内存的利用率和系统吞吐量,使得暂时不运行的进程从内存对换到外存上。 低级调度又称进程调度,其主要功能是根据一定的算法将cpu分派给就绪队列中的一个进程。进程调度是操作系统中最基本的一种调度,其调度策略的优劣直接影响整个系统的性能。

.1. Linux调度时机

Linux进程调度分为主动调度和被动调度两种方式:

自愿的调度随时都可以进行,内核里可以通过schedule()启动一次调度,当然也可以将进程状态设置为TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE,暂时放弃运行而进入睡眠;用户空间可以通过pause()达到同样的目的;如果为这种暂时的睡眠放弃加上时间限制,内核态有schedule_timeout,用户态有nanosleep()用于此目的;注意内核中这种主动放弃是不可见的,隐藏在每一个可能受阻的系统调用中,如open()、read()、select()等。

被动调度发生在系统调用返回的前夕、中断异常处理返回前、用户态处理软中断返回前。

自从Linux 2.6内核后,linux实现了抢占式内核,即处于内核态的进程也可能被调度出去。比如一个进程正在内核态运行,此时一个中断发生使另一个高权值进程就绪,在中断处理程序结束之后,linux2.6内核之前的版本会恢复原进程的运行,直到该进程退出内核态才会引发调度程序;而linux2.6抢占式内核,在处理完中断后,会立即引发调度,切换到高权值进程。为支持内核代码可抢占,在2.6版内核中通过采用禁止抢占的自旋锁来保护临界区。在释放自旋锁时(spin_unlock_mutex),同样会引发调度检查。而对那些长期持锁或禁止抢占的代码片段插入了抢占点,此时检查调度需求,以避免不合理的延迟发生。而在检查过程中,调度进程很可能就会中止当前的进程来让另外一个进程运行,只要新的进程不需要持有该锁。

1.2. Linux任务状态转换

Linux进程调度

1.3. Linux进程调度原理

1.3.1. 进程调度的一般原理

进程调度在近几个版本中都进行了重要的修改。我们以2.6.9版为例过行分析。在进行具体的代码分析之前。我们先学习一下关于进程调度的原理。

1:进程类型

在linux调度算法中,将进程分为两种类型,即:I/O消耗型和CPU消耗型。例如文本处理程序与正在执行的Make的程序。文本处理程序大部份时间都在等待I/O设备的输入,而make程序大部份时间都在CPU的处理上。因此为了提高响应速度,I/O消耗程序应该有较高的优先级,才能提高它的交互性。相反的,Make程序相比之下就不那么重要了,只要它能处理完就行了。因此,基于这样的原理,linux有一套交互程序的判断机制。

在task_struct结构中新增了一个成员:sleep_avg此值初始值为100。进程在CPU上执行时,此值减少。当进程在等待时,此值增加。最后,在调度的时候。根据sleep_avg的值重新计算优先级。

2:进程优先级

正如我们在上面所说的:交互性强的需要高优先级,交互性弱的需要低优先级。在linux系统中,有两种优先级:普通优先级和实时优先级。我们在这里主要分析的是普通优先级,实时优先级部份可自行了解。

3:运行时间片

进程的时间片是指进程在抢占前可以持续运行的时间。在linux中,时间片长短可根据优先级来调整。进程不一定要一次运行完所有的时间片。可以在运时的中途被切换出去。

4:进程抢占

当一个进程被设为TASK_RUNING状态时,它会判断它的优先级是否高于正在运行的进程,如果是,则设置调度标志位,调用schedule()执行进程的调度。当一个进程的时间片为0时,也会执行进程抢占。

1.3.2. Linux O(1)调度

Linux2.6实现O(1)调度,每个CPU都有两个进程队列,采用优先级为基础的调度策略。内核为每个进程计算出一个反映其运行“资格”的权值,然后挑选权值最高的进程投入运行。在运行过程中,当前进程的资格随时间而递减,从而在下一次调度的时候原来资格较低的进程可能就有资格运行了。到所有进程的资格都为零时,就重新计算。

调度程序运行时,要在所有可运行的进程中选择最值得运行的进程。选择进程的依据主要有进程的调度策略(policy)、静态优先级(priority)、动态优先级(counter)、以及实时优先级(rt-priority)四个部分。首先,Linux从整体上区分为实时进程和普通进程,二者调度算法不同,实时进程优先于普通进程运行。进程依照优先级的高低被依次调用,实时优先级级别最高。

从某种意义上讲,所有位于当前队列的任务都将被执行并且都将被移到“过期”队列之中(实时进程则例外,交互性强的进程也可能例外)。当这种事情发生时,情况就会有所变化,队列就会被进行切换,原来的“过期”队列成为当前队列,而空的当前队列也就变成了过期队列。

schedule()函数是完成进程调度的主要函数,并完成进程切换的工作。schedule()用于确定最高优先级进程的代码非常快捷高效,其性能的好坏对系统性能有着直接影响,它在/kernel/sched.c 中的定义如下:

{

int idx;

preempt_disable();

idx = sched_find_first_bit( array -> bitmap);

queue = array -> queue + idx;

next = list_entry( queue -> next, task_t, run_list);

prev = context_switch( rq, prev, next);

}

其中,sched_find_first_bit()能快速定位优先级最高的非空就绪进程链表,运行时间和就绪队列中的进程数无关,是实现 O(1)调度算法的一个关键所在。schedule()的执行流程:

首先,调用 pre_empt_disable(),关闭内核抢占,因为此时要对内核的一些重要数据结构进行操作,所以必须将内核抢占关闭;其次,调用 sched_find_first_bit()找到位图中的第1个置1的位,该位正好对应于就绪队列中的最高优先级进程链表;再者,调用context_switch()执行进程切换,选择在最高优先级链表中的第1个进程投入运行;详细过程如图所示:

Linux进程调度

图中的网格为140位优先级数组,queue[7]为优先级为7的就绪进程链表。此种算法保证了调度器运行的时间上限,加速了候选进程的定位过程。

时间片的计算方法与时机:

Linux2.4 调度系统在所有就绪进程的时间片都耗完以后在调度器中一次性重新计算,其中重算是用for循环相当耗时。

Linux2.6为每个CPU保留 active和expired两个优先级数组,active 数组中包含了有剩余时间片的任务,expired数组中包含了所有用完时间片的任务。当一个任务的时间片用完了就会重新计算其时间片,并插入到expired队列中,当 active队列中所有进程用完时间片时,只需交换指向active和expired队列的指针即可。此交换是实现O(1)算法的核心,由schedule()中以下程序来实现:

array = rq ->active;

if (unlikely(!array->nr_active)) {

rq -> active = rq -> expired;

rq -> expired = array;

array = rq ->active;

}

Linux进程有140个优先级,前100个分配给实时进程,后40个给普通进程使用。

在 Linux2.6 中,仍有三种调度策略:SCHED_OTHER、SCHED_FIFO 和 SCHED_RR。

1.3.3. 普通进程

SCHED_ORHER:普通进程,基于动态优先级进行调度,其动态优先级可以理解为调度器为每个进程根据多种因素计算出的权值。

Linux2.6中,优先级prio的计算不再集中在调度器选择next进程时,而是分散在进程状态改变的任何时候,这些时机有:

进程被创建时;

休眠进程被唤醒时;

从TASK_INTERRUPTIBLE 状态中被唤醒的进程被调度时;

因时间片耗尽或时间片过长而分段被剥夺 CPU 时;

在这些情况下,内核都会调用 effective_prio()重新计算进程的动态优先prio并根据计算结果调整它在就绪队列中的位置。

struct task_struct{

int prio,static_prio;

prio 是动态优先级,static_prio 是静态优先级(与最初nice相关)

prio_array_t *array;

记录当前 CPU 的活跃就绪队列

unsigned long sleep_avg;

进程的平均等待时间,取值范围[0,MAX_SLEEP_AVG],初值为0。

sleep_avg反映了该进程需要运行的紧迫性。进程休眠该值增加,如果进程当前正在运行该值减少。是影响进程优先级最重要的元素。值越大,说明该进程越需要被调度。

};

1.3.4. 实时进程

SCHED_FIFO:实时进程,实现一种简单的先进先出的调度算法。

SCHED_RR:实时进程,基于时间片的SCHED_FIFO,实时轮流调度算法。

SCHED_FIFO与SCHED_RR的区别是:当进程的调度策略为前者时,当前实时进程将一直占用CPU直至自动退出,除非有更紧迫的、优先级更高的实时进程需要运行时,它才会被抢占CPU;当进程的调度策略为后者时,它与其它优先级相同的实时进程以实时轮流算法去共同使用CPU,用完时间片放到运行队列尾部,注意实时进程并不会放入过期队列中。

虽然在一个CPU内,实时进程的调度方式可以认为是严格优先级的,但是对于SMP系统,每个CPU都有自己的运行队列,实时进程被分配到各CPU队列,高优先级的实时进程并不一定比低优先级的先运行。

1.4. 实时性

Linux2.6内核本身就是可抢占的,具有一定的实时性;而一些实时补丁的出现,更加增强了linux的实时性,达到软实时的标准,这其中著名的是Ingo’s RT patch。

该补丁把中断(IRQ)和软中断(softIRQ)全部线程化并赋予不同的优先级,实时任务可以有比中断线程更高的优先级;它使用Mutex替代spinlock来使得自旋锁完全可抢占;另外分解了内核中锁的粒度,增加了内核抢占点,进一步降低了延时。由于中断已经线程化了,很多中断关闭就没必要了,因而消除了很多中断关闭区域。

为了能并入主流内核,Ingo Molnar的实时补丁也采用了非常灵活的策略,它支持四种抢占模式:

1.No Forced Preemption (Server),这种模式等同于没有使能抢占选项的标准内核,主要适用于科学计算等服务器环境。

2.Voluntary Kernel Preemption (Desktop),这种模式使能了自愿抢占,但仍然失效抢占内核选项,它通过增加抢占点缩减了抢占延迟,因此适用于一些需要较好的响应性的环境,如桌面环境,当然这种好的响应性是以牺牲一些吞吐率为代价的。

3.Preemptible Kernel (Low-Latency Desktop),这种模式既包含了自愿抢占,又使能了可抢占内核选项,因此有很好的响应延迟,实际上在一定程度上已经达到了软实时性。它主要适用于桌面和一些嵌入式系统,但是吞吐率比模式2更低。

4.Complete Preemption (Real-Time),这种模式使能了所有实时功能,因此完全能够满足软实时需求,它适用于延迟要求为几十微秒或稍低的实时系统。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值