list_for_each與list_for_each_entry詳解
一、list_for_each
1.list_for_each原型
#define list_for_each(pos, head) \
for (pos = (head)->next, prefetch(pos->next); pos != (head); \
pos = pos->next, prefetch(pos->next))
它實際上是一個 for 循環,利用傳入的pos 作爲循環變量,從表頭 head開始,逐項向後(next方向)移動 pos ,直至又回到 head (prefetch() 可以不考慮,用於預取以提高遍歷速度)。
注意:此宏必要把list_head放在數據結構第一項成員,至此,它的地址也就是結構變量的地址。
2.使用方法(以訪問當前進程的子進程爲例):
struct list_head {
struct list_head *next, *prev;
};
在struct task_struct 中有如下定義:
struct list_head children;
所以
struct task_struct *task;
struct list_head *list;
list_for_each(list,¤t->chilidren) {
task = list_entry(list, struct task_struct, sibling);/*task指向當前的某個子進程*/
}
其中用到了函數list_entry():
這個函數的作用在圖1中表示就是可以通過已知的指向member子項的指針,獲得整個結構體的指針(地址)
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
二、list_for_each_entry:
在Linux內核源碼中,經常要對鏈表進行操作,其中一個很重要的宏是list_for_each_entry:
意思大體如下:
假設只有兩個結點,則第一個member代表head,
list_for_each_entry的作用就是循環遍歷每一個pos中的member子項。
圖1:
pos: pos:
___________ ____________
| | | |
| | | |
| ........... | | ................ |
| | | |
| | | |
| member: | _________|__> member |
| { | | | { |
| *prev; | | | *prev; |
| *next; --|---------- | *next;-------------
| } | | } | |
|—^———— | |____________| |
| |
| |
|_____________________________________________|
宏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))
list_entry((head)->next, typeof(*pos), member)返回(head)->next物理指針所處位置向前減去offsetof()個字節數據之後, 其父變量pos的物理地址,父變量的類型在編譯時由typeof(*pos)自動返回.
所以list_for_each_entry遍歷head 下面掛接的類型爲typeof(*pos)的childs結構體們,當然每個child結構體包含struct list_head node之類相似的雙向鏈表list_head類型項,就這樣通過循環pos將依次指向雙向鏈表上的各個child.(member就是child類型中被定義的變量名)
其中用到了函數list_entry():
這個函數的作用在圖1中表示就是可以通過已知的指向member子項的指針,獲得整個結構體的指針(地址)
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
和函數prefetch:
#define prefetch(x) __builtin_prefetch(x)
其中用到了builtin_prefetch:
prefetch的含義是告訴cpu那些元素有可能馬上就要用到,告訴cpu預取一下,這樣可以提高速度
其中用到了函數container_of():
493#define container_of(ptr, type, member) ({ \
494 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
495 (type *)( (char *)__mptr - offsetof(type,member) );})
其中又用到了offsetof()函數:
lxr上找到的源碼:
#define offset_of(type, memb) \
47 ((unsigned long)(&((type *)0)->memb))
offsetof(TYPE, MEMBER)
該宏在Linux內核代碼(版本2.6.22)中定義如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER);
分析:
(TYPE *)0,將 0 強制轉換爲 TYPE 型指針,記 p = (TYPE *)0,p是指向TYPE的指針,它的值是0。那麼 p->MEMBER 就是 MEMBER 這個元素了,而&(p->MEMBER)就是MENBER的地址,而基地址爲0,這樣就巧妙的轉化爲了TYPE中的偏移量。再把結果強制轉換爲size_t型的就OK了,size_t其實也就是int。
typedef __kernel_size_t size_t;
typedef unsigned int __kernel_size_t;
可見,該宏的作用就是求出MEMBER在TYPE中的偏移量。
關於list_entry的解釋
list_entry這樣定義:
#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
描述
我們使用list_entry()宏在linux鏈表中訪問鏈表數據。
原理爲指針ptr指向結構體type中的成員member;通過指針ptr,返回結構體type的起始地址。
定義中((size_t) &(type *)0)->member)意爲:把0地址轉化爲type結構的指針,然後獲取該結構中member成員的指針,並將其強制轉換爲size_t類型
解釋 &((type *)0)->member:
把“0”強制轉化爲指針類型,則該指針一定指向“0”(數據段基址)。因爲指針是“type *”型的,所以可取到以“0”爲基地址的一個type型變量member域的地址。那麼這個地址也就等於member域到結構體基地址的偏移字節數。
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))):
(char *)(ptr)使得指針的加減操作步長爲一字節,(unsigned long)(&((type *)0)->member)等於ptr指向的member到該member所在結構體基地址的偏移字節數。二者一減便得出該結構體的地址,轉換爲 (type *)型的指針。
例一;
container_of宏定義在[include/linux/kernel.h]中:
#define container_of(ptr, type, member) /
const typeof( ((type *)0)->member ) *__mptr = (ptr); /
(type *)( (char *)__mptr - offsetof(type,member) );
offsetof宏定義在[include/linux/stddef.h]中:
#define offsetof(type, member) ((size_t) &((type *)0)->member)
下面用一個測試程序test.c來說明
#include<stdio.h>
struct student{
char name[20];
char sex;
}stu={"zhangsan",'m'};
s
main()
{
struct student *stu_ptr; //存儲container_of宏的返回值
int offset; //存儲offsetof宏的返回值
//下面三行代碼等同於 container_of(&stu.sex,struct student, sex )參數帶入的情形
const typeof(((struct student*)0)->sex) *_mptr = &stu.sex;
//首先定義一個 _mptr指針,類型爲struct student結構體中sex成員的類型
//typeof 爲獲取(((struct student*)0)->sex)的類型,此處此類型爲char
//((struct student*)0)在offsetof處講解
offset = (int)(&((struct student *)0)->sex);
stu_ptr = (struct student *)((char*)_mptr - offset);
printf("offsetof stu.sex = %d/n",offset);
printf("stu_ptr->name:%s/tstu_ptr->sex:%c/n", stu_ptr->name, stu_ptr->sex);
return 0;
}
例二:
它的作用顯而易見,那就是根據一個結構體變量中的一個域成員變量的指針來獲取指向整個結構體變量的指針。比如,有一個結構體變量,其定義如下:
1. struct demo_struct {
2. type1 member1;
3. type2 member2;
4. type3 member3;
5. type4 member4;
6. };
7.
8. struct demo_struct demo;
同時,在另一個地方,獲得了變量demo中的某一個域成員變量的指針,比如:
1. type3 *memp = get_member_pointer_from_somewhere();
此時,如果需要獲取指向整個結構體變量的指針,而不僅僅只是其某一個域成員變量的指針,我們就可以這麼做:
1. struct demo_struct *demop = container_of(memp, struct demo_struct, member3);
首先,我們將container_of(memp, struct demo_struct, type3)根據宏的定義進行展開如下:
1. struct demo_struct *demop = ({ /
2. const typeof( ((struct demo_struct *)0)->member3 ) *__mptr = (memp); /
3. (struct demo_struct *)( (char *)__mptr - offsetof(struct demo_struct, member3) );})
其中,typeof是GNU C對標準C的擴展,它的作用是根據變量獲取變量的類型。因此,上述代碼中的第2行的作用是首先使用typeof獲取結構體域變量member3的類型爲 type3,然後定義了一個type3指針類型的臨時變量__mptr,並將實際結構體變量中的域變量的指針memp的值賦給臨時變量__mptr。
假設結構體變量demo在實際內存中的位置如下圖所示:
demo
+-------------+ 0xA000
| member1 |
+-------------+ 0xA004
| member2 |
| |
+-------------+ 0xA010
| member3 |
| |
+-------------+ 0xA018
| member4 |
+-------------+
則,在執行了上述代碼的第2行之後__mptr的值即爲0xA010。
再看上述代碼的第3行,其中需要說明的是offsetof,它定義在include/linux/stddef.h中,其定義如下:
1. 24#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
同樣,我們將上述的offsetof調用展開,即爲:
1. (struct demo_struct *)( (char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) );
可見,offsetof的實現原理就是取結構體中的域成員相對於地址0的偏移地址,也就是域成員變量相對於結構體變量首地址的偏移。
因此,offsetof(struct demo_struct, member3)調用返回的值就是member3相對於demo變量的偏移。結合上述給出的變量地址分佈圖可知,offsetof(struct demo_struct, member3)將返回0x10。
於是,由上述分析可知,此時,__mptr==0xA010,offsetof(struct demo_struct, member3)==0x10。
因此, (char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) == 0xA010 - 0x10 == 0xA000,也就是結構體變量demo的首地址(如上圖所示)。
由此,container_of實現了根據一個結構體變量中的一個域成員變量的指針來獲取指向整個結構體變量的指針的功能。