<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<atom:link href="https://www.gentoo-zh.org/extern.php?action=feed&amp;tid=456&amp;type=rss" rel="self" type="application/rss+xml" />
		<title><![CDATA[Gentoo中文社区 / linux源码解读（十四）：红黑树在内核的应用——红黑树原理和api解析]]></title>
		<link>https://www.gentoo-zh.org/viewtopic.php?id=456</link>
		<description><![CDATA[linux源码解读（十四）：红黑树在内核的应用——红黑树原理和api解析 最近发表的帖子。]]></description>
		<lastBuildDate>Sun, 09 Oct 2022 04:00:18 +0000</lastBuildDate>
		<generator>FluxBB</generator>
		<item>
			<title><![CDATA[linux源码解读（十四）：红黑树在内核的应用——红黑树原理和api解析]]></title>
			<link>https://www.gentoo-zh.org/viewtopic.php?pid=463#p463</link>
			<description><![CDATA[<p>1、红黑树是一种非常重要的数据结构，有比较明显的两个特点：</p><p>&#160; &#160; 插入、删除、查找的时间复杂度接近O(logN)，N是节点个数，明显比链表快；是一种性能非常稳定的二叉树！<br />&#160; &#160; 中序遍历的结果是从小到大排好序的</p><p>&#160; 基于以上两个特点，红黑树比较适合的应用场景:</p><p>&#160; &#160; 需要动态插入、删除、查找的场景，包括但不限于：<br />&#160; &#160; &#160; &#160; &#160; 某些数据库的增删改查，比如select * from xxx where 这类条件检索<br />&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;linux内核中进程通过红黑树组织管理，便于快速插入、删除、查找进程的task_struct<br />&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;linux内存中内存的管理：分配和回收。用红黑树组织已经分配的内存块，当应用程序调用free释放内存的时候，可以根据内存地址在红黑树中快速找到目标内存块<br />&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;hashmap中(key,value)增、删、改查的实现；java 8就采用了RBTree替代链表<br />&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;Ext3文件系统，通过红黑树组织目录项<br />&#160; &#160; 排好序的场景，比如：<br />&#160; &#160; &#160; &#160; &#160; linux定时器的实现：hrtimer以红黑树的形式组织，树的最左边的节点就是最快到期的定时</p><p>&#160; 从上述的应用场景可以看出来红黑树是非常受欢迎的一种数据结构，接下来深入分析一些典型的场景，看看linux的内核具体是怎么使用红黑树的！</p><p>&#160; &#160; &#160; &#160;2、先来看看红黑树的定义，在include\linux\rbtree.h文件中：</p><div class="codebox"><pre><code>struct rb_node {
    unsigned long  __rb_parent_color;
    struct rb_node *rb_right;
    struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
    /* The alignment might seem pointless, but allegedly CRIS needs it */</code></pre></div><p>&#160; 结构体非常简单，只有3个字段，凡是有一丁点开发经验的人员都会有疑问：红黑树有那么多应用场景，这个结构体居然一个应用场景的业务字段都没有，感觉就像个还没装修的毛坯房，这个该怎么用了？这恰恰是设计的精妙之处：红黑树在linux内核有大量的应用场景，如果把rb_node的定义加上了特定应用场景的业务字段，那这个结构体就只能在这个特定的场景下用了，完全没有了普适性，变成了场景紧耦合的；这样的结构体多了会增加后续代码维护的难度，所以rb_node结构体的定义就极简了，只保留了红黑树节点自身的3个属性：左孩子、右孩子、节点颜色（list_head结构体也是这个思路）；这么简单、不带业务场景属性的结构体该怎么用了？先举个简单的例子，看懂后能更快地理解linux源码的原理。比如一个班级有50个学生，每个学生有id、name和score分数，现在要用红黑树组织所有的学生，先定义一个student的结构体：</p><div class="codebox"><pre><code>struct Student{
    int id;
    char *name;
    int scroe
    struct rb_node s_rb;
};</code></pre></div><p>&#160; 前面3个都是业务字段，第4个是红黑树的字段（student和rb_node结构体看起来是两个分开的结构体，但经过编译器编译后会合并字段，最终就是一块连续的内存，有点类似c++的继承关系）；linux提供了红黑树基本的增、删、改、查、左旋、右旋、设置颜色等操作，如下：</p><div class="codebox"><pre><code>#define rb_parent(r)   ((struct rb_node *)((r)-&gt;rb_parent_color &amp; ~3)) //低两位清0
#define rb_color(r)   ((r)-&gt;rb_parent_color &amp; 1)                       //取最后一位
#define rb_is_red(r)   (!rb_color(r))                                  //最后一位为0？
#define rb_is_black(r) rb_color(r)                                     //最后一位为1？
#define rb_set_red(r)  do { (r)-&gt;rb_parent_color &amp;= ~1; } while (0)    //最后一位置0
#define rb_set_black(r)  do { (r)-&gt;rb_parent_color |= 1; } while (0)   //最后一位置1

static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p) //设置父亲
{
    rb-&gt;rb_parent_color = (rb-&gt;rb_parent_color &amp; 3) | (unsigned long)p;
}
static inline void rb_set_color(struct rb_node *rb, int color)          //设置颜色
{
    rb-&gt;rb_parent_color = (rb-&gt;rb_parent_color &amp; ~1) | color;
}
//左旋、右旋
void __rb_rotate_left(struct rb_node *node, struct rb_root *root);
void __rb_rotate_right(struct rb_node *node, struct rb_root *root);
//删除节点
void rb_erase(struct rb_node *, struct rb_root *);
void __rb_erase_color(struct rb_node *node, struct rb_node *parent, struct rb_root *root);
//替换节点
void rb_replace_node(struct rb_node *old, struct rb_node *new, struct rb_root *tree);//插入节点
  void rb_link_node(struct rb_node * node, struct rb_node * parent, struct rb_node ** rb_link);
