--- title: Linux 核心風格 Linked List 應用案例 tags: C 語言筆記 --- ## Linux 核心風格 Linked List 應用案例 ### Quick sort * [ sysprog21/linux-list 專案中的 quick-sort.c](https://github.com/sysprog21/linux-list/blob/master/examples/quick-sort.c) ```c= static void list_qsort(struct list_head *head) { // less 存放比 pivot 小的 node // greater 存放比 pivot 大的 node struct list_head list_less, list_greater; struct listitem *pivot; struct listitem *item = NULL, *is = NULL; // 如果 list 為空或者只有一點就直接 return if (list_empty(head) || list_is_singular(head)) return; // 把 less、greater 的 next、prev 指向自己(circular) INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); // 令第一個點為 pivot,並把該 pivot 從 list 中移除 pivot = list_first_entry(head, struct listitem, list); list_del(&pivot->list); // 利用 macro 來走訪整個 list,分類出 less 和 greater list_for_each_entry_safe (item, is, head, list) { if (cmpint(&item->i, &pivot->i) < 0) list_move_tail(&item->list, &list_less); else list_move_tail(&item->list, &list_greater); } // 對分類出來的 less 和 greater 遞迴做 quick sort,得到排序好的 less 和 greater list_qsort(&list_less); list_qsort(&list_greater); // 最後就是合併 less + pivot + greater list_add(&pivot->list, head); list_splice(&list_less, head); list_splice_tail(&list_greater, head); } ``` * 用上述的做法實作在 singly linked list * node1(head) -> node2 -> node3 -> ... ```c= node_t *quicksort(node_t *head) { if (!head || !head->next) return head; node_t *pivot = head; int pivot_val = pivot->val; node_t **cur = &(pivot->next); node_t *less_head = NULL; node_t **less_cur = &less_head; node_t *greater_head = NULL; node_t **greater_cur = &greater_head; while (*cur) { if ((*cur)->val <= pivot_val) { *less_cur = *cur; *cur = (*cur)->next; less_cur = &(*less_cur)->next; *less_cur = NULL; } else { *greater_cur = *cur; *cur = (*cur)->next; greater_cur = &(*greater_cur)->next; *greater_cur = NULL; } } less_head = quicksort(less_head); greater_head = quicksort(greater_head); node_t *ret = less_head ? less_head : pivot; if (less_head) { node_t *tmp; for (tmp = less_head; tmp->next;) tmp = tmp->next; tmp->next = pivot; } pivot->next = greater_head; return ret; } ``` ### WRITE_ONCE 探討 * ```list_add``` 的實作中 ```c= static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { if (!__list_add_valid(new, prev, next)) return; next->prev = new; new->next = next; new->prev = prev; WRITE_ONCE(prev->next, new); } static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } ``` * ```WRITE_ONCE(prev->next, new)``` 就是在做 ```prev->next = new``` 加上處理 race condition * ```WRITE_ONCE``` 的定義: ```c= #define WRITE_ONCE(x, val) \ ({ \ union { typeof(x) __val; char __c[1]; } __u = \ { .__val = (val) }; \ __write_once_size(&(x), __u.__c, sizeof(x)); \ __u.__val; \ }) ``` * 借用 union 的 ```type-punning```,簡單來說就是可以用 ```char __c[1]``` 來代表 ```val``` 的位址,當作 ```val``` 的指標來用 * ```__write_once_size``` 的定義 ```c= static __always_inline void __write_once_size(volatile void *p, void *res, int size) { switch (size) { case 1: *(volatile __u8_alias_t *) p = *(__u8_alias_t *) res; break; case 2: *(volatile __u16_alias_t *) p = *(__u16_alias_t *) res; break; case 4: *(volatile __u32_alias_t *) p = *(__u32_alias_t *) res; break; case 8: *(volatile __u64_alias_t *) p = *(__u64_alias_t *) res; break; default: barrier(); __builtin_memcpy((void *)p, (const void *)res, size); barrier(); } } ``` * 使用 ``` __may_alias__ ``` 來告知編譯器此型別允許 [aliasing](http://clhjoe.blogspot.com/2012/06/gcc-strict-aliasing.html) 的發生,避免激進的最佳化導致非預期的程式行為 * 簡單來說就是依照 ```size``` 來做 assign * 然後有加上 ```volatile``` 告訴編譯器這個值可能會有變動,不要做優化 * 最後的 ```default``` 就是其他大小的處理,```barrier()``` 意思就是告訴編譯器不能把 ```barrier()``` 前的 load/store 移動到 ```barrier()``` 後,反之同理 ### Linux 核心的 list_sort 實作 * linux 的 ```list_sort``` 是採用了 merge sort * ```list_sort``` 有效利用了 ```doubly linked list``` 中的 ```prev``` 與 ```next``` 指標 * 但只是單純的 merge 會產生 unbalanced 的問題,如 1028 個節點時,merge sort 做到結束時會產生 1024 個已排序的 ```linked list``` 與 4 個未排序的 ```linked list```,此時最差的情況就是四個都在 1024 個 list 的最後才找到 * 所以 ```list_sort``` 提出一種 worst-case optimal 的方法來避免這種不平衡的 merge * 大概的做法就是用一個 ```count``` 來負責甚麼時候要做 merge * 如果要產生 2^k+1^ 的 linked list 的話,便需要==兩個長度為 2^k^== 的 linked list,與長度為 ==2^k−1^,...,2^1^== 的 linked list 各一個,此時 merge 完後的 linked list 長度各為 ==2^k+1^, 2^k−1^,..., 2^1^== * 如果這時已經到 linked list 的結尾的話,只需要將最後一個節點從 2^1^ merge 到 2^k+1^ 即完成 merge sort * 可以發現中間 merge 的數量比例最多只會為 2:1(長度為 2^1^ 與 1 及 2^k+1^ 與 2^k^ 做 merge 的時候) | count | Merge | while-loop 開始時的 pending list | merge 後的 pending list | |:-----:|:-----:|:--------------------------------:|:-----------------------:| | 0 = (0)~2~ | No | NULL | x | | 1 = (1)~2~ | No | 1 | x | | 2 = (10)~2~ | Yes | 1-1 | 2 | | 3 = (11)~2~ | No | 1-2 | x | | 4 = (100)~2~ | Yes | 1-1-2 | 2-2 | | 5 = (101)~2~ | Yes | 1-2-2 | 1-4 | | 6 = (110)~2~ | Yes | 1-1-4 | 2-4 | | 7 = (111)~2~ | No | 1-2-4 | x | | 8 = (1000)~2~ | Yes | 1-1-2-4 | 2-2-4 | | 9 = (1001)~2~ | Yes | 1-2-2-4 | 1-4-4 | | 10 = (1010)~2~ | Yes | 1-1-4-4 | 2-4-4 | | 11 = (1011)~2~ | Yes | 1-2-4-4 | 1-2-8 | | 12 = (1100)~2~ | Yes | 1-1-2-8 | 2-2-8 | | 13 = (1101)~2~ | Yes | 1-2-2-8 | 1-4-8 | | 14 = (1110)~2~ | Yes | 1-1-4-8 | 2-4-8 | | 15 = (1111)~2~ | No | 1-2-4-8 | x | | 16 = (10000)~2~ | Yes | 1-1-2-4-8 | 2-2-4-8 | * 實作 * ```priv``` 是一個 pointer,可以用來傳輸 ```cmp``` 需要的參數 * ```head``` 要被排序的 list * ```cmp``` 是比較自定義大小的 function pointer * ```__attribute__((nonnull))``` 讓 compiler 可以對指定位置的 pointer (在此例中是第 2、3 個參數,```head``` 跟 ```cmp```) 是 NULL 時發出警告 ```c= __attribute__((nonnull(2,3))) void list_sort(void *priv, struct list_head *head, int (*cmp)(void *priv, struct list_head *a, struct list_head *b)) { struct list_head *list = head->next, *pending = NULL; size_t count = 0; /* Count of pending */ if (list == head->prev) /* Zero or one elements */ return; // 破壞掉 linked list 的 circular 結構,最後的節點(head->prev) 的 next 指向 NULL head->prev->next = NULL; ``` ```c= do { size_t bits; struct list_head **tail = &pending; /* Find the least-significant clear bit in count */ for (bits = count; bits & 1; bits >>= 1) tail = &(*tail)->prev; /* Do the indicated merge */ if (likely(bits)) { struct list_head *a = *tail, *b = a->prev; a = merge(priv, (cmp_func)cmp, b, a); /* Install the merged result in place of the inputs */ a->prev = b->prev; *tail = a; } /* Move one element from input list to pending */ list->prev = pending; pending = list; list = list->next; pending->next = NULL; count++; } while (list); ``` * merge 的實作,就正常地把 ```a```、```b``` merge 起來 ```c= __attribute__((nonnull(2,3,4))) static struct list_head *merge(void *priv, cmp_func cmp, struct list_head *a, struct list_head *b) { struct list_head *head, **tail = &head; for (;;) { /* if equal, take 'a' -- important for sort stability */ if (cmp(priv, a, b) <= 0) { *tail = a; tail = &a->next; a = a->next; if (!a) { *tail = b; break; } } else { *tail = b; tail = &b->next; b = b->next; if (!b) { *tail = a; break; } } } return head; } ``` * 最後的 ```list_sort``` 程式碼,就是在做把剩下的 list 全都 merge 起來 ```c= /* End of input; merge together all the pending lists. */ list = pending; pending = pending->prev; for (;;) { struct list_head *next = pending->prev; if (!next) break; list = merge(priv, (cmp_func)cmp, pending, list); pending = next; } /* The final merge, rebuilding prev links,回復至 circular linked list 的狀態 */ merge_final(priv, (cmp_func)cmp, head, pending, list); } ```