[精華]]Linux內核2.6.14源碼分析-雙向循環鏈表代碼分析(轉)


 

Linux內核源碼分析-鏈表代碼分析 
分析人:餘旭 
分析時間:2005年11月17日星期四 11:40:10 AM 
雨 溫度:10-11度 
編號:1-4 類別:準備工作 
Email:[email protected] 
時代背景:開始在www.linuxforum.net Linux內核技術論壇上面發貼,在網友的幫忙下,解決了一些問題。 
版權聲明:版權保留。本文用作其他用途當經作者本人同意,轉載請註明作者姓名 
All Rights Reserved. If for other use,must Agreed By the writer.Citing this text,please claim the writer's name. 
Copyright (C) 2005 YuXu 
************************************************** 
-------------雙向循環鏈表--------------------------- 
來源於:list.h 
設計思想:儘可能的代碼重用,化大堆的鏈表設計爲單個鏈表。 
鏈表的構造:如果需要構造某類對象的特定列表,則在其結構中定義一個類型爲list_head指針的成員,通過這個成員將這類對象連接起來,形成所需列表,並通過通用鏈表函數對其進行操作。其優點是隻需編寫通用鏈表函數,即可構造和操作不同對象的列表,而無需爲每類對象的每種列表編寫專用函數,實現了代碼的重用。 

如果想對某種類型創建鏈表,就把一個list_head類型的變量嵌入到該類型中,用list_head中的成員和相對應的處理函數來對鏈表進行遍歷。如果想得到相應的結構的指針,使用list_entry可以算出來。 
-------------防止重複包含同一個頭文件--------------- 
#ifndef _LINUX_LIST_H 
#define _LINUX_LIST_H 
... 
#endif 
用於防止重複包含同一個list.h頭文件 
-----------struct list_head{}及初始化宏--------- 
struct list_head 

struct list_head *next, *prev; 
}; 
list_head從字面上理解,好像是頭結點的意思。但從這裏的代碼來看卻是普通結點的結構體。在後面的代碼中將list_head當成普通的結點來處理。 

--LIST_HEAD_INIT()--LIST_HEAD()--INIT_LIST_HEAD()------ 
#define LIST_HEAD_INIT(name) { &(name), &(name) } 
#define LIST_HEAD(name) \ 
struct list_head name = LIST_HEAD_INIT(name) 
分析:name當爲結構體struct list_head{}的一個結構體變量,&(name)爲該結構體變量的地址。用name結構體變量的始地址將該結構體變量進行初始化。 

#define INIT_LIST_HEAD(ptr) do { \ 
(ptr)->next = (ptr); (ptr)->prev = (ptr); \ 
} while (0) 
1.ptr爲一個結構體的指針,而name爲一個結構體變量; 
2.ptr使用時候,當用括號,(ptr); 

------------__list_add()---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; 

1.普通的在兩個非空結點中插入一個結點,注意new,prev,next都不能是空值。 
2.即:適用於中間結點插入。首結點和尾結點則由於指針爲空,不能用此函數。 
3.在prev指針和next指針所指向的結點之間插入new指針所指向的結點。 

static inline void list_add(struct list_head *new, struct list_head *head) 

__list_add(new, head, head->next); 

在head和head->next兩指針所指向的結點之間插入new所指向的結點。 
即:在head指針後面插入new所指向的結點。此函數用於在頭結點後面插入結點。 
注意:對只有一個單個結點的鏈表,則head->next爲空,list_add()不能用。 

-------------list_add_tail()------------------- 
static inline void list_add_tail(struct list_head *new, struct list_head *head) 

__list_add(new, head->prev, head); 

在頭結點指針head所指向結點的前面插入new所指向的結點。也相當於在尾結點後面增加一個new所指向的結點。(條件是:head->prev當指向尾結點) 
注意: 
1.head->prev不能爲空,即若head爲頭結點,其head->prev當指向一個數值,一般爲指向尾結點,構成循環鏈表。 
2.對只有單個結點的頭結點調用此函數則會出錯。 

-----------__list_del()---list_del()-------------- 
static inline void __list_del(struct list_head * prev, struct list_head * next) 

next->prev = prev; 
prev->next = next; 

