Linux內核鏈表的研究與應用

前言:

在Linux內核中使用了大量的鏈表來組織其數據,其採用了雙向鏈表作爲其基本的數據結構。但是與我們傳統的數據結構中所學的雙向鏈表又有着本質的一些不同(其不包含數據域)。其主要是Linux內核鏈表在設計時給出了一種抽象的定義。
採用這種定義有以下兩種好處:1是可擴展性,2是封裝。可擴展性肯定是必須的,內核一直都是在發展中的,所以代碼都不能寫成死代碼,要方便修改和追加。將鏈表常見的操作都進行封裝,使用者只關注接口,不需關注實現。
分析內核中的鏈表我們可以做些什麼呢?
個人認爲我們可以將其複用到用戶態編程中,以後在用戶態下編程就不需要寫一些關於鏈表的代碼了,直接將內核中list.h中的代碼拷貝過來用。也可以整理出my_list.h,在以後的用戶態編程中直接將其包含到C文件中。

一.Linux 內核鏈表數據結構

1.其代碼位於include/linux/list.h中

鏈表的數據定義:
struct  list_head{
struct list_head  * next,*prev;
}
這個不含數據域的鏈表,可以嵌入到任何結構中,例如可以按如下方式定義含有數據域的鏈表:
struct  test_list{
void  *testdata;
        struct list_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來遍歷鏈表元素
發佈了5 篇原創文章 · 獲贊 6 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章