最近看了几个 RTOS 内核文件,发现内核用到的链表都使用宏来操做。
看内容应该是借鉴了 linux
的 list.h
代码。 好奇研究 linux 的链表代码,学习记录下。
下面是我常用的链表结构定义方式:
typedef struct _dlink_node {
void *pData; // 存放用户数据对象指针
struct _dlink_node *ptPre;
struct _dlink_node *ptNext;
} dlink_node_t;
typedef struct _dlink_list {
dlink_node_t *ptHead;
int32_t nCount;
} dlink_list_t;
使用方式一般是先定义一个链表控制块对象 dlink_list_t list
, 所有的节点增删改查都是针对这个链表对象进行操作。
比如插入一个数据的伪代码如下:
dlink_node_t node = {.pData = &UserData};
dlink_list_insert(&list, 1, &node);
然而,linux 的链表实现方式与此不同,没有定义的链表管理控制块 list。而是把 dlink_node_t
这样的节点直接定义到目标对象中。
通过 dlink_node_t
中的 pre
和 next
指针直接建立对象之间的联系。
注:可以直接在 linxu 主机中查看源码文件,我是用的腾讯的虚拟云主机
[root@lzlinks ~]# vim /usr/src/kernels/3.10.0-957.5.1.el7.x86_64/include/linux/types.h
[root@lzlinks ~]# vim /usr/src/kernels/3.10.0-957.5.1.el7.x86_64/include/linux/list.h
linux 中,链表结构体定义在 types.h
文件中, 定义如下:
struct list_head {
struct list_head *next, *prev;
};
可以看到,Linux 内核中定义的是一个仅仅只有两个指针,没有用户数据的双向循环链表结构体。
用户需要把 list
结构体内嵌到自己的数据对象中,通过 list.h
提供的链表操作宏,完成链表的初始化和增删改查操作。
struct user_object {
struct list_head list_node;
int data;
}
/**
\breief: usr object list
object object
...+++ ->next +++++++++ -> next +++++++++ -> next +++...
+ + list + + list + +
...+++ <- pre +++++++++ <- pre +++++++++ <- pre +++...
+ + data + + data + +
...+++ +++++++++ +++++++++ +++...
*/
linux 链表的常用API:
void list_add(struct list_head *new, struct list_head *head);
void list_add_tail(struct list_head *new, struct list_head *head);
// 各种宏定义,都是直接操作链表节点
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
这样做的好处是:
* 链表可以出现在任何位置
* 可以给链表任意命名
* 一个结构体可以属于多个链表,这是 Linux 内核的特性要求数据结构必须做到的
缺点是:
* 统计链表中节点的数量,需要遍历。当然也可以定义一个变量,作为计数器。
* 用户操作不当可能会破坏链表结构