在prev和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所指的結點,同時將entry所指向的結點指針域封死。 
對LIST_POISON1,LIST_POISON2的解釋說明: 
Linux 內核中解釋:These are non-NULL pointers that will result in page faults under normal circumstances, used to verify that nobody uses non-initialized list entries. 
#define LIST_POISON1 ((void *) 0x00100100) 
#define LIST_POISON2 ((void *) 0x00200200) 
常規思想是:entry->next = NULL; entry->prev = NULL; 
注意:Linux內核中的‘=’都與前後隔了一個空格,這樣比緊靠前後要清晰。 

---------------list_del_init()-------------------- 
static inline void list_del_init(struct list_head *entry) 

__list_del(entry->prev, entry->next); 
INIT_LIST_HEAD(entry); 

刪除entry所指向的結點,同時將entry所指向的結點的next,prev指針域指向自身。 

-----------list_move()--list_move_tail()---------- 
static inline void list_move(struct list_head *list, struct list_head *head) 

__list_del(list->prev, list->next); 
list_add(list, head); 

將list結點前後兩個結點互相指向彼此,刪除list指針所指向的結點,再將此結點插入head,和head->next兩個指針所指向的結點之間。 
即:將list所指向的結點移動到head所指向的結點的後面。 

static inline void list_move_tail(struct list_head *list, struct list_head *head) 

__list_del(list->prev, list->next); 
list_add_tail(list, head); 

刪除了list所指向的結點,將其插入到head所指向的結點的前面,如果head->prev指向鏈表的尾結點的話,就是將list所指向的結點插入到鏈表的結尾。 

---------------------list_empty()------------- 
static inline int list_empty(const struct list_head *head) 

return head->next == head; 

注意: 
1.如果是隻有一個結點,head,head->next,head->prev都指向同一個結點,則這裏會返回1,但鏈表卻不爲空,仍有一個頭結點 
2.return 後面不帶括號,且爲一個表達式。 
3.測試鏈表是否爲空,但這個空不是沒有任何結點,而是隻有一個頭結點。 

--------------------list_empty_careful()--------- 
static inline int list_empty_careful(const struct list_head *head) 

struct list_head *next = head->next; 
return (next == head) && (next == head->prev); 

分析: 
1.只有一個頭結點head,這時head指向這個頭結點,head->next,head->prev指向head,即:head==head->next==head->prev,這時候list_empty_careful()函數返回1。 
2.有兩個結點,head指向頭結點,head->next,head->prev均指向後面那個結點,即:head->next==head->prev,而head!=head->next,head!=head->prev.所以函數將返回0 
3.有三個及三個以上的結點,這是一般的情況,自己容易分析了。 
注意:這裏empty list是指只有一個空的頭結點,而不是毫無任何結點。並且該頭結點必須其head->next==head->prev==head 

---------------__list_splice()------------------ 
static inline void __list_splice(struct list_head *list, struct list_head *head) 

struct list_head *first = list->next; 
struct list_head *last = list->prev; 
struct list_head *at = head->next; 

first->prev = head; 
head->next = first; 

last->next = at; 
at->prev = last; 


--------------------list_splice()---------------- 
/** 
* list_splice - join two lists 
* @list: the new list to add. 
* @head: the place to add it in the first list. 
*/ 
static inline void list_splice(struct list_head *list, struct list_head *head) 

if (!list_empty(list)) 
__list_splice(list, head); 

分析: 
情況1: 
普遍的情況,每個鏈表都至少有3個以上的結點: 

====>此處作者畫了圖,可顯示不出來,鬱悶!!! 

========》待作者上傳一個word文檔,圖在裏面。 
------------------------------------------------------------------------------------------- 


這種情況會丟棄list所指向的結點,這是特意設計的,因爲兩個鏈表有兩個頭結點,要去掉一個頭結點。只要一個頭結點。 


--------------------------------------------------------------------------------------------------------------- 

特殊情況1: 
初始情況: 


------------------------------------------------------------------------ 

特殊情況2: 
初始情況: 

--------------------list_splice_init()----------------------------------- 
/** 
* list_splice_init - join two lists and reinitialise the emptied list. 
* @list: the new list to add. 
* @head: the place to add it in the first list. 

* The list at @list is reinitialised 
*/ 
static inline void list_splice_init(struct list_head *list, 
struct list_head *head) 

if (!list_empty(list)) 

