原文地址:http://blog.sina.com.cn/s/blog_533074eb0101do71.html
內核中經常採用鏈表來管理對象,先看一下內核中對鏈表的定義
struct list_head {
struct list_head *next, *prev;
};
一般將該數據結構嵌入到其他的數據結構中,從而使得內核可以通過鏈表的方式管理新的數據結構,看一個例子:
struct example {
member a;
struct list_head list;
member b;
};
1、鏈表的定義和初始化
您可以通過兩種方式來定義和初始化一個鏈表頭結點,例如,您想定義一個鏈表頭結點mylist,那麼您可以這麼做:
① LIST_HEAD(mylist); // 使用LIST_HEAD宏定義並初始化一個鏈表
也可以這麼做:
② struct list_head mylist; // 定義一個鏈表
INIT_LIST_HEAD(&mylist); // 使用INIT_LIST_HEAD函數初始化鏈表
可以看出方式①稍微簡單一點,我們先來分析一下LIST_HEAD宏:
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) /
struct list_head name = LIST_HEAD_INIT(name)
很容易看出LIST_HEAD(mylist);會被擴展爲:
struct list_head mylist = { &(mylist), &(mylist) };
list_head結構只有兩個成員:next和prev。從上面的代碼可以看出,next和prev都被賦值爲鏈表mylist的地址,也就是說,鏈表初始
化後next和prev都是指向自己的。
大多數情況下,list_head是被嵌入到其他數據結構中的,比如上面的example結構裏的list成員,那麼如何對list成員進行初始化?通過調用INIT_LIST_HEAD函數:
struct example test;
INIT_LIST_HEAD(&test.list);
該函數簡單地將list成員的prev和next指針指向自己。
可以看出鏈表結點在初始化時,都將prev和next指向自己。注意:對鏈表的初始化非常重要,因爲如果使用一個未被初始化的鏈表結點,很有可能會導致內核異常。例如,在對一個鏈表結點調用list_del函數後,接着再去對該結點進行一些操作。後面會有分析的:)
2、對鏈表常用的操作
對鏈表常用的操作無非就是增加、刪除、遍歷等。當然內核還提供很多其他的操作,如替換某個結點、將某個結點移動到鏈表尾端等等,這些操作都是通過調用基本的增加、刪除等操作完成的。
① 增加:list_add和list_add_tail
調用list_add可以將一個新鏈表結點插入到一個已知結點的後面;
調用list_add_tail可以將一個新鏈表結點插入到一個已知結點的前面;
下面分析它們的具體實現,它們都以不同的參數調用了相同的函數__list_add:
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
該函數將new結點插入到prev結點和next之間;
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
list_add函數中以new、head、head->next爲參數調用__list_add,將new結點插入到head和head->next之間,也就是將new結點插入到特定的已知結點head的後面;
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
而list_add_tail函數則以new、head->prev、head爲參數調用__list_add,將new結點插入到head->prev和head之間,也就是將new結點插入到特定的已知結點head的前面。
有了list_add和list_add_tail,我們可以很方便地實現棧(list_add)和隊列(list_add_tail),在本文的最後一節,我們再做詳細的分析。
② 刪除:list_del和list_del_init
調用list_del函數刪除鏈表中的一個結點;
調用list_del_init函數刪除鏈表中的一個結點,並初始化被刪除的結點(也就是使被刪除的結點的prev和next都指向自己);
下面分析它們的具體實現,它們都調用了相同的函數__list_del:
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
該函數實際的作用是讓prev結點和next結點互相指向;
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
該函數中以entry->prev和entry->next爲參數調用__list_del函數,使得entry結點的前、後結點繞過entry直接互相指向,然後將entry結點的前後指針指向LIST_POISON1和LIST_POISON2,從而完成對entry結點的刪除。此函數中的LIST_POISON1和LIST_POISON2有點讓人費解,因爲一般情況下我們刪除entry後,應該讓entry的prev和next指向NULL的,可是這裏卻不是,原因有待調查。
static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}
與list_del不同,list_del_init將entry結點刪除後,還會對entry結點做初始化,使得entry結點的prev和next都指向自己。
3、幾個重要的宏
內核提供了一組宏,以方便對鏈表進行管理,下面我只介紹到目前爲止,我遇到過的,可能會很少,因爲我接觸到的很有限,以後遇到其他的會添加進來的。下面開始我們的分析啦:)
① list_entry
前面說過,list_head結構通常被嵌入到其他數據結構中,以便內核可以通過鏈表的方式管理這些數據結構。假設這樣一種場景:我們已知類型爲example的對象的list成員的地址ptr(struct list_head *ptr),那麼我們如何通過ptr來得到該example對象的地址呢?答案很明顯,使用container_of宏。不過,在這樣的情況下我們應該通過使用list_entry宏來完成container_of宏的功能,因爲這樣更容易理解一點。其實list_entry宏很簡單:#define list_entry(ptr, type, member) container_of(ptr, type, member) ......
上述情況,我們可以這樣: list_entry(ptr, struct example, list); 來獲取example對象的指針。
② list_for_each_entry
對鏈表的一個重要的操作就是對鏈表進行遍歷,以達到某種應用目的,比如統計鏈表結點的個數等等。先來看看內核中對該宏的定義:
#define list_for_each_entry(pos, head, member) /
for (pos = list_entry((head)->next, typeof(*pos), member); /
prefetch(pos->member.next), &pos->member != (head); /
pos = list_entry(pos->member.next, typeof(*pos), member))
其中,pos是指向宿主結構的指針,在for循環中是一個迭代變量;head是要進行遍歷的鏈表頭指針;member是list_head成員在宿主結構中的名字。
未完,待續.....