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 * new , struct list_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 * new , struct list_head *head) |
7 |
79{ |
8 |
80 __list_add( new , head->prev, head); |
9 |
81} |
注意,這裡的形參prev和next是兩個連續的結點。這其實是資料結構中很普通的雙鏈表元素新增問題,在此不再贅述。下面的圖可供參考,圖中1~4分別對應__list_add函式的四條語句。
這裡又是一個呼叫關係,__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, struct list_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) / |