linux核心資料結構---連結串列(1)
Linux核心有一些基本的資料結構,這些資料結構是Linux實現的基礎,對於連結串列相信大家都不陌生,但是Linux核心中的連結串列與平常平常我們所使用的連結串列略有不同,第一次遇到或許會感到困惑。
先來看一個連結串列的節點,對於一個節點,分為兩部分,一部分是資料,另一部分是串聯資料的指標。Linux連結串列節點的定義如下(以下程式碼皆為3.5版本):
// include/linux/types.h struct list_head { struct list_head *next, *prev; };
這裡的定義有些奇怪,因為僅有前後節點的指標,並沒有資料,就像一串鏈子,只有線沒有線上的珠子,肯定是無法使用,那Linux核心如何把這些“珠子”附著到線上的呢?
來看一個簡單的例子:
struce simple { int data; struct list_head list; };
simple結構體的list成員指向下一個或者上一個simple的list,這樣便把節點串聯起來了,data作為“珠子”附著在list線上,但這樣仍然有一個問題,list成員僅僅指向下一個simple的list成員,那從list成員如何得到simple節點的地址呢?
答案是根據list成員的地址以及list成員在simple的位置便可以計算出simple物件的地址,這樣有些繁瑣,Linux提供了一個巨集,可以簡化這個過程:
// include/linux/list.h /** * 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) // include/linux/kernel.h #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #undef offsetof #ifdef __compiler_offsetof #define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER) #else #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif #endif /* __KERNEL__ */
可以看到,list_entry直接呼叫了container_of,container_of分為兩句,((type *)0)->member可以獲得member在結構體type中的偏移,假設有一個結構體在地址0的位置,那麼成員的地址便是成員對結構體的偏移,typeof是gcc的擴充套件,用於獲取變數的型別,offsetof(type,member) 獲取member成員在type中的偏移,然後使用member成員的指標ptr(複製成__mptr)減去偏移,即是結構體的地址。在我們的例子中,從list成員的地址獲取simple結構的地址如下:
simple * p = list_entry(ptr, struct simple, list);
這樣便解決了從list_head上獲取附著的資料的問題。接下來需要解決對連結串列的增刪改查的問題:
一、初始化連結串列:
初始化連結串列有兩種方法,LIST_HEAD_INIT和LIST_HEAD
#define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; }
建立一個指向自身的節點。
二、插入:
在節點後插入新節點list_add_tail,和在節點前插入新節點list_add:
/** * list_add - add a new entry * @new: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } /** * list_add_tail - add a new entry * @new: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); }
其中 __list_add 只是普通的連結串列操作,並無特別之處,可參見Linux原始碼檢視實現。
三、刪除節點:
static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; }
__list_del 把entry從連結串列中刪除,之後把entry連結串列指標複製成非空指標(如果使用會出現段錯誤)
四、檢查是否空連結串列
判斷一個連結串列是否為空,只需要看頭節點是否指向自己便可:
static inline int list_empty(const struct list_head *head) { return head->next == head; }
五、遍歷
遍歷是這幾種操作中最為複雜的,有四個函式:
#define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; pos != (head); pos = pos->prev) #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)) #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 和 list_for_each_prev 較為簡單,一個向後遍歷,另一個向前遍歷,list_for_each_entry和list_for_each_entry_reverse功能相似,不過不是對list_head操作,而是直接對結構體操作,如我們這裡的simple結構。根據之前的敘述也不難理解函式實現,只是在list_head上呼叫了list_entry獲取了完整結構。
六、例項
千言萬語不如一個例子來的直觀,我們通過一個簡單的例子說明一下如何使用核心連結串列:
#include <linux/list.h> #include <linux/kernel.h> #include <stdio.h> struct simple { int data; struct list_head list; }; int main() { int i = 0; struct simple * p; struct list_head * pos; LIST_HEAD(head); for (i = 0; i < 10; i++) { p = (struct simple*)malloc(sizeof(struct simple)); p->data = i * 10; list_add_tail(&p->list, &head); } list_for_each_entry(p, &head, list) { printf("for %d\n", p->data); } while (!list_empty(&head)) { pos = head.next; p = (struct simple*)list_entry(pos, struct simple, list); list_del(pos); printf("del %d\n", p->data); free(p); } return 0; }
編譯引數為
gcc -D__KERNEL__ -I/usr/src/linux-headers-3.2.0-27-generic/include/ -I/usr/src/linux-headers-3.2.0-27-generic/arch/ia64/include/ simple.c
其中標頭檔案中都是核心函式,需要巨集__KERNEL__,否則大部分定義會被忽略。
轉載自:http://tech.fancymore.com/page/143.html#more-143