双向链表结构 链接到标题

Zephyr 双向链表实现在下列文件中:zephyr/include/zephyr/sys/dlist.h

Zephyr 以 sys_dlist_t 存储双向链表数据 ( 即每个链表元素存储指向下一个和上一个元素的指针 ) 。与单向链表有专门的链表结构不同,双向链表直接使用节点数据结构作为链表结构:

/** @cond INTERNAL_HIDDEN */
struct _dnode {
    union {
        struct _dnode *head; /* 用作 sys_dlist_t 时,指向链表头  */
        struct _dnode *next; /* 用作 sys_dnode_t 时,指向下一个节点 */
    };
    union {
        struct _dnode *tail; /* 用作 sys_dlist_t 时,指向链表尾 */
        struct _dnode *prev; /* 用作 sys_dnode_t 时,指向前一个节点 */
    };
};

/**
 - @brief Doubly-linked list structure.
 */
typedef struct _dnode sys_dlist_t;
/**
 - @brief Doubly-linked list node structure.
 */
typedef struct _dnode sys_dnode_t;

双向链表的结构如下图:

这种结构在链表任意位置插入删除的时间复杂度都是 O ( 1 ) 。

API 对应 链接到标题

双向链表的大多数操作和单向链表相同,可以直接参考单链表的使用:

节点空间应该由用户分配,通常是嵌入到用户容器节点的结构中

struct container_node {
    sys_dnode_t node;
    void *data;
};

struct container_node *msg = malloc ( sizeof ( struct container_node )) ;

通过访问用户节点成员 msg->node 访问链表节点,当有链表节点 node 时 可以使用 SYS_DLIST_CONTAINER 获取用户节点:

sys_dnode_t *list_node = sys_dlist_get ( &test_list ) ;
struct container_node *msg = SYS_DLIST_CONTAINER ( list_node, msg, node ) ;

其他API对应,功能一致,有个别API不太一样(加粗标识):

  • sys_dlist_init ( ) sys_slist_init ( ) 初始化链表
  • sys_dlist_prepend ( ) sys_slist_prepend ( ) 在链表头部添加节点
  • sys_dlist_append ( ) ``sys_slist_append ( ) 在链表尾部添加节点
  • sys_dlist_insert ( ) ``sys_slist_insert ( ) 指定节点前插入新节点,单向链表是在指定节点后插入新节点
  • sys_dlist_remove ( ) ``sys_slist_remove ( ) 删除指定节点, 单向链表是删除指定节点后的节点
  • sys_dlist_peek_head ( ) : sys_slist_peek_head ( ) 读取链表头节点
  • sys_dlist_peek_tail ( ) : sys_slist_peek_tail ( ) 读取链表尾节点
  • sys_dlist_peek_next ( ) : sys_slist_peek_next ( ) 读取指定节点的下一个节点
  • sys_dlist_is_empty ( ) : sys_slist_is_empty ( ) 判断链表是否为空
  • sys_dlist_get ( ) : sys_slist_get ( ) 取出链表头节点
  • sys_dlist_len ( ) : sys_slist_len ( ) 获取链表长度
  • SYS_DLIST_STATIC_INIT: SYS_SLIST_STATIC_INIT 静态初始化链表
  • SYS_DLIST_FOR_EACH_NODE: SYS_SLIST_FOR_EACH_NODE 遍历链表节点
  • SYS_DLIST_FOR_EACH_NODE_SAFE: SYS_SLIST_FOR_EACH_NODE_SAFE 安全遍历链表节点
  • SYS_DLIST_FOR_EACH_CONTAINER: SYS_SLIST_FOR_EACH_CONTAINER 遍历链表容器节点
  • SYS_DLIST_FOR_EACH_CONTAINER_SAFE: SYS_SLIST_FOR_EACH_CONTAINER_SAFE 安全遍历链表容器节点

以上 API 在单向链表中都有对应的 API,使用方法可以参考 Zephyr 内核数据结构 单向链表 一文

无对应 API 链接到标题

以下API是双向链表特有:

  • sys_dlist_peek_prev 读取指定节点的前一个节点
  • sys_dnode_is_linked 检查节点是否在链表中
  • sys_dlist_insert_at 在特定位置插入节点

以上 API 中前两个很简单,参考头文件中说明即可,现在说明第三个:

static inline void sys_dlist_insert_at ( sys_dlist_t *list, sys_dnode_t *node,
    int ( *cond ) ( sys_dnode_t *node, void *data ) , void *data )

在链表 list 中,但节点 pos 的数据和 data 满足 cond 计算的条件时,将 node 节点插入到 pos 后。示例如下

struct container_node {
    sys_dnode_t node;
    void *data;
};

static sys_dlist_t test_list;
static struct container_node test_node_1;
static struct container_node test_node_2;
static struct container_node test_node_3;
static struct container_node test_node_4;

//匹配函数就是将 data 和链表中节点地址比较,相同则匹配
int cond ( sys_dnode_t *node, void *data )
{
    return ( node == data ) ? 1:0;
}

void main ( void )
{
//初始化双向链表
    sys_dlist_init ( &test_list ) ;

//向双向链表中加入三个节点
    sys_slist_append ( &test_list, &test_node_1.node ) ;
    sys_slist_append ( &test_list, &test_node_2.node ) ;
    sys_slist_append ( &test_list, &test_node_3.node ) ;

//在满足 cond 条件的 node 节点后插入 test_node_4,这里匹配的节点是 test_node_2
//相当于是在 test_node_2 后插入 test_node_4
    sys_dlist_insert_at ( &test_list, &test_node_4.node,
                cond, &test_node_2.node ) ;
}

双向链表的使用更多可以参考 zephyr/tests/unit/list/dlist.c

参考 链接到标题

https://docs.zephyrproject.org/3.5.0/kernel/data_structures/dlist.