转自:https://www.cnblogs.com/hellokitty2/p/15362630.html

1. 什么是红黑树,它们有什么用?
------------------------------------------------
红黑树是一种自平衡二叉搜索树,用于存储可排序的 键/值 数据对。 这不同于 基数树(用于有效地存储稀疏数组,因此使用长整数索引来插入/访问/删除节点)和哈希表(不保持排序以便于按顺序遍历,并且必须针对 特定大小和哈希函数,其中 rbtrees 可以优雅地扩展存储任意键)。

红黑树类似于 AVL 树,但为插入和删除提供更快的实时有界最坏情况性能(最多分别旋转两次和三次,以平衡树),速度稍慢(但仍然是 O(log n)) 查找时间。

引用 Linux Weekly News:
内核中使用了许多红黑树。 Deadline 和 CFQ I/O 调度器使用 rbtrees 来跟踪请求; 数据包 CD/DVD 驱动程序也做同样的事情。 高分辨率计时器代码使用 rbtree 来组织未完成的计时器请求。 ext3 文件系统在红黑树中跟踪目录条目。 虚拟内存区域 (VMA)
使用红黑树进行跟踪,“分层令牌桶”调度程序中的 epoll 文件描述符、加密密钥和网络数据包也是如此。

本文档介绍了 Linux rbtree 实现的使用。 有关红黑树的性质和实施的更多信息,请参阅:

Linux Weekly News 关于红黑树的文章: http://lwn.net/Articles/184495/

维基百科关于红黑树的条目: http://en.wikipedia.org/wiki/Red-black_tree

2. 红黑树的Linux实现
-------------------------------
Linux 的 rbtree 实现存在于文件 “lib/rbtree.c” 中。 要使用它要 “#include <linux/rbtree.h>”。

Linux rbtree 实现针对速度进行了优化,因此比更传统的树实现少了一层间接(和更好的缓存局部性)。 不是使用指针来分隔 rb_node 和数据结构,而是将 struct rb_node 的每个实例嵌入到它组织的数据结构中。 而不是使用比较回调函数指针,用户应该编写自己的树搜索和插入函数,这些函数调用提供的 rbtree 函数。 锁定也由 rbtree 代码的用户决定。

(1) 创建一个新的 rbtree
---------------------
rbtree 树中的数据节点是包含 struct rb_node 成员的结构:

  1. struct mytype {
  2. struct rb_node node;
  3. char *keystring;
  4. };

在处理指向嵌入式结构 rb_node 的指针时,可以使用标准的 container_of() 宏访问包含的数据结构。 此外,可以通过 rb_entry(node, type, member) 直接访问单个成员。

每个 rbtree 的根是一个 rb_root 结构,它通过以下方式初始化为空:

  1. struct rb_root mytree = RB_ROOT;

(2) 在 rbtree 中搜索值
----------------------------------

为您的树编写搜索函数相当简单:从根开始,比较每个值,并根据需要遵循左侧或右侧分支。

例子:

  1. struct mytype *my_search(struct rb_root *root, char *string) {
  2. struct rb_node *node = root->rb_node;
  3.  
  4. while (node) {
  5. struct mytype *data = container_of(node, struct mytype, node);
  6. int result;
  7.  
  8. result = strcmp(string, data->keystring);
  9.  
  10. if (result < 0)
  11. node = node->rb_left;
  12. else if (result > 0)
  13. node = node->rb_right;
  14. else
  15. return data;
  16. }
  17. return NULL;
  18. }

(3) 将数据插入 rbtree
-----------------------------

在树中插入数据涉及首先搜索插入新节点的位置,然后插入节点并重新平衡(“重新着色”)树。

插入的搜索与之前的搜索不同,它查找要嫁接新节点的指针的位置。 为了重新平衡,新节点还需要一个到其父节点的链接。

例子:

  1. int my_insert(struct rb_root *root, struct mytype *data) {
  2. struct rb_node **new = &(root->rb_node), *parent = NULL;
  3.  
  4. /* Figure out where to put new node */
  5. while (*new) { //*new为root->rb_node,然后往下遍历,直到指向的为NULL退出循环
  6. struct mytype *this = container_of(*new, struct mytype, node);
  7. int result = strcmp(data->keystring, this->keystring);
  8.  
  9. parent = *new;
  10. if (result < 0)
  11. new = &((*new)->rb_left);
  12. else if (result > 0)
  13. new = &((*new)->rb_right);
  14. else
  15. return FALSE;
  16. }
  17.  
  18. /* Add new node and rebalance tree. */
  19. rb_link_node(&data->node, parent, new);
  20. rb_insert_color(&data->node, root);
  21.  
  22. return TRUE;
  23. }

