进程调度程序是多任务操做系统的基础,它是确保进程能有效工做的一个内核子系统,负责决定哪一个进程投入运行、什么时候运行以及运行多长时间。只有经过进程调度程序的合理调度,系统资源才可以最大限度地发挥做用,多进程才会有并发执行的效果。在一组处于可运行状态的进程中选择一个来执行,是调度程序所需完成的基本工做。linux
在前期的 Linux 版本中,Linux 的调度程序都至关简陋,设计近乎原始,虽然容易理解,可是在可运行进程多或者多处理器的环境下都难以胜任。在 Linux2.5 系列内核中,开始采用一种叫作 O(1) 调度程序的新调度程序,解决了先前版本 Linux 调度程序的许多不足。在 Linux2.6 版本内核开发过程当中,又引入了新的进程调度算法,其中最为出名的是 RSDL 算法,该算法引入了公平调度的概念,随后演化为 CFS:彻底公平调度算法。算法
Linux 中,与进程调度相关的信息存放在进程描述符 task_struct 中:网络
struct task_struct { .... int prio, static_prio, normal_prio; // 进程优先级 unsigned int rt_priority; const struct sched_class *sched_class; // 调度器类 struct sched_entity se; // 调度实体 struct sched_rt_entity rt; // 实时进程的调度实体 unsigned int policy; // 调度策略 cpumask_t cpus_allowed; // 用于控制进程能够在哪一个处理器上运行 .... }
1、多任务操做系统并发
多任务操做系统就是可以同时并发地交互执行多个进程的操做系统。能够分为两类:非抢占式多任务系统和抢占式多任务系统。app
1.一、非抢占式多任务系统:模块化
在非抢占式多任务系统下,除非进程本身主动中止运行,不然它会一直执行下去。进程主动挂起本身的操做称为让步。理想状况下,进程一般会作出让步,以便让每一个可运行进程享有足够的处理器时间。这种机制有不少缺点:调度程序没法对每一个进程该执行多长时间作出统一规定,因此进程独占的处理器时间可能会很长;更糟的是,若是一个进程一直不做出让步,会使系统崩溃。如今的大多数系统都采用抢占式多任务模式,除了 MAC 0S 9(及其前身)以及 Windows 3.1(及其前身)。性能
1.二、抢占式多任务系统:优化
抢占式多任务系统下,由调度程序来决定何时中止一个程序的运行,以便其余进程可以获得执行机会,这个强制的挂起动做称为抢占。被抢占的进程仍然是处于 TASK_RUNNING 状态的,只是暂时没有被CPU运行。进程的抢占发生在进程处于用户态执行阶段,在内核态执行时是不能被抢占的。 Linux 是抢占式多任务系统。ui
2、调度策略url
调度策略决定调度程序在什么时候让什么进程运行,调度策略要根据多方因素拟定,以便让系统资源获得最大限度的利用。
2.一、I/O 消耗型(交互式进程)和处理器消耗型进程(批处理进程)
进程大体能够被分为 I/O 消耗型和处理器消耗型(还能够既是 I/O 消耗型也是处理器消耗型):
I/O消耗型进程的大部分时间用来提交 I/O 请求或是等待 I/O 请求。这样的进程常常处于可运行状态,但一般运行时间都很短,由于它在等待更多的 I/O 请求时最后总会阻塞(这里的 I/O 是指任何类型的可阻塞资源,如键盘输入、网络I/O)。
处理器消耗型进程则是把大部分时间用在执行代码上,除非被抢占,不然它们一般都一直不停地运行,由于它们没有太多的 I/O 需求。对于这类进程,调度策略每每是下降它们的调度频率,而延长其运行时间。一些须要进行大量计算工做的程序(如 MATLAB)就是处理器消耗型进程。
调度策略一般要在两个矛盾的目标中间寻找平衡:进程响应迅速和最大系统利用率。为此,调度程序一般采用一套很是复杂的算法来决定最值得运行的进程投入运行,但它每每并不保证低优先级进程会被公平对待。Linux 为了保证交互式应用和桌面系统的性能,对进程的响应作了优化,更倾向于优先调用 I/O 消耗型。
2.二、进程优先级
调度算法中最基本的一类就是基于优先级的调度。一般的作法是(Linux并未彻底采用),优先级高的进程先运行,低的后运行,相同优先级的进程按轮转方式进行调度。调度程序老是选择时间片未用尽而优先级最高的进程先运行。用户和系统均可以经过设置进程的优先级来影响系统的调度。
Linux 采用了两种不一样的优先级范围:
1)nice值:
nice值是全部 UNIX 系统中的标准化概念,可是不一样的 UNIX 系统因为调度算法不一样,nice 值的运用方式也有所差别。 nice 值的范围是从 -20 到 +19 ,默认值为 0;越大的 nice 值意味着更低的优先级,相比高 nice 值(低优先级)的进程,低 nice 值(高优先级)的进程能够得到更多的处理器时间。
在 Linux 系统中,nice 值表明时间片的比例。使用如下指令能够查看进程的 nice 值:
ps -eo uid,pid,nice
其中,nice 选项就是查看进程的 nice 值,在输出结果中,NI 那一列即为进程的 nice 值。
2)实时优先级:
实时优先级的值是能够配置的,默认状况下它的变化范围是从 0 到 99(包括0 和 99)。与 nice 值相反,实时优先级值越高意味着进程优先级越高,任何实时进程的优先级都高于普通的进程(即实时优先级和 nice 优先级处于两个互不相交的范畴)。可使用以下指令查看进程的实时优先级:
ps -eo uid,pid,rtprio
其中,rtprio 选项就是进程的实时优先级,在输出结果中, RTPRIO那一列就是。若是有进程对应列显示为“-”,则说明该进程不是实时进程。
2.三、时间片 与 处理器使用比
在 UNIX 系统中,进程在被抢占以前可以运行的时间是预先设置好的,被称为时间片。时间片是一个数值,它代表进程在被抢占前所能持续运行的时间。调度策略必须规定一个默认的时间片,既不能太长,也不能过短:时间片太长,则系统交互性较差;时间片过短,则会增大进程切换带来的处理器耗时。通常而言,系统的默认时间片很短,好比10ms。
可是,Linux 的 CFS 调度器并无直接分配时间片到进程,Linux 使用的是处理器使用比的方式,将处理器使用比划分给进程,这样一来,进程所得到的处理器时间实际上是和系统负载密切相关的。
处理器使用比受 nice 值影响,具备较高 nice 值的进程将得到较低的处理器使用比;具备较低 nice 值的进程将得到较高的处理器使用比。而 CFS 调度器,则会根据进程的处理器使用比来决定抢占的时机:若是消耗的使用比比当前进程小,则新进程马上投入运行,抢占当前进程;不然,将推迟其运行。经过摈弃时间片而是分配给进程一个处理器使用比的方式,CFS 确保了进程调度中能有恒定的公平性,而将切换频率置于不断的变更中。
3、Linux 调度算法
3.一、调度器类
Linux 以模块的方式提供调度器,这样作的目的是容许不一样类型的进程能够有针对性地选择调度算法。这种模块化结构被称为调度器类,它容许多种不一样的可动态添加的调度算法并存,调度属于本身范畴的进程。每一个调度器都有一个优先级,基础的调度器代码定义在 kernel/sched.c 文件中,它会按照优先级顺序遍历调度类,拥有一个可执行进程的最高优先级的调度器类胜出,去选择下面要执行的那一个程序。
在 linux 2.6.34 版本内核里,有三种调度器类:idle_sched_class、rt_sched_class 和 fail_sched_class,在最新的 linux 4.6版本里,已经增长到了五种,另外两种是 stop_sched_class 和 dl_sched_class:
调度器类 | 描述 | 对应调度策略 |
stop_sched_class | 优先级最高的线程,会中断全部其余线程,且不会被其余任务打断 | 无,不须要调度普通进程 |
dl_sched_class | 采用 EDF 最先截止时间优先算法调度实时进程 | SCHED_DEADLINE |
rt_sched_class | 采用提供 Roound_Robin 算法或者 FIFO 算法调度实时进程,具体调度策略由进程的 task_struct -> policy 指定 | SCHED_FIFO, SCHED_RR |
fair_sched_class | 采用 CFS 算法调度普通的非实时进程 | SCHED_NORMAL, SCHED_BATCH |
idle_sched_class | 每一个 CPU 的第一个pid=0线程:swapper,是一个静态线程;通常运行在开机过程和 CPU 异常的时候作 dump | SCHED_IDLE |
能够看到,五种调度器类共有六种调度策略(即调度算法),用于对不一样类型的进程进行调度,或者支持某些特殊的功能。每一个调度器都有一个优先级,这五个调度器的优先级顺序为:
stop_sched_class > dl_sched_class > rt_sched_class > fair_sched_class > idle_sched_class
另外,关于所对应的调度策略,在内核版本 2.6.34 中有以下定义:
/* * Scheduling policies */ #define SCHED_NORMAL 0 #define SCHED_FIFO 1 #define SCHED_RR 2 #define SCHED_BATCH 3 /* SCHED_ISO: reserved but not implemented yet */ #define SCHED_IDLE 5 /* Can be ORed in to make sure the process is reverted back to SCHED_NORMAL on fork */ #define SCHED_RESET_ON_FORK 0x40000000
SCHED_DEADLINE 在最新版本中有定义,暂不作叙述。上面的五种调度策略中, SCHED_NORMAL 和 SCHED_BATCH 是针对普通进程(非实时进程)的调度策略,都是经过 CFS 调度器来实现的。更进一步划分的话,SCHED_NORMAL 是针对交互式进程的调度策略;SCHED_BATCH 是针对批处理进程的调度策略。SCHED_FIFO 和 SCHED_RR 是针对实时进程的调度策略,稍后会再作细说。 SCHED_IDLE 的优先级最低,在系统空闲时才运行这类进程。
鉴于笔者所看的内核版本以及能力问题,暂时就先简单讨论一下实时调度器和彻底公平调度器这两种。实际上,大部分进程也都是属于实时调度器和彻底公平调度器的。
3.二、彻底公平调度
彻底公平调度(CFS)是一个针对普通进程的调度类,在 Linux 中被称为 SCHED_NORMAL(也被称做 SCHED_OTHER),CFS 算法实现定义在文件 kernel/sched_fair.c 中。
CFS 的出发点基于这样一个理念:进程调度的效果应该能让系统感受像是具有一个理想中的完美多任务处理器。在完美多任务处理器下,每一个进程将能得到 1/n 的处理器时间(n 指的是可运行进程的数量)。同时,咱们能够调度给它们无限小的时间周期,因此,在任何可测量周期内,咱们给予 n 个进程中每一个进程一样多的运行时间。可是,这种模型是不现实的,首先,咱们没法在一个处理器上真的同时运行多个进程,其次,每一个进程的运行时间过小的话,将会增大进程切换带来的额外消耗,这是不高效的。
CFS 的作法是容许每一个进程运行一段时间、循环轮转、选择运行最少的进程做为下一个运行进程,而不是采用分配给每一个进程时间片的作法;CFS 在全部可运行进程总数基础上计算出一个进程应该运行多久,而不是依靠 nice 值来计算时间片。每一个进程都按其权重在所有可运行进程中所占比例的“时间片”来运行,为了计算准确的时间片,CFS 为完美多任务中的无限小调度周期的近似值设立了一个目标,称为“目标延迟”,目标延迟越小,则系统的交互性越好,同时也更接近完美的多任务;与之相对的,是更高的切换代价和更差的系统总吞吐能力。同时,为了不可运行进程过多而致使每一个进程所得到的处理器使用比过小带来的巨额切换消耗,CFS 引入了最小粒度的概念,用来表示每一个进程所能得到的时间片的底线,默认状况下,最小粒度为 1ms。
在 CFS 策略下,任何进程所得到的处理器时间是由它本身和其余全部可运行进程 nice 值的相对差值决定的,nice 值对时间片的做用再也不是算数加权,而是几何加权。所对应的也再也不是一个绝对的时间片,而是处理器的使用比。CFS 经过保证分配每一个进程的处理器使用比的公平性,从而达到公平调度。固然,CFS 也不是完美的公平,而是近乎完美的多任务,可是确实在必定程度上提高了进程调度公平性。
3.三、实时调度
实时调度是针对实时进程的调度类,Linux 提供了两种实时调度策略:SCHED_FIFO 和 SCHED_RR。
SCHED_FIFO 实现了一种简单的、先入先出的算法:它不使用时间片。处于可运行状态的 SCHED_FIFO 级的进程回比任何 SCHED_NORMAL 级的进程都先获得调度。一旦一个 SCHED_FIFO 进程处于可执行状态,它就会一直执行,知道它本身受阻塞或显式的释放处理器为止。它不基于时间片,能够一直执行下去。只有更高优先级的 SCHED_FIFO 或者 SCHED_RR 任务才能抢占 SCHED_FIFO 任务。若是有两个或更多同优先级的 SCHED_FIFO 级进程,它们会轮流执行,可是依然只有在它们愿意让出处理器时才会退出。只要有 SCHED_FIFO 级进程在执行,其余级别较低的进程就只有等待它变为不可运行状态后才有机会执行。
SCHED_RR 与 SCHED_FIFO 大致相同,只是 SCHED_RR 级的进程在耗尽事先分配给它的时间后就不在继续执行了。也就是说,SCHED_RR 是带有时间片的 SCHED_FIFO —— 这是一种实时轮流调度算法。当 SCHED_RR 任务耗尽它的时间片时,处于同一优先级的其余实时进程被轮流调度。时间片只用来从新调度同一优先级的进程。对于 SCHED_FIFO 进程,高优先级老是当即抢占低优先级,可是低优先级进程决不能抢占 SCHED_RR 任务,即便它的时间片耗尽。
内核版本:2.6.34
参考:https://blog.csdn.net/gatieme/article/details/51702662
参考书籍:Linux内核设计与实现(第3版)