有趣的資料結構——Linux核心中的鏈表

來源:互聯網
上載者:User
    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 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.