深入淺出linux內核源代碼之雙向鏈表list_head(上)

原文:http://blog.csdn.net/fjb2080/article/details/5457609 

前言: 在linux 源代碼中有個頭文件爲list.h 。很多linux 下的源代碼都會使用這個頭文件,它裏面定義了一個結構, 以及定義了和其相關的一組函數,這個結構是這樣的:

 

struct list_head{

 struct list_head *next, *prev;

};

 

那麼這個頭文件又是有什麼樣的作用呢,這篇文章就是用來解釋它的作用,雖然這是linux 下的源代碼,但對於學習C 語言的人來說,這是算法和平臺沒有什麼關係。

 
一、雙向鏈表

學習計算機的人都會開一門課程《數據結構》,裏面都會有講解雙向鏈表的內容。

什麼是雙向鏈表,它看起來是這樣的:

 

struct dlist

{

 int no;

 void* data;

 struct dlist *prev, *next;

};

 

它的圖形結構圖如下:

 

 

 

如果是雙向循環鏈表,那麼就加上虛線所示。

 

現在有幾個結構體,它們是:

表示人的:

struct person

{

 int age;

 int weight;

};

表示動物的:

struct animal

{

 int age;

 int weight;

};

 

如果有一組filename 變量和filedata 變量,把它們存起來,我們會怎麼做,當然就用數組了,但我們想使用雙向鏈表,讓它們鏈接起來,那該怎麼做,唯一可以做的就是給每個結構加如兩個成員,如下:

 

表示人的:

struct person

{

 int age;

 int weight;

 struct person *next, *prev;

};

表示動物的:

struct animal

{

 int age;

 int weight;

 struct animal *next, *prev;

};

 

現在有一個人的一個鏈表的鏈頭指針person_head (循環雙向鏈表)和動物的鏈表的鏈頭指針ainimal_head ,我們要獲得特定年齡和特定體重的人或動物(假設不考慮重疊),那麼代碼看起來可能是這樣:

struct person * get_percent(int age, int weight)

{

…....

 struct person *p;

 for(p = person_head->next; p != person_head; p=p->next)

 {

 if(p->age == age && p->weight == weight)

 return p;

 }

…...

}

 

那同理, 要獲得一個特定年齡和重量的動物的函數get_animal(int age, int weight) 的代碼也是和上面的類似。

如果我們定義這樣的兩個函數,它們基本一樣,會不會覺得有點冗餘,如果是c++ 就好了,但這裏只說c 。

 

如果我們仔細觀察一下這兩個結構,我們會發現它們出了類型名字不一樣外,其它的都一樣。那麼我們考慮用一個宏來實現,這個宏看起來可能是這樣的。

 

#define get_one(list, age, weight, type, one) /

do /

{/

 type *p;/

 for(p = ((type*)list)->next; p != (type*)list; p=p->next)/

 if (p->age == age && p->weight == weight)/

 {/

 one = p;/

 break;/

 }/

}while(0)

 

那麼我們獲得一個年齡50 ,體重60 的人可以這樣:

struct person *one = NULL;

get_one(person_head, 50, 60, struct person, one);

if(one)

{

// get it

 …...

}

 

同樣獲得一個年齡20 ,體重130 的動物可以這樣:

struct animal *one = NULL;

get_one(animal_head, 50, 60, struct animal, one);

if(one)

{

// get it

 …...

}

 

我們再回過頭來看這兩個結構,它們的指向前和指向後的指針其實都差不多,那把它們綜合起來吧,所以看起來如下面:

 

struct list_head{

 struct list_head *next, *prev;

};

 

表示人的:

struct person

{

 int age;

 int weight;

 struct list_head list;

};

表示動物的:

struct animal

{

 int age;

 int weight;

 struct list_head list;

};

 

現在這個兩個結構看起來就更差不多一樣了。現在爲了方便,我們去掉那些暫時不用的數據,如下:

struct person

{

 struct list_head list;

};

表示動物的:

struct animal

{

 struct list_head list;

};

 