__list_splice(list, head); 
INIT_LIST_HEAD(list); 



--------------------\asm-i386\posix_types.h------- 
typedef unsigned int __kernel_size_t; 

------\linux\types.h---------size_t--------------- 
#ifndef _SIZE_T 
#define _SIZE_T 
typedef __kernel_size_t size_t; 
#endif 
-------------\linux\compiler-gcc4.h-------------- 
#define __compiler_offsetof(a,b) __builtin_offsetof(a,b) 
分析準備:__compiler_offsetof(),爲gcc編譯器中的編譯方面的參數,查閱gcc方面的文檔: 
--->gcc.pdf.Download from www.gnu.org。其中解釋如下: 
#define offsetof(type, member) __builtin_offsetof (type, member) 

自己分析:即:__builtin_offsetof(a,b)就是#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)。__builtin_offsetof(a,b)和offsetof(TYPE,MEMBER)本質一樣的,只是offsetof()宏是由程序員自己來設計(詳見後面講解)。而__builtin_offsetof()宏就是在編譯器中已經設計好了的函數,直接調用即可。明白了這個區別後,下面的代碼很好理解。
-------\linux\stddef.h-----offsetof()----------- 
#define __compiler_offsetof(a,b) __builtin_offsetof(a,b) 
------------------------------- 
#undef offsetof 
#ifdef __compiler_offsetof 
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER) 
#else 
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 
#endif 
1.對__compiler_offsetof()宏的分析: 
__compiler_offsetof來確認編譯器中是否內建了功能同offsetof()宏一樣的宏。若已經內建了這樣的宏,則offsetof()就是使用這個內建宏__compiler_offsetof()即:__builtin_offsetof()宏。如果沒有定義__compiler_offsetof()宏,則offsetof()宏就由程序員來設計之。 

2.對offsetof()宏的分析:(以下引用論壇)---曾經的騰訊QQ的筆試題。 
宿舍舍友參加qq筆試,回來討論一道選擇題,求結構中成員偏移。 
想起Linux內核鏈表,數據節點攜帶鏈表節點,通過鏈表訪問數據的方法,用到offsetof宏,今天把它翻了出來: 
#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER ) 

一共4步 
1. ( (TYPE *)0 ) 將零轉型爲TYPE類型指針; 
2. ((TYPE *)0)->MEMBER 訪問結構中的數據成員; 
3. &( ( (TYPE *)0 )->MEMBER )取出數據成員的地址; 
4.(size_t)(&(((TYPE*)0)->MEMBER))結果轉換類型.巧妙之處在於將0轉換成(TYPE*),結構以內存空間首地址0作爲起始地址,則成員地址自然爲偏移地址; 
舉例說明: 
#include<stdio.h> 
typedef struct _test 

char i; 
int j; 
char k; 
}Test; 
int main() 

Test *p = 0; 
printf("%p\n", &(p->k)); 

自己分析:這裏使用的是一個利用編譯器技術的小技巧,即先求得結構成員變量在結構體中的相對於結構體的首地址的偏移地址,然後根據結構體的首地址爲0,從而得出該偏移地址就是該結構體變量在該結構體中的偏移,即:該結構體成員變量距離結構體首的距離。在offsetof()中,這個member成員的地址實際上就是type數據結構中member成員相對於結構變量的偏移量。對於給定一個結構,offsetof(type,member)是一個常量,list_entry()正是利用這個不變的偏移量來求得鏈表數據項的變量地址。 

---------------------typeof()-------------------- 
--->我開始不懂,源代碼中也查不到,網上發貼請教。由liubo1977在www.linuxforum.net上的Linux內核技術論壇上解答,QQ:84915771 
答覆: 
unsigned int i; 
typeof(i) x; 
x=100; 
printf("x:%d\n",x); 
typeof() 是 gcc 的擴展,和 sizeof() 類似。 

------------------------ 
container_of()和offsetof()並不僅用於鏈表操作,這裏最有趣的地方是 ((type *)0)->member,它將0地址強制 "轉換" 爲 type 結構的指針,再訪問到 type 結構中的 member 成員。在 container_of 宏中,它用來給 typeof() 提供參數,以獲得 member 成員的數據類型; 

---------------container_of()-------------------- 
container_of() 來自\linux\kernel.h 
內核中的註釋:container_of - cast a member of a tructure 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 truct. 

