歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux內核 >> list.h linux內核鏈表分析

list.h linux內核鏈表分析

日期:2017/3/3 14:02:55   编辑:Linux內核
list.h 頭文件中定義了一些Linux內核鏈表操作相關的函數,自己在學習數據結構時研究了一下裡面的程序,裡面的代碼確實是簡潔又高效。

Linux內核鏈表概述

在傳統鏈表程序中,結構體數據中必須要有一個自身結構體類型的指針,用於指向下一個結構體。一個程序中可能要用到多個鏈表,不同鏈表用的結構體不一定相同,因此不得不為每個種結構體定義一套鏈表操作。而Linux內核鏈表把鏈表指針域的相關操作單獨抽離出來,封裝成一套接口,每一種鏈表都可使用這個接口。如果需要構造某種類型的鏈表時,只要將 struct list_head 類型成員加入到結構體中,用 struct list_head 類型成員將各個結構體連接起來,形成一個雙向循環鏈表。使用的時候只需要調用list.h中相關函數,不用自己再為每種鏈表單獨定義一套函數。

鏈表結構體

[code]struct list_head
{
    struct list_head *next;
    struct list_head *prev;
};

看得出來這是一個雙向鏈表,裡面只有兩個指針,沒有其他任何數據項,其他操作函數也只針對這兩個指針。這就注定了list.h中的鏈表操作可以嵌入到其他任意類型結構體中,具有很強的通用性和靈活性。

定義鏈表頭

[code]#define LIST_HEAD_INIT(name) { &(name), &(name) }

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

LIST_HEAD 宏用於定義並初始化鏈表頭。 LIST_HEAD 展開後形式為
[code]struct list_head name = {&name, &name}

可見LIST_HEAD定義鏈表頭的時候把前向指針和後向指針都初始化為自身。這時候鏈表是一個循環雙向空鏈表。
另外,也可以使用 INIT_LIST_HEAD 函數初始化鏈表頭:
[code]static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}

這個鏈表頭是一個獨立的 struct list_head 結構體類型數據,而其他的結點,是用戶定義的結構體類型中 struct list_head 類型的成員。

添加結點

在兩個結點中間插入一個新結點
[code]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;
}

在鏈表頭部添加一個結點,就是在鏈表頭和第一個結點中間插入一個新結點
[code]static inline void list_add(struct list_head *new, struct list_head *head)
{
    __list_add(new, head, head->next);
}

在鏈表尾部添加一個結點。因為這是雙向循環鏈表,鏈表的最後一個結點就是鏈表頭的前一個結點,要在鏈表尾部加入結點,就在鏈表頭和鏈表頭的前一個結點中間插入結點。
[code]static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);
}

可見無論是在頭部加入結點還是在尾部加入結點,都只要一步就搞定,無需遍歷整個鏈表,這就是雙向循環鏈表的好處。

刪除結點

內核中刪除鏈表代碼如下
[code]/*
 * Delete a list entry by making the prev/next entries
 * point to each other.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
    next->prev = prev;
    prev->next = next;
}

/**
 * list_del - deletes entry from list.
 * @entry: the element to delete from the list.
 * Note: list_empty() on entry does not return true after this, the entry is
 * in an undefined state.
 */
#ifndef CONFIG_DEBUG_LIST
static inline void __list_del_entry(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
}

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_entry(struct list_head *entry);
extern void list_del(struct list_head *entry);
#endif

用 list_del 函數刪除鏈表結點,首先用 __list_del 函數將結點從鏈表中移除,再把結點的前向指針指向和後向指針分別指向 LIST_POISON1 和 LIST_POISON2。 LIST_POISON1 和 LIST_POISON2 不是NULL,但引用到這兩個地址是會引起頁錯誤。我們在調試出錯代碼的時候經常會返回指針的值,我們很多時候出錯都是指針為NULL造成的,這時候我們如果發現是值是 LIST_POISON1 或 LIST_POISON2 的話,那我們馬上可以將注意力轉移到鏈表上來了,並且是因為誤用了一個已經從鏈表中刪除掉的節點。

鏈表遍歷

[code]/**
 * list_for_each    -   iterate over a list
 * @pos:    the &struct list_head to use as a loop cursor.
 * @head:   the head for your list.
 */
#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

源碼中將鏈表的遍歷操作直接定義成宏,使用起來非常方便。但是如果在遍歷鏈表過程中,有對鏈表進行插入、刪除結點的操作,pos指針指向的內容被改變,就不能使用這種方法遍歷了,必須使用一種更安全的方法。
[code]/**
 * list_for_each_safe - iterate over a list safe against removal of list entry
 * @pos:    the &struct list_head to use as a loop cursor.
 * @n:      another &struct list_head to use as temporary storage
 * @head:   the head for your list.
 */
#define list_for_each_safe(pos, n, head) \
    for (pos = (head)->next, n = pos->next; pos != (head); \
        pos = n, n = pos->next)

list_for_each_save 又使用了另一個變量n,保存pos下一個結點的指針,這事及時pos被改變,也不會影響鏈表的遍歷操作。
這些方法在鏈表遍歷中得到的只是結構體中struct list_head類型成員的地址,而不是整個結構體的地址,要得到結構體的地址,要使用另一個宏定義。
[code]/**
 * 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 是一個根據結構體成員的地址獲取結構體地址的宏。這個宏的具體分析,可以參見我另一篇博客container_of 宏 分析
有了創建鏈表,添加結點,刪除結點,遍歷鏈表這些函數,就實現鏈表的基本操作了。lish.h 中還有很多高級及操作,比如鏈表結點移動,鏈表合並等,這裡就不一一介紹了。
流雲非晚
Copyright © Linux教程網 All Rights Reserved