Linux进程调度程序的具体细节

性别之间的一个差别,据说,是处理多任务的能力。现在,就看你说话的人是持有这种看法的女人还是男人,但是,我还没有那么大胆参加这种争论。然而,我能够告诉你的是,Linux明确无疑地这么做。而且,事实上,许多系统也许没有充分利用多任务。理由在这里。

追溯从前,桌面计算机没有多任务。这些计算机每次运行一个专门的任务。这些日子过去了;现在,甚至连最小型的嵌入式操作系统一般也有某些方面的多任务处理能力。

对于操作系统开发者来说,实现多任务并不简单。但解释起来却足够简单——必须在所有不同任务之间分配CPU时间。然而,不仅如此,还有一大堆问题。一些进程比另一些进程更重要并且必须有定期运行。其他不太重要的可以闲着无事坐在背后。而且为了其他人,应该让程序决定何时放弃控制,返回操作系统,抑或应由OS决定给各任务在挂起前一个指定的运行时间?

Linux监督所有这一切的部分就是调度程序,事实上,调度程序提供若干处理多任务的不同算法。没有一个算法单独对于每一个场合都是完美的;例如,一个用于负荷交互式用户的通用Linux服务器可能需要与专用网络服务器不同的分时处理方法。Linux内核实际上允许用户在启动时选择将要使用的是哪一种调度方法。这得不到广泛的赏识,因此,许多Linux系统事实上可能按对它来说也许是次优的方式进行调度。

事实上,调度程序自身不是真正分离的程序,而是每一个进程执行的进程。无论何时,任何正在运行的进程入睡或者因等待输入输出不得不阻塞,就调用调度例行程 序,弄清楚是否需要将控制切换到另一个任务,而且,如果是这样的话,还要确定是哪一个任务。这不会因应用程序开发者的吩咐而发生,因为这些对调度程序的调 用都嵌入Linux内核之中的系统调用。

任何定期与设备——象磁盘和键盘——通信的进程,要花大量等待时间,因为CPU比任何I/O设备要快得多。这个空闲时间让给其他进程运行更好。相反,复杂数学计算的程序可能把大量时间花在捣弄数字而没有任何I/O活动。

操作系统有两条路可以走下去:操作系统可能让进程放弃控制(CPU),只要它们因等待某事某物而阻塞,这称为协同多任务处理。或者,OS可能获得控制并强制进程暂时放弃(CPU),因而另一个进程可以运行。后一种情形更适合强计算的实例并通称为抢先式多任务处理。

Linux使用协同和抢先多任务处理两者。如果一个进程没有自动定期地把控制退还,它就是抢先的。

每一个Linux进程分配一定数量的时间——即时间片——执行任务,在内核停下它让另一个进程运行之前。如果进程自愿放弃,则是冷静的;只要进程要求读或写I/O设备,这件事一般就自动发生。在这种情况下,我们说,这个进程让出CPU。如果时间片期满,那么,内核停止这个进程。我们说,这些进程被抢占。

不只这两种情形。一个进程可能等待某事发生——例如,象Web服务器或者SMTP一样的网络服务——在它们有任何工作要做之前,无所事事等待客户的连接。这不仅仅是从慢速磁盘或者打字缓慢的用户读数据的问题,而且实际上在事件发生之前没有什么活动。

在这种情况下,我们说,进程被阻塞。直到事件发生,它才再次运行。一个阻塞进程根本就不能使用CPU,而且在唤醒它的事件发生之前,完全不必调度它再次运行。

调度程序还有更多的事情。无论何时,给予进程运行机会的时机一到,调度程序就要计算出应该选择哪一个进程。为了帮助做出选择,Linux给每个进程一个优先权。较高优先权者可能在较低优先权者之前运行。虽然优先权受用户影响,但最终由内核控制,而且随着时间的推移,优先权可能增加或者降低。特别地,奖励运行得好的进程——即那些因频繁让出而罕有被抢占者。

恰好地,这种奖励方案趋向照顾与用户交互的进程。一个定期从键盘获取输入的应用程序将定期让出,只要它等待输入。另一方面,执行大量计算因而不得不被抢占的程序实际上根本不涉及用户。这类进程将收到一个负的奖励。

你可参见下列两个脚本图示。一个将做得非常多,另一个做得不多。

[bash]

#!/bin/bash

while [ true ]

do

sleep 0.1

done

and

#!/bin/bash

x = 0

while [ true ]

do

x = $(($x + 1))

done

[/bash]

第一个脚本花全部时间睡觉。另一个坚持不懈地给一个数增值。这毫无意义,但目的是没完没了地消耗CPU。让我们分别把它们称为yielder.sh和preempter.sh。

设置这样运行:

./yielder.sh &

./preempter.sh &

接下来,重复运行这个命令检查它们的优先级:

ps –C yielder –C preempter –o etime,pri,cmd

你看到什么?

使用向上箭头键并回车,每隔几秒重复调用ps。很快就会看到这两个脚本优先数的变化——PRI栏。yielder.sh的优先权增加,而preempter.sh的优先权减少。这是因为调度程序分别给一个正的和负的奖励。

如果没有运行别的程序,那么,这两个进程将稳定在给它们的奖励不再改变的一点。在一个忙碌的服务器上,奖励可能连续上下浮动,因为其他进程也在启动和终止。

如果你真诚希望系统尽其最大努力运行你的复杂计算,你马上就会明白,这恐怕是不可能的。一方面,Linux有这样一个动机:想要确保对于任何交互式使用系统的用户性能不会下降。另一方面,这是你的系统,它应当做你要做的。

而要达到预定目标,做你希望你可以做的,可通过另一个称为“nice”的值直接改变调度程序的决定。这样说是因为nice值让你细致入微地对待系统中的其他任务。给进程一个正的nice值,进程将有一个较低的总优先权。相反,给它一个负的nice值,调度程序将提升其优先权。只有超级用户可以分配一个负的nice值;非特权用户可做的唯一事情是降低自己进程的优先权,在优先队列颠簸而行。

用负的nice值再运行preempter.sh进程如下:

sudo nice –n -5 ./preempter.sh &

通过如上所述的ps命令检验有影响的优先权并与前面的结果相比。

nice值一经设置,它在进程生命期就是固定的,除非使用称为renice的命令改变它。除此之外,没有别的可以影响它。使用renice更改现有进程的nice值的命令是这样的:

sudo renice -10 –p 50432

(其中,50432是进程ID即想要renice的进程的pid。)此命令将告诉你,前一个优先权和新的优先权。

再一次,非特权用户也许只能增加nice值;即使已经是正的值,这些用户也不能降低它。

现在有些不同:一切都适用于“正常”进程。有一些事情尚未讨论,就是紧要的实时应用,它必须绝对保证,软件在事件发生后的某一时间间隔内作出反应。与生命 监测硬件相互作用的医学上的应用程序不过是一个例子;如果这种应用程序在应该运行的时候不能运行,那么,生命可能会失去。

Linux专门给实时应用程序提供平常得不到的优先级范围。命令chrt启动这样的应用软件。当它们的优先权超出正常应用的范围时,保证它们经常地运行。值得注意还有,实时进程没有nice值或奖励;这些进程永远固定在某个值。

下次回来,关于Linux进程,我会告诉你更多,包括如何把内核拧成适合你特殊的多任务需要。