1. 程式人生 > >list.h標頭檔案分析

list.h標頭檔案分析

序:覺得這哥們寫的不錯,再轉來給大家看。

雙鏈表的應用在核心中隨處可見,list.h標頭檔案集中定義了雙鏈表(struct list_head結構體)的相關操作。比如這裡的一個頭檔案中就有大量的struct list_head型的資料。

關於list.h的分析,網上資料很多,這裡只是記錄我在分析list.h中遇到的問題。

0.struct list_head結構體

可能這樣寫,更讓我們習慣:

1 struct list_head {
2 struct list_head *next;
3 struct list_head *prev;
4 };

這個結構經常作為成員與其他資料型別一起組成一個新的結構體(後文若無特別提示,“新結構體”均指類似下面舉例的巢狀型結構體)

,比如:

1 struct stu
2 {
3 char name[20];
4 int id;
5 struct list_head list;
6 }

我們已經看到,struct list_head這個結構比較特殊,它內部沒有任何資料,只是起到連結連結串列的作用。對於它當前所在的這個結點來說,next指向下一個結點,prev指向上一個結點。通常我們通過指向struc list_head的指標pos來獲取它所在結點的地址,盡而獲取其他資料。也許你現在還比較困惑這一過程,彆著急,後面有特別解釋。

1.連結串列的初始化

其實可以從後往前看,這樣更容易理解。INIT_LIST_HEAD函式形成一個空連結串列。這個list變數一般作為頭指標(非頭結點)。

1 28static inline void INIT_LIST_HEAD(struct list_head *list)
2 29{
3 30        list->next = list;
4 31        list->prev = list;
5 32}

下面的巨集生成一個頭指標name,如何生成?請看LIST_HEAD_INIT(name)。

1 25#define LIST_HEAD(name) /
2 26        struct list_head name = LIST_HEAD_INIT(name)

LIST_HEAD_INIT(name)將name的地址直接分別賦值給next和prev,那麼它們事實上都指向自己,也形成一個空連結串列。現在再回頭看巨集LIST_HEAD(name),它其實就是一個定義並初始化作用。

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

3.新增元素

這兩個函式分別給連結串列頭結點後,頭結點前新增元素。前者可實現棧的新增元素,後者可實現佇列的新增元素。
static inline void list_add(struct list_head *new, struct list_head *head);
static inline void list_add_tail(struct list_head *new, struct list_head *head);

這兩個函式如何實現的?它們均呼叫的下面函式:

1 41static inline void __list_add(struct list_head *new,
2 42                              struct list_head *prev,
3 43                              struct list_head *next)
4 44{
5 45        next->prev = new;
6 46        new->next = next;
7 47        new->prev = prev;
8 48        prev->next = new;
9 49}

現在我們要關注的是,list_add和list_add_tail兩函式在呼叫__list_add函式時,對應的各個引數分別是什麼?通過下面所列程式碼,我們可以發現這裡的引數運用的很巧妙,類似JAVA中的封裝。

1 64static inline void list_add(struct list_head *newstructlist_head *head)
2 65{
3 66        __list_add(new, head, head->next);
4 67}
5
6 78static inline void list_add_tail(struct list_head *newstructlist_head *head)
7 79{
8 80        __list_add(new, head->prev, head);
9 81}

注意,這裡的形參prev和next是兩個連續的結點。這其實是資料結構中很普通的雙鏈表元素新增問題,在此不再贅述。下面的圖可供參考,圖中1~4分別對應__list_add函式的四條語句。


3.刪除元素

這裡又是一個呼叫關係,__list_del函式具體的過程很簡單,分別讓entry節點的前後兩個結點(prev和next)“越級”指向彼此。請注意這個函式的後兩句話,它屬於不安全的刪除。

1 103static inline void list_del(struct list_head *entry)
2 104{
3 105        __list_del(entry->prev, entry->next);
4 106        entry->next = LIST_POISON1;
5 107        entry->prev = LIST_POISON2;
6 108}

想要安全的刪除,那麼可以呼叫下面函式。還記得INIT_LIST_HEAD(entry)嗎,它可以使entry節點的兩個指標指向自己。

1 140static inline void list_del_init(struct list_head *entry)
2 141{
3 142        __list_del(entry->prev, entry->next);
4 143        INIT_LIST_HEAD(entry);
5 144}

4.替換元素

用new結點替換old結點同樣很簡單,幾乎是在old->prev和old->next兩結點之間插入一個new結點。畫圖即可理解。