//遍历红黑树
extern struct rb_node *rb_next(const struct rb_node *); //后继
extern struct rb_node *rb_prev(const struct rb_node *); //前驱
extern struct rb_node *rb_first(const struct rb_root *);//最小值
extern struct rb_node *rb_last(const struct rb_root *); //最大值</code></pre></div><p>&#160; 上面的操作接口传入的参数都是rb_node，怎么才能用于来操作用户自定义业务场景的红黑树了，就比如上面的student结构体？既然这些接口的传入参数都是rb_node，如果不改参数和函数实现，就只能按照别人的要求传入rb_node参数，自定义结构体的字段怎么才能“顺带”加入红黑树了？这个也简单，自己生成结构体，然后把结构体的rb_node参数传入即可，如下：</p><div class="codebox"><pre><code>/*
 将对象加到红黑树上
 s_root            红黑树root节点
 ptr_stu        对象指针
 rb_link        对象节点所在的节点
 rb_parent        父节点
 */
void student_link_rb(struct rb_root *s_root, struct Student *ptr_stu,
        struct rb_node **rb_link, struct rb_node *rb_parent)
{
    rb_link_node(&amp;ptr_stu-&gt;s_rb, rb_parent, rb_link);
    rb_insert_color(&amp;ptr_stu-&gt;s_rb, s_root);
}

