對於鏈表,我想每一個程式員都很瞭解。結構就像一根鏈條一樣,一節接一節,而對它的訪問有點像冰糖葫蘆,如果你要吃第二個,你必須先吃掉第一個。這就是單向鏈表。當然它也有更進階的,比如,迴圈鏈表,雙向鏈表,雙向迴圈鏈表。Linux核心的標準鏈表就是環形雙向迴圈鏈表。
Linux中的鏈表有點特殊,它沒有前端節點,它的尾節點就直接指向首節點。於是構成了一個很大的環,所以每一個節點都是前端節點,你可以從任意節點出發,沿任意的方向逐一查看鏈表或者是它的一部分。
Linux核心中的鏈表是很優秀的設計,核心中所欲用到鏈表的地方都使用了它,所以在你的代碼中也最好用這些已有的鏈表介面,別螳臂擋車,阻擋潮流了。
下面我們來看看Linux核心中的鏈表給我們提供了什麼樣的功能。
首先,鏈表結構體定義在標頭檔<linux/list.h>中,形式很簡單:
struct list_head {
struct list_head *next, prev;
};
一個list_head結構體本身沒有什麼意義,,通常需要把它嵌入到你自己的結構體中:
struct my_struct {
struct list_head list;
unsigned long dog;
void *cat;
};
鏈表在使用前必須初始化,由於多數元素都是動態建立(也許這就是你需要使用鏈表的原因),所以最常見的情況是在運行時初始化鏈表。
struct my_struct *p;
p->dog = 0;
p->cat = NULL;
INIT_LIST_HEAD(&p->list);
直接聲明和初始化一個靜態鏈表:
static LIST_HEAD(fox);
上述語句聲明並初始化名為fox的靜態鏈表。
不需要與任何鏈表的內部成員打交到,你僅僅將鏈表結構插入到你自己的資料中就可以了。
操作鏈結表 核心提供了一組函數來操作鏈結表,這些函數都要使用一個或多個list_head結構體指標做參數。因為函數都是用C語言以內嵌函式形式實現的,所以它們的原型都在標頭檔<linuxlist.h>中。
有趣的是,所有這些函數的複雜度都是O(1),也就是說,無論這些函數操作的鏈表大小如何,無論它們得到額參數如何,它們都在恒定的時間裡完成。
1,給鏈表增加一個節點:在給定節點之後
list_add(struct list_head *new, struct list_head *head)
2,給鏈表增加一個節點:在給定節點之前
list_add_tail(struct list_head *new, struct list_head *head)
3,從鏈表刪除一個節點:
lsit_del(struct list_head *entry)
需要自己手動釋放entry結構體
4,從鏈表中刪除一個節點,並對其進行重新初始化
lsit_del_init(struct list_head *entry)
5,把節點從一個鏈表移到另一個鏈表:
list_move(struct list_head *list, struct list_head *head)
list_move_tail(struct list_head *list, struct list_head *head)
6,檢查鏈表是否為空白:
lsit_empty(struct list_head *head)
如果指定的鏈表為空白,該函數返回非0值,否則返回0
7,合并兩個鏈表
list_splice(struct list_head *list, struct list_head *head)
list_splice_init(struct list_head *list, struct list_head *head)
如果你已經得到了next和prev指標,那你可以使用內部操作函數,從而省下提領指標的時間,內建函式與外部函數重名,只是在前面加了兩條底線。
遍曆鏈表:
核心給我們提供了一組非常好用的介面來遍曆鏈表,不過它們的複雜度為O(n),n為鏈表中的元素數目。list_for_each()宏就是用來遍曆鏈表,它有兩個參數,第一個用來指向當前項,第二個是需要遍曆的鏈表,但是它得到的只是lsit_head的指標,而我麼您需要的是插入了list_head的結構體。list_entry()宏可以協助我們它有三個參數,一個是指向給定的鏈表元素的指標,一個是其中嵌入了鏈表的結構體類型的引用,另一個是結構體中鏈表成員的名稱:如下是用法:
struct list_head *p;
struct my_struct * my;
list_for_each(p, &mine->list) {
my = list_entry(p, struct my_struct, list);
}
還有一些宏:list_for_each_prev() list_for_each_safe() 可以選擇使用
最後一個宏只能用來防止鏈表刪除操作,為了防止並發訪問實際的鏈表資料,應該使用其他的鎖