linux内核-进程调度

来源:互联网 发布:网络电玩 编辑:程序博客网 时间:2024/04/30 03:32

调度策略

linux调度基于分时技术:多个进程“时间多路复用”方式运行,因为cpu的时间被分成片,每个可运行进程分配一片。单处理机在任何时刻只能运行一个进程。如果当前运行的时间片到期,进程还没运行完毕,进程切换就发生。调度策略也是根据进程优先级对它们分类。有事复杂的算法求出进程当前的优先级,单最后的结果是相同的:每个进程都与一个值关联,这个值表示进程如何适当的分配给cpu。

在Linux中,进程的优先级是动态的。

另一种分类方法把进程分为三类:交互式进程;批处理进程;实时进程;

交互式进程具有较高的优先级,不伦时间片多长,他们都会很快处理批处理进程。

早期的Linux版本中的调度算法非常简单易懂:在每次进程切换时,内核扫描可运行的进程链表,计算进程的优先级,然后选择最佳的进程执行。这个算法的主要缺点是选择最佳所消耗的时间与可用的进程数有关,因此这个算法的开销过大。

普通进程的调度:

每个普通进程都有自己的优先级,调度程序使用静态优先级评价这个进程与其他进程的调度的程度。内核从100到139的数表示进程的静态优先级,值越大静态优先级越低,新进程总是继承父进程的优先级。

基本时间片:静态优先级本质上决定了进程的基本时间片,即进程用完了以前的时间片时,系统分配给进程的时间片长度。静态优先级和时间片的关系如下:

基本时间片=(140-静态优先级)*20 (5);分别对应静态优先级<120 和>=120两种情况

静态优先级越高,其时间片越长。其结果是,与优先级低的进程相比,通常优先级较高的进程获得更长的cpu时间片。

动态优先级和平均睡眠时间:普通进程除了拥有静态优先级,还有动态优先级,100-139,动态优先级是调度程序选择新进程的时候所使用的数。它与静态优先级的关系用下面的经验公式表示:动态优先级=max{100.min(静态优先级-bonus+5,139)}

bonus的值在0-10,值小于5表示降低优先级表示惩罚,值大于5表示增加优先级来奖励。bonus的值依赖于进程的平均睡眠时间。

活动进程和过期进程:

活动进程:这些进程还没有用完他的时间片,因此允许他们运行。

过期进程:这些可运行的进程已经用完了她们的时间片,并因此被禁止运行,知道所有的活动进程都过期。

实时进程的调度:每个实时进程都与一个实时优先级有关,实时优先级1-99.调度程序总让优先级高的进程执行,换句话说,实时进程运行的过程中,禁止低优先级进程执行。

在下述情况之一,实时进程被取代:

进程被另一个高优先级的实时进程抢占;

进程执行了阻塞操作并进入休眠状态;

进程停止或被杀死;

进程调用yield主动让出cpu;

进程时基于时间片轮转的实时进程,并且用完了他的时间片。

进程调度所使用的数据结构

进程链表连接所有的进程描述符,而运行队列链表连接所有的可运行进程。

数据结构RUNQUEUE:数据结构RUNQUEUEQ是调度程序最重要的数据结构。系统中的每个cpu都有自己的运行队列,所有的runqueue结构存放在没cpu变量中。

runqueue数据结构中最重要的字段是与可运行进程的链表相关的字段。系统中每个进程属于且只属于一个运行队列,runqueue结构中active字段指向arrays中两个pro_array_t数据结构之一:对应于包含活动进程的可运行进程集合。相反expired字段指向数组中的另一个prio_array_t数据结构:对应于包含过期进程的可运行队列。

父进程创建子进程,父进程的生育节拍数被分成两等分:一份给父进程,一份给子进程,通俗的讲,一个进程不能通过创建更多的后代来霸占资源。

调度程序所使用函数

scheduler_tick():维持当前最新的time_slice计数器。

try_to_wake_up():唤醒睡眠进程

recalc_task_prio()更新进程的动态优先级

schedule()选择要被执行的新进程

load_balace():维持多处理机系统中运行队列的平衡。

schedule_tick()函数主要步骤:

1、把转换成纳秒的TSC当前值存入本地运行队列timestamp_last_tick字段。这个世界是从函数sched_clock()函数获得的。

2、检查当前进程是否是本地cpu的swap进程,如果是执行下面:a,如果本地运行队列除了swapper进程,还包括另一个可运行进程,就设置当前进程的TIF_NEED_RESCHED字段,强迫重新调度。b:跳转到7

3、检查current->array是否指向本地运行队列的活动链表。如果不是说明进程已经过期还没替换:设置TIF_NEED_RESCHED标志,以强制重新调度并跳转到第七步。

4、获得this_rq()_>lock自旋锁

5、递减当前进程时间片

6、释放自旋锁

7、调用rebalance()函数,该函数应该保证不同的cpu的运行队列包含数量基本相同的可运行进程。

更新实时进程的时间片:

