《Linux内核设计与实现》第四章学习笔记

第四章 进程调度

【学习时间:1小时45分 撰写博客时间:2小时10分钟】算法

【学习内容:Linux的进程调度实现、抢占和上下文切换、与调度相关的系统调用】安全

调度程序负责决定将哪一个进程投入运行,什么时候运行以及运行多长时间。进程调度程序可看作在可运行态进程之间分配有限的处理器时间资源的内核子系统。服务器

最大限度利用处理器时间的原则:只要有能够执行的进程,那么总会有程序正在执行。网络

1、多任务

1.概念:多任务操做系统就是能同时并发地交互执行多个进程的操做系统,在单处理器机器上这会产生多个进程在同时运行的幻觉,在多处理器机器上,这会使多个进程在不一样的处理机上真正同时、并行地运行。并发

  • 不管在单处理器或者多处理器机器上,多任务操做系统都能使多个进程处于堵塞或者睡眠状态,也就是说,实际上不被投入执行,直到工做确实就绪
  • 这些任务尽管位于内存,但并不处于可运行状态。相反,这些进程利用内核阻塞本身,直到某一事件(键盘输入网络数据、过一段时间等)发生。所以,现代Linux系统也许有100个进程在内存,可是只有一个处于可运行状态

2. 分类:多任务系统能够划分为两类函数

  • 非抢占式多任务。进程会一直执行直到本身主动中止运行(这一步骤称为让步)
  • 抢占式多任务。Linux/Unix使用的是抢占式的方式;强制的挂起进程的动做就叫作抢占。进程在被抢占以前可以运行的时间是预先设置好的(也就是进程的时间片)

2、Linux的进程调度

在Linux 2.5开发系列的内核中,调度程序作了大手术,开始采用了一种叫作O(1)调度程序的新调度程序——它是由于其算法的行为而得名的。性能

  • 它解决了先前版本Linux调度程序的许多不足,引入了许多强大的新特性和性能特征,这里主要要感谢静态时间片算法和针对每一处理器的运行队列,它们帮助咱们摆脱了先前调度程序设计上的限制
  • O(1)调度程序虽然对于大服务器的工做负载很理想,可是在有不少交互程序要运行的桌面系统上则表现不佳,由于其缺乏交互进程,自2.6内核系统开发初期,开发人员为了提升对交互程序的调度性能引入了新的进程调度算法,其中最为著名的是“反转楼梯最后期限调度算法,该算法吸收了队列理论,将公平调度的概念引入了Linux调度程序。而且最终在2.6.23内核版本中替代了O(1)调度算法,它此刻被称为“彻底公平调度算法”,或者简称CFS

3、策略

  策略决定调度程序在什么时候让什么进程运行。学习

3.1 I/O消耗型和处理器消耗型的进程

1. I/O消耗型进程优化

  • 进程的大部分时间用来提交I/O请求或者等待I/O请求
  • 多数用户图形界面(GUI)都属于I/O密集型

2. 处理器耗费型spa

  • 时间大多数用在执行代码上
  • 例如MATLAB
  • 每每要延长运行时间并下降调度频率

3. 调度策略一般要在两个矛盾的目标中间寻找平衡:进程响应迅速(响应时间短)和最大系统利用率(高吞吐量),为了知足上述需求,调度程序一般采用一套很是复杂的算法来决定最值得运行的进程投入运行,可是它每每并不保证低优先级进程会被公平对待,Unⅸ系统的调度程序更倾向于I/O消耗型程序,以提供更好的程序响应速度,Linux为了保证交互式应用和桌面系统的性能,因此对进程的响应作了优化(缩短响应想间)更倾向于优先调度I/O消耗型进程,虽然如此,调度程序也并未忽略处理器消耗型的进程。

3.2 进程优先级

1. 基于优先级的调度:优先极高的进程先运行,相同优先级的进程按照轮转方式进行调度。

2. 优先级分为两类:

  • nice值(从-20——+19):默认值为0;数值越大意味着优先级越低;能够经过 ps-el查看系统进程列表并找到NI标记列对应的优先级
  • 实时优先级(从0——99):越高的实时优先级级数意味着进程优先级越高

注:两者互不交互。

3.3 时间片

  1. 时间片表示进程在被抢占以前所可以持续运行的时间。
  2. 调度策略必须肯定一个默认的时间片。
  3. Linux的CFS调度器并无直接划分时间片到进程,而是将处理器的使用比例划分给了进程。即其抢占时机取决于新的可执行程序消耗了多少处理器使用比,若是消耗的使用比比当前进程小,则新进程当即投入运行抢占当前进程。

3.4 调度策略的活动

