Linux中通用鏈表(list)的解析

 

在Linux中的list是系統通用鏈表,它廣泛應用在系統各個地方,如系統消息隊列、進程列表等地。可以說,只要有表的地方,就會用到這個list。
今天我們先解析一下跟初始化有關的代碼:

1. 表頭結點結構
struct list_head {
         struct list_head *next, *prev;
};
這和我們通常寫鏈表一樣,定義一個表頭結點。
從next, prev可以看出,這是一個雙向鏈表。
但區別是,這個結點結構中並沒有內容,也就是我們常說的type data;
這也就是“通用”的體現吧。

2. #define LIST_HEAD_INIT(name) { &(name), &(name) }
定義一個LIST_HEAD初始化的宏,後面的大括號中內容我們目前似乎看不懂。

     #define LIST_HEAD(name) \
         struct list_head name = LIST_HEAD_INIT(name)

這樣兩個合在一起就看懂了,當執行LIST_HEAD(name)這個宏時,會創建一個頭結點,並用&name初始化這個結點的兩個成員指針。

3. static inline void INIT_LIST_HEAD(struct list_head *list)
{
         list->next = list;
         list->prev = list;
}
這是一個初始化頭結點函數,把next和prev指向本身。
1. 下面介紹list的插入函數:
#ifndef CONFIG_DEBUG_LIST
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;
}
#else
extern void __list_add(struct list_head *new,
                   struct list_head *prev,
                   struct list_head *next);
#endif
這個函數的3個參數分別是:
new: 要插入的結點;
prev: 插入之後new的前一個結點;
next: 插入之後new的後一個結點.

下面接着的是:
#ifndef CONFIG_DEBUG_LIST
static inline void list_add(struct list_head *new, struct list_head *head)
{
     __list_add(new, head, head->next);
}
#else
extern void list_add(struct list_head *new, struct list_head *head);
#endif
這是將上面的3參函數改爲2參函數的調用, 表示把new插入到head後面.

同理:
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
     __list_add(new, head->prev, head);
}
這表示把new插入到head前面.

接下來的:
static inline void __list_add_rcu(struct list_head * new,
         struct list_head * prev, struct list_head * next)
{
     new->next = next;
     new->prev = prev;
     smp_wmb();
     next->prev = new;
     prev->next = new;
}
引領的是幾個rcu protected的插入函數.


2. 下面介紹list的刪除函數:
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
     next->prev = prev;
     prev->next = next;
}
通過要刪除結點的前後兩結點作爲參數,使它們互相指向.
static inline void list_del_init(struct list_head *entry)
{
     __list_del(entry->prev, entry->next);
     INIT_LIST_HEAD(entry);
}
刪除entry結點, 並把它的prev和next值指向安全區(即自己).

#ifndef CONFIG_DEBUG_LIST
static inline void list_del(struct list_head *entry)
{
     __list_del(entry->prev, entry->next);
     entry->next = LIST_POISON1;
     entry->prev = LIST_POISON2;
}
#else
extern void list_del(struct list_head *entry);
#endif
通過調用上面的__list_del函數實現刪除結點, 並且指定entry結點prev,next指針的值.
兩個宏在linux/poison.h中定義如下:
/********** include/linux/list.h **********/
/*
* 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)
細心的人可能發現了, 結點在被從list中刪除後並沒有釋放, 這是因爲在創建和插入結點的時候也沒有申請資源, 在C/C++中的原則是哪裏申請哪裏釋放.
rcu_del的函數同rcu_add, 不再說明.

3. 下面介紹list的替換函數:
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;
}
這個函數用new結點替換list中的old結點.
static inline void INIT_LIST_HEAD(struct list_head *list)
{
     list->next = list;
     list->prev = list;
}
這個函數把參數中的list結點的prev和next指向自己.
static inline void list_replace_init(struct list_head *old,
                     struct list_head *new)
{
     list_replace(old, new);
     INIT_LIST_HEAD(old);
}
這個函數是綜合上面兩個函數, 在用new替換old之後, 使old結點的prev和next指向安全區(即自己).
下面說一下list的move相關函數和empty相關判斷函數:
1. static inline void list_move(struct list_head *list, struct list_head *head)
{
         __list_del(list->prev, list->next);
         list_add(list, head);
}
先解釋一下參數, list是要移動的結點, head是移動的前一個位置.
即: 把list結點移動到head的後一個位置.
這是通過兩步完成的:
先把list結點從鏈表中刪除, 再把list結點插入到適當的位置.

2. 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);
}
這是和move對應的tail函數.
即: 把list結點移動到head的前一個位置.

3. static inline int list_is_last(const struct list_head *list,
                 const struct list_head *head)
{
     return list->next == head;
}
這個函數判斷list結點是否爲該鏈表的最後一個結點.
其中參數list是被判斷結點, head是該鏈表的頭結點.

4. static inline int list_empty(const struct list_head *head)
{
     return head->next == head;
}
這個函數判斷鏈表是否爲空, 是通過比較head和head->next的值是否相等實現的, 這與表結點的初始化方法有關.
因爲初始化/重置表結點時, 把該結點的prev和next都指向本身.
參數head是表頭結點.

5. static inline int list_empty_careful(const struct list_head *head)
{
     struct list_head *next = head->next;
     return (next == head) && (next == head->prev);
}
這個函數判斷鏈表是否爲空, 並且檢查是否有進程在修改prev和next成員.
首先記錄下head->next, 比較next和head是否相等, 同時比較next和prev是否相等, 要確定它們都是指向head本身才行.
這個函數只能與帶有_init的函數一起使用(如__list_del_init),因爲這些函數是有"重置"操作的.
下面介紹Linux通用鏈表(list)的splice(合併)函數:
1. 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合併到另一鏈表的head的後一個位置.

2. static inline void list_splice_init(struct list_head *list,
                     struct list_head *head)
{
     if (!list_empty(list)) {
         __list_splice(list, head);
     }
}
這個函數是上面的應用版, 加了一個判斷list是否爲空.

3. 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);
     }
}
這是一個init版的splice應用, 加判斷, 並且在合併之後對list進行重置. 
介紹一下list中的關鍵函數container_of:
/**
* list_entry - get the struct for this entry
* @ptr:     the &struct list_head pointer.
* @type:     the type of the struct this is embedded in.
* @member:     the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
     container_of(ptr, type, member)

關於container_of見kernel.h中:
/**
* container_of - cast a member of a structure 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 struct.
*
*/
#define container_of(ptr, type, member) ({             \
         const typeof( ((type *)0)->member ) *__mptr = (ptr);     \
         (type *)( (char *)__mptr - offsetof(type,member) );})