#define container_of(ptr, type, member) ({ \ 
const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 
(type *)( (char *)__mptr - offsetof(type,member) );}) 
自己分析: 
1.(type *)0->member爲設計一個type類型的結構體,起始地址爲0,編譯器將結構體的起始的地址加上此結構體成員變量的偏移得到此結構體成員變量的偏移地址,由於結構體起始地址爲0,所以此結構體成員變量的偏移地址就等於其成員變量在結構體內的距離結構體開始部分的偏移量。即:&(type *)0->member就是取出其成員變量的偏移地址。而其等於其在結構體內的偏移量:即爲:(size_t)(& ((type *)0)->member)經過size_t的強制類型轉換後,其數值爲結構體內的偏移量。該偏移量這裏由offsetof()求出。 

2.typeof( ( (type *)0)->member )爲取出member成員的變量類型。用其定義__mptr指針.ptr爲指向該成員變量的指針。__mptr爲member數據類型的常量指針,其指向ptr所指向的變量處。 

3.(char *)__mptr轉換爲字節型指針。(char *)__mptr - offsetof(type,member) )用來求出結構體起始地址(爲char *型指針),然後(type *)( (char *)__mptr - offsetof(type,member) )在(type *)作用下進行將字節型的結構體起始指針轉換爲type *型的結構體起始指針。 
這就是從結構體某成員變量指針來求出該結構體的首指針。指針類型從結構體某成員變量類型轉換爲該結構體類型。 

-----------茶餘飯後一點小資料---------------------- 
學辛苦了,看點收集的小東東: 
以下文字摘自微軟中國研究院前任院長,現微軟高級副總裁李開復先生《一封寫給中國學生的信》: 
“我的老闆 Rick室Rashid博士是目前微軟公司主管研究的高級副總裁,他已經功成名就,卻始終保持一顆學習和進取的心。現在,他每年仍然編寫大約50,000行程序。他認爲:用最新的技術編程可以使他保持對計算機最前沿技術的敏感,使自己能夠不斷進步。今天,有些博士生帶低年級的本科生和碩士生做項目,就自滿地認爲自己已經沒有必要再編程了。其實,這樣的做法是很不明智的。” 

--------------arch-v32\cache.h------------------ 
#ifndef _ASM_CRIS_ARCH_CACHE_H 
#define _ASM_CRIS_ARCH_CACHE_H 

/* A cache-line is 32 bytes. */ 
#define L1_CACHE_BYTES 32 
#define L1_CACHE_SHIFT 5 
#define L1_CACHE_SHIFT_MAX 5 

#endif /* _ASM_CRIS_ARCH_CACHE_H */ 
分析: 
也可用#define L1_CACHE_BYTES (1UL<<L1_CACHE_SHIFT)來實現 

-------------asm-i386\cache.h-------------------- 

#ifndef __ARCH_I386_CACHE_H 
#define __ARCH_I386_CACHE_H 

/* L1 cache line size */ 
#define L1_CACHE_SHIFT (CONFIG_X86_L1_CACHE_SHIFT) 
#define L1_CACHE_BYTES (1 << L1_CACHE_SHIFT) 

//largest L1 which this arch supports 
#define L1_CACHE_SHIFT_MAX 7

#endif 
分析:cache行在32位平臺上多爲32字節,但在I386平臺上也有128字節的。 

----------\linux\prefetch.h-------------------- 
這裏是內核中的解釋:(含有自己的分析) 
/* 
prefetch(x) attempts to pre-emptively get the memory pointed to 
by address "x" into the CPU L1 cache. 
prefetch(x) should not cause any kind of exception, prefetch(0) is 
specifically ok. 

prefetch() should be defined by the architecture, if not, the 
#define below provides a no-op define.
prefetch()當由體系結構來決定,否則就定義爲空宏 
有3類prefetch()宏: 
There are 3 prefetch() macros: 

prefetch(x) - prefetches the cacheline at "x" for read-->預取讀 
prefetchw(x) - prefetches the cacheline at "x" for write-->預取寫 
spin_lock_prefetch(x) - prefectches the spinlock *x for taking 

there is also PREFETCH_STRIDE which is the architecure-prefered 
"lookahead" size for prefetching streamed operations. 
PREFETCH_STRIDE用於預取操作流。 
These cannot be do{}while(0) macros. 
*/ 
#define _LINUX_PREFETCH_H 

