轉自:http://blog.csdn.net/yanook/article/details/7199513
Linux核心中有很多種鏈表,如果對每一種鏈表都使用單獨的資料結構去表示,那麼需要對每個鏈表實現一組原語操作,包括初始化、插入、刪除等。於是,Linux核心定義了一個很有趣的資料結構: list_head struct list_head {
struct list_head * next, * prev;
}; 複製代碼
乍一看這定義,似乎很普通,但妙就妙在普通上。 通常我們的做法總是將資料嵌入到鏈表的節點中,類似下面的定義方法: struct list_node {
data_type data;
list_node * next, * prev;
} 複製代碼
示意圖如下:
但是,這裡正好相反,將鏈表節點嵌入到資料結構中: struct data_type {
data;
list_head;
} 複製代碼
示意圖如下(其中增加了一個啞元):
在這種鏈表中,所有的鏈表基本操作都是針對list_head資料結構進行,而不是針對包含list_head的資料結構,所以無論什麼資料,鏈表操作都得到了統一。那麼,現在碰到一個問題,因為所有鏈表操作涉及到的指標都是指向list_head資料結構的,而不是包含的資料結構,那麼怎樣從list_head的地址得到包含其的資料結構的地址呢。我們來看linux核心中的 list_entry(p,t,m)這個宏: #define list_entry(ptr, type, member) \
container_of(ptr, type, member) 複製代碼
跟蹤到container_of宏: #define container_of(ptr, type, member) ({ \
const typeof ( ((type * ) 0 ) -> member ) * __mptr = (ptr); \
(type * )( ( char * )__mptr - offsetof(type,member) );}) 複製代碼
這裡面offsetof不需要跟蹤,我們也能理解這個宏的意思了。先簡單對宏的三個參數說明一下。ptr是指向list_head資料結構的指標,type是容器資料結構的類型,member是list_head在type中的名字。直接看下面的範例程式碼: struct data{
xxx;
list_head list1;
list_head list2;
xxx;
};
struct data vardat = {初始化};
list_head * p = & vardat.list1;
list_head * q = & vardat.list2;
list_entry(p, struct data, list1) == & vardat;
list_entry(q, struct data, list2) == & vardat; 複製代碼
從上面這個例子可以看出,vardat可以同時掛到兩個(或更多)鏈表上,其中list1是第一個鏈表上的一個節點,list2是第二個鏈表上的節點,從&list1和&list2都可以得到vardat,上面提出的問題也就解決了。
前面跳過了 offsetof這個宏,相信有不少讀者對這個宏也會感興趣,那麼我們現在來看看這個宏是怎麼實現的。跟蹤這個宏會發現有兩種定義,一種是__compiler_offsetof(a,b),繼續跟蹤得到__builtin_offsetof(a,b),這就不看了;我們看另一種定義: #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
看過之後恍然大悟,原來這麼簡單,把一個TYPE類型的指向0的指標,其MEMBER自然就是offset,妙哉。
小結一下這種鏈表的優點: (1)所有鏈表基本操作都是基於list_head指標的,因此添加類型時,不需要重複寫鏈表基本操作函數(2)一個container資料結構可以含有多個list_head成員,這樣就可以同時掛到多個不同的鏈表中,例如linux核心中會將進程資料結構( task_struct)同時掛到任務鏈表、優先順序鏈表等多個鏈表上,下面會有更多說明。
來看看linux核心中提供的鏈表基本操作(不做說明的資料類型都是list_head *): list_add( new , head); // 將new插入到head元素後面
list_add_tail( new , head); // 額,跟上面的區別就不用解釋了,不過這裡的head是真正的鏈表頭
list_del(entry); // 刪除entry節點
list_empty(head); // 檢查是否為空白鏈表
list_entry(ptr, type, member); // 前面解釋過了
list_for_each(pos, head); // 遍曆列表,每次迴圈是通過pos返回節點list_head指標
// 下面這個最有用。
list_for_each_entry(pos, head, member); // 同上,但通過pos返回的是container資料結構的地址。 複製代碼
慢。發現一個問題了,list_entry中需要type,為啥list_for_each_entry不需要呢。簡單,pos是你給的一個container資料結構的指標,在宏的實現中,用typeof(*pos)就得到type了。
我們來看看linux中的task_struct這個資料結構,它存放的是進程資訊,只看跟鏈表有關的內容: struct task_struct {
// xxxxxxx
struct hlist_head preempt_notifiers;
struct list_head rcu_node_entry;
struct list_head tasks;
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent's children list */
struct list_head ptraced;
struct list_head ptrace_entry;
struct list_head thread_group;
// 還有好多list,不抄了……
} 複製代碼
其中tasks是所有進程組成的鏈表,因此要遍曆所有進程,可以用這個宏: #define for_each_process(p) \
for (p = & init_task ; (p = next_task(p)) != & init_task ; )
#define next_task(p) \
list_entry((p) -> tasks.next, struct task_struct, tasks) 複製代碼 這樣的代碼是不是很酷呢。。
------------------------跑題分割線------------------------ 筆者在做mit的作業系統實驗jos時,遇到了這樣的鏈表實現,下面是其代碼中給出的使用樣本: struct Frob
{
int frobozz;
LIST_ENTRY(Frob) frob_link; /* this contains the list element pointers */
};
LIST_HEAD(Frob_list, Frob) /* defines struct Frob_list as a list of Frob */
struct Frob_list flist; /* declare a Frob list */
LIST_INIT( & flist); /* clear flist (globals are cleared anyway) */
flist = LIST_HEAD_INITIALIZER( & flist); /* alternate way to clear flist */
if (LIST_EMPTY( & flist)) /* check whether list is empty */
printf( " list is empty\n " );
struct Frob * f = LIST_FIRST( & flist); /* f is first element in list */
f = LIST_NEXT(f, frob_link); /* now f is next (second) element in list */
f = LIST_NEXT(f, frob_link); /* now f is next (third) element in list */
for (f = LIST_FIRST( & flist); f != 0 ; /* iterate over elements in flist */
f = LIST_NEXT(f, frob_link))
printf( " f %d\n " , f -> frobozz);
LIST_FOREACH(f, & flist, frob_link) /* alternate way to say that */
printf( " f %d\n " , f -> frobozz);
f = LIST_NEXT(LIST_FIRST( & flist)); /* f is second element in list */
LIST_INSERT_AFTER(f, g, frob_link); /* add g right after f in list */
LIST_REMOVE(g, frob_link); /* remove g from list (can't insert twice!) */
LIST_INSERT_BEFORE(f, g, frob_link); /* add g right before f */
LIST_REMOVE(g, frob_link); /* remove g again */
LIST_INSERT_HEAD( & flist, g, frob_link); /* add g as first element in list */
複製代碼
可以看出,這裡的用法跟linux核心差不多,用一套宏可以對各種鏈表進行操作,但是仔細閱讀相關宏的代碼之後發現,它跟linux核心中的list_head有很大的區別,它更像是C++中的template,在編譯的時候,會為每一種列表產生相應的代碼。