如果当前进程是FIFO的实时进程,函数scheduler_tick()什么都不做。实际上这种情况下,current所表示的进程不可能比其优先级低或优先级相等的进程所抢占,因此维持当前的时间片计数是没有意义的。

如果current表示基于时间片的轮转的实时进程,scheduler_tick()就递减他的时间片计数器,并检查时间片是否被用完:如果函数确定时间片确实用完了,就执行一系列操作达到抢占的目的,如果有必要的话,就尽快抢占。

第一步调用task_timeslice()重填进程的时间片计数器。

第二步:调用set_tsk_need_resched()设置进程的TIF_NEED_RESCHED标志。该标志强制调用schedule()函数,以便current指向的进程能被另外一个有相同优先级的实时进程取代。

最后一步:把进程描述符移到与当前进程优先级相应的运行队列活动链表的尾部。把current指向的进程放到链表的尾部,可以保证在每个优先级与他相同的实时进程获得时间片以前,他不会再次被选中。

更新进程的时间片:如果当前进程是普通进程则执行下面操作:

1、递减时间片计数器。

2、如果时间片用完:调用dequeue_task从可运行进程的this_rq()-》active集合中删除current指向的进程,设置TIF_NEED_RESCHED标志,更新current指向的进程的动态优先级,重填时间片,如果本地运行队列数据结构的expired_timestamp字段等于0(过期进程集合为空),就把当前时钟节拍赋给expired_timestamp,把当前进程插入活动进程集合或过期进程集合。

3、没有用完检查进程的时间片是否太长。

schedule()函数:实现调度程序。它的任务是从运行队列的链表中找到一个进程,并随后将cpu分配给这个进程。可以采取直接调用或延迟调用的方式

直接调用:如果current进程因不能获得必须的资源而要立刻被阻塞,就直接调用调度程序。在这种情况下,要阻塞的进程的内核路径按照下述方式执行。

把current进程插入适当的等待队列;设置进状态为等待;调用schedule;检查资源是否可用,如果不可用则跳转到第2步;一旦资源可用就从等待队列中删除current进程。

内核反复检查进程需要的资源是否可用,如果不可用就调度schedule把菜谱分配给其他进程。稍后,把调度程序再次允许把cpu分给这个进程,再次检查资源是否可用。

延迟调用:也可以把current进程的TIF_NEED_RESCHED标志设为1,而已延迟方式调动调度程序,一下是延迟调用的调度程序的基本例子:

当current进程用完了他的时间片,由scheduler_tick完成schedule的调用;当一个被唤醒的进程优先级比当前仅存的优先级高时,由try_to_wake_up函数完成延迟调用;当系统发出sched_setscheduler()时

进程切换前schedule的所执行的操作:schedule函数的任务之一就是用另外一个进程替换当前执行的进程。因此该函数的关键结果是设置一个叫做next的变量,使它指向被选中的进程,该进程替代current进程,如果系统中没有优先级高于current的进程则,next与current相等,不发生任何进程切换

schedule完成进程切换时所执行的操作:现在schedule函数已经要让next进程投入运行。内核将立刻访问next进程的thread_info数据结构,其地址存放在next进程描述符的顶部位置。减少prev的平均睡眠时间,并把它补充给进程所使用的cpu时间片,随后更新进程时间戳。

进程切换后schedule所执行的操作:switch_to宏之后紧接着的指令并不由next进程立即执行,而是稍后当调度程序又选择prev执行时有prev执行。然而在那个时刻prev局部变量并不是指向我们开始描述schedule时所体寒出的那个进程。


多处理器系统总的运行队列的平衡:

正如我们所想的一样,schedule函数从本地cpu的运行队列中挑选新的进程运行。因此,一个指定的cpu只能执行其相应的运行队列的可运行进程。另外,一个可运行进程总是放在某个cpu的可运行队列中:任何一个可运行进程不能同时出现在两个可运行队列。内核周期性的检查运行队列的工作量是否平衡,并在需要的时候吧一些进程从一个队列迁移到另一个队列,LINUX提出了一种基于调度域概念的复杂的运行队列平衡算法。

调度域实际上是一个cpu集合,他们的工作量应当由内核保存平衡,最上层的调度域包括几个子域,每个子调度域包括一个cpu的子集。

为了保存系统中运行队列的平衡,每次经过一次时钟节拍,schedule_tick()就调用rebalance_tick函数。它接受的参数是本地cpu下标,本地运行队列地址以及一个标志idle。

与调度相关的系统调用:

nice系统调用,允许进程改变他们的基本优先级。

getpriority和setpriority系统调用给定组中所有进程基本优先级。

sched_getaffinity和sched_setaffinity系统调用,分别返回和设置cpu进程亲和力掩码。

与实时进程相关的系统调用:sche_getscheduler()和sched_setscheduler()系统调用:查询由pid参数所表示的进程的调度策略。

sched_yeild系统调用允许进程在不被挂起的情况下自愿放弃cpu。








原创粉丝点击