1.Linux2.4调度器 Linux2.4.18 中使用的调度器采用基于优先级的设计,这个调度器和 Linus 在 1992 年发布的调度器没有大的区别。该调度器的 pick next 算法非常简单:
对运行队列中所有进程的优先级进行依次进行比较,选择最高优先级的进程作为下一个被调度的进程。(运行队列是 Linux 内核中保存所有就绪进程的队列) 。
术语 pick next :用来指从所有候选进程中挑选下一个要被调度的进程的过程。
每个进程被创建时都被赋予一个时间片。时钟中断递减当前运行进程的时间片,当进程的时间片被用完时,它必须等待重新赋予时间片才能有机会运行。
Linux2.4 调度器保证只有当所有 RUNNING 进程的时间片都被用完之后,才对所有进程重新分配时间片。这段时间被称为一个
epoch。这种设计保证了每个进程都有机会得到执行。
各种进程对调度的需求并不相同,Linux2.4 调度器主要依靠改变进程的优先级,来满足不同进程的调度需求。事实上,所有后来的调度器都主要依赖修改进程优先级来满足不同的调度需求。
(1)实时进程 实时进程的优先级是静态设定的,而且始终大于普通进程的优先级。因此只有当 runqueue 中没有实时进程的情况下,普通进程才能够获得调度。
实时进程采用两种调度策略:SCHED_FIFO 和 SCHED_RR。FIFO 采用先进先出的策略,对于所有相同优先级的进程,最先进入 runqueue 的进程总能优先获得调度;Round Robin 采用更加公平的轮转策略,使得相同优先级的实时进程能够轮流获得调度。
(2)普通进程 对于普通进程,调度器倾向于提高交互式进程的优先级,因为它们需要快速的用户响应。普通进程的优先级主要由进程描述符中的 Counter 字段决定 (还要加上 nice 设定的静态优先级) 。进程被创建时子进程的 counter 值为父进程 counter 值的一半,这样保证了任何进程不能依靠不断地 fork() 子进程从而获得更多的执行机会。
Linux2.4 调度器是如何提高交互式进程的优先级的呢?
如前所述,当所有 RUNNING 进程的时间片被用完之后,调度器将重新计算所有进程的 counter 值,所有进程不仅包括 RUNNING 进程,也包括处于睡眠状态的进程。处于睡眠状态的进程的 counter 本来就没有用完,在重新计算时,他们的 counter 值会加上这些原来未用完的部分,从而提高了它们的优先级。交互式进程经常因等待用户输入而处于睡眠状态,当它们重新被唤醒并进入 runqueue 时,就会优先于其它进程而获得 CPU。从用户角度来看,交互式进程的响应速度就提高了。
该调度器的缺点
(1)多并发时性能不好 调度器选择进程时需要遍历整个运行队列,从中选出最佳人选,因此该算法的执行时间与进程数成正比。另外每次重新计算 counter
所花费的时间也会随着系统中进程数的增加而线性增长,当进程数很大时,更新 counter 操作的代价会非常高,导致系统整体的性能下降。
这是由于调度器是使用一个复杂度为 O(n) 的算法实现的。在这种调度器中,
调度任务所花费的时间是一个系统中任务个数的函数。换而言之,活动的任务越多,调度任务所花费的时间越长。在任务负载非常重时,处理器会因调度消耗掉大量的时间,用于任务本身的时间就非常少了。因此,这个算法缺乏可伸缩性。
(2)多CPU共享一个运行队列 在对称多处理系统(SMP)中,2.6 版本之前的调度器对所有的处理器都使用一个运行队列。这意味着一个任务可以在任何处理器上进行调度 —— 这对于负载均衡来说是好事,但是对于内存缓存来说却是个灾难。例如,假设一个任务正在 CPU-1 上执行,其数据在这个处理器的缓存中。如果这个任务被调度到 CPU-2 上执行,那么数据就需要先在 CPU-1 使其无效,并将其放到 CPU-2 的缓存中。
以前的调度器还使用了一个运行队列锁;因此在 SMP 系统中,选择一个任务执行就会阻碍其他处理器操作这个运行队列。结果是空闲处理器只能等待这个处理器释放出运行队列锁,这样会造成效率的降低。
(3)无法实现抢占 Linux2.4内核是非抢占的,当进程处于内核态时不会发生抢占,这对于真正的实时应用是不能接受的。
(4)交互式进程的优化并不完善 Linux2.4 识别交互式进程的原理基于以下假设,即交互式进程比批处理进程更频繁地处于SUSPENDED状态。然而现实情况往往并非如此,有些批处理进程虽然没有用户交互,但是也会频繁地进行IO操作,比如一个数据库引擎在处理查询时会经常地进行磁盘IO,虽然它们并不需要快速地用户响应,还是被提高了优先级。当系统中这类进程的负载较重时,会影响真正的交互式进程的响应时间。
2.Linux2.6内核的优势 2.6 版本的调度器是由 Ingo Molnar 设计并实现的。
Ingo 从 1995 年开始就一直参与 Linux
内核的开发。他编写这个新调度器的动机是为唤醒、上下文切换和定时器中断开销建立一个完全 O(1) 的调度器。触发对新调度器的需求的一个问题是
Java™ 虚拟机(JVM)的使用。Java 编程模型使用了很多执行线程,在 O(n) 调度器中这会产生很多调度负载。O(1)
调度器在这种高负载的情况下并不会受到太多影响,因此 JVM 可以有效地执行。
优势
(1)调度算法复杂度为复杂度为 O(1)
为了使这个过程的效率更高,内核使用了一个位图来定义给定优先级列表上何时存在任务。因此,在大部分体系架构上,会使用一条 find-first-bit-set
指令在 5 个 32 位的字(140
个优先级)中哪一位的优先级最高。查找一个任务来执行所需要的时间并不依赖于活动任务的个数,而是依赖于优先级的数量。这使得 2.6
版本的调度器成为一个复杂度为 O(1) 的过程,因为调度时间既是固定的,而且也不会受到活动任务个数的影响。
(2)更好的支持SMP系统
每个CPU一个运行队列,避免多个CPU通过加锁对一个运行队列进行操作时产生竞争。
为了在 CPU 之间维护任务负载的均衡,任务可以重新进行分发:将任务从负载重的 CPU 上移动到负载轻的 CPU 上。Linux 2.6 版本的调度器使用负载均衡(load balancing) 提供了这种功能。每隔 200ms,处理器都会检查 CPU 的负载是否不均衡;如果不均衡,处理器就会在 CPU 之间进行一次任务均衡操作。
(3)允许抢占
当高优先级的任务准备运行时低优先级的任务就不能执行了。调度器会抢占低优先级的进程,并将这个进程放回其优先级列表中,然后重新进行调度。
3.Linux2.6调度结构 每个 CPU 都有一个运行队列,其中包含了 140 个优先级列表,它们是按照先进先出的顺序进行服务的。被调度执行的任务都会被添加到各自运行队列优先级列表的末尾。每个任务都有一个时间片,这取决于系统允许执行这个任务多长时间。运行队列的前 100 个优先级列表保留给实时任务使用,后 40 个用于用户任务。如下图所示。
4.Linux2.6调度器 为了防止任务独占 CPU 从而会饿死其他需要访问 CPU 的任务,Linux 2.6 版本的调度器可以动态修改任务的优先级。这是
通过惩罚 CPU 绑定的任务而奖励 I/O 绑定的任务实现的。I/O 绑定的任务通常使用 CPU 来设置 I/O,然后就睡眠等待 I/O 操作完成。这种行为为其他任务提供了 CPU 的访问能力。
由于 I/O 绑定型的任务对于 CPU 访问来说是无私的,因此其优先级减少(奖励)最多 5 个优先级。CPU 绑定的任务会通过将其优先级增加最多 5 个优先级进行惩罚。
任务到底是 I/O 绑定的还是 CPU 绑定的,这是根据交互性原则确定的。
任务的交互性指标是根据任务执行所花费的时间与睡眠所花费的时间的对比程度进行计算的。注意,由于 I/O 任务先对 I/O 进行调度,然后再进行睡眠,因此 I/O 绑定的任务会在睡眠和等待 I/O 操作完成上面花费更多的时间。这会提高其交互性指标。
用户响应能力更好 与用户进行通信的任务都是交互型的,因此其响应能力应该比非交互式任务更好。由于与用户的通信(不管是向标准输出上发送数据,还是通过标准输入等待输入数据)都是 I/O 绑定型的,因此提高这些任务的优先级可以获得更好的交互式响应能力。
有一点值得注意,优先级的调整只会对用户任务进行,对于实时任务来说并不会对其优先级进行调整。
参考文献1.Linux 调度器内幕. http://www.ibm.com/developerworks/cn/linux/l-scheduler/
2.Linux 调度器发展简述.
阅读(1323) | 评论(0) | 转发(0) |