(5) 删除或替换 rbtree 中的现有数据
------------------------------------------------

要从树中删除现有节点,请调用:

例子:

  1. void rb_erase(struct rb_node *victim, struct rb_root *tree);
  2.  
  3. struct mytype *data = mysearch(&mytree, "walrus");
  4. if (data) {
  5. rb_erase(&data->node, &mytree);
  6. myfree(data);
  7. }

要将树中的现有节点替换为具有相同键的新节点,请调用:

  1. void rb_replace_node(struct rb_node *old, struct rb_node *new, struct rb_root *tree);

以这种方式替换节点不会对树重新排序:如果新节点与旧节点的键不同,则 rbtree 可能会损坏。

(6) 遍历存储在 rbtree 中的元素(按排序顺序)
--------------------------------------------------

提供了四个函数来按排序顺序迭代 rbtree 的内容。 这些适用于任意树,不需要修改或包装(除了锁定目的)::

  1. struct rb_node *rb_first(struct rb_root *tree);
  2. struct rb_node *rb_last(struct rb_root *tree);
  3. struct rb_node *rb_next(struct rb_node *node);
  4. struct rb_node *rb_prev(struct rb_node *node);

要开始迭代,请使用指向树根的指针调用 rb_first() 或 rb_last(),这将返回指向包含在树的第一个或最后一个元素中的节点结构的指针。 要继续,请通过在当前节点上调用 rb_next() 或 rb_prev() 来获取下一个或上一个节点。 当没有更多节点时,这将返回 NULL。

迭代器函数返回一个指向嵌入结构 rb_node 的指针,可以使用 container_of() 宏从中访问包含的数据结构,并且可以通过 rb_entry(node, type, member)直接访问各个成员。

例子:

  1. struct rb_node *node;
  2. for (node = rb_first(&mytree); node; node = rb_next(node))
  3. printk("key=%s\n", rb_entry(node, struct mytype, node)->keystring);

3. 缓存的 rbtrees
--------------

计算最左边(最小)节点对于二叉搜索树来说是一项非常常见的任务,例如对于遍历或用户依赖于他们自己的逻辑的特定顺序。 为此,用户可以使用 'struct rb_root_cached'
将 O(logN) rb_first() 调用优化为简单的指针获取,从而避免潜在的昂贵的树迭代。 这是在维护的运行时开销可以忽略不计的情况下完成的; 尽管内存占用更大。

类似于 rb_root 结构,缓存的 rbtrees 被初始化为空通过::

  1. struct rb_root_cached mytree = RB_ROOT_CACHED;

缓存的 rbtree 只是一个普通的 rb_root,带有一个额外的指针来缓存最左边的节点。 这允许 rb_root_cached 存在于 rb_root 所在的任何地方,这允许支持增强树以及仅几个额外的接口:

  1. struct rb_node *rb_first_cached(struct rb_root_cached *tree);
  2. void rb_insert_color_cached(struct rb_node *, struct rb_root_cached *, bool);
  3. void rb_erase_cached(struct rb_node *node, struct rb_root_cached *);

插入和擦除调用都有各自对应的增强树:

  1. void rb_insert_augmented_cached(struct rb_node *node, struct rb_root_cached *, bool, struct rb_augment_callbacks *);
  2. void rb_erase_augmented_cached(struct rb_node *, struct rb_root_cached *, struct rb_augment_callbacks *);

4. 支持增强型 rbtrees
-----------------------------

增强的 rbtree 是一个每个节点中存储了“一些”附加数据的 rbtree,其中节点 N 的附加数据必须是以 N 为根的子树中所有节点内容的函数。此数据可用于增强一些新功能到rbtree。 增强的 rbtree 是一个构建在基本 rbtree 基础设施之上的可选功能。 需要此功能的 rbtree 用户必须在插入和删除节点时使用用户提供的增强回调来调用增强函数。