可能又會有些人會問了,struct list_head 都不是struct persion 和struct animal 類型,怎麼可以做鏈表的指針呢?其實,無論是什麼樣的指針,它的大小都是一樣的,32 位的系統中,指針的大小都是32 位(即4 個字節),只是不同類型的指針在解釋的時候不一樣而已,那麼這個struct list_head 又是怎麼去做這些結構的鏈表指針呢,那麼就請看下一節吧:)。

 
二、struct list_head 結構的操作

首先,讓我們來看下和struct list_head 有關的兩個宏,它們定義在list.h 文件中。

 

#define LIST_HEAD_INIT(name) { &(name), &(name) }

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

#define INIT_LIST_HEAD(ptr) do { /

 (ptr)->next = (ptr); (ptr)->prev = (ptr); /

} while (0)

 

這兩個宏是用了定義雙向鏈表的頭節點的,定義一個雙向鏈表的頭節點,我們可以這樣:

 

struct list_head head;

LIST_HEAD_INIT(head);

 

又或者直接這樣:

LIST_HEAD(head);

 

這樣,我們就定義並初始化了一個頭節點。

#define LIST_HEAD_INIT(name) { &(name), &(name) }

就是用head 的地址初始化其兩個成員next 和prev ,使其都指向自己。

 

我們再看下和其相關的幾個函數,這些函數都作爲內聯函數也都定義list.h 中,這裏要說明一下linux 源碼的一個風格,在下面的這些函數中以下劃線開始的函數是給內部調用的函數,而以符開始的函數就是對外使用的函數,這些函數一般都是調用以下劃線開始的函數,或是說是對下劃線開始的函數的封裝。

 
2.1 增加節點的函數

static inline void __list_add();

static inline void list_add();

static inline void list_add_tail();

 

其實看源代碼是最好的講解了,這裏我再簡單的講一下。

/**

* __list_add - Insert a new entry between two known consecutive entries.

* @new:

* @prev:

* @next:

*

* This is only for internal list manipulation where we know the prev/next

* entries already!

*/

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;

}

這個函數在prev 和next 間插入一個節點new 。

 

 

/**

* 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);

}

這個函數在head 節點後面插入new 節點。

 

/**

* 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);

}

這個函數和上面的那個函數相反,它在head 節點的前面插入new 節點。

 
2.2  從鏈表中刪除節點的函數

/**

* __list_del -

* @prev:

* @next:

*

* 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.

*/

static __inline__ void list_del(struct list_head *entry)

{

 __list_del(entry->prev, entry->next);

}

 

/**

* list_del_init - deletes entry from list and reinitialize it.

* @entry: the element to delete from the list.

*/

static __inline__ void list_del_init(struct list_head *entry)

{

 __list_del(entry->prev, entry->next);

 INIT_LIST_HEAD(entry);

}

 

這裏簡單說一下,list_del(struct list_head *entry) 是從鏈表中刪除entry 節點。list_del_init(struct list_head *entry)  不但從鏈表中刪除節點,還把這個節點的向前向後指針都指向自己,即初始化。

 

那麼,我們怎麼判斷這個鏈表是不是空的呢!上面我說了,這裏的雙向鏈表都是有一個頭節點,而我們上面看到,定義一個頭節點時我們就初始化了,即它的prev 和next 指針都指向自己。所以這個函數是這樣的。

/**

* list_empty - tests whether a list is empty

* @head: the list to test.

*/

static __inline__ int list_empty(struct list_head *head)

{

 return head->next == head;

}

 

講了這幾個函數後,這又到了關鍵了,下面講解的一個宏的定義就是對第一節中,我們所要說的爲什麼在一個結構中加入struct list_head 變量就把這個結構變成了雙向鏈表呢,這其中的關鍵就是怎麼通過這個struct list_head 變量來獲取整個結構的變量,下面這個宏就爲你解開答案:

 

