4. O(1) 核心数据结构:prio_array_t 结构体、位图 bitmap、active/expired 双数组机制
O(1) 调度算法之所以能在常数时间内完成进程选择,核心在于其精巧的数据结构设计。它摒弃了 O(n) 算法中遍历整个就绪队列的做法,通过 prio_array_t 结构体、位图 bitmap 和 active/expired 双数组机制,将调度开销从“与进程数相关”降为“与优先级数相关”,且优先级数固定为 140,从而实现严格的时间复杂度 O(1)。
4.1 prio_array_t 结构体:就绪队列的容器
prio_array_t 是 O(1) 调度器中管理就绪进程的核心数据结构。每个 CPU 拥有两个这样的结构体实例:一个用于 active 数组,一个用于 expired 数组。其定义如下:
struct prio_array_t {
unsigned long bitmap[BITMAP_SIZE]; // 优先级位图,用于快速查找非空队列
struct list_head queue[MAX_PRIO]; // 140 个优先级链表头
int nr_active; // 当前数组中活跃进程总数
};
该结构体通过三个关键字段实现了高效的进程管理:
- queue[MAX_PRIO]:一个包含 140 个链表头的数组,每个链表对应一个优先级(0~139)。同一优先级的进程通过双向链表链接,新进程插入链表尾部,调度时从头部取出。
- bitmap[BITMAP_SIZE]:一个位图数组,用于标记哪些优先级队列非空。Linux 2.6 中 BITMAP_SIZE 通常为 5(140 位需要 5 个 32 位无符号整数)。
- nr_active:记录当前数组中所有进程的总数,用于快速判断是否需要切换 active/expired 数组。
4.2 位图 bitmap:O(1) 查找的加速器
位图是 O(1) 调度器实现常数时间查找的关键。调度器不需要遍历 140 个队列来寻找最高优先级的非空队列,而是通过位图操作一步定位。
查找过程分为两步:
- 查找第一个非零位图字:使用
__ffs()(find first set)指令或软件模拟,在位图数组中从低位到高位找到第一个非零的 unsigned long。 - 在该字内查找第一个置位位:同样使用
__ffs()找到该字内最低位的 1,该位的索引即为最高优先级(数值最小)的非空队列。
例如,若 bitmap[0] = 0x00000080,表示优先级 7 的队列非空,调度器直接定位到 queue[7] 并取出队首进程。
4.3 active/expired 双数组机制:时间片轮转的基石
O(1) 调度器使用两个 prio_array_t 实例——active 和 expired——来实现公平的时间片轮转,同时避免 O(n) 算法中重新插入时的遍历开销。
双数组的工作流程如下:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 进程从 active 数组被调度 | 调度器从 active 数组的最高优先级非空队列中取出进程执行 |
| 2 | 时间片耗尽 | 进程的时间片计数器减至 0,触发重新调度 |
| 3 | 计算新时间片 | 根据进程的静态优先级和交互性计算新的时间片长度 |
| 4 | 插入 expired 数组 | 进程被放入 expired 数组对应优先级的队列尾部 |
| 5 | active 数组为空 | 当 active 数组的 nr_active 变为 0 时,交换 active 和 expired 指针 |
| 6 | 继续调度 | 调度器开始从新的 active 数组(原 expired)中选取进程 |
这种机制确保了所有进程都能在一个轮转周期内得到执行,且优先级高的进程获得更多 CPU 时间。
4.4 数据结构关系总览
下图展示了 O(1) 调度器核心数据结构之间的关系:
4.5 数据结构对比总结
下表总结了 O(1) 调度器核心数据结构与 O(n) 调度器的关键差异:
| 特性 | O(n) 调度器 | O(1) 调度器 |
|---|---|---|
| 就绪队列结构 | 单个全局链表 | 140 个优先级链表 + 位图 |
| 进程选择方式 | 遍历链表计算 goodness 值 | 位图扫描 + 链表头取出 |
| 时间复杂度 | O(n) | O(1) |
| 时间片管理 | 重新计算并插入链表 | 双数组指针交换 |
| 多核扩展性 | 全局锁,扩展性差 | 每 CPU 独立数据结构 |
| 交互式进程优化 | 无专门机制 | 时间片未用完可留在 active |
通过 prio_array_t 结构体、位图 bitmap 和 active/expired 双数组机制的协同工作,O(1) 调度器在 Linux 2.6 内核中实现了里程碑式的性能提升,为后续 CFS 调度器的设计奠定了数据结构基础。