实现增强 rbtree 操作的 C 文件必须包含 <linux/rbtree_augmented.h> 而不是 <linux/rbtree.h>。 请注意, linux/rbtree_augmented.h 公开了一些您不希望依赖的 rbtree 实现细节; 请坚持那里记录的 API,并且不要在头文件中包含 <linux/rbtree_augmented.h> 以尽量减少您的用户意外依赖此类实现细节的机会。

在插入时,用户必须更新通向插入节点的路径上的增强信息,然后像往常一样调用 rb_link_node() 和 rb_augment_inserted() 而不是通常的 rb_insert_color() 调用。 如果 rb_augment_inserted() 重新平衡 rbtree,它将回调到用户提供的函数中,以更新受影响子树的增强信息。

擦除节点时,用户必须调用 rb_erase_augmented() 而不是 rb_erase()。 rb_erase_augmented() 回调用户提供的函数以更新受影响子树的增强信息。

在这两种情况下,回调都是通过 struct rb_augment_callbacks 提供的。 必须定义 3 个回调:

  1. - 传播回调,它更新给定节点及其祖先的增强值,直到给定的停止点(或 NULL 以一直更新到根)。
  2. - 复制回调,将给定子树的增强值复制到新分配的子树根。
  3. - 树旋转回调,将给定子树的增强值复制到新分配的子树根,并重新计算前子树根的增强信息。

rb_erase_augmented() 的编译代码可能会内联传播和复制回调,这会导致函数很大,因此每个增强的 rbtree 用户应该有一个 rb_erase_augmented() 调用站点,以限制编译代码的大小。

示例用法
^^^^^^^^^^^^^

Interval tree 是 增强rbtree 的一个例子。 参考 - Cormen、Leiserson、Rivest 和 Stein 的“算法简介”。有关区间树的更多详细信息:

经典的 rbtree 有一个单一的键,它不能直接用于存储区间范围,如 [lo:hi] 并快速查找与新 lo:hi 的任何重叠或查找是否有新 lo 的 lo:hi 的精确匹配。

但是,可以扩充 rbtree 以以结构化的方式存储此类间隔范围,从而可以进行有效的查找和精确匹配。

每个节点中存储的这个“额外信息”是其后代节点中的最大 hi (max_hi) 值。 只需查看节点及其直接子节点即可在每个节点上维护此信息。 这将用于 O(log n) 查找最低匹配(所有可能匹配中的最低起始地址),例如:

  1. struct interval_tree_node *interval_tree_first_match(struct rb_root *root, unsigned long start, unsigned long last)
  2. {
  3. struct interval_tree_node *node;
  4.  
  5. if (!root->rb_node)
  6. return NULL;
  7. node = rb_entry(root->rb_node, struct interval_tree_node, rb);
  8.  
  9. while (true) {
  10. if (node->rb.rb_left) {
  11. struct interval_tree_node *left = rb_entry(node->rb.rb_left, struct interval_tree_node, rb);
  12. if (left->__subtree_last >= start) {
  13. /*
  14. * 左子树中的一些节点满足 Cond2。 迭代找到最左边的这样的节点 N。如果它也满足 Cond1,
  15. * 那就是我们正在寻找的匹配项。 否则,因为 N 右边的节点也不能满足 Cond1,所以没有匹配区间。
  16. */
  17. node = left;
  18. continue;
  19. }
  20. }
  21. if (node->start <= last) { /* Cond1 */
  22. if (node->last >= start) /* Cond2 */
  23. return node; /* node is leftmost match 满足在[start, last]区间里*/
  24. if (node->rb.rb_right) {
  25. node = rb_entry(node->rb.rb_right, struct interval_tree_node, rb);
  26. if (node->__subtree_last >= start)
  27. continue;
  28. }
  29. }
  30. return NULL; /* No match */
  31. }
  32. }

