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

來源:互聯網
上載者:User

轉自: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,在編譯的時候,會為每一種列表產生相應的代碼。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.