# Linux 2.6内核调度器剖析:O(1)时间复杂度的队列实现原理
Linux 2.6内核引入的O(1)调度器标志着操作系统调度算法的重大突破。本文深入解析其核心数据结构与算法实现,揭示如何在常数时间内完成进程调度决策。
## O(1)调度器的核心设计哲学
传统调度器的时间复杂度与进程数量线性相关,而O(1)调度器通过精巧的数据结构设计实现了突破。其核心在于维护两个关键数组:活跃队列和过期队列,每个队列包含140个优先级链表。
```c
/* Linux 2.6.11 内核调度器数据结构定义 */
/* 位于:kernel/sched.c */
#define MAX_PRIO 140
#define USER_PRIO(p) ((p)-MAX_RT_PRIO)
#define MAX_USER_PRIO (MAX_PRIO-MAX_RT_PRIO)
struct prio_array {
unsigned int nr_active; /* 队列中活动进程总数 */
unsigned long bitmap[BITMAP_SIZE]; /* 优先级位图 */
struct list_head queue[MAX_PRIO]; /* 优先级队列链表数组 */
};
struct runqueue {
spinlock_t lock; /* 运行队列锁 */
unsigned long nr_running; /* 可运行进程数 */
unsigned long nr_switches; /* 上下文切换计数 */
struct task_struct *curr; /* 当前运行进程 */
/* 关键:两个优先级数组 */
struct prio_array *active; /* 活跃进程数组 */
struct prio_array *expired; /* 过期进程数组 */
struct prio_array arrays[2]; /* 实际存储的两个数组 */
/* 其他调度统计信息 */
unsigned long expired_timestamp;
unsigned long long timestamp_last_tick;
};
```
## 常数时间调度的实现机制
### 位图索引与优先级队列
O(1)调度的核心秘密在于使用位图快速定位最高优先级进程:
```c
/* 查找最高优先级进程的实现 */
static inline struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev)
{
struct prio_array *array = rq->active;
struct list_head *queue;
struct task_struct *next;
int idx;
/* 关键步骤1:在常数时间内找到最高优先级 */
idx = sched_find_first_bit(array->bitmap);
/* 关键步骤2:从对应优先级队列获取第一个进程 */
queue = array->queue + idx;
next = list_entry(queue->next, struct task_struct, run_list);
/* 从队列中移除选中的进程 */
list_del_init(&next->run_list);
/* 如果该优先级队列变空,更新位图 */
if (list_empty(queue)) {
__clear_bit(idx, array->bitmap);
array->nr_active--;
}
rq->curr = next;
return next;
}
/* 位图查找函数实现 */
int sched_find_first_bit(const unsigned long *b)
{
int bit;
/* 快速扫描字长位图 */
#if BITS_PER_LONG == 64
if (b[0])
bit = __ffs(b[0]);
else if (b[1])
bit = __ffs(b[1]) + 64;
else
bit = __ffs(b[2]) + 128;
#else
/* 32位系统需要检查4个字 */
if (b[0])
bit = __ffs(b[0]);
else if (b[1])
bit = __ffs(b[1]) + 32;
else if (b[2])
bit = __ffs(b[2]) + 64;
else
bit = __ffs(b[3]) + 96;
#endif
return bit;
}
```
### 活跃/过期队列交换策略
两个队列的巧妙切换确保了调度的公平性:
```c
/* 队列交换的时机判断 */
static inline void
check_expired_timestamp(struct rq *rq, struct task_struct *p)
{
unsigned long sleep_time;
/* 检查进程时间片是否耗尽 */
if (p->time_slice > 1)
return;
/* 计算进程睡眠时间 */
sleep_time = rq->timestamp_last_tick - p->timestamp;
/* 如果进程已用完时间片且睡眠时间超过阈值 */
if (sleep_time > TIMESLICE_GRANULARITY) {
/* 将进程移动到过期队列 */
dequeue_task(p, rq->active);
enqueue_task(p, rq->expired);
/* 如果活跃队列为空,交换队列指针 */
if (!rq->active->nr_active) {
struct prio_array *array = rq->active;
rq->active = rq->expired;
rq->expired = array;
}
}
}
/* 进程入队操作 */
static void enqueue_task(struct task_struct *p, struct prio_array *array)
{
/* 获取进程优先级 */
int prio = p->prio;
struct list_head *queue = array->queue + prio;
/* 将进程添加到对应优先级队列尾部 */
list_add_tail(&p->run_list, queue);
/* 更新位图:设置该优先级位 */
__set_bit(prio, array->bitmap);
/* 增加活动进程计数 */
array->nr_active++;
p->array = array;
}
/* 进程出队操作 */
static void dequeue_task(struct task_struct *p, struct prio_array *array)
{
/* 从链表中移除进程 */
list_del_init(&p->run_list);
/* 如果该优先级队列变空,清除位图中对应位 */
if (list_empty(array->queue + p->prio)) {
__clear_bit(p->prio, array->bitmap);
}
/* 减少活动进程计数 */
array->nr_active--;
p->array = NULL;
}
```
## 优先级计算与时间片分配
进程优先级的动态计算是O(1)调度器的另一个关键:
```c
/* 优先级计算函数 */
static int effective_prio(struct task_struct *p)
{
int bonus, prio;
/* 基于睡眠时间计算奖励值 */
bonus = MAX_BONUS * p->sleep_avg / MAX_SLEEP_avg;
bonus = bonus - MAX_BONUS / 2;
/* 计算新的优先级 */
prio = p->static_prio - bonus;
/* 确保优先级在有效范围内 */
if (prio < MAX_RT_PRIO)
prio = MAX_RT_PRIO;
if (prio > MAX_PRIO-1)
prio = MAX_PRIO-1;
return prio;
}
/* 时间片分配算法 */
static unsigned int task_timeslice(struct task_struct *p)
{
int base, user_prio;
/* 根据进程类型分配基础时间片 */
if (rt_task(p)) {
/* 实时进程获得固定时间长的时间片 */
base = RR_INTERVAL;
} else {
/* 普通进程时间片与优先级相关 */
user_prio = USER_PRIO(p->static_prio);
base = MIN_TIMESLICE + ((MAX_TIMESLICE - MIN_TIMESLICE) *
(MAX_USER_PRIO - user_prio) / (MAX_USER_PRIO - 1));
}
return base;
}
/* 调度时钟中断处理 */
void scheduler_tick(void)
{
struct rq *rq = cpu_rq(smp_processor_id());
struct task_struct *curr = rq->curr;
<"g6.p5k3.org.cn"><"q3.p5k3.org.cn"><"t7.p5k3.org.cn">
rq->timestamp_last_tick = sched_clock();
/* 更新当前进程的时间片 */
if (rt_task(curr)) {
/* 实时进程处理 */
if (--curr->time_slice) {
/* 时间片未用完,继续运行 */
return;
}
curr->time_slice = task_timeslice(curr);
set_tsk_need_resched(curr);
} else {
/* 普通进程时间片递减 */
curr->time_slice--;
if (curr->time_slice <= 0) {
/* 时间片耗尽,需要重新调度 */
dequeue_task(curr, rq->active);
set_tsk_need_resched(curr);
curr->prio = effective_prio(curr);
enqueue_task(curr, rq->active);
}
}
}
```
## 多处理器负载均衡实现
O(1)调度器在多核系统上的扩展性同样重要:
```c
/* 负载均衡数据结构 */
struct sched_domain {
struct sched_group *groups; /* 调度组链表 */
unsigned long min_interval; /* 最小平衡间隔 */
unsigned long max_interval; /* 最大平衡间隔 */
unsigned int busy_factor; /* 繁忙因子 */
unsigned int imbalance_pct; /* 不平衡百分比阈值 */
/* 多级调度域支持 */
struct sched_domain *parent; /* 父调度域 */
struct sched_domain *child; /* 子调度域 */
unsigned int level; /* 调度域层级 */
int nr_balance_failed; /* 平衡失败计数 */
};
/* 负载均衡核心算法 */
static int load_balance(int this_cpu, struct rq *this_rq,
struct sched_domain *sd, enum cpu_idle_type idle)
{
struct rq *busiest;
unsigned long imbalance;
int nr_moved;
/* 步骤1:找到最繁忙的运行队列 */
busiest = find_busiest_queue(sd, this_cpu, idle, &imbalance);
if (!busiest)
goto out;
/* 步骤2:计算需要迁移的负载量 */
if (busiest->nr_running <= 1)
goto out;
/* 步骤3:执行进程迁移 */
nr_moved = move_tasks(this_rq, this_cpu, busiest,
imbalance, sd, idle);
/* 步骤4:更新统计信息 */
if (nr_moved) {
schedstat_inc(sd, lb_imbalance[idle]);
schedstat_add(sd, lb_gained[idle], nr_moved);
}
out:
return nr_moved;
}
/* 进程迁移实现 */
static int move_tasks(struct rq *this_rq, int this_cpu, struct rq *busiest,
unsigned long max_load_move,
struct sched_domain *sd, enum cpu_idle_type idle)
{
struct list_head *head, *curr;
struct task_struct *tmp;
int moved = 0;
head = &busiest->array->queue[busiest->array->nr_active - 1];
/* 从最低优先级开始扫描,寻找可迁移的进程 */
curr = head->prev;
while (curr != head) {
tmp = list_entry(curr, struct task_struct, run_list);
curr = curr->prev;
/* 检查进程是否适合迁移 */
if (!can_migrate_task(tmp, busiest, this_cpu, sd, idle))
continue;
/* 执行迁移操作 */
pull_task(busiest, this_rq, tmp);
moved++;
/* 达到最大迁移数量时停止 */
if (moved >= max_load_move)
break;
}
return moved;
}
```
## 调度器的性能优化技术
### 缓存友好性设计
```c
/* 缓存对齐的数据结构布局 */
struct runqueue {
/* 频繁访问的热数据放在结构体开头 */
struct task_struct *curr ____cacheline_aligned;
struct prio_array *active;
spinlock_t lock;
/* 较少访问的冷数据 */
unsigned long expired_timestamp;
unsigned long nr_uninterruptible;
/* 每CPU变量优化 */
} ____cacheline_aligned_in_smp;
/* 快速路径与慢速路径分离 */
asmlinkage void __sched schedule(void)
{
struct task_struct *prev, *next;
struct rq *rq;
int cpu;
need_resched:
/* 禁止抢占 */
preempt_disable();
cpu = smp_processor_id();
rq = cpu_rq(cpu);
/* 快速路径:检查是否需要真正调度 */
prev = rq->curr;
if (likely(prev->state == TASK_RUNNING &&
!need_resched())) {
preempt_enable_no_resched();
return;
}
/* 慢速路径:执行完整调度 */
spin_lock_irq(&rq->lock);
/* 检查当前进程状态 */
if (unlikely(prev->state == TASK_DEAD))
prev->state = TASK_RUNNING;
/* 选择下一个要运行的进程 */
next = pick_next_task(rq, prev);
/* 执行上下文切换 */
if (likely(prev != next)) {
rq->nr_switches++;
rq->curr = next;
<"wang.p5k3.org.cn"><"zi.p5k3.org.cn"><"xixi.p5k3.org.cn">
/* 执行架构相关的上下文切换 */
context_switch(rq, prev, next);
}
spin_unlock_irq(&rq->lock);
preempt_enable();
}
```
### 实时进程支持
```c
/* 实时进程调度类 */
const struct sched_class rt_sched_class = {
.next = &fair_sched_class,
.enqueue_task = enqueue_task_rt,
.dequeue_task = dequeue_task_rt,
.yield_task = yield_task_rt,
.check_preempt_curr = check_preempt_curr_rt,
.pick_next_task = pick_next_task_rt,
.put_prev_task = put_prev_task_rt,
.set_curr_task = set_curr_task_rt,
.task_tick = task_tick_rt,
};
/* 实时进程入队操作 */
static void enqueue_task_rt(struct rq *rq, struct task_struct *p, int wakeup)
{
struct rt_prio_array *array = &rq->rt.active;
struct list_head *queue;
/* 实时进程有自己的优先级数组 */
queue = array->queue + p->prio;
list_add_tail(&p->run_list, queue);
/* 更新实时进程位图 */
__set_bit(p->prio, array->bitmap);
/* 实时进程抢占检查 */
if (p->prio < rq->rt.highest_prio)
rq->rt.highest_prio = p->prio;
}
/* 实时进程选择算法 */
static struct task_struct *pick_next_task_rt(struct rq *rq)
{
struct rt_prio_array *array = &rq->rt.active;
struct task_struct *next;
int idx;
/* 实时进程也使用位图快速查找 */
idx = sched_find_first_bit(array->bitmap);
if (idx >= MAX_RT_PRIO)
return NULL;
queue = array->queue + idx;
next = list_entry(queue->next, struct task_struct, run_list);
return next;
}
```
## 算法复杂度分析
O(1)调度器的性能优势来源于以下几个方面:
```c
/* 算法复杂度对比分析 */
/*
* 传统O(n)调度器伪代码:
*
* struct task_struct *pick_next_task_old() {
* for_each_task(p) { // O(n)循环
* if (p->goodness > max_goodness) {
* next = p;
* max_goodness = p->goodness;
* }
* }
* return next;
* }
*
* O(1)调度器改进:
* 1. 优先级位图查找:O(1)
* 2. 链表操作:O(1)
* 3. 队列交换:O(1)
*
* 总时间复杂度:O(1),与进程数量无关
*/
/* 性能基准测试函数框架 */
#ifdef CONFIG_SCHED_DEBUG
static void sched_benchmark(void)
{
struct timespec start, end;
unsigned long long time_ns;
int i;
/* 测试调度器选择时间 */
getnstimeofday(&start);
for (i = 0; i < 1000000; i++) {
pick_next_task(cpu_rq(0), NULL);
}
getnstimeofday(&end);
time_ns = timespec_to_ns(&end) - timespec_to_ns(&start);
printk(KERN_INFO "O(1)调度器选择耗时:%llu ns/次\n",
time_ns / 1000000);
}
#endif
```
Linux 2.6内核的O(1)调度器通过位图索引、双队列交换和精心设计的数据结构,成功将调度算法的时间复杂度从O(n)降低到O(1)。这种设计不仅提升了单处理器性能,也为多核系统的负载均衡提供了坚实基础。其核心思想——通过空间换时间、预计算和缓存友好设计——至今仍在现代调度器设计中发挥影响,成为操作系统调度算法发展的重要里程碑。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/19545/viewspace-3109563/ ,如需转载,请注明出处,否则将追究法律责任。