前言
Linux內核實現了一下常用的內建數據結構,主要有:
- 鏈表
- 隊列
- 映射
- 二叉樹
今天詳細學習一下鏈表的知識,鏈表是一種存放和操作可變數量元素(常稱爲節點)的數據結構。Linux內核的標準鏈表就是採用環形雙向鏈表形式實現的。
鏈表數據結構
傳統的鏈表是將數據存放在鏈表節點中;而Linux內核的方式與衆不同,它的鏈表節點只有兩個指針(prev和next),鏈表節點保存在用戶數據結構中。
鏈表代碼在頭文件< linux/list.h>中聲明,數據結構很簡單:
struct list_head
{
struct list_head *next;
struct list_head *prev;
}
next指針指向下一個鏈表節點,prev指向上一個鏈表節點。然後這個鏈表節點list_head一般保存在數據的結構體內:
struct fox
{
unsigned long tail_length; //尾巴長度
unsigned long weight; //重量
bool is_fantastic; //狐狸是否奇妙?
struct list_head list; //鏈表節點存放在此處
}
這樣在以後對鏈表的操作都是針對鏈表節點list_head進行的,然後根據list_head就可以找到其所在的數據結構,這是通過list_entry()函數實現的:
list_entry(ptr,type,member);
/*
ptr是指向list_head類型的鏈表的指針
type是數據的結構體,struct fox
member是數據結構體中的一個域,類型爲list_head
函數的作用就是根據結構的成員指針找到其所在結構體的指針。
*/
聲明和初始化一個鏈表
Linux提供了兩種方式初始化鏈表。
一種是使用LIST_HEAD()這個宏:
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
LIST_HEAD(fox_list);就是定義並初始化了名爲fox_list的鏈表,鏈表頭爲fox_list,其next、prev指針都指向自己。
另一種是先定義list_head指針變量,然後用INIT_LIST_HEAD()將其初始化爲鏈表:
struct fox *red_fox;
red_fox=kmalloc(sizeof(*red_fox),GFP_LERNEL);
red_fox->tail_length=40;
red_fox->weigth=6;
red_fox->is_fantastic=false;
INIT_LIST_HEAD(&red_fox->list);//注意參數爲指針
添加和刪除節點
向鏈表中添加一個節點:
list_add(struct list_head *new, struct list_head *head);
例:
list_head(&fox->list, &fox_list);
//fox是新建的數據結構體,fox_list是之前初始化的鏈表
把節點增加到鏈表尾:
list_add_tail(struct list_head *new, struct list_head *head);
從鏈表中刪除一個節點:
list_del(struct list_head *list);
例:
list_del(&fox->list);
注意,該刪除操作並不會釋放list或包含list的結構體所長用的內存。僅僅是將其從鏈表中移除。
從鏈表中刪除一個節點並對其初始化:
list_del_init(struct list_head *list);
把節點從一個鏈表移動到另一個鏈表:
list_move(struct list_head *list, struct list_head *head);
//把list項從鏈表中移除,並添加到另一個鏈表的head節點後面
把節點從一個鏈表移動到另一個鏈表的末尾:
list_move_tail(struct list_head *list, struct list_head *head);
檢查鏈表是否爲空:
list_empty(struct list_head *head);
若鏈表爲空返回非0值,否則返回0。
合併兩個鏈表:
list_splice(struct list_head *list, struct list_head *head);
list_splice_init(struct list_head *list, struct list_head *head);
//合併後初始化原來的鏈表
將list指向的鏈表插入到指定的鏈表head節點後面。
遍歷鏈表
最簡單的方法就是使用list_for_each()宏遍歷鏈表,再通過list_entry()獲取完整數據結構體
例:
struct list_head *p;
struct fox *f;
list_for_each(p, &fox_list)
{
f=list_entry(p, struct fox, list);
}
以上兩個函數可以合併爲一個:list_for_each_entry()
struct fox *f;
list_for_each_entry(f, &fox_list, list)
{
//f就遍歷了所有的數據結構體;
}
反向遍歷鏈表:
list_for_each_entry_reverse(pos, head, member);
遍歷的同時安全刪除節點:
list_for_each_entry_safe(pos, next, head, member);