3. 红黑树数据结构基础

红黑树(Red-Black Tree)是 CFS 调度器选择就绪队列的核心数据结构。它是一棵自平衡的二叉查找树,能够在 O(log n) 时间内完成插入、删除和查找操作。理解红黑树的 5 大性质、旋转操作和颜色规则,是掌握 CFS 调度器“按虚拟时间排序”机制的前提。

面试高频提示: 面试官常问“为什么 CFS 选择红黑树而不是 AVL 树或堆?”核心答案:红黑树插入/删除的旋转次数更少(最多 2 次旋转),适合调度器频繁的进程入队/出队场景;AVL 树更严格平衡但旋转更多,堆无法支持按虚拟时间有序遍历。

3.1 红黑树 5 大性质

红黑树通过以下 5 条性质保证树的高度不超过 2log₂(n+1),从而确保 O(log n) 的时间复杂度:

编号 性质描述 违反后果
1 每个节点要么是红色,要么是黑色 颜色属性失效,无法进行平衡判断
2 根节点必须是黑色 可能导致红-红冲突向上传递到根
3 所有叶子节点(NIL)都是黑色 空指针视为黑色,简化双红修正
4 红色节点的两个子节点必须是黑色(不能有连续红色) 出现红-红冲突,需旋转或变色修复
5 从任意节点到其每个叶子节点的所有路径包含相同数量的黑色节点 黑高不等,树失去平衡保证
黑高(Black-Height)概念: 从节点 x 到叶子节点的路径上黑色节点的数量(不包含 x 本身)。性质 5 保证了所有路径的黑高相等,这是红黑树平衡性的核心度量。
红黑树5大性质结构图 红黑树 5 大性质结构图 根 (黑) 左子 (红) 右子 (黑) 左左 (黑) 左右 (黑) 右左 (红) 右右 (黑) NIL NIL NIL NIL NIL NIL NIL NIL 性质 4:红色节点不能有红色子节点(如左子红 → 左左黑 ✓) | 性质 5:每条路径黑节点数相同(此图黑高 = 2)

3.2 左旋与右旋操作

旋转是红黑树维持平衡的基本操作,在不破坏二叉查找树性质的前提下,改变节点间的父子关系。CFS 调度器在插入新进程或更新进程虚拟时间后,通过旋转修复红黑树。

3.2.1 左旋(Left Rotation)

左旋将节点 x 的右子节点 y 提升为 x 的父节点,x 成为 y 的左子节点。适用于右子树过重的情况。

/* 左旋伪代码 */
LEFT-ROTATE(T, x)
    y = x.right                // 设置 y
    x.right = y.left           // y 的左子树变为 x 的右子树
    if y.left != T.nil
        y.left.parent = x
    y.parent = x.parent        // 连接 x 的父节点到 y
    if x.parent == T.nil
        T.root = y
    else if x == x.parent.left
        x.parent.left = y
    else
        x.parent.right = y
    y.left = x                 // 将 x 放在 y 的左边
    x.parent = y

3.2.2 右旋(Right Rotation)

右旋是左旋的对称操作,将节点 y 的左子节点 x 提升为 y 的父节点,y 成为 x 的右子节点。适用于左子树过重的情况。

/* 右旋伪代码 */
RIGHT-ROTATE(T, y)
    x = y.left                 // 设置 x
    y.left = x.right           // x 的右子树变为 y 的左子树
    if x.right != T.nil
        x.right.parent = y
    x.parent = y.parent        // 连接 y 的父节点到 x
    if y.parent == T.nil
        T.root = x
    else if y == y.parent.left
        y.parent.left = x
    else
        y.parent.right = x
    x.right = y                // 将 y 放在 x 的右边
    y.parent = x
面试避坑: 旋转操作的时间复杂度是 O(1),只改变指针指向,不涉及节点颜色变化。颜色变化在旋转之后通过 RB-INSERT-FIXUP 或 RB-DELETE-FIXUP 完成。面试时不要混淆“旋转”和“变色”的先后顺序。
左旋与右旋操作对比图 左旋与右旋操作对比 左旋 (LEFT-ROTATE) x y β → y x β 右旋 (RIGHT-ROTATE) y x β → x y β 左旋:x 的右子 y 上升为父,x 变为 y 的左子 | 右旋:y 的左子 x 上升为父,y 变为 x 的右子

3.3 节点颜色规则与修复流程

红黑树的颜色规则是平衡的核心机制。插入新节点时,默认将节点染为红色,然后通过修复流程消除红-红冲突。删除节点时,如果删除的是黑色节点,会破坏黑高,需要通过修复恢复性质 5。

3.3.1 插入修复的 3 种情况

情况 触发条件 操作 后续
Case 1 叔节点为红色 父节点、叔节点变黑,祖父节点变红 将祖父节点作为新插入节点继续向上检查
Case 2 叔节点为黑色,且插入节点是右子节点 对父节点左旋,转换为 Case 3 旋转后原父节点变为子节点
Case 3 叔节点为黑色,且插入节点是左子节点 父节点变黑,祖父节点变红,对祖父节点右旋 修复完成,树恢复平衡
CFS 调度器中的实际应用: 当新进程被创建并加入就绪队列时,内核调用 rb_link_node() 将节点插入红黑树,然后调用 rb_insert_color() 执行上述修复流程。插入键值为进程的 vruntime,红黑树按 vruntime 从小到大排序。

3.3.2 删除修复的核心思想

删除黑色节点会导致经过该节点的路径黑高减 1。修复策略是:将“额外黑色”向上传递,直到遇到红色节点(将其染黑)或到达根节点。删除修复涉及 4 种情况,核心操作包括旋转和变色。

红黑树插入修复流程决策图 红黑树插入修复流程决策图 插入新节点(红色) 父节点 是黑色? 是 插入完成,树合法 否 叔节点 是红色? 是(红) Case 1:变色 向上递归检查 否(黑) 插入节点 是左子? 是 Case 3:右旋+变色 否 Case 2:左旋 → 转为 Case 3

3.4 红黑树在 CFS 中的角色总结

CFS 组件 红黑树角色 键值 操作频率
就绪队列 (cfs_rq) 按 vruntime 排序的进程树 vruntime 每次调度周期更新
调度实体 (sched_entity) 红黑树节点 vruntime 进程创建/唤醒/睡眠时插入/删除
pick_next_entity 查找最左节点(最小 vruntime) — 每次调度触发
update_curr 更新 vruntime 后可能需要旋转 — 时钟 tick 触发
面试加分点: 红黑树在 CFS 中并非“完全公平”的数学保证,而是实现“按 vruntime 有序”的高效数据结构。真正的公平性由 vruntime 的递推公式和权重计算保证。红黑树只是让调度器能在 O(log n) 时间内找到“最需要运行”的进程。