關於container_of和list_for_each_entry 及其相關函式的分析

weixin_34126215發表於2015-11-17
Linux程式碼看的比較多了,經常會遇到container_of和list_for_each_entry,特別是 list_for_each_entry比較多,因為Linux經常用到連結串列,雖然知道這些函式的大概意思,但一旦出現一個類似的函式比如 list_for_each_entry_safe就又會感到頭大,所以下定決心分析總結一下這些函式的用法,以後再看到這些面孔的時候也會輕鬆很多,讀 Linux程式碼的時候不會那麼吃力。
我們知道list_for_each_entry會用到list_entry,而list_entry用到container_of,所以首先講講container_of。

在講container_of之前我們不得不提到offsetof,因為在container_of中會使用到它,所以我們看下來,把list_for_each_entry函式的用法理順我們對整個Linux中經常用到的一些函式就會比較清楚了。
  1. offsetof                                                                                                                                                                                                                                                                                        /**/                                                                                                                                                                                                                                                                                                 #define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)                                                                                                                                                                           理解offsetof的關鍵在於&((TYPE *)0)->MEMBER,幾乎可以說只要理解了這一部分,後面的幾個函式都能夠解決,那麼我們看看這一部分究竟完成了怎樣的工作。根據優先順序的順 序,最裡面的小括號優先順序最高,TYPE *將整型常量0強制轉換為TYPE型的指標,且這個指標指向的地址為0,也就是將地址0開始的一塊儲存空間對映為TYPE型的物件,接下來再對結構體中 MEMBER成員進行取址,而整個TYPE結構體的首地址是0,這裡獲得的地址就是MEMBER成員在TYPE中的相對偏移量。再將這個偏移量強制轉換成 size_t型資料(無符號整型)。                                                                                                                                                                                                            所以整個offsetof的功能就是獲取MEMBER成員在TYPE型資料中的偏移量。接下來我們可以講一下container_of了。
  2. container_of                                                                                                                                                                                                                                                                                /**
    * container_of - cast a member of a structure out to the containing structure
    * @ptr:        the pointer to the member.
    * @type:       the type of the container struct this is embedded in.
    * @member:     the name of the member within the struct.
    *
    */
    #define container_of(ptr, type, member) ({                      \
         const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
         (type *)( (char *)__mptr - offsetof(type,member) );})                                                                                                                                                                                                    首先可以看出container_of被預定義成一個函式,函式的第一句話,通過((type *)0)->member定義一個MEMBER型的指標__mptr,這個指標指向ptr,所以第一句話獲取到了我們要求的結構體,它的成員 member的地址,接下來我們用這個地址減去成員member在結構體中的相對偏移量,就可以獲取到所求結構體的地址, (char *)__mptr - offsetof(type,member)就實現了這個過程,最後再把這個地址強制轉換成type型指標,就獲取到了所求結構體指標,define預定 義返回最後一句話的值,將所求結構體指標返 回。                                                                                                                                                                                                                                                                                           所以整個container_of的功能就是通過指向結構體成員member的指標ptr獲取指向整個結構體的指標。container_of清楚了,那 list_entry就更是一目瞭然了。
  3. list_entry                                                                                                                                                                                                                                                                                      /**
    * list_entry - get the struct for this entry
    * @ptr:     the &struct list_head pointer.
    * @type:     the type of the struct this is embedded in.
    * @member:     the name of the list_struct within the struct.
    */
    #define list_entry(ptr, type, member) \
           container_of\ (ptr,type,member)                                                                                                                                                                                                                                       list_entry 的功能等同於container_of。接下來分析我們最終想要知道的list_for_each_entry的實現過程。                                                           
  4. list_for_each_entry                                                                                                                                                                                                                                                                  /**
    * list_for_each_entry     -     iterate over list of given type
    * @pos:     the type * to use as a loop cursor.
    * @head:     the head for your list.
    * @member:     the name of the list_struct within the struct.
    */
    #define list_for_each_entry(pos, head, member)                    \
         for (pos = list_entry((head)->next, typeof(*pos), member);     \
              &pos->member != (head);      \
              pos = list_entry(pos->member.next, typeof(*pos), member))
    在理解了list_entry的基礎上分析list_for_each_entry本來是一件比較輕鬆的事情,但在這裡還是要強調一下雙向連結串列及連結串列頭的 概念,否則對list_for_each_entry的理解還是一知半解。建立一個雙向連結串列通常有一個獨立的用於管理連結串列的連結串列頭,連結串列頭一般是不含有實 體資料的,必須用INIT_LIST_HEAD()進行初始化,表頭建立以後,就可以將帶有資料結構的實體連結串列成員加入到連結串列中,連結串列頭和連結串列的關係如圖 所示:                                                                                                                                                                                                                             連結串列頭和連結串列的關係清楚了,我們才能完全理解list_for_each_entry。list_for_each_entry被預定義成一個 for迴圈語句,for迴圈的第一句話獲取(head)->next指向的member成員的資料結構指標,也就是將pos初始化為除連結串列頭之外的 第一個實體連結串列成員,for的第三句話通過pos->member.next指標遍歷整個實體連結串列,當pos->member.next再次 指向我們的連結串列頭的時候跳出for迴圈。整個過程沒有對連結串列頭進行遍歷(不需要被遍歷),所以使用list_for_each_entry遍歷連結串列必須從 連結串列頭開始。                                                                                                                                                                                                                                                                                          因此可以看出,list_for_each_entry的功能就是遍歷以head為連結串列頭的實體連結串列,對實體連結串列中的資料結構進行處理。
  5. list_for_each_entry_safe                                                                                                                                                                                                                                                       /**
    * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
    * @pos:     the type * to use as a loop cursor.
    * @n:          another type * to use as temporary storage
    * @head:     the head for your list.
    * @member:     the name of the list_struct within the struct.
    */
    #define list_for_each_entry_safe(pos, n, head, member)               \
         for (pos = list_entry((head)->next, typeof(*pos), member),     \
              n = list_entry(pos->member.next, typeof(*pos), member);     \
              &pos->member != (head);                         \
              pos = n, n = list_entry(n->member.next, typeof(*n), member))
    相比於list_for_each_entry,list_for_each_entry_safe用指標n對連結串列的下一個資料結構進行了臨時儲存,所以 如果在遍歷連結串列的時候可能要刪除連結串列中的當前項,用list_for_each_entry_safe可以安全的刪除,而不會影響接下來的遍歷過程(用n 指標可以繼續完成接下來的遍歷, 而list_for_each_entry則無法繼續遍歷)。

相關文章