插入/删除是使用以下增强回调定义的:

  1. static inline unsigned long compute_subtree_last(struct interval_tree_node *node)
  2. {
  3. unsigned long max = node->last, subtree_last;
  4. if (node->rb.rb_left) {
  5. subtree_last = rb_entry(node->rb.rb_left, struct interval_tree_node, rb)->__subtree_last;
  6. if (max < subtree_last)
  7. max = subtree_last;
  8. }
  9. if (node->rb.rb_right) {
  10. subtree_last = rb_entry(node->rb.rb_right, struct interval_tree_node, rb)->__subtree_last;
  11. if (max < subtree_last)
  12. max = subtree_last;
  13. }
  14. return max;
  15. }
  16.  
  17. static void augment_propagate(struct rb_node *rb, struct rb_node *stop)
  18. {
  19. while (rb != stop) {
  20. struct interval_tree_node *node = rb_entry(rb, struct interval_tree_node, rb);
  21. unsigned long subtree_last = compute_subtree_last(node);
  22. if (node->__subtree_last == subtree_last)
  23. break;
  24. node->__subtree_last = subtree_last;
  25. rb = rb_parent(&node->rb);
  26. }
  27. }
  28.  
  29. static void augment_copy(struct rb_node *rb_old, struct rb_node *rb_new)
  30. {
  31. struct interval_tree_node *old = rb_entry(rb_old, struct interval_tree_node, rb);
  32. struct interval_tree_node *new = rb_entry(rb_new, struct interval_tree_node, rb);
  33.  
  34. new->__subtree_last = old->__subtree_last;
  35. }
  36.  
  37. static void augment_rotate(struct rb_node *rb_old, struct rb_node *rb_new)
  38. {
  39. struct interval_tree_node *old = rb_entry(rb_old, struct interval_tree_node, rb);
  40. struct interval_tree_node *new = rb_entry(rb_new, struct interval_tree_node, rb);
  41.  
  42. new->__subtree_last = old->__subtree_last;
  43. old->__subtree_last = compute_subtree_last(old);
  44. }
  45.  
  46. static const struct rb_augment_callbacks augment_callbacks = {
  47. augment_propagate, augment_copy, augment_rotate
  48. };
  49.  
  50. void interval_tree_insert(struct interval_tree_node *node, struct rb_root *root)
  51. {
  52. struct rb_node **link = &root->rb_node, *rb_parent = NULL;
  53. unsigned long start = node->start, last = node->last;
  54. struct interval_tree_node *parent;
  55.  
  56. while (*link) {
  57. rb_parent = *link;
  58. parent = rb_entry(rb_parent, struct interval_tree_node, rb);
  59. if (parent->__subtree_last < last)
  60. parent->__subtree_last = last;
  61. if (start < parent->start)
  62. link = &parent->rb.rb_left;
  63. else
  64. link = &parent->rb.rb_right;
  65. }
  66.  
  67. node->__subtree_last = last;
  68. rb_link_node(&node->rb, rb_parent, link);
  69. rb_insert_augmented(&node->rb, root, &augment_callbacks);
  70. }
  71.  
  72. void interval_tree_remove(struct interval_tree_node *node, struct rb_root *root)
  73. {
  74. rb_erase_augmented(&node->rb, root, &augment_callbacks);
  75. }

5. 补充:

Interval tree上的每个节点的 key 值是一个区间,区间的起点和终点通常为整数,同一层节点所代表的区间相互不会重叠,叶子节点的区间是单位长度,不能再分了。区间树可以参考:https://www.jianshu.com/p/e23ab4bc7dec