#ifndef ARCH_HAS_PREFETCH 
static inline void prefetch(const void *x) {;} 
#endif 

#ifndef ARCH_HAS_PREFETCHW 
static inline void prefetchw(const void *x) {;} 
#endif 

#ifndef ARCH_HAS_SPINLOCK_PREFETCH 
#define spin_lock_prefetch(x) prefetchw(x) 
#endif 

#ifndef PREFETCH_STRIDE 
#define PREFETCH_STRIDE (4*L1_CACHE_BYTES) 
#endif //PREFETCH_STRIDE 

static inline void prefetch_range(void *addr, size_t len) 

#ifdef ARCH_HAS_PREFETCH 
char *cp; 
char *end = addr + len; 

for (cp = addr; cp < end; cp += PREFETCH_STRIDE) 
prefetch(cp); 
#endif //ARCH_HAS_PREFETCH 


#endif //_LINUX_PREFETCH_H 

-----asm-x86_64\processor.h---prefetch()--------- 
static inline void prefetch(void *x) 

asm volatile("prefetcht0 %0" :: "m" (*(unsigned long *)x)); 

分析: 
將x指針作強制類型轉換爲unsigned long *型,然後取出該內存操作數,送入高速緩存。 

----------------list_for_each()------------------ 
#define list_for_each(pos, head) \ 
for (pos = (head)->next; prefetch(pos->next), pos != (head); \ 
pos = pos->next) 

----------------__list_for_each()----------------- 
Linux Kernel 2.6.14中的解釋中的精華部分: 
/** 
* This variant differs from list_for_each() in that it's the simplest possible list iteration code, no prefetching is done.Use this for code that knows the list to be very short (empty or 1 entry) most of the time. 
*/ 
#define __list_for_each(pos, head) \ 
for (pos = (head)->next; pos != (head); pos = pos->next) 
list_for_each()有prefetch()用於複雜的表的遍歷,而__list_for_each()無prefetch()用於簡單的表的遍歷. 
注意:head在宏定義中用了括號將其括起來. 

----------------list_for_each_prev()------------- 
#define list_for_each_prev(pos, head) \ 
for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \ 
pos = pos->prev) 
解釋類似上面的list_for_each()。 

----------------list_for_each_safe()-------------- 
內核中解釋的精華部分: 
/* 
* list_for_each_safe - iterate over a list safe against removal of list entry 
*/ 
#define list_for_each_safe(pos, n, head) \ 
for (pos = (head)->next, n = pos->next; pos != (head); \ 
pos = n, n = pos->next) 
這是說你可以邊遍歷邊刪除,這就叫safe。十分精彩。剛開始時,我也一直不理解safe的意思,後來在www.linuxforum.net論壇上搜索list_for_each_safe找到了解答。 

----------------list_entry()-------------------- 
#define list_entry(ptr, type, member) \ 
container_of(ptr, type, member) 
分析: 
list_entry()函數用於將指向某鏈表結點成員的指針調整到該鏈表的開始處,並指針轉換爲該鏈表結點的類型。 

-------------list_for_each_entry()--------------- 
#define list_for_each_entry(pos, head, member) \ 
for (pos = list_entry((head)->next, t ypeof(*pos), member); \ 
prefetch(pos->member.next), &pos->member != (head); \ 
pos = list_entry(pos->member.next, typeof(*pos), member)) 
分析: 
1.楊沙洲--國防科技大學計算機學院--2004年8月指出: 
大多數情況下,遍歷鏈表的時候都需要獲得鏈表節點數據項,也就是說list_for_each()和list_entry()總是同時使用。對此Linux給出了一個list_for_each_entry()宏。 

2.這是用於嵌套的結構體中的宏:(這個程序例子來自:《Linux內核分析及編程》作者:倪繼利 電子工業出版社) 
struct example_struct 

struct list_head list; 
int priority; 
... //其他結構體成員 
}; 
struct example_struct *node = list_entry(ptr,struct example_struct,list); 

自己分析:對比list_entry(ptr,type,member)可知有以下結果: 
其中list相當於member成員,struct example_struct相當於type成員,ptr相當於ptr成員。而list{}成員嵌套於example_struct{}裏面。ptr指向example_struct{}中的list成員變量的。在list_entry()作用下,將ptr指針迴轉指向struct example_struct{}結構體的開始處。 