1 120static inline void list_replace(struct list_head *old,
2 121                                struct list_head *new)
3 122{
4 123        new->next = old->next;
5 124        new->next->prev = new;
6 125        new->prev = old->prev;
7 126        new->prev->next = new;
8 127}

同樣,想要安全替換,可以呼叫:

1 129static inline void list_replace_init(struct list_head *old,
2 130                                        struct list_head *new)
3 131{
4 132        list_replace(old, new);
5 133        INIT_LIST_HEAD(old);
6 134}

5.移動元素

理解了刪除和增加結點,那麼將一個節點移動到連結串列中另一個位置,其實就很清晰了。list_move函式最終呼叫的是__list_add(list,head,head->next),實現將list移動到頭結點之後;而list_move_tail函式最終呼叫__list_add_tail(list,head->prev,head),實現將list節點移動到連結串列末尾。

01 151static inline void list_move(struct list_head *list, structlist_head *head)
02 152{
03 153        __list_del(list->prev, list->next);
04 154        list_add(list, head);
05 155}
06 156
07
08 162static inline void list_move_tail(struct list_head *list,
09 163                                  struct list_head *head)
10 164{
11 165        __list_del(list->prev, list->next);
12 166        list_add_tail(list, head);
13 167}

6.測試函式

接下來的幾個測試函式,基本上是“程式碼如其名”。

list_is_last函式是測試list是否為連結串列head的最後一個節點。

1 174static inline int list_is_last(const struct list_head *list,
2 175                                const struct list_head *head)
3 176{
4 177        return list->next == head;
5 178}

下面的函式是測試head連結串列是否為空連結串列。注意這個list_empty_careful函式,他比list_empty函式“仔細”在那裡呢?前者只是認為只要一個結點的next指標指向頭指標就算為空,但是後者還要去檢查頭節點的prev指標是否也指向頭結點。另外,這種仔細也是有條件的,只有當其他cpu的連結串列操作只有list_del_init()時,否則仍然不能保證安全。

01 184static inline int list_empty(const struct list_head *head)
02 185{
03 186        return head->next == head;
04 187}
05
06 202static inline int list_empty_careful(const struct list_head *head)
07 203{
08 204        struct list_head *next = head->next;
09 205        return (next == head) && (next == head->prev);
10 206}

下面的函式是測試head連結串列是否只有一個結點:這個連結串列既不能是空而且head前後的兩個結點都得是同一個結點。

1 226static inline int list_is_singular(const struct list_head *head)
2 227{
3 228        return !list_empty(head) && (head->next == head->prev);
4 229}

7.將連結串列左轉180度

正如註釋說明的那樣,此函式會將這個連結串列以head為轉動點,左轉180度。整個過程就是將head後的結點不斷的移動到head結點的最左端。如果是單個結點那麼返回真,否則假。

1 212static inline void list_rotate_left(struct list_head *head)
2 213{
3 214        struct list_head *first;
4 215
5 216        if (!list_empty(head)) {
6 217                first = head->next;
7 218                list_move_tail(first, head);
8 219        }
9 220}

上述函式每次都呼叫 list_move_tail(first, head);其實我們將其分解到“最小”,那麼這個函式每次最終呼叫的都是:__list_del(first->prev,first->next);和__list_add(list,head->prev,head);這樣看起來其實就一目瞭然了。

8.將連結串列一分為二

這個函式是將head後至entry之間(包括entry)的所有結點都“切開”,讓他們成為一個以list為頭結點的新連結串列。我們先從巨集觀上看,如果head本身是一個空連結串列則失敗;如果head是一個單結點連結串列而且entry所指的那個結點又不再這個連結串列中,也失敗;當entry恰好就是頭結點,那麼直接初始化list,為什麼?因為按照剛才所說的切割規則,從head後到entry前事實上就是空結點。如果上述條件都不符合,那麼就可以放心的“切割”了。

01 257static inline void list_cut_position(struct list_head *list,
02 258                struct list_head *head, struct list_head *entry)
03 259{
04 260        if (list_empty(head))
05 261                return;
06 262        if (list_is_singular(head) &&
07 263                (head->next != entry && head != entry))
08 264                return;
09 265        if (entry == head)
10 266                INIT_LIST_HEAD(list);
11 267        else
12 268                __list_cut_position(list, head, entry);
13 269}

具體如何切割,這裡的程式碼貌似很麻煩,可是我們畫出圖後,就“一切盡在不言中”了。

