最近看了幾個 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 內核的特性要求數據結構必須做到的
缺點是:
* 統計鏈表中節點的數量,需要遍歷。當然也可以定義一個變量,作爲計數器。
* 用戶操作不當可能會破壞鏈表結構