Linux内核红黑树1—Documentation/rbtree.txt翻译的更多相关文章

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

    转自:https://blog.csdn.net/npy_lp/article/details/7420689 内核源码:linux-2.6.38.8.tar.bz2 关于二叉查找树的概念请参考博文& ...

  2. Documentation/kobject.txt翻译--sysfs

    你从未想过的关于kobjects,ksets和ktypes的一切:作者:Greg Kroah-Hartman <gregkh@linuxfoundation.org>:上次更新时间:200 ...

  3. 红黑树(三)之 Linux内核中红黑树的经典实现

    概要 前面分别介绍了红黑树的理论知识 以及 通过C语言实现了红黑树.本章继续会红黑树进行介绍,下面将Linux 内核中的红黑树单独移植出来进行测试验证.若读者对红黑树的理论知识不熟悉,建立先学习红黑树 ...

  4. linux/Documentation/kobject.txt

    Everything you never wanted to know about kobjects, ksets, and ktypes Greg Kroah-Hartman <gregkh@ ...

  5. Documentation/sched-bwc.txt 的中文翻译

    Chinese translated version of Documentation/sched-bwc.txt If you have any comment or update to the c ...

  6. Linux内网渗透

    Linux虽然没有域环境,但是当我们拿到一台Linux 系统权限,难道只进行一下提权,捕获一下敏感信息就结束了吗?显然不只是这样的.本片文章将从拿到一个Linux shell开始,介绍Linux内网渗 ...

  7. 卸载Linux内置的AMP软件

    卸载Linux内置的AMP软件 在安装Linux软件的LAMP环境时,必须有一个前提:必须要完全卸载掉系统内置的AMP软件. 1.卸载httpd软件(Apache) 如果在卸载软件时出现依赖关系,我们 ...

  8. 将CMD内的显示内容输出到txt文件

    将CMD内的显示内容输出到txt文件 xxxx -t >c:\test.txt        //xxxx为命令  如ping www.baidu.com //-t >c:\test.tx ...

  9. 三十道linux内核面试题

      1. Linux中主要有哪几种内核锁? Linux的同步机制从2.0到2.6以来不断发展完善.从最初的原子操作,到后来的信号量,从大内核锁到今天的自旋锁.这些同步机制的发展伴随Linux从单处理器 ...

  10. LINUX内核面试题摘选

    转载:http://blog.csdn.net/zm1_1zm/article/details/77231197 1) Linux中主要有哪几种内核锁? 答:Linux的同步机制从2.0到2.6以来不 ...

随机推荐

  1. C-01\编译器和链接器以及真正的入口函数

    编译器: 工具 编译器 路径 VC++6.0 CL.EXE(一段shell)只负责分析命令行参数,真正功能实现在C1.DLL.C1XX.DLL.C2.DLL C:\Program Files (x86 ...

  2. 线程基础知识 04 synchronized锁的四种状态和升级

    转https://www.cnblogs.com/mingyueyy/p/13054296.html 1 轻量级锁和重量级锁简要说明 线程调度本来是由操作系统来管理的.现在,操作系统上跑了一个虚拟机J ...

  3. JDBC工具类,减少代码冗余好帮手

    首先要在scr下创建一个file文件 当然 需要初始的注册驱动和数据库操作都可以实现,才可以用jdbc工具类进行减多少代码冗余~可以看前面一篇的博客,就是写如何连接jdbc哈~代码运行成功的快乐真的好 ...

  4. 学习Java Day13

    今天学习了对象与类,类是构造对象的模板或蓝图.由类构造对象的过程称为创建类的实例.类与类之间的关系有依赖,聚合,继承. 然后学习了如何构造对象.

  5. 五大数据类型 - 字符串 - 列表 list - 集合set - 有序集合 - 哈希 hashMap

    基础知识 redis默认有16个数据库:默认使用的是第0个. 可以使用select num切换 查看DB大小 DBSIZE 查看所有的key **keys ** 清空当前数据库 flushdb 清空全 ...

  6. Solon v2.1.4 发布。支持 java、kotlin、groovy!

    本次发布,重点测试和验证了在 java.kotlin.groovy 三种 jvm 语言里,开箱即用的特性.并发布 Solon Initializr: https://solon.noear.org/s ...

  7. Error in created hook: "TypeError: Cannot read properties of undefined (reading 'get')"

    写Vue 的时候常遇到的错误之一就是XXXXXX未定义,然后来一个undefined这种问题一般都是创建钩子出错:无法读取未定义的 xxx 属性. 此错误一般多出现在 created() 中, Vue ...

  8. because another server process exited abnormally and possibly corrupted shared memory

    一.环境 系统平台:Linux x86-64 Red Hat Enterprise Linux 7 版本:4.1.1   二.症状 Red Hat 7.2操作系统部署HGDB 4.1.1后,日志经常出 ...

  9. sql查询多个结果字段通过逗号分隔为同一行显示、sql查询结果有符号分隔的字段拆分多行显示

    一.sql查询多个结果通过逗号分隔为同一行显示 sql查询数据结果 select e.ctrl_desc from t_ctrl_entry e inner join CodeGroupKeyCode ...

  10. python基本语法入门

    思维导图 https://gitee.com/starry-tong/python-data/blob/pyimage/day03.png python语法注释 """注 ...