4.2 Linux 调度算法
kernel/sched.c
2.5 版时重写了大部分代码。重新设计的原因有:
. 充分实现 O(1) 调度。不管有多少进程,新调度程序彩的每个算法都能在恒定时间内完成。
. 全面实现 SMP 的可扩展性,每个处理哭拥有自己的销和自己的可执行队列。
. 强化 SMP 的亲和力。昼将相关的一组任务分配给一个 CPU 进行连续的执行。只有在需要平衡任务队列的大小时才在 CPU 之间移动进程。
. 加强交互性能。即使在系统处于相当负载的情况下,也能保证系统的响应,并立即调度交互式进程。
. 保证公平。在合理设定的时间范围内,没有进程会处于饥饿状态。同样也没有进程能显失公平地得到大量的时间片。
. 虽然最常见的优化情况是系统中只有 1 ~ 2 个可运行进程,但是优化也完全有能力扩展到具有多处理器且每个处理器上运行了多个进程的系统中。
4.2.1 可执行队列
调度程序中最基本的数据结构是运行队列(runqueue),定义见 kernel/sched.c ,由结构 runqueue 表示。它是给定处理器上的可执行进程的链表,每个处理器一个,每个可投入运行的进程都惟一地归属于一个可执行队列。runqueue 中还包含每个处理器的调度信息。
一些宏: cpu_rq(processor) 返回给定处理器可执行队列的指针;this_rq() 返回当前处理器的可执行队列;task_rq(task) 反回指定任务所在的队列指针。
在对可执行队列进行操作以前,应该先锁住它。不过一个处理器很少需要锁其它处理器的 runqueue。
struct runqueue *rq;
unsigned long flags;
rq = task_rq_lock(task, &flags);
/* 对任务队列 rq 进行操作 */
task_rq_unlock(rq, &flags);
或者可以用 this_rq_lock() 来锁住当前的可执行,用 rq_unlock(struct runqueue *rq) 释放给定队列上的锁。
struct runqueue * rq;
rq = this_rq_lock();
/* 操作此 rq */
rq_unlock(rq);
为了避免死锁,要锁住多个运行队列的代码时必须按照可执行队列地址从低向高的顺序:
/* 锁定 */
if (rq1 == rq2)
spin_lock(&rq1->lock);
else{
if (rq1 < rq2){
spin_lock(&rq1->lock);
spin_lock(&rq2->lock);
} else {
spin_lock(&rq2->lock);
spin_lock(&rq1->lock);
}
}
/* 操作 rq1 rq2 */
/* 释放锁 */
spin_unlock(&rq1->lock);
if (rq1 != rq2)
spin_unlock(&rq2->lock);
这些步骤可以简化为:
double_rq_lock(rq1, rq2);
/* 操作两个 rq ... */
double_rq_unlock(rq1, rq2);
这些代码的原理就是,嵌套的锁必须以相同的顺序操作。
4.2.2 优先级数组
每个任务队列都有两个优先组数组,一个活跃的和一个过期的。
/* kernel/sched.c */
#define MAX_PRIO 140
#define BITMAP_SIZE 5
struct prio_array{
int nr_active; /* 任务数目 */
unsigned long bitmap[BITMAP_SIZE]; /* 优先级位图 */
struct list_head queue[MAX_PRIO]; /* 优先级队列 */
}
sched_find_first_bit()
find-first-set