雙向鏈表及鏈表頭:
建立一個雙向鏈表通常有一個獨立的用於管理鏈表的鏈表頭,鏈表頭一般是不含有實體數據的,必須用INIT_LIST_HEAD()進行初始化,表頭建立以后,就可以將帶有數據結構的實體鏈表成員加入到鏈。
INIT_LIST_HEAD (&nphy_dev_list);
定義:
#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))
它實際上是一個 for 循環,利用傳入的 pos 作為循環變量,從表頭 head 開始,逐項向后(next 方向)移動 pos,直至又回head.
我們將for循環分解為一下三點:
1. for循環初始化 pos = list_entry((head)->next, typeof(*pos), member);
2. for循環執行條件 &pos->member != (head);
3. 每循環一次執行 pos = list_entry(pos->member.next, typeof(*pos), member))
typeof()是取變量的類型,這里是取指針pos所指向數據的類型。
先看第一個:
#define list_entry(ptr, type, member) \ container_of(ptr, type, member)
#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
講講container_of:作用:根據一個結構體變量中的一個域成員變量的指針來獲取指向整個結構體變量的指針。
所以(type *)0)就是將0強轉為一個地址,這個地址(0x0000)指向的是類型type的數據。當然,這里是一個技巧,並不是真的在地址0x0000存放了我們的數據。
((type *)0)->member的作用,這里的‘->’很顯然是通過指針指取結構體成員的操作。指針就是剛才通過0強轉的地址。所以也就是相當於地址0x0000 是結構體類型type的首地址,通過->’取其中的成員變量member。
typeof( ((type *)0)->member ) *__mptr = (ptr):知道member成員的類型,定義一個指針變量__mptr,指向的類型是member的類型,其初始化為ptr的值
offsetof(type,member):求出member在結構體中的偏移量
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
根據優先級的順序,最里面的小括號優先級最高,TYPE *將整型常量0強制轉換為TYPE型的指針,且這個指針指向的地址為0,也就是將地址0開始的一塊存儲空間映射為TYPE型的對象,接下來再對結構體中MEMBER成員進行取址,而整個TYPE結構體的首地址是0,這里獲得的地址就是MEMBER成員在TYPE中的相對偏移量。再將這個偏移量強制轉換成size_t型數據(無符號整型)。
(char *)__mptr - offsetof(type,member)就是__mptr - offset,即member類型的指針減去member在結構體中的偏移量。
小結:通過上面的分析,和定義出的注釋,container_of的作用很明顯了 -- 獲得結構體的地址。那么我們需要給他提供三個參數,分別是:ptr:member成員的指針 type:結構體類型 member:成員member的名字。
這樣我們就能通過container_of(ptr, type, member)的返回值,得到結構體的地址。