【Linux内存管理】 Linux内核红黑树(Red-Black Tree)实现原理与应用详
Linux内核红黑树(Red-Black Tree)实现原理与应用详解
1. 红黑树核心特性
1.1 什么是红黑树?(通俗版)
简单来说,红黑树是一种**“稍微有点不平衡,但效率极高”**的二叉查找树。
我们可以用管理学来打个比方:
-
普通二叉树 (BST):像一个初创公司。如果运气不好(比如按顺序插入 1, 2, 3, 4, 5),公司结构就会变成“一条线”(链表),老板找最底层的员工需要层层传递,效率极低(O(n))。
-
AVL树:像一个强迫症CEO管理的公司。它要求任何部门的层级差不能超过1层。一旦有人晋升导致层级不平,马上进行部门重组(旋转)。这种管理方式查找效率极高(O(log n)),但重组(旋转)太频繁,内耗严重。
-
红黑树:像一个务实的CEO。它允许部门之间存在一定的层级差(最长路径不超过最短路径的2倍)。它引入了“红黑卡”制度(颜色规则)来维持这种近似平衡。这样既保证了查找速度够快,又避免了频繁的部门重组(旋转),达到了完美的平衡。
深度解读:“务实CEO”与“红黑卡制度”
我们可以把红黑树看作一个**“懂取舍”的企业管理者**,而红黑规则就是他制定的管理制度:
-
“红黑卡制度”运作原理:
- 黑色节点 = “正式经理”:根据黑高性质,从CEO(根)到任何最底层员工,中间经过的“正式经理”数量必须相同。这保证了公司的骨架是平衡的。
- 红色节点 = “实习生”:根据不红红规则,“实习生”不能直接汇报给“实习生”(两个红色节点不能相连)。这意味着,两个实习生中间至少要隔一个正式经理。
-
为什么是 2 倍?
- 最短路径:全都是“正式经理”(全黑)。假设长度为 N N N。
- 最长路径:为了拉长路径,我们可以在每两个“正式经理”之间插一个“实习生”(红黑相间)。因为不能连续插两个实习生,所以最多只能插 N N N 个。长度为 2 N 2N 2N。
结论:红黑树通过这套制度,容忍了树结构一定程度的不平衡(2倍差异),从而避免了像 AVL 树那样为了追求极致完美而付出高昂的维护代价。这是一种工程学上的**“中庸之道”智慧**。
-

图0:三种树结构的直观对比
为什么需要红黑树?
普通二叉查找树(BST)在最坏情况下(例如按顺序插入节点)会退化成链表,查找、插入和删除的时间复杂度从 O ( log n ) O(\log n) O(logn) 变为 O ( n ) O(n) O(n)。平衡二叉树(AVL树)虽然保证了严格的平衡,但维护平衡的旋转操作开销较大。
红黑树通过牺牲严格的平衡(高度差不超过两倍),换取了更高效的插入和删除性能。它保证了任何基本动态集合操作的时间复杂度为 O ( log n ) O(\log n) O(logn)。
核心权衡:牺牲了什么?换来了什么?
- 牺牲了“绝对完美”:
- AVL 树要求高度差不超过 1,而红黑树允许高度差达到 2 倍。这意味着在查找时,红黑树可能比 AVL 树多跳跃几次,查找速度略慢(微乎其微)。
- 换取了“更少的折腾”:
- AVL 树为了维持严苛的平衡,插入/删除时需要频繁旋转(类似频繁搬家)。
- 红黑树标准宽松,插入/删除后往往只需简单的变色或极少量的旋转。插入和删除速度大大提升。
总结:红黑树是一个**“聪明的妥协者”**。它在“频繁变动数据”(如Linux进程调度、内存管理)的场景下,获得了比 AVL 树更好的综合性能。
1.2 节点颜色规则
红黑树的每个节点都增加了一个存储位来表示节点的颜色(红或黑)。通过对任何一条从根到叶子的简单路径上各个节点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近似平衡的。
红黑树的5个性质:
- 每个节点是红色或黑色。
- 根节点是黑色。
- 每个叶节点(NIL节点,空节点)是黑色的。
- 如果一个节点是红色的,则它的两个子节点都是黑色的。(即不能有连续的两个红色节点)
- 对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。(黑高性质)

图1:红黑树结构示例,展示了节点着色和黑高性质
1.3 高度平衡约束
由性质4和性质5可以推导出红黑树的关键结论:一棵有 n n n 个内部节点的红黑树的高度至多为 2 log 2 ( n + 1 ) 2\log_2(n+1) 2log2(n+1)。
- 最短路径: 全黑节点路径。
- 最长路径: 红黑交替路径(因为不能有连续红节点,且黑节点数相同)。
因此,最长路径长度不会超过最短路径长度的2倍。这种近似平衡足以保证 O ( log n ) O(\log n) O(logn) 的搜索效率,同时相比AVL树减少了插入删除时的旋转次数。
1.4 插入/删除时的旋转操作
当插入或删除节点破坏了红黑树的性质时,需要通过变色和旋转来恢复平衡。
- 左旋(Left Rotation): 逆时针旋转,将右子节点提升为父节点。
- 右旋(Right Rotation): 顺时针旋转,将左子节点提升为父节点。