4、Linux调度算法

4.1 调度器类

  1. Linux调度器是以模块方式提供的(也就是调度器类),目的是容许不一样类型的进程能够有针对性地选择调度算法。
  2. 调度器类容许多种不一样的可动态添加的调度算法并存,调度属于本身范畴的进程。
  3. 每一个调度器都有一个优先级,基础的调度器代码定义在sched_ fair.c文件中,它会按照优先级顺序遍历调度类,拥有一个可执行进程的最高优先级的调度器类胜出,去选择下面要执行的那一个程序。彻底公平调度(CS)是一个针对普通进程的调度类,在Linux中称为SCHED_NORMAL。

4.2 Unix系统中的进程调度

  1. 将nice值映射到时间片的话,就必须将nice值对应处处理器的绝对时间;这样会致使进程切换没法最优进行。
  2. 若是使用相对nice值,所带来的效果将会极大取决于其nice的初始值。
  3. 若是执行nice值到时间片的映射,时间片极大受制于定时器。

  CFS采用的方法是对时间片分配方式进行根本性的从新设计(就进程调度器而言)彻底摒弃时间片而是分配给进程一个处理器使用比重,经过这种方式,CFS的确保了进程调度中能有恒定的公平性,而将切换频率置于不断变更中。

4.3 公平调度

CFS基于一个简单的理念:进程调度的效果应当如同系统具有一个理想中的完美任务处理器。CFS的作法以下:

  • 容许每一个进程运行一段时间、循环轮转、选择运行最少的进程做为下一个运行进程
  • nice值做为进程得到的处理器运行比的权重(而不是彻底由nice决定时间片)
  • 每一个进程都按照其权重在所有的可运行进程中所占的比例对应的“时间片”来运行

在理想状况下,完美的多任务处理器模型应该是这样的:咱们能在5ms内同时运行两个进程,它们各自使用处理器一半的能力。

5、Linux调度的实现

CFS相关代码位于kernel/sched_fair.c中。它有四个组成部分:

  • 时间记帐
  • 进程选择
  • 调度器入口
  • 睡眠和唤醒

5.1 时间记帐

  全部的调度器都必须对进程运行时间作记帐。多数Unix系统,分配一个时间片给每个进程。那么当每次系统时钟节拍发生时,时间片都会被减小一个节拍周期。

1. 调度器实体结构

  调度器实体结构做为一个名为se的成员变量,嵌入在进程描述符struct task_ struct内。

2. 虚拟实时

  • vruntime变量存放进程的虚拟运行时间
  • update_ curr()计算当前进程的执行时间,是由系统定时器周期性调用的

5.2 进程选择

1. CFS算法核心:选择具备最小vrntime的任务

2. 具体作法:利用红黑树rbtree(以节点形式存储数据的二叉树)

3. 举例:

  • 选择下一个任务:从根节点中序遍历二叉树,一直到叶子节点(也就是vrntime最小的进程)
  • 向树中加入进程:在进程变为可执行状态或者经过fork()调用第一次建立进程
  • 从树中删除进程:发生在进程阻塞或者终止的时候

5.3 调度器入口

  1. 进程调度的主要入口点是函数schedule(),定义在kernel/sched.c中。这正是内和其余部分用于调度进程调度器的入口。
  2. 这一函数最重要的工做就是调用pick_ next_ state(),依次检查每个调度类,并从最高优先级的调度类中,选择最高优先级进程。

5.4 睡眠和唤醒

1. 等待队列:休眠经过等待队列进行处理,等待队列是由某些事件发生的进程组成的简单链表。

  • 进程把本身标记成休眠状态,从可执行红黑树中移除
  • 放入等待队列——由等待某些时间发生的进程组成的链表,内核用wake_ queue_ head_ t来表明等待队列
  • 将进程加入到一个等待队列中:

2. 唤醒

唤醒操做由函数wake_ up()进行:

  • 它会调用函数try_ to _ wake_ up()将进程设置为TASK_ RUNNING状态,调用enqueue_ task()将进程放入红黑树中
  • 存在虚假唤醒进程的状态

6、抢占和上下文切换

上下文切换,就是从一个可执行进程切换到另外一个可执行进程,由定义在kernel/schedule.c中的context_ switch()函数负责处理。完成了两项工做:

  • 调用switch_mm(),负责把虚拟内存从上一个进程映射切换到新的进程中
  • 调用switch_to(),负责从上一个进程的处理器状态切换到新进程的处理器状态

