转自: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 成员的结构:

struct mytype {
struct rb_node node;
char *keystring;
};

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

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

struct rb_root mytree = RB_ROOT;

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

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

例子:

struct mytype *my_search(struct rb_root *root, char *string) {
struct rb_node *node = root->rb_node; while (node) {
struct mytype *data = container_of(node, struct mytype, node);
int result; result = strcmp(string, data->keystring); if (result < 0)
node = node->rb_left;
else if (result > 0)
node = node->rb_right;
else
return data;
}
return NULL;
}

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

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

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

例子:

int my_insert(struct rb_root *root, struct mytype *data) {
struct rb_node **new = &(root->rb_node), *parent = NULL; /* Figure out where to put new node */
while (*new) { //*new为root->rb_node,然后往下遍历,直到指向的为NULL退出循环
struct mytype *this = container_of(*new, struct mytype, node);
int result = strcmp(data->keystring, this->keystring); parent = *new;
if (result < 0)
new = &((*new)->rb_left);
else if (result > 0)
new = &((*new)->rb_right);
else
return FALSE;
} /* Add new node and rebalance tree. */
rb_link_node(&data->node, parent, new);
rb_insert_color(&data->node, root); return TRUE;
}

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

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

例子:

void rb_erase(struct rb_node *victim, struct rb_root *tree);

struct mytype *data = mysearch(&mytree, "walrus");
if (data) {
rb_erase(&data->node, &mytree);
myfree(data);
}

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

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

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

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

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

struct rb_node *rb_first(struct rb_root *tree);
struct rb_node *rb_last(struct rb_root *tree);
struct rb_node *rb_next(struct rb_node *node);
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)直接访问各个成员。

例子:

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

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

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

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

struct rb_root_cached mytree = RB_ROOT_CACHED;

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

struct rb_node *rb_first_cached(struct rb_root_cached *tree);
void rb_insert_color_cached(struct rb_node *, struct rb_root_cached *, bool);
void rb_erase_cached(struct rb_node *node, struct rb_root_cached *);

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

void rb_insert_augmented_cached(struct rb_node *node, struct rb_root_cached *, bool, struct rb_augment_callbacks *);
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 个回调:

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

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) 查找最低匹配(所有可能匹配中的最低起始地址),例如:

struct interval_tree_node *interval_tree_first_match(struct rb_root *root, unsigned long start, unsigned long last)
{
struct interval_tree_node *node; if (!root->rb_node)
return NULL;
node = rb_entry(root->rb_node, struct interval_tree_node, rb); while (true) {
if (node->rb.rb_left) {
struct interval_tree_node *left = rb_entry(node->rb.rb_left, struct interval_tree_node, rb);
if (left->__subtree_last >= start) {
/*
* 左子树中的一些节点满足 Cond2。 迭代找到最左边的这样的节点 N。如果它也满足 Cond1,
* 那就是我们正在寻找的匹配项。 否则,因为 N 右边的节点也不能满足 Cond1,所以没有匹配区间。
*/
node = left;
continue;
}
}
if (node->start <= last) { /* Cond1 */
if (node->last >= start) /* Cond2 */
return node; /* node is leftmost match 满足在[start, last]区间里*/
if (node->rb.rb_right) {
node = rb_entry(node->rb.rb_right, struct interval_tree_node, rb);
if (node->__subtree_last >= start)
continue;
}
}
return NULL; /* No match */
}
}

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