3.pos當指向外層結構體,比如指向struct example_struct{}的結點,最開始時候,pos當指向第一個結點。而head開始時候也是指向第一個外層結點的裏面的這個內嵌的鏈表結構體struct list_head{},(head)->next則指向後繼的一個外層結點的內嵌的鏈表結點struct list_head{} list。member即是指出該list爲其內嵌的結點。 
思路:用pos指向外層結構體的結點,用head指向內層嵌入的結構體的結點。用(head)->next,pos->member.next(即:ptr->list.next)來在內嵌的結構體結點鏈表中遍歷。每遍歷一個結點,就用list_entry()將內嵌的pos->member.next指針迴轉爲指向該結點外層結構體起始處的指針,並將指針進行指針類型轉換爲外層結構體型pos。&pos->member! = (head)用pos外層指針引用member即:list成員,與內層嵌入的鏈表之頭結點比較來爲循環結束條件。 

-------------list_for_each_entry_reverse()------- 
#define list_for_each_entry_reverse(pos, head, member) \ 
for (pos = list_entry((head)->prev, typeof(*pos), m+ember); \ 
prefetch(pos->member.prev), &pos->member != (head); \ 
pos = list_entry(pos->member.prev, typeof(*pos), member)) 
分析類似上面。 

---------------list_prepare_entry()--------------- 
1.函數背景:來自楊沙洲.國防科技大學計算機學院.2004年8月.www.linuxforum.net Linux 內核技術論壇: 
楊在貼子中指出:如果遍歷不是從鏈表頭開始,而是從已知的某個pos結點開始,則可以使用list_for_each_entry_continue(pos,head,member)。有時還會出現這種需求,即經過一系列計算後,如果pos有值,則從pos開始遍歷,如果沒有,則從鏈表頭開始,爲此,Linux專門提供了一個list_prepare_entry(pos,head,member)宏,將它的返回值作爲list_for_each_entry_continue()的pos參數,就可以滿足這一要求。 

2.內核中的list_prepare_entry()的註釋及代碼: 
/** 
* list_prepare_entry - prepare a pos entry for use as a start point in 
* @pos: the type * to use as a start point 
* @head: the head of the list 
* @member: the name of the list_struct within the struct. 
*/ 

內核源代碼: 
#define list_prepare_entry(pos, head, member) \ 
((pos) ? : list_entry(head, typeof(*pos), member)) 
分析: 
:前面是個空值,即:若pos不爲空,則pos爲其自身。等效於: 
(pos)? (pos): list_entry(head,typeof(*pos),member) 
注意內核格式::前後都加了空格。 

------------list_for_each_entry_continue()-------- 
3.內核中的list_for_each_entry_continue()的註釋及代碼: 
/** 
* list_for_each_entry_continue - iterate over list of given type 
*continuing after existing point 
* @pos: the type * to use as a loop counter. 
* @head: the head for your list. 
* @member: the name of the list_struct within the struct. 
*/ 

內核源代碼: 
#define list_for_each_entry_continue(pos, head, member) \ 
for (pos = list_entry(pos->member.next, typeof(*pos), member); \ 
prefetch(pos->member.next), &pos->member != (head); \ 
pos = list_entry(pos->member.next, typeof(*pos), member)) 
分析見list_prepare_entry()中的函數背景。 

-------------list_for_each_entry_safe()----------- 
1.函數背景:來自楊沙洲.國防科技大學計算機學院.2004年8月.www.linuxforum.net Linux 內核技術論壇: 
楊在貼子中指出:list_for_each_entry_safe(pos, n, head,member),它們要求調用者另外提供一個與pos同類型的指針n,在for循環中暫存pos下一個節點的地址,避免因pos節點被釋放而造成的斷鏈。 

2.內核中的註釋與源代碼: 
/** 
* 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 counter. 
* @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_safe_continue()------- 
#define list_for_each_entry_safe_continue(pos, n, head, member) \ 
for (pos = list_entry(pos->member.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)) 

------------------------------------------------ 



--------------------
姓名:餘旭 Linux Kernel 2.6.14源代碼剖析

文章選項: 打印

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