概述 链接到标题

链表的搜索时间复杂度为 O(N) ,在链表管理的成员规模变大时,搜索的算法成本变大。对于这种情况,Zephyr 提供了一个红黑树实现,对于大小为 N 的树,搜索和删除操作的时间复杂度为 O(log2(N)) 。在 Zephyr 中内核调度算法和 p4wq 中使用了红黑树。

Zephyr 中红黑树的实现代码为:

  • zephyr/include/zephyr/sys/p4wq.h
  • zephyr/lib/os/rb.c

Zephyr 实现的是一个传统的红黑树,其算法不是本文的讨论重点,这里只列出一些 Zephyr 的实现要点:

  • 从树根到任何叶子的路径长度不超过其他叶子路径长度的两倍
  • 红色子节点不能有红色子节点
  • Zephyr 的旋转并不只是简单的交换节点内部数据指针实现,还有更为复杂的边缘条件处理
  • Zephyr 的树节点只包含两个指针,空间开销和双向链表一样 ( 通常的实现会有 3 个指针,多一个指向父节点 )

结构体 链接到标题

链接到标题

使用 struct rbtree 管理红黑树,对于红黑树外部只设置 lessthan_fn,其它的为内部管理使用:

struct rbtree {
    /** Root node of the tree */
    struct rbnode *root;
    /** Comparison function for nodes in the tree */
    rb_lessthan_t lessthan_fn;
    /** @cond INTERNAL_HIDDEN */
    int max_depth;
# ifdef CONFIG_MISRA_SANE
    struct rbnode *iter_stack [Z_MAX_RBTREE_DEPTH];
    unsigned char iter_left [Z_MAX_RBTREE_DEPTH];
# endif
    /** @endcond */
};

节点 链接到标题

使用 struct rbnode 管理树节点

struct rbnode {
    /** @cond INTERNAL_HIDDEN */
    struct rbnode *children [2];
    /** @endcond */
};

与 Zephyr 的链表一样,rbtree 中的节点存在于用户管理的内存中的结构,因此使用树前要先定义用户节点

struct user_rbnode {
    struct rbnode rbnode;
    sys_dlist_t dlnode;
    uint32_t priority;
    void *data;
};

需要注意:与用户可以直接通过链表节点遍历链表不一样,rbnode 中的数据是完全不透明的,用户无法提取二叉树拓扑进行「手动」遍历。

初始化 链接到标题

使用 struct rbtree 管理红黑树,对于该结构,没有特定的 API 进行初始化,对该结构成员赋值 0。赋值时需要提供比较函数来帮助红黑树进行排序判断:

typedef bool ( *rb_lessthan_t ) ( struct rbnode *a, struct rbnode *b ) ;

当比较函数认为节点 a「小于」节点 b 时此函数应返回布尔值 True。但注意,树中的节点不允许「相等」,必须具有固定顺序才能使红黑树算法正常工作。

static bool rb_lessthan ( struct rbnode *a, struct rbnode *b )
{
    struct user_rbnode *au = CONTAINER_OF ( a, struct user_rbnode, rbnode ) ;
    struct user_rbnode *bu = CONTAINER_OF ( b, struct user_rbnode, rbnode ) ;

    if ( au->priority != bu->priority ) {
        return au->priority > bu->priority;
    }

    return ( uintptr_t ) a <( uintptr_t ) b;
}

典型的初始化过程

struct rbtree rb;
memset ( &rb, 0, sizeof ( struct rbtree )) ;
rb.lessthan_fn = rb_lessthan;

操作 链接到标题

使用 rb_insert ( ) 插入节点, rb_remove ( ) 删除节点。由 rb_get_min ( ) rb_get_max ( ) 完成对树中的「第一个」和「最后一个」节点的访问 ( 在比较函数定义的顺序的意义上 ) 。另外还提供 rb_contains ( ) 来判断节点是否属于指定的树。如果属于则返回 True, 示例代码如下:

struct user_rbnode n1 = {.priority = 1};
struct user_rbnode n2 = {.priority = 2};
struct user_rbnode n3 = {.priority = 3};
struct user_rbnode n4 = {.priority = 4};
struct user_rbnode n5 = {.priority = 5};
struct user_rbnode n6 = {.priority = 6};

rb_insert ( &rb, &n3.rbnode ) ;
rb_insert ( &rb, &n4.rbnode ) ;
rb_insert ( &rb, &n1.rbnode ) ;
rb_insert ( &rb, &n6.rbnode ) ;
rb_insert ( &rb, &n5.rbnode ) ;
rb_insert ( &rb, &n2.rbnode ) ;

//此时 min 是 n1, max 是 n6,和插入的时间顺序无关,只和比较顺序有关
struct rbnode* min = rb_get_min ( &rb ) ;
struct rbnode* max = rb_get_max ( &rb ) ;

//此时 n1 还在树中,exist = True
bool exist = rb_contains ( &rb, &n1.rbnode ) ;

//移除最大和最小节点
rb_remove ( &rb, &n1.rbnode ) ;
rb_remove ( &rb, &n6.rbnode ) ;

//此时 n1 已从树中删除,exist = False
exist = rb_contains ( &rb, &n1.rbnode ) ;

//此时 min 是 n2, max 是 n5
min = rb_get_min ( &rb ) ;
max = rb_get_max ( &rb ) ;

遍历 链接到标题

Zephyr 提供了两种机制来遍历 rbtree 中的所有节点。一种是 rb_walk ( ) ,在调用时指定回调 typedef void ( *rb_visit_t ) ( struct rbnode *node, void *cookie ) ; 访问函数,树代码按顺序为每个节点调用该函数。这样做的优点是实现非常简单,但对用户来说 API 会稍显繁琐。还要注意的是 rb_walk ( ) 是一个递归实现。示例代码如下:


void rb_visit ( struct rbnode *node, void *cookie )
{
    struct user_rbnode *au = CONTAINER_OF ( a, struct user_rbnode, rbnode ) ;
    printf ( "Node %p priority %u cookie ( tree ) %p\n", au, au->priority, cookie ) ;
}

rb_walk ( &rb, rb_visit, &rb ) ;

另一种是遍历宏 RB_FOR_EACHRB_FOR_EACH_CONTAINER,它与链表的类似,使用嵌套代码块,并且是非递归的,默认情况下需要节点数对数大小的堆栈空间 ( 可以根据需要配置为使用固定/最大大小的缓冲区,以避免动态分配 )RB_FOR_EACH_CONTAINER 为指向用户容器的指针进行遍历,一般情况下都只会使用这一个

struct user_rbnode *au;
RB_FOR_EACH_CONTAINER ( &rb, au, rbnode ) {
             printf ( "Node %p priority %u cookie ( tree ) %p\n", au, au->priority, rb ) ;
        }

参考 链接到标题

https://docs.zephyrproject.org/3.5.0/kernel/data_structures/rbtree.html