void add_student(struct rb_root *s_root, struct Student *stu, struct Student **stu_header)
{
    struct rb_node **rb_link, *rb_parent;
    // 插入红黑树
    student_link_rb(s_root, stu, rb_link, rb_parent);
}</code></pre></div><p>&#160; 假如以score分数作为构建红黑树的key，构建的树如下：每个student节点的rb_right和rb_left指针指向的都是rb_node的起始地址，也就是_rb_parent_color的值，但是score、name、id这些值其实才是业务上急需读写的，怎么得到这些字段的值了?<br /><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202201/2052730-20220113193503526-1259156607.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; &#160; &#160; &#160;linux的开发人员早就想好了读取的方法：先得到student实例的开始地址，再通过偏移读字段不就行了么？如下：</p><div class="codebox"><pre><code>#define container_of(ptr, type, member) ({                \
    const typeof( ((type *)0)-&gt;member ) *__mptr = (ptr);  \
    (type *)( (char *)__mptr - offsetof(type,member) );})</code></pre></div><p>&#160; 通过上面的宏定义就能得到student实例的首地址了，用法如下：调用container_of方法，传入rbnode的实例（确认student实例的位置）、student结构体和内部rb_node的位置（用以计算rb_node在结构体内部的偏移，然后反推student实例的首地址）：得到student实例的首地址，接下来就可以愉快的直接使用id、name、score等字段了；</p><div class="codebox"><pre><code>struct Student* find_by_id(struct rb_root *root, int id)
{
    struct Student *ptr_stu = NULL;
    struct rb_node *rbnode = root-&gt;rb_node;
    while (NULL != rbnode)
    {
//最核心的代码：三个参数分别时rb_node的实例，student结构体的定义和内部的rb_node字段位置
        struct Student *ptr_tmp = container_of(rbnode, struct Student, s_rb);
        if (id &lt; ptr_tmp-&gt;id)
        {
            rbnode = rbnode-&gt;rb_left;
        }
        else if (id &gt; ptr_tmp-&gt;id)
        {
            rbnode = rbnode-&gt;rb_right;
        }
        else
        {
            ptr_stu = ptr_tmp;
            break;
        }
    }
    return ptr_stu;
}</code></pre></div><p>&#160; 总结一下红黑树使用的大致流程：</p><p>&#160; &#160; 开发人员根据业务场景需求定义结构体的字段，务必包含rb_node；<br />&#160; &#160; 生成结构体的实例stu，调用rb_link_node添加节点构建红黑树。当然传入的参数是stu-&gt;s_rb<br />&#160; &#160; 遍历查找的时候根据找s_rb实例、自定义结构体、rb_node在结构体的名称得到自定义结构体实例的首地址，然后就能愉快的读写业务字段了！</p><p>&#160; &#160; &#160; &#160;3、上述的案例够简单吧，linux内部各种复杂场景使用红黑树的原理和这个一毛一样，没有任何本质区别！理解了上述案例的原理，也就理解了linux内核使用红黑树的原理！接下来看看红黑树一些关机api实现的方法了：</p><p>&#160; &#160; &#160;（1）红黑树是排好序的，中序遍历的结果就是从小到大排列的；最左边就是整棵树的最小节点，所以一直向左就能找到第一个、也是最小的节点；</p><div class="codebox"><pre><code>/*
 * This function returns the first node (in sort order) of the tree.
 */
struct rb_node *rb_first(const struct rb_root *root)
{
    struct rb_node    *n;

    n = root-&gt;rb_node;
    if (!n)
        return NULL;
    while (n-&gt;rb_left)
        n = n-&gt;rb_left;
    return n;
}</code></pre></div><p>&#160; 同理：一路向右能找到整棵树最大的节点</p><div class="codebox"><pre><code>struct rb_node *rb_last(const struct rb_root *root)
{
    struct rb_node    *n;

    n = root-&gt;rb_node;
    if (!n)
        return NULL;
    while (n-&gt;rb_right)
        n = n-&gt;rb_right;
    return n;
}</code></pre></div><p>&#160; （2）找到某个节点下一个节点：比如A节点数值是50，从A节点的右孩开始（右孩所有节点都比A大），往左找 as far as get null；也就是整个树中比A大的最小节点；这个功能可以用来做条件查询！</p><div class="codebox"><pre class="vscroll"><code>struct rb_node *rb_next(const struct rb_node *node)
{
    struct rb_node *parent;

    if (RB_EMPTY_NODE(node))
        return NULL;

    /*
     * If we have a right-hand child, go down and then left as far
     * as we can.
     */
    if (node-&gt;rb_right) {
        node = node-&gt;rb_right;
        while (node-&gt;rb_left)
            node=node-&gt;rb_left;
        return (struct rb_node *)node;
    }

    /*
     * No right-hand children. Everything down and left is smaller than us,
     * so any &#039;next&#039; node must be in the general direction of our parent.
     * Go up the tree; any time the ancestor is a right-hand child of its
     * parent, keep going up. First time it&#039;s a left-hand child of its
     * parent, said parent is our &#039;next&#039; node.
     */
    while ((parent = rb_parent(node)) &amp;&amp; node == parent-&gt;rb_right)
        node = parent;

    return parent;
}</code></pre></div><p>&#160; 同理，找到整个树中比A小的最大节点：</p><div class="codebox"><pre><code>struct rb_node *rb_prev(const struct rb_node *node)
{
    struct rb_node *parent;

    if (RB_EMPTY_NODE(node))
        return NULL;

    /*
     * If we have a left-hand child, go down and then right as far
     * as we can.
     */
    if (node-&gt;rb_left) {
        node = node-&gt;rb_left;
        while (node-&gt;rb_right)
            node=node-&gt;rb_right;
        return (struct rb_node *)node;
    }

    /*
     * No left-hand children. Go up till we find an ancestor which
     * is a right-hand child of its parent.
     */
    while ((parent = rb_parent(node)) &amp;&amp; node == parent-&gt;rb_left)
        node = parent;

    return parent;
}</code></pre></div><p>&#160; （3）替换一个节点：把周围的指针改向，然后改节点颜色</p><div class="codebox"><pre><code>void rb_replace_node(struct rb_node *victim, struct rb_node *new,
             struct rb_root *root)
{
    struct rb_node *parent = rb_parent(victim);