01 231static inline void __list_cut_position(struct list_head *list,
02 232                struct list_head *head, struct list_head *entry)
03 233{
04 234        struct list_head *new_first = entry->next;
05 235        list->next = head->next;
06 236        list->next->prev = list;
07 237        list->prev = entry;
08 238        entry->next = list;
09 239        head->next = new_first;
10 240        new_first->prev = head;
11 241}

圖示:

9.合併連結串列

既然我們可以切割連結串列,那麼當然也可以合併了。先看最基本的合併函式,就是將list這個連結串列(不包括頭結點)插入到prev和next兩結點之間。這個程式碼閱讀起來不困難,基本上是“見碼知意”。

01 271static inline void __list_splice(const struct list_head *list,
02 272                                 struct list_head *prev,
03 273                                 struct list_head *next)
04 274{
05 275        struct list_head *first = list->next;
06 276        struct list_head *last = list->prev;
07 277
08 278        first->prev = prev;
09 279        prev->next = first;
10 280
11 281        last->next = next;
12 282        next->prev = last;
13 283}

理解了最基本的合併函式,那麼將它封裝起來,就可以形成下面兩個函數了,分別在head連結串列的首部和尾部合併。這裡的呼叫過程類似增加,刪除功能。

01 290static inline void list_splice(const struct list_head *list,
02 291                                struct list_head *head)
03 292{
04 293        if (!list_empty(list))
05 294                __list_splice(list, head, head->next);
06 295}
07
08 302static inline void list_splice_tail(struct list_head *list,
09 303                                struct list_head *head)
10 304{
11 305        if (!list_empty(list))
12 306                __list_splice(list, head->prev, head);
13 307}

合併兩個連結串列後,list還指向原連結串列,因此應該初始化。在上述兩函式末尾新增初始化語句INIT_LIST_HEAD(list);後,就安全了。

10.遍歷

下面我們要分析連結串列的遍歷。雖然涉及到遍歷的巨集比較多,但是根據我們前面分析的那樣,掌握好最基本的巨集,其他巨集就是進行“封裝”。便利中的基本巨集是:

1 381#define __list_for_each(pos, head) /
2 382        for (pos = (head)->next; pos != (head); pos = pos->next)

head是整個連結串列的頭指標,而pos則不停的往後移動。但是你有沒有覺得,這裡有些奇怪?因為我們在上篇文章中說過,struct list_head結構經常和其他資料組成新的結構體,那麼現在我們只是不停的遍歷新結構體中的指標,如何得到其他成員?因此我們需要搞懂list_entry這個巨集:

1 348#define list_entry(ptr, type, member) /
2 349        container_of(ptr, type, member)

這個巨集的作用是通過ptr指標獲取type結構的地址,也就是指向type的指標。其中ptr是指向member成員的指標。這個list_entry巨集貌似很簡單的樣子,就是再呼叫container_of巨集,可是當你看了container_of巨集的定義後……

1 443#define container_of(ptr, type, member) ({                      /
2 444        const typeof( ((type *)0)->member ) *__mptr = (ptr);    /
3 445        (type *)( (char *)__mptr - offsetof(type,member) );})

是不是讓人有點抓狂?別急,我們一點點來分析。

首先這個巨集包含兩條語句。第一條:const typeof( ((type *)0)->member ) *__mptr = (ptr);首先將0轉化成type型別的指標變數(這個指標變數的地址為0×0),然後再引用member成員(對應就是((type *)0)->member ))。注意這裡的typeof(x),是返回x的資料型別,那麼 typeof( ((type *)0)->member )其實就是返回member成員的資料型別。那麼這條語句整體就是將__mptr強制轉換成member成員的資料型別,再將ptr的賦給它(ptr本身就是指向member的指標)。

第二句中,我們先了解offsetof是什麼?它也是一個巨集被定義在:linux/include/stddef.h中。原型為:

1 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER);

這個貌似也很抓狂,不過耐心耐心:((TYPE *)0)->MEMBER)這個其實就是提取type型別中的member成員,那麼&((TYPE *)0)->MEMBER)得到member成員的地址,再強制轉換成size_t型別(unsigned int)。但是這個地址很特別,因為TYPE型別是從0×0開始定義的,那麼我們現在得到的這個地址就是member成員在TYPE資料型別中的偏移量。

我們再來看第二條語句, (type *)( (char *)__mptr – offsetof(type,member) )求的就是type的地址,即指向type的指標。不過這裡要注意__mptr被強制轉換成了(char *),為何要這麼做?因為如果member是非char型的變數,比如為int型,並且假設返回值為offset,那麼這樣直接減去偏移量,實際上__mptr會減去sizeof(int)*offset!這一點和指標加一減一的原理相同。