static inline unsigned long compute_subtree_last(struct interval_tree_node *node)
{
unsigned long max = node->last, subtree_last;
if (node->rb.rb_left) {
subtree_last = rb_entry(node->rb.rb_left, struct interval_tree_node, rb)->__subtree_last;
if (max < subtree_last)
max = subtree_last;
}
if (node->rb.rb_right) {
subtree_last = rb_entry(node->rb.rb_right, struct interval_tree_node, rb)->__subtree_last;
if (max < subtree_last)
max = subtree_last;
}
return max;
} static void augment_propagate(struct rb_node *rb, struct rb_node *stop)
{
while (rb != stop) {
struct interval_tree_node *node = rb_entry(rb, struct interval_tree_node, rb);
unsigned long subtree_last = compute_subtree_last(node);
if (node->__subtree_last == subtree_last)
break;
node->__subtree_last = subtree_last;
rb = rb_parent(&node->rb);
}
} static void augment_copy(struct rb_node *rb_old, struct rb_node *rb_new)
{
struct interval_tree_node *old = rb_entry(rb_old, struct interval_tree_node, rb);
struct interval_tree_node *new = rb_entry(rb_new, struct interval_tree_node, rb); new->__subtree_last = old->__subtree_last;
} static void augment_rotate(struct rb_node *rb_old, struct rb_node *rb_new)
{
struct interval_tree_node *old = rb_entry(rb_old, struct interval_tree_node, rb);
struct interval_tree_node *new = rb_entry(rb_new, struct interval_tree_node, rb); new->__subtree_last = old->__subtree_last;
old->__subtree_last = compute_subtree_last(old);
} static const struct rb_augment_callbacks augment_callbacks = {
augment_propagate, augment_copy, augment_rotate
}; void interval_tree_insert(struct interval_tree_node *node, struct rb_root *root)
{
struct rb_node **link = &root->rb_node, *rb_parent = NULL;
unsigned long start = node->start, last = node->last;
struct interval_tree_node *parent; while (*link) {
rb_parent = *link;
parent = rb_entry(rb_parent, struct interval_tree_node, rb);
if (parent->__subtree_last < last)
parent->__subtree_last = last;
if (start < parent->start)
link = &parent->rb.rb_left;
else
link = &parent->rb.rb_right;
} node->__subtree_last = last;
rb_link_node(&node->rb, rb_parent, link);
rb_insert_augmented(&node->rb, root, &augment_callbacks);
} void interval_tree_remove(struct interval_tree_node *node, struct rb_root *root)
{
rb_erase_augmented(&node->rb, root, &augment_callbacks);
}

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. springboot多模块controller访问的问题

    参考 https://blog.csdn.net/qq_25091649/article/details/88429512 情况一:在主类直接写controller接口,能够访问到 @SpringBo ...

  2. JAVA虚拟机22-原子性、可见性与有序性、先行发生原则

    1 简介 Java内存模型是围绕着在并发过程中如何处理原子性.可见性和有序性这三个特征来建立的,我们逐个来看一下哪些操作实现了这三个特性   2 原子性 2.1 操作指令 由Java内存模型来直接保证 ...

  3. vuluhub_jangow-01-1.0.1

    前言 靶机:jangow-01-1.0.1 攻击机:kali linux2022.4 靶机描述 打靶ing 靶机探测 使用nmap扫描网段 点击查看代码 ┌──(root㉿kali)-[/home/k ...

  4. Node版本管理工具 - Nvm的下载、安装配置与使用

    1.业务背景 不同时期的项目使用的Node版本也不一样,随着版本的更新一直在使用的Node版本也在不断升级,本文介绍一个Node版本的管理工具,可自由切换版本. 2.Nvm下载与安装 1)下载 下载地 ...

  5. Requset02

    其他功能: 1. 获取请求参数通用方式:不论get还是post请求方式都可以使用下列方法来获取请求参数 1. String getParameter(String name):根据参数名称获取参数值 ...

  6. centos7中配置vnc服务多用户

    我用的本地yum源. 注意: 在配置root用户时,设置vnc密码.vncpasswd 添加普通用户时,要先在root用户下添加普通用户(如:vnc1,vnc2) useradd vnc1 passw ...

  7. [代码审计基础 03]-RCE-fork,system,execve

    RCE-fork,system,execve 简单来讲: 数据流进入了控制流 紧紧抓住输入 不同数据层的交汇处,往往是漏洞点 远程代码执行 PHP eval() assert() preg_reple ...

  8. 【KAWAKO】MNN-1.2.0版本交叉编译遇到的错误与解决方法

    目录 在使用gcc-linaro-7.5.0-aarch64-linux-gnu.gcc-linaro-6.3.1-aarch64-linux-gnu交叉编译链对MNN1.2.0进行交叉编译的过程中, ...

  9. LG P5244 [USACO19FEB] Mowing Mischief P

    \(\text{Code}\) #include <bits/stdc++.h> #define IN inline #define eb emplace_back using names ...

  10. TrueNAS安装 一个厉害的nas系统

    转载: 戴俊财--个人学习网站 https://www.daijuncai.cn/?p=128