/**

* 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) /

 ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

 

乍一看下,不知道這個宏在說什麼,沒關係,我舉個例子來爲你一一解答 :)

 

首先,我們還是用上面的結構:

struct person

{

 int age;

 int weight;

 struct list_head list;

};

 

我們一看到這樣的結構就應該知道它定義了一個雙向鏈表,下面來看下。

我們有一個指針:

struct list_head *pos;

現在有這個指針,我們怎麼去獲得這個指針所在的結構的變量(即是struct person 變量,其實是struct person 指針)呢?看下面這樣使用:

 

struct person *one = list_entry(pos, struct person, list);

 

不明白是吧,展開一下 list_entry 結構如下:

((struct person *)((char *)(pos) - (unsigned long)(&((struct person *)0)->list)))

 

我慢慢的分解,首先分成兩部分(char *)(pos) 減去(unsigned long)(&((struct person *)0)->list) 然後轉換成(struct person *) 類型的指針。

 

(char *)(pos) :是將pos 由struct list_head* 轉換成char* ,這個好理解。

(unsigned long)(&((struct person *)0)->list) :先看最裏面的(struct person *)0) ,它是把0 地址轉換成struct person 指針,然後(struct person *)0)->list 就是指向list 變量,之後是&((struct person *)0)->list 是取這個變量的地址,最後是(unsigned long)(&((struct person *)0)->list) 把這個變量的地址值變成一個整形數!

這麼複雜啊,其實說白了,這個(unsigned long)(&((struct person *)0)->list) 的意思就是取list 變量在struct person 結構中的偏移量。

用個圖形來說(unsigned long)(&((struct person *)0)->list ,如下:

 

 

而(unsigned long)(&((struct person *)0)->list 就是獲取這個offset 的值。

 

((char *)(pos) - (unsigned long)(&((struct person *)0)->list))

 

就是將pos 指針往前移動offset 位置,即是本來pos 是struct list_head 類型,它即是list 。即是把pos 指針往struct person 結構的頭地址位置移動過去,如上圖的pos 和虛箭頭。

當pos 移到struct person 結構頭後就轉換成(struct person *) 指針,這樣就可以得到struct person * 變量了。

 

所以我們再回到前面的句子

struct person *one = list_entry(pos, struct person, list);

就是由pos 得到pos 所在的結構的指針,動物就可以這樣:

struct animal *one = list_entry(pos, struct animal, list);

 

下面我們再來看下和struct list_head 相關的最後一個宏。

 
2.3 list_head 的遍歷的宏

/**

* list_for_each - iterate over a list

* @pos: the &struct list_head to use as a loop counter.

* @head: the head for your list.

*/

#define list_for_each(pos, head) /

 for (pos = (head)->next; pos != (head); pos = pos->next)

 

/**

* list_for_each_safe - iterate over a list safe against removal of list entry

* @pos: the &struct list_head to use as a loop counter.

* @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(pos, head) 是遍歷整個head 鏈表中的每個元素,每個元素都用pos 指向。

list_for_each_safe(pos, n, head) 是用於刪除鏈表head 中的元素,不是上面有刪除鏈表元素的函數了嗎,爲什麼這裏又要定義一個這樣的宏呢。看下這個宏後面有個safe 字,就是說用這個宏來刪除是安全的,直接用前面的那些刪除函數是不安全的。這個怎麼說呢,我們看下下面這個圖,有三個元素a ,b ,c 。

 

 

現在,我們要刪除b 元素,下面是刪除的算法(先只用刪除函數):

struct list_head *pos;

list_for_each(pos, myhead)

{

 if (pos == b)

 {

 list_del_init(pos);

 //break;

 }

 。。。

}

上面的算法是不安全的,因爲當我們刪除b 後,如下圖這樣:

 

上刪除pos 即b 後,list_for_each 要移到下一個元素,還需要用pos 來取得下一個元素,但pos 的指向已經改變,如果不直接退出而是在繼續操作的話,就會出錯了。

 

而 list_for_each_safe 就不一樣了,如果上面的代碼改成這樣:

struct list_head *pos, *n;

list_for_each_safe(pos, n, myhead)

{

 if (pos == b)

 {

 list_del_init(pos);

 //break;

 }

 。。。

}

 

這裏我們使用了n 作爲一個臨時的指針,當pos 被刪除後,還可以用n 來獲得下一個元素的位置。

 

說了那麼多關於list_head 的東西,下面應該總結一下,總結一下第一節想要解決的問題。請看下章分析

發佈了2 篇原創文章 · 獲贊 4 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章