有了這個指標,那麼就可以隨意引用其內的成員了。關於此巨集的更具體瞭解,不妨親自動手測試這裡的程式。

好了,現在不用抓狂了,因為了解了list_entry巨集,接下來的事情就很簡單了。

下面這個巨集會得到連結串列中第一個結點的地址。

1 359#define list_first_entry(ptr, type, member) /
2 360        list_entry((ptr)->next, type, member)

真正遍歷的巨集登場了,整個便利過程看起來很簡單,可能你對prefetch()陌生,它的作用是預取節點,以提高速度。

1 367#define list_for_each(pos, head) /
2 368        for (pos = (head)->next; prefetch(pos->next), pos != (head); /
3 369                pos = pos->next)

我們再來看一開始我們舉例的那個便利巨集。注意它和上述便利巨集的區別就是沒有prefetch(),因為這個巨集適合比較少結點的連結串列。

1 381#define __list_for_each(pos, head) /
2 382        for (pos = (head)->next; pos != (head); pos = pos->next)

接下來這個遍歷巨集貌似長相和上面那幾個稍有不同,不過理解起來也不困難,倒著(從最後一個結點)開始遍歷連結串列。

1 389#define list_for_each_prev(pos, head) /
2 390        for (pos = (head)->prev; prefetch(pos->prev), pos != (head); /
3 391                pos = pos->prev)

下面兩個巨集是上述兩個便利巨集的安全版,我們看它安全在那裡?它多了一個與pos同類型的n,每次將下一個結點的指標暫存起來,防止pos被釋放時引起的連結串列斷裂。

1 399#define list_for_each_safe(pos, n, head) /
2 400        for (pos = (head)->next, n = pos->next; pos != (head); /
3 401                pos = n, n = pos->next)
4
5 409#define list_for_each_prev_safe(pos, n, head) /
6 410        for (pos = (head)->prev, n = pos->prev; /
7 411             prefetch(pos->prev), pos != (head); /
8 412             pos = n, n = pos->prev)

前面我們說過,用在list_for_each巨集進行遍歷的時候,我們很容易得到pos,我們都知道pos儲存的是當前結點前後兩個結點的地址。而通過list_entry巨集可以獲得當前結點的地址,進而得到這個結點中其他的成員變數。而下面兩個巨集則可以直接獲得每個結點的地址,我們接下來看它是如何實現的。為了方便說明以及便於理解,我們用上文中的結構struct stu來舉例。pos是指向struct stu結構的指標;list是一個雙鏈表,同時也是這個結構中的成員,head便指向這個雙鏈表;member其實就是這個結構體中的list成員。

在for迴圈中,首先通過list_entry來獲得第一個結點的地址;&pos->member != (head)其實就是&pos->list!=(head);它是用來檢測當前list連結串列是否到頭了;最後在利用list_entry巨集來獲得下一個結點的地址。這樣整個for迴圈就可以依次獲得每個結點的地址,進而再去獲得其他成員。理解了list_for_each_entry巨集,那麼list_for_each_entry_reverse巨集就顯而易見了。

1 420#define list_for_each_entry(pos, head, member)                          /
2 421        for (pos = list_entry((head)->next, typeof(*pos), member);      /
3 422             prefetch(pos->member.next), &pos->member != (head);        /
4 423             pos = list_entry(pos->member.next, typeof(*pos), member))
5
6 431#define list_for_each_entry_reverse(pos, head, member)                  /
7 432        for (pos = list_entry((head)->prev, typeof(*pos), member);      /
8 433             prefetch(pos->member.prev), &pos->member != (head);        /
9 434             pos = list_entry(pos->member.prev, typeof(*pos), member))

下面這兩個巨集是從當前結點的下一個結點開始繼續(或反向)遍歷。

1 456#define list_for_each_entry_continue(pos, head, member)                 /
2 457        for (pos = list_entry(pos->member.next, typeof(*pos), member);  /
3 458             prefetch(pos->member.next), &pos->member != (head);        /
4 459             pos = list_entry(pos->member.next, typeof(*pos), member))
5
6 470#define list_for_each_entry_continue_reverse(pos, head, member)         /
7 471        for (pos = list_entry(pos->member.prev, typeof(*pos), member);  /
8 472             prefetch(pos->member.prev), &pos->member != (head);        /
9 473             pos = list_entry(pos->member.prev, typeof(*pos), member))

與上述巨集不同的是,這個巨集是從當前pos結點開始遍歷。

1 483#define list_for_each_entry_from(pos, head, member)                     /