1. 程式人生 > >linux核心資料結構---連結串列(1)

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