红黑树是平衡二叉树的一种,它有很好的性质,树中的结点都是有序的,而且因为它本身就是平衡的,所以查找也不会出现非常恶劣的情况,基于二叉树的操作的时间复杂度是O(log(N))。Linux内核在管理vm_area_struct时就是采用了红黑树来维护内存块的。
先到include/linux/rbtree.h中看一下红黑树的一些定义,如下:
struct rb_node
{
unsigned long rb_parent_color;
#define RB_RED 0
#define RB_BLACK 1
struct rb_node *rb_right;
struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
struct rb_root只是struct rb_node*的一个包装,这样做的好处是看起来不用传递二级指针了。不错,很简单。再看一下下面几个重要的宏,细心的你一定会发现,rb_parent_color其实没那么简单,Andrea Arcangeli在这里使用了一个小的技巧,不过非常棒。正如名字所暗示,这个成员其实包含指向parent的指针和此结点的颜色!它是怎么做到的呢?很简单,对齐起了作用。既然是sizeof(long)大小的对齐,那么在IA-32上,任何rb_node结构体的地址的低两位肯定都是零,与其空着不用,还不如用它们表示颜色,反正颜色就两种,其实一位就已经够了。
这样,提取parent指针只要把rb_parent_color成员的低两位清零即可:
#define rb_parent(r) ((struct rb_node *)((r)->rb_parent_color & ~3))
取颜色只要看最后一位即可:
#define rb_color(r) ((r)->rb_parent_color & 1)
测试颜色和设置颜色也是水到渠成的事了。需要特别指出的是下面的一个内联函数:
static inline void rb_link_node(struct rb_node * node, struct rb_node * parent, struct rb_node ** rb_link);
它把parent设为node的父结点,并且让rb_link指向node。
我们把重点集中在lib/rbtree.c上,看看一些和红黑树相关的重要算法。开始之前我们一起回忆一下红黑树的规则:
1. 每个结点要么是红色要么是黑色;
2. 根结点必须是黑色;
3. 红结点如果有孩子,其孩子必须都是黑色;
4. 从根结点到叶子的每条路径必须包含相同数目的黑结点。
这四条规则可以限制一棵排序树是平衡的。
__rb_rotate_left是把以root为根的树中的node结点进行左旋,__rb_rotate_right是进行右旋。这两个函数是为后面的插入和删除服务,而不是为外部提供接口。
新插入的结点都设为叶子,染成红色,插入后如果破坏了上述规则,通过调整颜色和旋转可以恢复,二叉树又重新平衡。插入操作的接口函数是
void rb_insert_color(struct rb_node *node, struct rb_root *root);
它把已确定父结点的node结点融入到以root为根的红黑树中,具体算法的分析可以参考[1]中第14.3节,这里的实现和书中的讲解几乎完全一样。怎么确定node的父结点应该在调用rb_insert_color之前通过手工迭带完成。值得指出的一点是,虽然插入操作需要一个循环迭代,但是总的旋转次数不会超过两次!所以效率还是很乐观的。
删除操作多多少少都有点麻烦,它要先执行像普通二叉查找树的“删除”,然后根据删除结点的颜色来判断是否执行进一步的操作。删除的接口是:
void rb_erase(struct rb_node *node, struct rb_root *root);
其实它并没有真正删除node,而只是让它和以root为根的树脱离关系,最后它还要判断是否调用__rb_erase_color来调整。具体算法的讲解看参考[1]中第13.3和14.4节,__rb_erase_color对应书中的RB-DELETE-FIXUP,此处的实现和书上也基本上一致。
其余的几个接口就比较简单了。
struct rb_node *rb_first(struct rb_root *root);
在以root为根的树中找出并返回最小的那个结点,只要从根结点一直向左走就是了。
struct rb_node *rb_last(struct rb_root *root);
是找出并返回最大的那个,一直向右走。
struct rb_node *rb_next(struct rb_node *node);
返回node在树中的后继,这个稍微复杂一点。如果node的右孩子不为空,它只要返回node的右子树中最小的结点即可;如果为空,它要向上查找,找到迭带结点是其父亲的左孩子的结点,返回父结点。如果一直上述到了根结点,返回NULL。
struct rb_node *rb_prev(struct rb_node *node);
返回node的前驱,和rb_next中的操作对称。
void rb_replace_node(struct rb_node *victim, struct rb_node *new, struct rb_root *root);
用new替换以root为根的树中的victim结点。
红黑树接口使用的一个典型例子如下:
static inline struct page * rb_search_page_cache(struct inode * inode,
unsigned long offset)
{
struct rb_node * n = inode->i_rb_page_cache.rb_node;
struct page * page;
while (n)
{
page = rb_entry(n, struct page, rb_page_cache);
if (offset < page->offset)
n = n->rb_left;
else if (offset > page->offset)
n = n->rb_right;
else
return page;
}
return NULL;
}
static inline struct page * __rb_insert_page_cache(struct inode * inode,
unsigned long offset,
struct rb_node * node)
{
struct rb_node ** p = &inode->i_rb_page_cache.rb_node;
struct rb_node * parent = NULL;
struct page * page;
while (*p)
{
parent = *p;
page = rb_entry(parent, struct page, rb_page_cache);
if (offset < page->offset)
p = &(*p)->rb_left;
else if (offset > page->offset)
p = &(*p)->rb_right;
else
return page;
}
rb_link_node(node, parent, p);
return NULL;
}
static inline struct page * rb_insert_page_cache(struct inode * inode,
unsigned long offset,
struct rb_node * node)
{
struct page * ret;
if ((ret = __rb_insert_page_cache(inode, offset, node)))
goto out;
rb_insert_color(node, &inode->i_rb_page_cache);
out:
return ret;
}
因为红黑树的这些良好性质和实现中接口的简易性,它被广泛应用到内核编程中,大大提高了内核的效率。
参考资料:
1. Introduction to Algorithms, Thomas H. Cormen, Charles E. Leiserson, and Ronald L. Rivest, MIT Press.
2. Understanding the Linux Kernel, 3rd Edition, Daniel P. Bovet, Marco Cesati, O'Reilly.
3. Linux Kernel 2.6.19 source code.
分享到:
相关推荐
当向红黑树中插入新节点时,首先将其标记为红色。由于插入红色节点可能会破坏红黑树的性质,因此插入后需要进行旋转和重新着色来维护平衡。常见的旋转操作有左旋和右旋,如右旋(RB_ROTATE_RIGHT)和左旋(RB_ROTATE...
红黑树是计算机科学中用于高效管理数据结构的重要算法之一,尤其在Linux内核中扮演着关键角色。这种自平衡二叉查找树确保了插入、删除和搜索操作的时间复杂度保持在O(log(N)),提高了数据操作的效率。在Linux内核中...
linux内核红黑树使用例子,linux内核的红黑树调用例子.
用Linux内核红黑树封装的一个通用型的红黑树 如何使用该红黑树: 见rbtest1.c和rbtest2.c 直接make生成rbtest1和rbtest2 作者:rcyh 日期:2011年7月21日 ------------------------------------------ ...
本项目的目标是将Linux内核中的红黑树代码移植到Windows 64位环境中,并在Visual Studio 2022(VS2022)中完成编译。 移植工作主要包括以下几个关键步骤: 1. **理解红黑树算法**:首先,需要深入理解红黑树的基本...
在Linux内核中,红黑树被广泛应用于各种数据结构和算法中,如内存管理、VFS(虚拟文件系统)、进程调度等,以提供高效的数据检索和管理。 红黑树的关键特性有以下五条: 1. 每个节点要么是红色,要么是黑色。 2. 根...
红黑树在 Linux 虚拟内存区域管理中的应用主要体现在三个方面:红黑树的定义和优点、Linux 中 VMA 的相关内容、红黑树在 Linux 内核中的实现。红黑树的高效操作使其成为 Linux 内核中虚拟存储区域管理的首选算法。
封装了linux 内核 红黑树,纯C语言,外层已经封装好了,直接使用,有压力测试,很不错
3. 并发控制:Linux内核中的红黑树可能使用了spinlocks或其他同步原语,而Windows上需要使用互斥量(Mutex)、临界区(Critical Section)等。 4. 数据结构:可能需要调整数据结构以适应Windows API的风格,例如,...
在Linux内核中,红黑树被广泛用于数据结构的高效管理,例如在内存分配、VFS(虚拟文件系统)和进程调度等场景。移植到Windows平台意味着代码需要适应不同的编译环境,这通常涉及到头文件、宏定义和库函数的调整。在...
红黑树是一种自平衡的二叉查找树,它在Linux内核中的应用非常广泛,特别是在虚拟内存区域管理中。红黑树的高效操作使得虚拟内存区域的删除、查找和插入的时间复杂度降低到O(logn)。 红黑树的特点是每个节点都带有...
这种数据结构在操作系统、编译器、数据库系统等领域有着广泛的应用,尤其是在Linux内核中,红黑树被用于实现如内存管理、I/O调度等关键功能。 首先,让我们深入理解红黑树的基本特性: 1. **颜色属性**:每个节点...
`rbtree.c`和`rbtree.h`包含了Linux内核中红黑树的实现代码和头文件。`rbtree.txt`可能是对红黑树实现的文档说明或者详细注解。 在`rbtree.c`中,你可以期待看到以下关键函数和数据结构: - `rb_node`:表示红黑树...
19. 进程调度核心数据结构:进程调度的核心数据结构是run_queue,其中包含了待调度进程的红黑树以及各种状态的链表。 20. 加载、卸载模块:通过insmod、rmmod命令分别加载和卸载内核模块,动态扩展内核功能。 21. ...
5. 内核数据结构:解析链表、红黑树、哈希表等数据结构在内核中的应用。 最后,"Linux操作系统内核分析(在线图书).txt"可能包含了一些在线资源,这些资源可能提供了更多关于Linux内核的实时更新和社区讨论。 ...
总之,红黑树是一种重要的数据结构,广泛应用于许多系统和库中,如Linux内核中的内存分配器。C++实现红黑树不仅需要对二叉树有深入理解,还需要掌握颜色转换和旋转技巧。通过实践和调试,可以逐步完善实现,使其更...
链表和红黑树是两种在计算机科学中广泛使用的数据结构,它们在操作系统,特别是Linux内核中扮演着重要角色。这里我们主要探讨这两种数据结构的特性、用途以及如何在实际编程中应用。 首先,链表是一种线性数据结构...
Linux内核采用CFS(Completely Fair Scheduler,完全公平调度器),基于红黑树结构,根据进程的虚拟运行时间来公平地分配CPU时间片。此外,还有实时调度策略,如SCHED_FIFO和SCHED_RR,优先满足实时任务的需求。 ...
它基于红黑树(RB-tree)结构来维护就绪队列,并根据时间片公平地分配CPU时间。 8. **I/O子系统**:包括块I/O、异步I/O(AIO)和中断驱动的I/O。理解这些I/O模型可以帮助你设计高效的磁盘访问策略。 通过这些图解...