Linux內核鏈表的研究與應用
Author:tiger-john
Time:2012-12-20
Mail:jibo.tiger@gmail.com
Blog:http://blog.csdn.net/tigerjb/article/details/8299599 linux
轉載請註明出處。 編程
前言:
在Linux內核中使用了大量的鏈表來組織其數據,其採用了雙向鏈表做爲其基本的數據結構。可是與咱們傳統的數據結構中所學的雙向鏈表又有着本質的一些不一樣(其不包含數據域)。其主要是Linux內核鏈表在設計時給出了一種抽象的定義。 緩存
採用這種定義有如下兩種好處:1是可擴展性,2是封裝。可擴展性確定是必須的,內核一直都是在發展中的,因此代碼都不能寫成死代碼,要方便修改和追加。將鏈表常見的操做都進行封裝,使用者只關注接口,不需關注實現。 安全
分析內核中的鏈表咱們能夠作些什麼呢? 數據結構
我的認爲咱們能夠將其複用到用戶態編程中,之後在用戶態下編程就不須要寫一些關於鏈表的代碼了,直接將內核中list.h中的代碼拷貝過來用。也能夠整理出my_list.h,在之後的用戶態編程中直接將其包含到C文件中。 函數
一.Linux 內核鏈表數據結構
1.其代碼位於include/linux/list.h中,3.0內核中將其數據結構定義放在了include/linux/types.h中
鏈表的數據定義: post
struct list_head{ fetch
struct list_head * next,*prev; 優化
} spa
這個不含數據域的鏈表,能夠嵌入到任何結構中,例如能夠按以下方式定義含有數據域的鏈表:
struct test_list{
void *testdata;
structlist_head list;
};
說明:
1>list 域隱蔽了鏈表的指針特性
2>struct list_head 能夠位於結構的任何位置,能夠給其起任何名字。
3>在一個結構體中能夠有多個list域。
以struct list_head 爲基本對象,對鏈表進行插入、刪除、合併以及遍歷等各類操做。
2.內核鏈表數據結構的設計思想是:
儘量的代碼重用,化大堆的鏈表設計爲單個鏈表。
3.使用循環鏈表的好處:
雙向循環鏈表的效率是最高的,找頭節點,尾節點,直接前驅,直接後繼時間複雜度都是O (1) ,而使用單鏈表,單向循環鏈表或其餘形式的鏈表是不能完成的。
4.鏈表的構造:
若是須要構造某類對象的特定列表,則在其結構中定義一個類型爲list_head
指 針的成員(例如上面所說的struct test_list),經過這個成員將這類對象鏈接起來,造成所需列表,並經過通用鏈表函數對其進行操做。其優勢 是隻需編寫通用鏈表函數,便可構造和操做不一樣對象的列表,而無需爲每類對象的每種列表編寫專用函數,實現了代碼的重用。
5.如何內核鏈表使用:
若是想對某種類型建立鏈表,就把一個list_head類型的變量嵌入到該類型中,用list_head中的成員和相對應的處理函數來對鏈表進行遍歷。若是想獲得相應的結構的指針,使用list_entry能夠算出來。
二.鏈表的聲明和初始化宏
實際上,struct list_head 只定義了鏈表結點,並無專門定義鏈表頭,能夠 使用如下兩個宏
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
1>name 爲結構體struct list_head{}的一個結構體變量,&(name)爲該結構體變量的地址。用name結構體變量的始地址將改結構體變量進行初始化。
2>LIST_HEAD_INIT(name)函數宏只進行初始化
3>LIST_HEAD(name)函數宏聲明並進行初始化
4>若是要聲明並初始化本身的鏈表頭mylist_head,則直接調用LIST_HEAD:
LIST_HEAD(mylist_head)
調用以後,mylist_head的next,prev指針都初始化爲指向本身,這樣就有了一個空鏈表。所以能夠得知在Linux中用頭指針的next是否指向本身來判斷鏈表是否爲空。
5>除了LIST_HEAD宏在編譯時靜態初始化,還可使用內嵌函數INIT_LIST_HEAD(struct list_head *list)在運行時進行初始化。
例如:調用INIT_LIST_HEAD(&mylist_head)對mylist_head鏈表進行初始化。
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
注不管是採用哪一種方式,新生成的鏈表頭的指針next,prev都初始化爲指向本身
三.鏈表的基本操做(插入,刪除,判空)
1.判斷鏈表是否爲空
1>function:
函數判讀鏈表是否爲空鏈表,若是爲空鏈表返回1,不然返回0.
2>函數接口
static inline int list_empty(const struct list_head *head)
static inline int list_empty_careful(const struct list_head *head)
3>如下兩個函數都是判斷一個鏈表是否是爲空鏈表,
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
static inline int list_empty_careful(const struct list_head *head)
{
struct list_head *next = head->next;
return (next == head) && (next == head->prev);
}
list_empty() 函數和list_empty_careful()函數都是用來檢測鏈表是否爲空的。可是稍有區別的就是第一個鏈表使用的檢測方法是判斷表頭的結點的下一個 結點是否爲其自己,若是是則返回爲1,不然返回0。第二個函數使用的檢測方法是判斷表頭的前一個結點和後一個結點是否爲其自己,若是同時知足則返回0,否 則返回值爲1。
這主要是爲了應付另外一個cpu正在處理同一個鏈表而形成next、prev不一致的狀況。但代碼註釋也認可,這一安全保障能力有限:除非其餘cpu的鏈表操做只有list_del_init(),不然仍然不能保證安全,也就是說,仍是須要加鎖保護。
2.鏈表的插入
1>function:
將一個新結點插入到鏈表中(在表頭插入和表尾插入)
2>Linux內核鏈表提供了兩個函數:
static inline void list_add(struct list_head *new, struct list_head *head)
static inline void list_add_tail(struct list_head *new, struct list_head *head)
3>函數實現
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add_tail(new, head->prev, head);
}
4>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;
}
__list_add(new,prev,next)函數表示在prev和next之間添加一個新的節點new
5>對於list_add()函數中的__list_add(new, head, head->next)表示的在head和head->next之間加入一個新的節點。即表頭插入法(即先插入的後輸出,能夠用來實現一個棧)
6> 對於list_add_tail()中的__list_add(new, head->prev, head)表示在head->prev(雙向循環鏈表的最後一個結點)和head之間添加一個新的結點。即表尾插入法(先插入的先輸出,能夠用來實 現一個隊列)
3.鏈表的刪除
1>function:
將一個結點從鏈表中刪除
2>函數接口
static inline void list_del(struct list_head *entry)
static inline void list_del_init(struct list_head *entry)
注:在3.0內核中新添加了
static inline void __list_del_entry(struct list_head *entry)
3>函數實現
static inline void __list_del(struct list_head * prev, struct list_head* next)
{
next->prev = prev;
prev->next = next;
}
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}
static inline void __list_del_entry(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
}
(1)__list_del(entry->prev, entry->next)表示將entry的前一個和後一個之間創建關聯。
(2)list_del() 函數將刪除後的prev、next指針分別被設爲LIST_POSITION2和LIST_POSITION1兩個特殊值,這樣設置是爲了保證不在鏈表中 的節點項不可訪問。對LIST_POSITION1和LIST_POSITION2的訪問都將引發頁故障。
注意:
這裏的entry結點所佔用的內存並無被釋放。
LIST_POSTION1和LIST_POSTION1在linux/poison.h中定義
#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)
POISON_POINTER_ELTA在linux/poison.h中定義
#ifdef CONFIG_ILLEGAL_POINTER_VALUE
#define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL)
#else
#define POISON_POINTER_DELTA 0
#endif
其中POISON_POINTER_DELTA的值在CONFIG_ILLEGAL_POINTER_VALUE中未配置時爲0
(3)list_del_init這個函數首先將entry從雙向鏈表中刪除以後,而且將entry初始化爲一個空鏈表。
說明:list_del(entry)和list_del_init(entry)惟一不一樣的是對entry的處理,前者是將entry設置爲不可用,後者是將其設置爲一個空的鏈表的開始。
(4)__list_del_entry()函數實現只是簡單的對__list_del進行了簡單的封裝實現了只用傳入一個entry結點便可將其從鏈表中刪除;與list_del的不一樣點是隻是簡單的刪除結點,但並無使entry結點不可用。
注意:在3.0內核中listd_move,和list_move_tail函數內部改爲調用__list_del_entry。2.6內核中調用的是__list_del函數。
四.鏈表的其餘操做
1.結點的替換
1>function:
結點的替換是將old的結點替換成new
2>linux內核提供了兩個函數接口:
static inline void list_replace(struct list_head *old,struct list_head *new)
static inline void list_replace_init(struct list_head *old,struct list_head *new)
3> list_replace()函數的實現:
static inline void list_replace(struct list_head *old, struct list_head *new)
{
new->next = old->next;
new->next->prev = new;
new->prev = old->prev;
new->prev->next = new;
}
list_repleace()函數只是改變new和old的指針關係,然而old指針並無釋放。
4> list_replace_init()函數的實現:
static inline void list_replace_init(struct list_head *old, struct list_head *new)
{
list_replace(old, new);
INIT_LIST_HEAD(old);
}
List_repleace_init首先調用list_replace改變new和old的指針關係,而後調用INIT_LIST_HEAD(old)將old結點初始化空結點。
2結點的移動
1>function:
將一個結點從一個鏈表中刪除,添加到另外一個鏈表中。
2>linxu內核鏈表中提供了兩個接口
static inline void list_move(struct list_head *list, struct list_head *head)
static inline void list_move_tail(struct list_head *list, struct list_head *head)
前者添加的時候使用的是頭插法,後者使用的是尾插法
3> list_move函數實現
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del_entry(list);
list_add(list, head);
}
List_move函數調用__list_del_entry()函數將lis結點刪除,而後調用list_add()函數將其插入到head結點中(使用頭插法)
4>list_move_tail函數實現
static inline void list_move_tail(struct list_head *list, struct list_head *head)
{
__list_del_entry(list);
list_add_tail(list, head);
}
list_move_tail()函數調用_list_del_entry函數將list結點刪除,而後調用list_add_tail函數將list結點插入到head結點中(使用尾插法)
3檢測是否爲最後結點
1>function:
判斷一個結點是否是鏈表head的最後一個結點。
2>linux內核函數接口
static inline int list_empty(const struct list_head *list, const struct list_head *head )
{
return list->next == head;
}
若是結點的後繼爲頭結點,則說明此結點爲最後一個結點。
4.檢測鏈表中是否只有一個結點
1>function:
函數是用來判斷head這個鏈表是否是隻有一個成員結點(不算帶頭結點的那個head),是則返回1,不然返回0.
2>函數接口:
static inline int list_is_singular(const struct list_head *head)
3>函數實現:
static inline int list_is_singular(const struct list_head *head)
{
return !list_empty(head) && (head->next == head->prev);
}
首先判斷鏈表是否爲空,若是非空則判斷
5.鏈表的旋轉
1>function:
這個函數把head後的第一個結點放到最後位置。
2> 函數接口:
static inline void list_rotate_left(struct list_head *head)
3>lis_rotate_left函數實現
static inline void list_rotate_left(struct list_head *head)
{
struct list_head *first;
if (!list_empty(head)) {
first = head->next;
list_move_tail(first, head);
}
}
List_rotate_left函數每次將頭結點後的一個結點放到head鏈表的末尾,直到head結點後沒有其餘結點。
6.分割鏈表
1>function:
函數將head(不包括head結點)到entry結點之間的全部結點截取下來添加到list鏈表中。該函數完成後就產生了兩個鏈表head和list
2>函數接口:
static inline void list_cut_position(struct list_head *list,struct list_head *head,struct list_head *entry)
list: 將截取下來的結點存放到list鏈表中
head: 被剪切的鏈表
entry: 所指位於由head所指的鏈表內。它是分割線,entry以前的結點存放到list之中。Entry以後的結點存放到head鏈表中。
3>list_cut_position函數實現:
static inline void list_cut_position(struct list_head *list,
struct list_head *head, struct list_head *entry)
{
if (list_empty(head))
return;
if (list_is_singular(head) &&
(head->next != entry && head != entry))
return;
if (entry == head)
INIT_LIST_HEAD(list);
else
__list_cut_position(list, head, entry);
}
函數進行了判斷:
(1)head鏈表是否爲空,若是爲空直接返回不進行操做。不然進行第二步操做
(2)head是否只有一個結點,若是隻有一個結點則繼續判斷該結點是不是entry,若是不是entry則直接返回。(即head鏈表中沒有entry結點則返回,不進行操做)
(3)若是entry指向head結點,則不進行剪切。直接將list鏈表初始化爲空.
(4)判斷完後,調用__list_cut_position(list,head,entry)函數完成鏈表剪切。
4>__list_splice函數:
(1)功能:函數將head(不包括head結點)到entry結點之間的全部結點截取下來添加到list鏈表。將entry以後的結點放到head鏈表中。
(2)函數接口:
static inline void __list_cut_position(struct list_head *list, struct list_head *head, struct list_head *entry)
(3)__list_cut_position函數實現
static inline void __list_cut_position(struct list_head *list, struct list_head *head, struct list_head *entry)
{
struct list_head *new_first = entry->next;
list->next = head->next;
list->next->prev = list;
list->prev = entry;
entry->next = list;
head->next = new_first;
new_first->prev = head;
}
函數實現過程見圖:
起始:
函數執行完:
說明:list_cut_position只是添加了一些條件判斷,真正的操做是在__list_cut_position函數中完成。
7.鏈表合併
1>function:
實現將list鏈表(不包括list結點)插入到head鏈表中
2>Linux內核鏈表提供了四個函數接口
static inline void list_splice(const struct list_head *list,struct list_head *head)
static inline void list_splice_init(struct list_head *list,struct list_head *head)
static inline void list_splice_tail(struct list_head *list,struct list_head *head)
static inline void list_splice_tail_init(struct list_head *list,struct list_head *head)
list:新添加的鏈表
head:將list鏈表添加到head鏈表中
3>函數實現
static inline void __list_splice(const struct list_head *list, struct list_head *prev, struct list_head *next)
{
struct list_head *first = list->next;
struct list_head *last = list->prev;
first->prev = prev;
prev->next = first;
last->next = next;
next->prev = last;
}
static inline void list_splice(const struct list_head *list, struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head, head->next);
}
static inline void list_splice_tail(struct list_head *list, struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head->prev, head);
}
static inline void list_splice_init(struct list_head *list, struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head, head->next);
INIT_LIST_HEAD(list);
}
}
static inline void list_splice_tail_init(struct list_head *list, struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head->prev, head);
INIT_LIST_HEAD(list);
}
}
(1)四個函數都調用了__list_splice()函數來實現添加鏈表。
(2)list_splice和list_splice_init表示將新添加的鏈表用頭插法插入在head鏈表中.他們之間的區別是.list_splice中的list結點沒有被初始化,list_splice_init將list結點進行了初始化。
(3) list_splice_tail 和list_splice_init表示將新添加的鏈表list用尾插入法插入在head鏈表中。他們之間的區別是list_splice_tail和 list_splice_init的區別是list_splice_tail中的list結點沒有被初始化,而 list_splice_tail_init將list結點進行了初始化。
(4)list_splice 和list_splice_tail都調用了__list_splice實現將整個list鏈表(不包括list結點)添加到head鏈表中,他們的區別 是list_splice將list鏈表添加到head結點以後,list_splice_tail將list鏈表添加尾結點以後
五.內核鏈表的遍歷操做
遍歷鏈表經常使用操做。爲了方便核心應用遍歷鏈表,linux鏈表將遍歷操做抽象成幾個宏。在分析遍歷宏以前,先分析下如何從鏈表中訪問到咱們所須要的數據項
1.list_entry(ptr,type,member)
1>function:
經過成員指針得到整個結構體的指針
Linux鏈表中僅保存了數據項結構中list_head成員變量的地址,能夠經過list_entry宏經過list_head成員訪問到做爲它的全部者的結點數據
2>接口:
list_entry(ptr,type,member)
ptr:ptr是指向該數據結構中list_head成員的指針,即存儲該數據結構中鏈表的地址值。
type:是該數據結構的類型。
member:改數據項類型定義中list_head成員的變量名。
3>list_entry宏的實現
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
list_entry宏調用了container_of宏,關於container_of宏的用法見:
2.list_first_entry(ptr,type,member)
1>function:
這裏的 ptr是一個鏈表的頭結點,這個宏就是取的這個鏈表第一元素所指結構體的首地址。
2>接口:
list_first_entry(ptr,type,member)
ptr:ptr是指向該數據結構中list_head成員的指針,即存儲該數據結構中鏈表的地址值。此處的ptr是一個鏈表的頭結點
type:是該數據結構的類型。
member:該數據項類型定義中list_head成員的變量名。
3>list_entry宏的實現
#define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member)
list_first_entry調用了list_entry來獲取ptr鏈表結點後的第一個結點其結構體的地址。
六鏈表遍歷
1.list_for_each(pos,head)
1>function:
實際上就是一個for循環,以順時針方向遍歷雙向循環鏈表,因爲是雙向循環鏈表,因此循環終止條件是pos!=head.在遍歷過程當中,不能刪除pos(必須保證pos->next有效),不然會形成SIGSEGV錯誤。
2>接口:
list_for_each(pos,head)
pos:pos是一個輔助指針(即鏈表類型),用於鏈表遍歷
head:鏈表的頭指針(即結構體中成員struct list_head)
3>list_for_each實現
#define list_for_each(pos,head) \
for(pos= (head->next);prefetch(pos->next),pos !=(head); \
pos = pos->head)
Ø pos是輔助指針,pos是從第一個結點開始的,並無訪問結點,直到pos到達結點指針head的時候結束
Ø 遍歷是雙循環鏈表的基本操做,head爲 頭節點,遍歷過程當中首先從(head)->next開始,當pos==head時退出,故head節點並無訪問,這和鏈表的結構設計有關,一般頭 節點都不含有其它有效信息,所以能夠把頭節點做爲雙向鏈表遍歷一遍的檢測標誌來使用。在list_for_each宏中讀者可能發現一個比較陌生的面孔, 咱們在此就不將prefetch展開了講解了,有興趣的讀者能夠本身查看下它的實現,其功能是預取內存的內容,也就是程序告訴CPU哪些內容可能立刻用 到,CPU預先其取出內存操做數,而後將其送入高速緩存,用於優化,是的執行速度更快。
2.__list_for_each
1>function:
功能和list_for_each相同,即以順時針方向遍歷雙向循環鏈表,只不過去掉了prefetch(pos->next)。在遍歷過程當中,不能刪除pos(必須保證pos->next有效),不然會形成SIGSEGV錯誤。
2> __list_for_each(pos,head)
pos:pos是一個輔助指針(即鏈表類型),用於鏈表遍歷
head:鏈表的頭指針。
3>實現:
#define __list_for_each(pos,head) \
for (pos =(head)->next;pos!=(head);pos = pos->next)
區別:__list_for_each沒有采用pretetch來進行預取。
3.list_for_each_prev
1>function:
逆向遍歷鏈表。在遍歷過程當中,不能刪除pos(必須保證pos->next有效),不然會形成SIGSEGV錯誤。
2>接口:
list_for_each_prev(pos,head)
3>實現
#define list_for_each_prev(pos,head) \
for(pos = (head)->prev;prefetch(pos->prev),pos !=(head); \
pos = pos->prev)
注:實現方法與list_for_each相同,不一樣的是用head的前趨結點進行遍歷。實現鏈表的逆向遍歷。
4.list_for_each_safe
1>function:
以順時針方向遍歷鏈表,與list_for_each不一樣的是使用list_head結構體變量n做爲臨時存儲變量。主要用於鏈表刪除時操做。
2>接口
list_for_each_safe(pos,n,head)
pos:pos是一個輔助指針(即鏈表類型),用於鏈表遍歷
head:鏈表的頭指針
n:臨時指針用於佔時存儲pos的下一個指針
3>list_for_each_safe實現
#define list_for_each_safe(pos,n,head) \
for (pos = (head)->next,n = pos->next; pos != (head); \
pos = n, n = pos->next)
前 面介紹了用於鏈表遍歷的幾個宏,它們都是經過移動pos指針來達到遍歷的目的。但若是遍歷的操做中包含刪除pos指針所指向的節點,pos指針的移動就會 被中斷,由於list_del(pos)將把pos的next、prev置成LIST_POSITION2和LIST_POSITION1的特殊值。當 然,調用者徹底能夠本身緩存next指針使遍歷操做可以連貫起來,但爲了編程的一致性,Linxu內核鏈表要求調用者另外提供一個與pos同類型的指針 n,在for循環中暫存pos下一個節點的地址,避免因pos節點被釋放而形成的斷鏈。
5.list_for_each_prev_safe
1>function:
功能與list_for_each_prev相同,用於逆向遍歷鏈表。不一樣的是使用list_head結構體變量n做爲臨時存儲變量。主要用於鏈表刪除時操做。
2>接口:
list_for_each_prev_safe(pos,n,head)
list_for_each_safe(pos,n,head)
pos:pos是一個輔助指針(即鏈表類型struct list_head),用於鏈表遍歷
head:鏈表的頭指針
n:臨時指針用於佔時存儲pos的下一個指針
3>list_for_each_prev_safe實現
#define list_for_each_safe(pos,n,head) \
for (pos = (head)->prev,n = pos->prev; \
prefecth(pos->prev),pos!=(head); \
pos = n, n = pos->prev)
6.用鏈表外的結構體地址來進行遍歷,而不用鏈表的地址進行遍歷
1>funtion:
遍 歷鏈表,所不一樣的是它是根據鏈表的結構體地址來進行遍歷。大多數狀況下,遍歷鏈表的時候都須要得到鏈表節點數據項,也就是說 list_for_each()和list_entry()老是同時使用。與list_for_each()不一樣,這裏的pos是數據項結構指針類型,而 不是(struct list_head 類型。
Linxu提供瞭如下函數:
list_for_each_entry
list_for_each_entry_safe
list_for_each_entry_reverse
list_for_each_entry_safe_reverse
2>函數接口
list_for_each_entry(pos,head,member)
pos:用於遍歷的指針,只是它的數據類型是結構體類型而不是strut list_head 類型
head:鏈表頭指針
member:該數據項類型定義中list_head成員的變量名。
list_for_each_entry_safe(pos,n,head,member)
pos:用於遍歷的指針,只是它的數據類型是結構體類型而不是strut list_head 類型
n: 臨時指針用於佔時存儲pos的下一個指針
head:鏈表頭指針
member:該數據項類型定義中list_head成員的變量名。
list_for_each_entry_rverse(pos,head,member)
pos:用於遍歷的指針,只是它的數據類型是結構體類型而不是strut list_head 類型
head:鏈表頭指針
member:該數據項類型定義中list_head成員的變量名。
list_for_each_entry_safe_reverse(pos,n,head,member)
pos:用於遍歷的指針,只是它的數據類型是結構體類型而不是strut list_head 類型
n: 臨時指針用於佔時存儲pos的下一個指針
head:鏈表頭指針
member:該數據項類型定義中list_head成員的變量名。
3>函數實現
(1)list_for_each_entry實現
#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))
這 個函數是根據member成員遍歷head鏈表,而且將每一個結構體的首地址賦值給pos,這樣的話,咱們就能夠在循環體裏面經過pos來訪問該結構體變量 的其餘成員了。大多數狀況下,遍歷鏈表的時候都須要得到鏈表節點數據項,也就是說list_for_each()和list_entry()老是同時使 用。與list_for_each()不一樣,這裏的pos是數據項結構指針類型,而不是struct list_head 類型。
(2)list_for_each_entry_safe(pos,n,head,member)實現
#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與list_for_each_entry功能相同,不一樣的是list_for_each_entry_safe主要用於鏈表刪除時進行遍歷操做。
(3) list_for_each_entry_reverse實現
#define list_for_each_entry_reverse(pos, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
逆向遍歷鏈表。功能和list_for_each_entry相同,不一樣的是採用逆序遍歷。與list_for_each_prev不一樣的是這裏的pos是數據項結構指針類型,而不是struct list_head 類型。
(4)list_for_each_entry_safe_reverse實現
#define list_for_each_entry_safe_reverse(pos, n, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member), \
n = list_entry(pos->member.prev, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.prev, typeof(*n), member))
逆向遍歷鏈表,與list_for_each_entry_reverse不一樣的是該函數主要用於刪除操做時進行遍歷。與list_for_each_prev_safe不一樣的是這裏的pos是數據項結構指針類型,而不是struct list_head 類型
7.list_prepare_entry
1>function:
這個函數是若是pos非空,那麼pos的值就爲其自己,若是pos爲空,那麼就從鏈表頭強制擴展一個虛pos指針,這個宏定義是爲了在list_for_entry_continue()中使用作準備的。
2>接口:
list_prepare_entry(pos,head,member)
pos: 用於遍歷的指針,只是它的數據類型是結構體類型而不是strut list_head 類型
head:鏈表的頭指針
member:該數據項類型定義中list_head成員的變量名。
3>list_prepare_entry 實現
#define list_prepare_entry(pos, head, member) \
((pos) ? : list_entry(head, typeof(*pos), member))
若是pos非空,那麼pos的值就爲其自己,若是pos爲空,那麼就從鏈表頭強制擴展一個虛pos指針.
8 若是遍歷不是從鏈表頭開始,而是從已知的某個結點pos開始遍歷
1>Linux內核鏈表提供瞭如下函數實現從已知的某個結點pos開始遍歷
list_for_each_entry_continue
list_for_each_entry_continue_reverse
list_for_each_entry_safe_continue
2>函數接口:
list_for_each_entry_continue(pos,head,member)
pos: 用於遍歷的指針,只是它的數據類型是結構體類型而不是strut list_head 類型
head: 鏈表的頭指針
member: 該數據項類型定義中list_head成員的變量名。
list_for_each_entry_continue_reverse(pos,head,member)
pos: 用於遍歷的指針,只是它的數據類型是結構體類型而不是strut list_head 類型
head: 鏈表的頭指針
member: 該數據項類型定義中list_head成員的變量名。
list_for_each_entry_safe_continue(pos,n,head,member)
pos: 用於遍歷的指針,只是它的數據類型是結構體類型而不是strut list_head 類型
n: 臨時指針用於佔時存儲pos的下一個指針
head: 鏈表的頭指針
member: 該數據項類型定義中list_head成員的變量名
3>函數實現:
#define list_for_each_entry_continue(pos, head, member) \
for (pos = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
#define list_for_each_entry_continue_reverse(pos, head, member) \
for (pos = list_entry(pos->member.prev, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
#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))
說明:
(1)list_for_each_entry_continue:從已知的某個結點pos後一個結點開始,進行遍歷。
(2)list_for_each_entry_continue_reverse:從已知的某個結點前一個結點開始進行逆序遍歷
(3)list_for_each_entry_safe_continue:從已知的某個結點pos後一個結點開始進行遍歷,與list_for_each_entry_continue不一樣的是,它主要用於鏈表進行刪除時進行的遍歷。
(4)list_for_each_entry_continue 中,傳入的pos不能爲NULL,必須是已經指向鏈表某個結點的有效指針。而在list_for_each_entry中對傳入的pos無要求,能夠爲 空。所以list_for_entry_continue經常與list_prepare_entry宏一塊兒使用,以確保pos非空。
9.從當前某個結點開始進行遍歷
1>function:
從當前某個結點開始進行遍歷,list_for_entry_continue是從某個結點以後開始進行遍歷。Linux提供瞭如下函數進行從當前某個結點開始進行遍歷
list_for_each_entry_from(pos,head,member)
list_for_each_entry_safe_from(pos,n,head,member)
2>接口
list_for_each_entry_from(pos,head,member)
pos: 用於遍歷的指針,只是它的數據類型是結構體類型而不是strut list_head 類型
head: 鏈表的頭指針
member:該數據項類型定義中list_head成員的變量名
list_for_each_entry_safe_from(pos,n,head,member)
pos:用於遍歷的指針,只是它的數據類型是結構體類型而不是strut list_head 類型
n: 臨時指針用於佔時存儲pos的下一個指針
head:鏈表的頭指針
member該數據項類型定義中list_head成員的變量名
3>函數實現
(1)list_for_each_entry_from實現
#define list_for_each_entry_from(pos, head, member) \
for (; &pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
能夠看到for循環從當前結點開始進行遍歷。
(2)list_for_each_entry_safe_from實現
#define list_for_each_entry_safe_from(pos, n, head, member) \
for (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_from相同,不一樣的是list_for_each_entry_from不能用於遍歷鏈表時進行刪除操做,而list_for_entry_safe_entry_from能夠用於鏈表遍歷時進行刪除操做。
10.list_safe_reset_next
1>function:
經過pos結構體指針得到結構體n的指針(具體在什麼場合使用不太清楚,往大牛指點)
2>接口:
list_safe_reset_next(pos,n,member)
pos:用於遍歷循環的指針(用於list_for_each_entry_safe遍歷中),只是它的數據類型是結構體類型而不是strut list_head 類型
n:在list_for_each_entry_safe中用於臨時存儲post的下一個指針
member: 該數據項類型定義中list_head成員的變量名
六.內核鏈表的應用
分 析了內核鏈表就要對其進行應用。我的認爲咱們能夠將其複用到用戶態編程中,之後在用戶態下編程就不須要寫一些關於鏈表的代碼了,直接將內核中list.h 中的代碼拷貝過來用。也能夠整理出my_list.h,在之後的用戶態編程中直接將其包含到C文件中。固然,咱們也能夠在內核層對其進行應用。
代碼見:http://download.csdn.net/detail/tigerjb/4887030
說明:切記在刪除元素時,要用list_for_each_safe,而不能用list_for_each來遍歷鏈表元素