Linux核心中有很多種鏈表,如果對每一種鏈表都使用單獨的資料結構去表示,那麼需要對每個鏈表實現一組原語操作,包括初始化、插入、刪除等。於是,Linux核心定義了一個很有趣的資料結構: list_headstruct 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,在編譯的時候,會為每一種列表產生相應的代碼。轉載請註明出處:http://www.cnblogs.com/stephenjy/archive/2010/02/09/1666166.html