[轉載] 終於理解list_entry和list_for_each_entry

原文地址: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成員在宿主結構中的名字。

 

未完,待續.....

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章