用黑体标出的是文章的主线,未用黑体标出的内容是对黑体内容的解释或注解。
每次调用调度器时,它会挑选具有最高等待时间的进程,把CPU提供给该进程。如果经常发生这种情况,那么进程的不公平待遇不会累积,不公平会均匀分布到系统中的所有进程。
如果通过轮流运行各个进程来模拟多任务,那么当前运行的进程,其待遇显然好于哪些等待调度 器选择的进程,即等待的进程受到了不公平的对待。不公平的程度正比于等待时间。
所有的可运行进程都按时间在一个红黑树中排序,所谓时间即其等待时间。等待CPU时间最长的 进程是最左侧的项,调度器下一次会考虑该进程。等待时间稍短的进程在该树上从左至右排序。这个红黑树称之为就绪队列
红黑树的查找、插入、删除操作需要的时间复杂度为O(log n),比Linux的旧调度器的性能更差,后者以O(1)调度器著称,即 其运行时间与需要处理的进程的数目无关。但除非大量进程同时处于可运行状态,否则新调度器的对数级时间造 成的性能下降是可以忽略的。实际上,这种情况不会发生。
除了红黑树外,就绪队列还装备了虚拟时钟。
该时钟的时间流逝速度慢于实际的时钟,精确的 速度依赖于当前等待调度器挑选的进程的数目。假定该队列上有4个进程,那么虚拟时钟将以实际时 钟四分之一的速度运行。如果以完全公平的方式分享计算能力,那么该时钟是判断等待进程将获得多 少CPU时间的基准。在就绪队列等待实际的20秒,相当于虚拟时间5秒。4个进程分别执行5秒,即可 使CPU被实际占用20秒。
假定就绪队列的虚拟时间由fair_clock给出,而进程的等待时间保存在wait_runtime。为排序 红黑树上的进程,内核使用差值fair_clock - wait_runtime。
fair_clock是完全公平调度的情况 下进程将会得到的CPU时间的度量,而wait_runtime直接度量了实际系统的不足造成的不公平。
在进程允许运行时,将从wait_runtime减去它已经运行的时间。这样,在按时间排序的树中它 会向右移动到某一点,另一个进程将成为最左边,下一次会被调度器选择。但请注意,在进程运行时 fair_clock中的虚拟时钟会增加。这实际上意味着,进程在完全公平的系统中接收的CPU时间份额, 是推演自在实际的CPU上执行花费的时间。这减缓了削弱不公平状况的过程:减少wait_runtime等 价于降低进程受到的不公平对待的数量,但内核无论如何不能忘记,用于降低不公平性的一部分时间, 实际上属于处于完全公平世界中的进程。 再次假定就绪队列上有4个进程,而一个进程实际上已经等 待了20秒。现在它允许运行10秒:此后的wait_runtime是10,但由于该进程无论如何都会得到该时间段中的10/4 = 2秒,因此实际上只有8秒对该进程在就绪队列中的新位置起了作用。
遗憾的是,这样做远远不够。
- 进程的不同优先级(即,nice值)必须考虑,更重要的进程必须比次要进程更多的CPU时间 份额。
- 进程不能切换得太频繁,因为上下文切换,即从一个进程改变到另一个,是有一定开销的。 在切换发生得太频繁时,过多时间花费在进程切换的过程中,而不是用于实际的工作。 另一方面,两次相邻的任务切换之间,时间也不能太长,否则会累积比较大的不公平值。对 多媒体系统来说,进程运行太长时间也会导致延迟增大。
可以用两种方法激活调度。
一种是直接的,比如进程打算睡眠或出于其他原因放弃CPU;另一种是通过周期性机制,以固定的频率运行,不时检测是否有必要进行进程切换。这两个组件称为通用调度器(generic scheduler)或核心调度器(core scheduler)。
通用调度器和其他两个组件进行交互。
- 调度类用于判断接下来运行哪个进程。内核支持不同的调度策略(完全公平调度、实时调度、在无事可做时调度空闲进程),调度类使得能够以模块化方法实现这些策略,即一个类的代码不需要与其他类的代码交互。在调度器被调用时,它会查询调度器类,得知接下来运行哪个进程。每个进程都刚好属于某一调度类,各个调度类负责管理所属的进程。通用调度器自身完全不涉及进程管理,其工作都委托给调度器类。
- 在选中将要运行的进程之后,必须执行底层任务切换。这需要与CPU的紧密交互。
进程的相对优先级属性
task_struct采用了3个成员来表示进程的优先级:prio和normal_prio表示动态优先级,static_prio表示进程的静态优先级。静态优先级是进程启动时分配的优先级。它可以用nice和sched_setscheduler系统调用修改,否则在进程运行期间会一直保持恒定。normal_priority表示基于进程的静态优先级和调度策略计算出的优先级。因此,即使普通进程和实时进程具有相同的静态优先级,其普通优先级也是不同的。进程分支时,子进程会继承普通优先级。但调度器考虑的优先级则保存在prio。由于在某些情况下内核需要暂时提高进程的优先级,因此需要第3个成员来表示。由于这些改变不是持久的,因此静态和普通优先级不受影响。