container_of在Linux Kernel中的應用非常廣泛,它用於獲得某結構中某成員的入口地址.

關於offsetof見stddef.h中:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
TYPE是某struct的類型 0是一個假想TYPE類型struct,MEMBER是該struct中的一個成員. 由於該struct的基地址爲0, MEMBER的地址就是該成員相對與struct頭地址的偏移量.
關於typeof,這是gcc的C語言擴展保留字,用於聲明變量類型.
const typeof( ((type *)0->member ) *__mptr = (ptr);意思是聲明一個與member同一個類型的指針常量 *__mptr,並初始化爲ptr.
(type *)( (char *)__mptr - offsetof(type,member) );意思是__mptr的地址減去member在該struct中的偏移量得到的地址, 再轉換成type型指針. 該指針就是member的入口地址了.
舉例說明:
約定: 地址由高向低分配, 分配後指針指向高地址, 結構按逆序由高向低存儲, 不做對齊處理.
struct snow
{
     char name;    // 1
     int size;     // 4
     time_t hold; // 16
};
struct snow *p = (struct snow)malloc(sizeof(struct snow));
上面我們已經在堆中0x121至0x101分配了一個snow的struct, 並用p指針指向這段內存地址.
如果我們想取得p指向的結構中size變量的入口地址, 可以用container_of(p, struct snow, size);
這個調用的步驟是:
1` 取得p的指針地址, 121
2` 取得size在struct中的偏移量, 16
3` 相減獲得size的入口地址, 105

通過這一個簡單的例子應該能理解container_of的工作原理了, 這只是一個模仿的例子, 具體實現原理請參考ULK中的內存管理.
介紹一些list的iterate over函數:
1. #define list_for_each(pos, head) \
     for (pos = (head)->next; prefetch(pos->next), pos != (head); \
             pos = pos->next)
關於這個遍歷的循環似乎沒什麼好說的, 從head->next開始, 用next指針遍歷, prefetch的是將指針推入CPU L1 cache的操作.

#define __list_for_each(pos, head) \
     for (pos = (head)->next; pos != (head); pos = pos->next)
同上, 少了prefetch操作.

#define list_for_each_prev(pos, head) \
     for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \
             pos = pos->prev)
這是用prev指針遍歷的帶prefetch的操作.

#define list_for_each_safe(pos, n, head) \
     for (pos = (head)->next, n = pos->next; pos != (head); \
         pos = n, n = pos->next)
安全的iterate over函數, 參數n是用於臨時存儲的鏈表.

2. #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即container_of函數.
pos是一個type結構型指針, head是list頭結點, member是struct中的成員.
可以看出, 這個函數是用next指針對member成員的遍歷.

#define list_for_each_entry_reverse(pos, head, member)             \
     for (pos = list_entry((head)->prev, typeof(*pos), member);     \
          prefetch(pos->member.prev), &pos->member != (head);      \#define list_for_each_entry_from(pos, head, member)              \
     for (; prefetch(pos->member.next), &pos->member != (head);     \
          pos = list_entry(pos->member.next, typeof(*pos), member))
          pos = list_entry(pos->member.prev, typeof(*pos), member))
同上, 是用prev指針對member成員的遍歷.

#define list_prepare_entry(pos, head, member) \
     ((pos) ? : list_entry(head, typeof(*pos), member))
獲得pos指針, 用於list_for_each_entry_continuer(). 當如果pos爲空時, 獲得head結點的member成員入口地址.

#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))
用next指針對member成員進行從pos開始的繼續遍歷.

#define list_for_each_entry_from(pos, head, member)              \
     for (; prefetch(pos->member.next), &pos->member != (head);     \
          pos = list_entry(pos->member.next, typeof(*pos), member))
用next指針對member成員進行從當前位置開始的繼續遍歷.

3. safe系列和rcu系統函數類似於上面的, 不再重複.

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