图2:红黑树的左旋和右旋操作示意图
2. Linux内核具体实现
Linux内核中的红黑树实现非常精简且高效,主要定义在 include/linux/rbtree.h 和 lib/rbtree.c 中。
2.1 头文件结构 include/linux/rbtree.h
内核采用了侵入式数据结构设计,将 rb_node 嵌入到宿主结构体中。
struct rb_node {
unsigned long __rb_parent_color;
struct rb_node *rb_right;
struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
struct rb_root {
struct rb_node *rb_node;
};
-
__rb_parent_color的奥秘:
Linux内核为了节省空间,利用了地址对齐的特性。由于rb_node结构体是按sizeof(long)对齐的(通常是4或8字节),其地址的低2位始终为0。内核利用最低位来存储颜色信息(0为红,1为黑,或者反之,具体取决于实现宏),而高位部分存储父节点指针。#define rb_parent(r) ((struct rb_node *)((r)->__rb_parent_color & ~3))
2.2 核心操作函数 lib/rbtree.c
内核提供了一系列通用的红黑树操作函数:
rb_insert_color(struct rb_node *, struct rb_root *):插入节点后的重新着色与旋转平衡。rb_erase(struct rb_node *, struct rb_root *):删除节点并重新平衡。rb_next(const struct rb_node *)/rb_prev(...):查找中序遍历的前驱/后继节点。
插入操作示例(逻辑摘要):
插入新节点总是先涂成红色(性质5保持不变,可能违反性质4)。修复过程主要处理连续红节点的情况:
// 伪代码逻辑
while (parent is RED) {
if (parent is left child of grandparent) {
uncle = grandparent->right;
if (uncle is RED) {
// Case 1: 叔叔是红色 -> 变色
set parent BLACK;
set uncle BLACK;
set grandparent RED;
node = grandparent;
} else {
if (node is right child) {
// Case 2: 叔叔是黑色,当前是右孩子 -> 左旋
node = parent;
left_rotate(node);
}
// Case 3: 叔叔是黑色,当前是左孩子 -> 变色 + 右旋
set parent BLACK;
set grandparent RED;
right_rotate(grandparent);
}
} else {
// 对称情况...
}
}
set root BLACK;
2.3 内核优化版本 vs 普通红黑树
- 侵入式设计(Intrusive):
rb_node不包含数据,而是作为成员嵌入到宿主结构中(如struct task_struct)。通过container_of宏获取数据。- 优点: 不需要频繁的内存分配/释放(节点内存随宿主结构分配);更缓存友好。
- 增强型红黑树(Augmented Rbtree): 内核支持“增强”红黑树(
rbtree_augmented.h),允许在节点中维护子树的附加信息(如子树中的最大值、区间范围等)。这对于区间树(Interval Tree)等应用至关重要。 - 无锁/RCU支持: 虽然红黑树本身的修改通常需要锁保护,但内核实现中考虑了与RCU(Read-Copy-Update)的配合,允许在特定条件下的并发读取。
- 缓存优化:
rb_first,rb_next等迭代函数经过高度优化,尽量减少分支预测失败。
3. 实际应用案例
红黑树在Linux内核中无处不在,主要用于需要高效查找、排序和范围查询的场景。
3.1 进程调度:CFS (Completely Fair Scheduler)
CFS 调度器使用红黑树来管理处于 RUNNING 状态的进程。
- Key(键值): 进程的
vruntime(虚拟运行时间)。 - Value(节点): 进程描述符
task_struct中的se.run_node。

图3:CFS调度器中的红黑树,最左侧节点(min_vruntime)总是下一个被调度的进程
工作原理:
CFS 总是选择红黑树最左侧(Leftmost)的节点作为下一个运行的进程,因为它的 vruntime 最小,最需要CPU时间。
- 查找最左节点:
O
(
1
)
O(1)
O(1) (内核会缓存
rb_leftmost指针)。 - 插入/删除进程:
O
(
log
n
)
O(\log n)
O(logn)。当进程运行一段时间后,
vruntime增加,会被重新插入树的右侧;当进程睡眠或终止时,从树中删除。
3.2 内存管理:VMA (Virtual Memory Area)
每个进程的虚拟地址空间由 mm_struct 管理,其中的 mmap 字段指向一个红黑树的根,用于存储该进程所有的虚拟内存区域(vm_area_struct)。
- Key: 内存区域的起始地址
vm_start。 - 作用: 当发生缺页异常(Page Fault)时,内核需要快速判断故障地址属于哪个 VMA。红黑树使得查找特定的 VMA 的时间复杂度为 O ( log n ) O(\log n) O(logn),这对于拥有大量动态链接库和内存映射的大型程序非常关键。
struct mm_struct {
struct vm_area_struct *mmap; /* list of VMAs */
struct rb_root mm_rb; /* Red-Black tree of VMAs */
...
};
3.3 文件系统:高精度计时器与I/O调度
- 高精度计时器(High-resolution Timers): 使用红黑树按过期时间组织挂起的定时器,最早过期的定时器位于最左侧,能够快速响应。
- Ext3/Ext4 文件系统: 用于目录项索引,加速在包含大量文件的目录中查找特定文件。
4. 参考文献
- Linux Kernel Source Code (v4.4.94):
include/linux/rbtree.h,lib/rbtree.c - Documentation/rbtree.txt: Linux内核自带文档
- Cormen, T. H., et al. Introduction to Algorithms. MIT Press. (红黑树算法原理)
- Love, R. Linux Kernel Development. Addison-Wesley.
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)