ITPub博客

首页 > 应用开发 > IT综合 > Linux 2.6内核调度器剖析:O(1)时间复杂度的队列实现原理

Linux 2.6内核调度器剖析:O(1)时间复杂度的队列实现原理

IT综合 作者:bhluo 时间:2026-01-30 17:21:36 0 删除 编辑

# 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/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2015-11-01

  • 博文量
    895
  • 访问量
    934329