标签:
【学习时间:1小时45分 撰写博客时间:2小时10分钟】
【学习内容:Linux的进程调度实现、抢占和上下文切换、与调度相关的系统调用】
调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间。进程调度程序可看做在可运行态进程之间分配有限的处理器时间资源的内核子系统。
最大限度利用处理器时间的原则:只要有可以执行的进程,那么总会有程序正在执行。
1.概念:多任务操作系统就是能同时并发地交互执行多个进程的操作系统,在单处理器机器上这会产生多个进程在同时运行的幻觉,在多处理器机器上,这会使多个进程在不同的处理机上真正同时、并行地运行。
2. 分类:多任务系统可以划分为两类
在Linux 2.5开发系列的内核中,调度程序做了大手术,开始采用了一种叫做O(1)调度程序的新调度程序——它是因为其算法的行为而得名的。
策略决定调度程序在何时让什么进程运行。
1. I/O消耗型进程
2. 处理器耗费型
3. 调度策略通常要在两个矛盾的目标中间寻找平衡:进程响应迅速(响应时间短)和最大系统利用率(高吞吐量),为了满足上述需求,调度程序通常采用一套非常复杂的算法来决定最值得运行的进程投入运行,但是它往往并不保证低优先级进程会被公平对待,Unⅸ系统的调度程序更倾向于I/O消耗型程序,以提供更好的程序响应速度,Linux为了保证交互式应用和桌面系统的性能,所以对进程的响应做了优化(缩短响应想间)更倾向于优先调度I/O消耗型进程,虽然如此,调度程序也并未忽略处理器消耗型的进程。
1. 基于优先级的调度:优先极高的进程先运行,相同优先级的进程按照轮转方式进行调度。
2. 优先级分为两类:
注:二者互不交互。
CFS采用的方法是对时间片分配方式进行根本性的重新设计(就进程调度器而言)完全摒弃时间片而是分配给进程一个处理器使用比重,通过这种方式,CFS的确保了进程调度中能有恒定的公平性,而将切换频率置于不断变动中。
CFS基于一个简单的理念:进程调度的效果应当如同系统具备一个理想中的完美任务处理器。CFS的做法如下:
在理想情况下,完美的多任务处理器模型应该是这样的:我们能在5ms内同时运行两个进程,它们各自使用处理器一半的能力。
CFS相关代码位于kernel/sched_fair.c中。它有四个组成部分:
所有的调度器都必须对进程运行时间做记账。多数Unix系统,分配一个时间片给每一个进程。那么当每次系统时钟节拍发生时,时间片都会被减少一个节拍周期。
1. 调度器实体结构
调度器实体结构作为一个名为se的成员变量,嵌入在进程描述符struct task_ struct内。
2. 虚拟实时
1. CFS算法核心:选择具有最小vrntime的任务
2. 具体做法:利用红黑树rbtree(以节点形式存储数据的二叉树)
3. 举例:
1. 等待队列:休眠通过等待队列进行处理,等待队列是由某些事件发生的进程组成的简单链表。
2. 唤醒
唤醒操作由函数wake_ up()进行:
上下文切换,就是从一个可执行进程切换到另一个可执行进程,由定义在kernel/schedule.c中的context_ switch()函数负责处理。完成了两项工作:
在内核返回用户空间的时候,它知道自己是安全的,因为既然它可以继续去执行当前进程,那么它当然可以再去选择一个新的进程去执行。所以,内核无论是在中断处理程序还是在系统调用后返回,都会检查need_resched标志,如果它被设置了,那么,内核会选择一个其他(更合适的进程投入运行。从中断处理程序或系统调用返回的返回路径都是跟体系结构相关的,在entry.S(此文件不仅包含内核入口部分的程序,内核退出部分的相关代码也在其中)文件中通过汇编语言来实现。简而言之,用户抢占在以下情况时产生:
与其他大部分的Unⅸ变体和其他大部分的操作系统不同,Linux完整地支持内核抢占,在不支持内核抢占的内核中,内核代码可以一直执行,到它完成为止,也就是说,调度程序没有办法在一个内核级的任务正在执行的时候重新调度——内核中的各任务是以协作方式调度的不具备抢占性。内核代码一直要执行到完成(返回用户空间)或明显的阻塞为止,在2.6版的内核中,内核引入了抢占能力;现在,只要重新调度是安全的,内核就可以在任何时间抢占正在执行的任务。
Linux的实时调度算法提供了―种软实时工作方式,软实时的含义是,内核调度进程,尽力使进程在它的限定时间到来前运行,但内核不保证总能满足这些进程的要求。相反,硬实时系统保证在一定条件下,可以满足任何调度的要求。Linux对于实时任务的调度不做任何保证。虽然不能保证硬实时工作方式,但Linux的实时调度算法的性能还是很不错的。2.6版的内核可以满足严格的时间要求。
Linux调度程序提供强制的处理器绑定机制。虽然它尽力通过一种软的(或者说自然的)亲和性试图使进程尽量在同一个处理器上运行,但它也允许用户强制指定“这个进程无论如何都必须在这些处理器上运行”。这种强制的亲和性保存在进程的一个位掩码标志中。该掩码标志的每一位对应一个系统可用的处理器,默认情况下所有的位都被设置。
Linux通过sched_ yield()系统调用,提供了一种让进程显式地将处理器时间让给其他等待执行进程的机制,它是通过将进程从活动队列中(因为进程正在执行,所以它肯定位于此队列当中)移到过期队列中实现的,由此产生的效果不仅抢占了该进程并将其放入优先级队列的最后面,还将其放入过期队列中—这样能确保在一段时间内它都不会再被执行了,由于实时进程不会过期,所以属于例外,它们只被移动到其优先级队列的最后面(不会放到过期队列中)。
在Linux以的早期版本中,进程只会被放置到优先级队列的末尾,放弃的时间往往不会太长,现在,应用程序甚至内核代码在调用sched_ yield()前,应该仔细考虑是否真的希望放弃处理器时间。内核代码为了方便,可以直接调用sched_ yield(),先要确定给定进程确实处于可执行状态,然后再调用sched_ yield(),用户空间的应用程序直接使用sched_ yield()系统调用就可以。
通过对本章进程调度的学习,我了解到进程调度程序是内核重要的组成部分,因为运行着的进程首先在使用计算机。但是满足进程调度的各种需要是较难实现的。例如公平调度中,越小的调度周期就会表现出越好的交互性,也更接近于“同时完成多任务”这一目标。然而系统必须承受更高的切换代价和更差的系统吞吐量,即鱼与熊掌不可兼得。不过,Linux内核的新CFS调度程序尽量满足了各个方面的需求,并以较完善的可伸缩性和新颖的方法提供了最佳的解决方案。
标签:
原文地址:http://www.cnblogs.com/20135228guoyao/p/5384449.html