`
jaguar13
  • 浏览: 62580 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

Linux内核中的红黑树

阅读更多

红黑树是平衡二叉树的一种,它有很好的性质,树中的结点都是有序的,而且因为它本身就是平衡的,所以查找也不会出现非常恶劣的情况,基于二叉树的操作的时间复杂度是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.314.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.

分享到:
评论

相关推荐

    Linux内核红黑树

    linux内核中红黑树的实现的源代码(c源代码)

    Linux内核中红黑树算法的实现详解

    红黑树是平衡二叉树的一种,它有很好的性质,树中的结点都是有序的,而且因为它本身就是平衡的,所以查找也不会出现非常恶劣的情况,基于...那么本文将详细的介绍Linux内核中红黑树算法的实现,有需要的可以参考借鉴。

    linux内核红黑树使用例子

    linux内核红黑树使用例子,linux内核的红黑树调用例子.

    Linux内核红黑树封装的通用红黑树

    用Linux内核红黑树封装的一个通用型的红黑树 如何使用该红黑树: 见rbtest1.c和rbtest2.c 直接make生成rbtest1和rbtest2 作者:rcyh 日期:2011年7月21日 ------------------------------------------ ...

    内核红黑树MAP--C语言

    封装了linux 内核 红黑树,纯C语言,外层已经封装好了,直接使用,有压力测试,很不错

    移植linux内核的红黑树代码,并适配到windows64中,在vs2022中编译通过

    红黑树的vs2022工程

    linux红黑树源码

    linux中的红黑树,被广泛的应用在linux内核的模块中。 高质量的代码值得拥有。

    Linux内核Map完整实现!

    Linux内核Map完整实现,内有Windows与linux红黑树结构实现

    linux内核中移植的红黑树代码,适配windows, linux, gnuc工具链

    包含Makefile的完整工程。 也同样可以在vs2022上运行,需要自己建立vs工程。同样支持gnuc工具编译

    内核的红黑树以及链表实现

    从内核中单独将红黑书和链表抽出来,可以直接include使用,不需要依赖其他头文件。

    链表和红黑树测试代码

    linux内核双向环形链表和红黑树,源码学习,摘录部分代码编译成库,进行测试

    通用红黑树(Tree-Map)容器纯C实现

    算法部分直接剪裁自Linux内核中的rbtree 作者主要是在此基础上封装了一个通用的容器 里面含有 test例子 以及 benchmark基准测试 另外这个是Windows和Linux都可以用的 由于Linux内核的rbtree用了很多C99语法,笔者还...

    红黑树实现

    从linux内核提取出来的红黑树实现。在普通的用户程序中即可使用

    红黑树应用 (1).mp4

    内容包括:C/C++,Linux,Nginx,golang,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,ffmpeg,流媒体, 音视频,CDN,P2P,K8S,Docker,Golang,TCP/IP,协程,嵌入式,ARM,DPDK等等。。。

    Linux下一种高性能定时器池的实现

    本文提出一种linux用户空间下的一种高性能定时器池的实现方法,实现主要基于时间轮和红黑树,以及linux内核提供了一种利于管理的定时器句柄timerfd。结合红黑树、位图、时间轮等技术,设计一种高性能级定时器池,池...

    linux内核实时进程的调度原理

    普通进程调度类:fair_sched_class1)无论实时进程还是普通进程,调度的关键都在于调度的时机、下一个进程的选取、优先级队列(实时进程中使用)或红黑树的维护(普通进程使用)。2)对于实时进程来说,下一个进程是...

    Linux-Insight:Linux内核源码认知层,承上启下的分析

    内核模块分析框架 duanery 2019年1月23日 Linux爱好者 核心原理简介 ... percpu的逻辑,如hrtimer即每cpu一颗红黑树挂接定时器,runqueue即每CPU运行操作系统,挂接进程。 5界面 proc文件系统接口,

    linux的VMALLOC虚拟地址空间管理

    linux内核使用vm_struct结构体表示映射的地址空间,并且被组织在链表vmlist中,同时为了快速搜索VMA中一块连续的虚拟地址空间采用了红黑树进行管理,另外根据红黑树的层次结构将红黑树的各节点信息保存在vmap_area_...

    嵌入式Linux C编程入门(第2版) PPT

    8.2.4 arm linux中红黑树使用实例 247 8.3 哈希表 249 8.3.1 哈希表的概念及作用 249 8.3.2 哈希表的构造方法 250 8.3.3 哈希表的处理冲突方法 252 8.3.4 arm linux中哈希表使用实例 253 本章小结...

    rbtree.tar.bz2

    Linux内核源码下的红黑树源码,进行封装处理,可以调用使用,在我的工作项目已经使用,没有什么问题,欢迎下载

Global site tag (gtag.js) - Google Analytics