一:调度策略
调度策略:决定什么时候以怎样的方式选择一个新进程运行的这组规则就是调度策略。
Linux的调度基于分时技术,分时依赖于定时中断,因此对进程是透明的。
调度时传统分类法把进程分为两类:
(1)I/O受限-频繁地使用I/O设备,并花费很多时间等待I/O操作的完成。
(2)CPU受限-需要大量CPU时间的数值计算应用程序
另一种分类法把进程分为三类:
(1)交互式进程:经常与用户交互,,接受输入后,进程需很快被唤醒
(2)批处理进程:不必与用户交互,经常在后台运行。
(3)实时进程:有很强的调度需要,绝不会被低优先级的进程阻塞。
1.1 进程的抢占
Linux的进程是抢占式的,若进程进入TASK_RUNNING状态,内核检查它的动态优先级是否大于当前正运行进程的优先级,若是,则当前执行进程被中断,并调用调度程序选择这个可运行的程序执行。
但被抢占的进程并没有被挂起,因为它还处于TASK_RUNNING状态,只不过不再使用CPU。
1.2 时间片
时间片的长短对系统性能是很重要的,不能太长也不能太短。
时间片太短,由进程切换引起的系统额外开销就会变得非常高。
时间片太长,会降低系统的响应能力,可能会对用户的请求反应迟钝。
要选择尽可能长,同时能保持良好响应时间的一个时间片。
二:调度算法
Linux的进程调度类型有三种:
先入先出的实时进程,时间片轮转的实时进程,普通的分时进程。
2.1普通进程的调度
内核从100到139的数来表示普通进程的静态优先级,值越大静态优先级越低。
新进程总是继承其父进程的静态优先级。
2.1.1基本时间片
基本时间片(单位为ms)=(140-静态优先级)*20 若静态优先级<120
(140-静态优先级)*5 若静态优先级>=120
静态优先级本质上决定了进程的基本时间片,优先级较高的进程获得更长的CPU时间片。
2.1.2 动态优先级和平均睡眠时间
动态优先级是调度程序在选择新程序来运行的时候使用的数。值的范围是100~139,值越小优先级越高。
动态优先级=max(100,min(静态优先级-bouns+5139))
bouns是值为0~10的数,值小于5表示降低动态优先级作为惩罚,值大于5表示增加动态优先级作为奖励。
bouns的值与进程的平均睡眠时间有关。
平均睡眠时间是进程在睡眠状态所消耗的平均纳秒数。
如进程满足:动态优先级<=3*静态优先级/4+28,则被看作是交互式进程。
2.1.3 活动和过期进程
活动进程:这些进程还没有用完它们的时间片,因此允许它们运行。
过期进程:这些可运行进程已经用完了它们的时间片,并因此被禁止运行,直到所有的活动进程都过期。
用完其时间片的交互式进程通常仍然是活动进程。
2.2 实时进程的调度
每个实时进程都和一个实时优先级相关,实时优先级是一个范围从1~99的值,数字越小优先级越高。调度程序总是让优先级高的进程运行。
实时进程能被另一个进程取代的情况如下:
(1) 进程被另外一个具有更高实时优先级的进程抢占。
(2)进程执行了阻塞操作并进入睡眠。
(3)进程停止或被杀死。
(4)进程通过系统调用sched_yield()自愿放弃CPU。
(5)进程是基于时间片轮转的实时进程而且用完了它的时间片。
三:调度程序所使用的数据结构
系统中的每个CPU都有它自己的运行队列,所有的runquene结构存放在runquene每CPU变量中,宏this_rq()产生本地CPU运行队列的地址。系统中的每个可运行进程属于且只属于一个运行队列,且只可能在拥有该运行队列的CPU上执行,但可运行进程可以从一个运行队列迁移到另一个运行队列。
runqueue结构的active字段指向对应着包含活动进程的可运行进程的集合的prio_array_t结构。
runqueue结构的expired字段指向对应着包含过期进程的可运行进程的集合的prio_array_t结构。
当新进程被创建的时候,函数sched_fork()使得父进程剩余的节拍数被划分为两等份,一份给父进程,另一份给子进程,可避免一个进程通过创建多个后代来霸占资源。如果父进程的时间片只剩下一个时钟节拍,则划分操作强行把父进程时间片设置为0。
四:调度程序所使用的函数
主要用于完成调度工作的函数有:
scheduler_tick()---维持当前最新的time_slice计数器
try_to_wake_up()---唤醒睡眠进程
recalc_task_prio()---更新进程的动态优先级
schedule()---选择要被执行的新进程
load_balance()---维持多处理器系统中运行队列的平衡
try_to_wake_up()函数通过把进程状态设置为TASK_RUNNING,并把该进程插入本地CPU的运行队列来唤醒睡眠或停止的进程。
函数recalc_task_prio()更新进程的平均睡眠时间和动态优先级。
函数schedule()实现调度程序,它的任务是从运行队列的链表中找到一个进程,并随后将CPU分配给这个进程。
五:多处理器系统中运行队列的平衡
任何一个可运行程序都不可能同时出现在两个或多个运行队列中,一个保持可运行状态的进程通常被限制在一个固定的CPU上。
如果可运行队列绝大多数都在同一个运行队列中,那么系统中的一个CPU就会超负荷,因此,内核周期性地检查运行队列的工作量是否平衡,需要时把一些进程从一个运行进程迁移到另一个运行进程。
5.1调度域
调度域实际上是一个CPU集合,它们的工作量应当由内核保持平衡。调度域采取分层的组织形式,最上层的调度域包括多个子调度域,每个子调度域包括一个CPU子集。
每个调度域被依次划分成一个或多个组,每个组代表调度域的一个CPU子集,工作量的平衡总是在调度域的组之间来完成。因而只有在某调度域某个组的总工作量远低于同一个调度域的另一个组的工作量时,才把进程从一个CPU迁移到另一个CPU。
每个调度域由一个sched_domain描述符表示,而调度域中的每个组由sched_group描述符表示。
load_balance()函数用来检查是否调度域处于严重的不平衡状态。它检查是否可以通过把最繁忙的组中的一些进程迁移到本地CPU的运行队列来减轻不平衡的状况,若可以,函数尝试实现迁移。
move_tasks()函数来实现把进程从源运行队列迁移到本地运行队列。
六:与调度相关的系统调用