    /* Set the surrounding nodes to point to the replacement */
    __rb_change_child(victim, new, parent, root);
    if (victim-&gt;rb_left)
        rb_set_parent(victim-&gt;rb_left, new);
    if (victim-&gt;rb_right)
        rb_set_parent(victim-&gt;rb_right, new);

    /* Copy the pointers/colour from the victim to the replacement */
    *new = *victim;
}</code></pre></div><p>&#160; （4）插入一个节点：分不同情况左旋、右旋；</p><div class="codebox"><pre class="vscroll"><code>static __always_inline void
__rb_insert(struct rb_node *node, struct rb_root *root,
        void (*augment_rotate)(struct rb_node *old, struct rb_node *new))
{
    struct rb_node *parent = rb_red_parent(node), *gparent, *tmp;

    while (true) {
        /*
         * Loop invariant: node is red
         *
         * If there is a black parent, we are done.
         * Otherwise, take some corrective action as we don&#039;t
         * want a red root or two consecutive red nodes.
         */
        if (!parent) {
            rb_set_parent_color(node, NULL, RB_BLACK);
            break;
        } else if (rb_is_black(parent))
            break;

        gparent = rb_red_parent(parent);

        tmp = gparent-&gt;rb_right;
        if (parent != tmp) {    /* parent == gparent-&gt;rb_left */
            if (tmp &amp;&amp; rb_is_red(tmp)) {
                /*
                 * Case 1 - color flips
                 *
                 *       G            g
                 *      / \          / \
                 *     p   u  --&gt;   P   U
                 *    /            /
                 *   n            n
                 *
                 * However, since g&#039;s parent might be red, and
                 * 4) does not allow this, we need to recurse
                 * at g.
                 */
                rb_set_parent_color(tmp, gparent, RB_BLACK);
                rb_set_parent_color(parent, gparent, RB_BLACK);
                node = gparent;
                parent = rb_parent(node);
                rb_set_parent_color(node, parent, RB_RED);
                continue;
            }

            tmp = parent-&gt;rb_right;
            if (node == tmp) {
                /*
                 * Case 2 - left rotate at parent
                 *
                 *      G             G
                 *     / \           / \
                 *    p   U  --&gt;    n   U
                 *     \           /
                 *      n         p
                 *
                 * This still leaves us in violation of 4), the
                 * continuation into Case 3 will fix that.
                 */
                tmp = node-&gt;rb_left;
                WRITE_ONCE(parent-&gt;rb_right, tmp);
                WRITE_ONCE(node-&gt;rb_left, parent);
                if (tmp)
                    rb_set_parent_color(tmp, parent,
                                RB_BLACK);
                rb_set_parent_color(parent, node, RB_RED);
                augment_rotate(parent, node);
                parent = node;
                tmp = node-&gt;rb_right;
            }

            /*
             * Case 3 - right rotate at gparent
             *
             *        G           P
             *       / \         / \
             *      p   U  --&gt;  n   g
             *     /                 \
             *    n                   U
             */
            WRITE_ONCE(gparent-&gt;rb_left, tmp); /* == parent-&gt;rb_right */
            WRITE_ONCE(parent-&gt;rb_right, gparent);
            if (tmp)
                rb_set_parent_color(tmp, gparent, RB_BLACK);
            __rb_rotate_set_parents(gparent, parent, root, RB_RED);
            augment_rotate(gparent, parent);
            break;
        } else {
            tmp = gparent-&gt;rb_left;
            if (tmp &amp;&amp; rb_is_red(tmp)) {
                /* Case 1 - color flips */
                rb_set_parent_color(tmp, gparent, RB_BLACK);
                rb_set_parent_color(parent, gparent, RB_BLACK);
                node = gparent;
                parent = rb_parent(node);
                rb_set_parent_color(node, parent, RB_RED);
                continue;
            }

            tmp = parent-&gt;rb_left;
            if (node == tmp) {
                /* Case 2 - right rotate at parent */
                tmp = node-&gt;rb_right;
                WRITE_ONCE(parent-&gt;rb_left, tmp);
                WRITE_ONCE(node-&gt;rb_right, parent);
                if (tmp)
                    rb_set_parent_color(tmp, parent,
                                RB_BLACK);
                rb_set_parent_color(parent, node, RB_RED);
                augment_rotate(parent, node);
                parent = node;
                tmp = node-&gt;rb_left;
            }

            /* Case 3 - left rotate at gparent */
            WRITE_ONCE(gparent-&gt;rb_right, tmp); /* == parent-&gt;rb_left */
            WRITE_ONCE(parent-&gt;rb_left, gparent);
            if (tmp)
                rb_set_parent_color(tmp, gparent, RB_BLACK);
            __rb_rotate_set_parents(gparent, parent, root, RB_RED);
            augment_rotate(gparent, parent);
            break;
        }
    }
}</code></pre></div><p>&#160; rb_node最牛逼的地方：去掉了业务属性的字段，和业务场景松耦合，让rb_node结构体和对应的方法可以做到在不同的业务场景通用；同时配合container_of函数，又能通过rb_node实例地址快速反推出业务结构体实例的首地址，方便读写业务属性的字段，这种做法高！实在是高！</p><p>&#160; &#160;4、红黑树为什么这么牛？个人认为最核心的要点在于其动态的高度调整！换句话说：在增、删、改的过程中，为了避免红黑树退化成单向链表，红黑树会动态地调整树的高度，让树高不超过2lg(n+1)；相比AVL 树，红黑树只需维护一个黑高度，效率高很多；这样一来，增删改查的时间复杂度就控制在了O(lgn)! 那么红黑树又是怎么控制树高度的了？就是红黑树那5条规则（这不是废话么？）！最核心的就是第4、5点！</p><p>&#160; &#160; &#160; &#160;（1）先看看第4点：任何相邻的节点都不能同时为红色，也就是说：红节点是被黑节点隔开的；随意选一条从根节点到叶子节点的路径，因为要满足这点，所以每存在一个红节点，至少对应了一个黑节点，即红色节点个数&lt;=黑色节点个数；假如黑色节点数量是n，那么整棵树节点的数量&lt;=2n;</p><p>&#160; &#160; （2）再看看第5点：每个节点，从该节点到达其可达叶子节点的所有路径，都包含相同数目的黑色节点；新加入的节点初始颜色是红色，如果其父节点也是红色，就需要挨个往上回溯更改每个父节点的颜色了！更改颜色后如果打破了第5点，就需要通过旋转重构红黑树，本质上是降低整棵树的高度，避免整棵树退化成链表，举个例子：初始红黑树如下：</p><p><span class="postimg"><img src="https://img2022.cnblogs.com/blog/2052730/202201/2052730-20220126162624124-202312556.png" alt="FluxBB bbcode 测试" /></span></p><p>&#160; &#160; &#160; 增加8节点，节点初始是红色，是7节点的右子节点；因为7节点也是红色，所以要调整成黑色；但是这样一来，2-&gt;4-&gt;6-&gt;7就有3个黑节点了，这时需要继续往上回溯6、4、2节点，分别更改这3个节点的颜色，导致根节点2成了红色，同时5和6都是红色，这两个节点都不符合规定；此时再左旋4节点，让4来做根节点，降低了树的高度，后续再增删改查时还是能保持时间复杂度是O(n)!</p><p><span class="postimg"><img src="https://img2022.cnblogs.com/blog/2052730/202201/2052730-20220126162931262-609950209.png" alt="FluxBB bbcode 测试" /></span></p><p>参考：</p><p>1、https://www.bilibili.com/video/BV135411h7wJ?p=1&#160; 红黑树介绍</p><p>2、https://cloud.tencent.com/developer/article/1922776&#160; 数据结构 红黑树</p><p>3、https://blog.csdn.net/weixin_46381158/article/details/117999284 红黑树基本用法</p><p>4、https://rbtree.phpisfuture.com/ 红黑树在线演示</p><p>5、https://segmentfault.com/a/1190000023101310&#160; 红黑树前世今生</p>]]></description>
			<author><![CDATA[dummy@example.com (batsom)]]></author>
			<pubDate>Sun, 09 Oct 2022 04:00:18 +0000</pubDate>
			<guid>https://www.gentoo-zh.org/viewtopic.php?pid=463#p463</guid>
		</item>
	</channel>
</rss>