6.1 用户抢占

  在内核返回用户空间的时候,它知道本身是安全的,由于既然它能够继续去执行当前进程,那么它固然能够再去选择一个新的进程去执行。因此,内核不管是在中断处理程序仍是在系统调用后返回,都会检查need_resched标志,若是它被设置了,那么,内核会选择一个其余(更合适的进程投入运行。从中断处理程序或系统调用返回的返回路径都是跟体系结构相关的,在entry.S(此文件不只包含内核入口部分的程序,内核退出部分的相关代码也在其中)文件中经过汇编语言来实现。简而言之,用户抢占在如下状况时产生:

  • 从系统调返回用户空间时。
  • 从中断处理程序返回用户空间时

6.2 内核抢占

  与其余大部分的Unⅸ变体和其余大部分的操做系统不一样,Linux完整地支持内核抢占,在不支持内核抢占的内核中,内核代码能够一直执行,到它完成为止,也就是说,调度程序没有办法在一个内核级的任务正在执行的时候从新调度——内核中的各任务是以协做方式调度的不具有抢占性。内核代码一直要执行到完成(返回用户空间)或明显的阻塞为止,在2.6版的内核中,内核引入了抢占能力;如今,只要从新调度是安全的,内核就能够在任什么时候间抢占正在执行的任务。

7、实时调度策略

  Linux的实时调度算法提供了―种软实时工做方式,软实时的含义是,内核调度进程,尽力使进程在它的限定时间到来前运行,但内核不保证总能知足这些进程的要求。相反,硬实时系统保证在必定条件下,能够知足任何调度的要求。Linux对于实时任务的调度不作任何保证。虽然不能保证硬实时工做方式,但Linux的实时调度算法的性能仍是很不错的。2.6版的内核能够知足严格的时间要求。

8、与调度相关的系统调用

8.1 与调度策略和优先级相关的系统调用

  • sched_ setscheduler()和 sched_ getscheduler()分别用于设置和获取进程的调度策略和实时优先级。与其余的系统调用类似,它们的实现也是由许多参数检查、初始化和清理构成的。其实最重要的工做在于读取或改写进程task_ struct的policy和rt_ priority的值
  • sched_ setscheduler()和 sched_ getscheduler()分别用于设置和获取进程的实时优先级。这两个系统调用获取封装在sched_ param特殊结构体的rt_ priority中。实时调度策略的的最大优先级:是MAX_ USERRT_PRIO减1。最小优先级等于1
  • 对于―个普通的进程,nice函数能够将给定进程的静态优先级增长一个给定的量。只有超级用户才能在调用它时使用负值,从而提升进程的优先级。nice函数会调用内核的set_ user_ nice函数,这个函数会设置进程的的task_ struct的static_ prio值

8.2 与处理器绑定有关的系统调用

  Linux调度程序提供强制的处理器绑定机制。虽然它尽力经过一种软的(或者说天然的)亲和性试图使进程尽可能在同一个处理器上运行,但它也容许用户强制指定“这个进程不管如何都必须在这些处理器上运行”。这种强制的亲和性保存在进程的一个位掩码标志中。该掩码标志的每一位对应一个系统可用的处理器,默认状况下全部的位都被设置。

8.3 放弃处理器时间

  Linux经过sched_ yield()系统调用,提供了一种让进程显式地将处理器时间让给其余等待执行进程的机制,它是经过将进程从活动队列中(由于进程正在执行,因此它确定位于此队列当中)移到过时队列中实现的,由此产生的效果不只抢占了该进程并将其放入优先级队列的最后面,还将其放入过时队列中—这样能确保在一段时间内它都不会再被执行了,因为实时进程不会过时,因此属于例外,它们只被移动到其优先级队列的最后面(不会放到过时队列中)。

  在Linux以的早期版本中,进程只会被放置到优先级队列的末尾,放弃的时间每每不会太长,如今,应用程序甚至内核代码在调用sched_ yield()前,应该仔细考虑是否真的但愿放弃处理器时间。内核代码为了方便,能够直接调用sched_ yield(),先要肯定给定进程确实处于可执行状态,而后再调用sched_ yield(),用户空间的应用程序直接使用sched_ yield()系统调用就能够。

总结

  经过对本章进程调度的学习,我了解到进程调度程序是内核重要的组成部分,由于运行着的进程首先在使用计算机。可是知足进程调度的各类须要是较难实现的。例如公平调度中,越小的调度周期就会表现出越好的交互性,也更接近于“同时完成多任务”这一目标。然而系统必须承受更高的切换代价和更差的系统吞吐量,即鱼与熊掌不可兼得。不过,Linux内核的新CFS调度程序尽可能知足了各个方面的需求,并以较完善的可伸缩性和新颖的方法提供了最佳的解决方案。

相关文章
相关标签/搜索