---
tags: linux kernel
---
# 研讀 Linux 核心原始程式碼 [list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c)
contributed by < [`Risheng1128`](https://github.com/Risheng1128) >
> [Linux2022 開發紀錄(lab0)](https://hackmd.io/@Risheng/linux2022-lab0)
> [Linux2023 開發紀錄(lab0)](https://hackmd.io/@Risheng/linux2023-lab0)
首先 Linux kernel 的 merge sort 主要由三個函式 `list_sort()` 、 `merge()` 及 `merge_final()` 組成,以下開始分析每個函式所做的功能
在分析函式之前,先了解函式的 prototype ,每個函式的宣告都擁有函式屬性 `__attribute__((nonnull()))` ,參考 [`__attribute__((nonnull))` function attribute](https://www.keil.com/support/man/docs/armcc/armcc_chr1359124976631.htm) 可以得知 `nonnull` 是用來使特定函數的引數為 `NULL` 時,編譯器會發出警告
> This function attribute specifies function parameters that are not supposed to be null pointers. This enables the compiler to generate a warning on encountering such a parameter.
## list_sort()
以下為 `list_sort()` 的定義及引數
> `__attribute__((nonnull(2,3))) void list_sort(void *priv, struct list_head *head, list_cmp_func_t cmp)`
> @priv: private data, opaque to list_sort(), passed to @cmp
> @head: the list to sort
> @cmp: the elements comparison function
這邊根據原始碼對 `cmp` 做一些分析
1. 首先是 `cmp` 的資料型態 `list_cmp_func_t` ,根據 [`include/linux/list_sort.h`](https://github.com/torvalds/linux/blob/master/include/linux/list_sort.h) 可以找到,可以得知 `cmp` 其實是一個函式指標,指向回傳型態為 `int` ,且引數有 `void *` 及 2 個 `const struct list_head *` 的函式
```c
typedef int __attribute__((nonnull(2,3))) (*list_cmp_func_t)(void *, const struct list_head *, const struct list_head *);
```
2. 當 `cmp` 回傳大於 0 時,表示執行 ascending sort
3. 當 `cmp` 回傳小於等於 0 時,表示執行 descending sort 或是保留原本順序
接著來到分析 merge sort 在 Linux kernel 的實際樣貌,這邊參考[前人的精華](https://hackmd.io/@RinHizakura/HkEuhNwGO#list_sort)及原始碼搭配分析
```
* This mergesort is as eager as possible while always performing at least
* 2:1 balanced merges. Given two pending sublists of size 2^k, they are
* merged to a size-2^(k+1) list as soon as we have 2^k following elements.
*
* Thus, it will avoid cache thrashing as long as 3*2^k elements can
* fit into the cache. Not quite as good as a fully-eager bottom-up
* mergesort, but it does use 0.2*n fewer comparisons, so is faster in
* the common case that everything fits into L1.
```
首先查看註解的部份,可以得知該 merge sort 的鏈節串列總是保持 2:1 ,即比較長的 linked list 至少要是短的 2 倍長。假設有兩個 2^k^ 大小的 linked list , merge sort 不會選擇直接合併,而是會等到有第 3 個長度為 2^k^ 的 linked list 才會開始合併,變成 2^k+1^ 及 2^k^ 兩個 linked list ,並符合前面所說的 2:1
如果前述所說的這 3 個 linked list ,都可以被放進 cache 裡,則可以避免 [Cache thrashing](https://en.wikipedia.org/wiki/Thrashing_(computer_science)) 的問題,即不會發生 cache miss
```
* The merging is controlled by "count", the number of elements in the
* pending lists. This is beautifully simple code, but rather subtle.
*
* Each time we increment "count", we set one bit (bit k) and clear
* bits k-1 .. 0. Each time this happens (except the very first time
* for each bit, when count increments to 2^k), we merge two lists of
* size 2^k into one list of size 2^(k+1).
*
* This merge happens exactly when the count reaches an odd multiple of
* 2^k, which is when we have 2^k elements pending in smaller lists,
* so it's safe to merge away two lists of size 2^k.
*
* After this happens twice, we have created two lists of size 2^(k+1),
* which will be merged into a list of size 2^(k+2) before we create
* a third list of size 2^(k+1), so there are never more than two pending.
*
```
由上述的註解可以歸納出以下幾點:
1. 變數 `count` 用來計算在 pending list 的 `element` 總數
2. 當 `count` 加 `1` 後,會將第 `k` 個位元設為 `1` ,而 `k-1` 到 `0` 位元,則會被清為 `0`
3. merge 會在 `count` 達到 2^k^ 的奇數倍時發生,且 merge 發生兩次後會產生 2 個 2^k+1^ 大小的 linked list
```
* The number of pending lists of size 2^k is determined by the
* state of bit k of "count" plus two extra pieces of information:
*
* - The state of bit k-1 (when k == 0, consider bit -1 always set), and
* - Whether the higher-order bits are zero or non-zero (i.e.
* is count >= 2^(k+1)).
*
* There are six states we distinguish. "x" represents some arbitrary
* bits, and "y" represents some arbitrary non-zero bits:
* 0: 00x: 0 pending of size 2^k; x pending of sizes < 2^k
* 1: 01x: 0 pending of size 2^k; 2^(k-1) + x pending of sizes < 2^k
* 2: x10x: 0 pending of size 2^k; 2^k + x pending of sizes < 2^k
* 3: x11x: 1 pending of size 2^k; 2^(k-1) + x pending of sizes < 2^k
* 4: y00x: 1 pending of size 2^k; 2^k + x pending of sizes < 2^k
* 5: y01x: 2 pending of size 2^k; 2^(k-1) + x pending of sizes < 2^k
* (merge and loop back to state 2)
```
本來想先從註解開始理解,但上方的註解實在是太難懂了..,因此決定從程式碼一起理解
首先可以看到 Linux kernel 在排序之前將 linked list 環狀的部份切開
```c
/* Convert to a null-terminated singly-linked list. */
head->prev->next = NULL;
```
接著開始尋找變數 `count` 位於最低位元的 clear bit 位置
```c
/* Find the least-significant clear bit in count */
for (bits = count; bits & 1; bits >>= 1)
tail = &(*tail)->prev;
```
再來出現了一個巨集函式 `likely()` ,可以在 [/include/linux/compiler.h](https://github.com/torvalds/linux/blob/master/include/linux/compiler.h) 找到定義
```c
if (likely(bits)) {
```
發現一共有兩種定義
```c
//第一種
# ifndef likely
# define likely(x) (__branch_check__(x, 1, __builtin_constant_p(x)))
# endif
//第二種
# define likely(x) __builtin_expect(!!(x), 1)
```
分析整個標頭檔的邏輯後,可以發現要選擇第一種還是第二種的 `likely()` 是由 `CONFIG_TRACE_BRANCH_PROFILING` 、 `DISABLE_BRANCH_PROFILING` 及 `__CHECKER__` 作決定 (由以下原始碼得知)
```c
#if defined(CONFIG_TRACE_BRANCH_PROFILING) \
&& !defined(DISABLE_BRANCH_PROFILING) && !defined(__CHECKER__)
```
首先分析第一種的定義,出現了幾個沒看過得巨集函式 `__branch_check__()` 、`__builtin_constant_p()` ,繼續往下尋找
可以在 [/include/linux/compiler.h](https://github.com/torvalds/linux/blob/master/include/linux/compiler.h) 找到 `__branch_check__()` 的定義
```c
#define __branch_check__(x, expect, is_constant) ({ \
long ______r; \
static struct ftrace_likely_data \
__aligned(4) \
__section("_ftrace_annotated_branch") \
______f = { \
.data.func = __func__, \
.data.file = __FILE__, \
.data.line = __LINE__, \
}; \
______r = __builtin_expect(!!(x), expect); \
ftrace_likely_update(&______f, ______r, \
expect, is_constant); \
______r; \
})
```
嗯...有點難懂,不過慢慢查後還是可以得知一些訊息,首先是 `__aligned` 及 `__section` ,可以在 [`include/linux/compiler_attributes.h`](https://github.com/torvalds/linux/blob/master/include/linux/compiler_attributes.h) 找到定義
```c
#define __aligned(x) __attribute__((__aligned__(x)))
#define __section(section) __attribute__((__section__(section)))
```
再來是 `__func__` 、 `__FILE__` 及 `__LINE__` 都是 gcc 的預設巨集,從 `__func__` 可以參考 [Function Names as Strings](https://gcc.gnu.org/onlinedocs/gcc/Function-Names.html) , `__LINE++` 及 `__FILE__` 可以參考 [Standard Predefined Macros](https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html)
- `__FILE__`
> This macro expands to the name of the current input file, in the form of a C string constant. This is the path by which the preprocessor opened the file, not the short name specified in ‘#include’ or as the input file name argument. For example, "/usr/local/include/myheader.h" is a possible expansion of this macro.
- `__LINE__`
> This macro expands to the current input line number, in the form of a decimal integer constant. While we call it a predefined macro, it’s a pretty strange macro, since its “definition” changes with each new line of source code.
接著是很重要的 `__builtin_expect()` ,這個巨集也是 gcc 的預設巨集,參考[Other Built-in Functions Provided by GCC](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)可以更清楚的知道 `__builtin_expect()` 的意義
- `__builtin_expect()`
> You may use __builtin_expect to provide the compiler with branch prediction information. In general, you should prefer to use actual profile feedback for this (-fprofile-arcs), as programmers are notoriously bad at predicting how their programs actually perform. However, there are applications in which this data is hard to collect.
最後是函式 `ftrace_likely_update()` ,可以在 [`kernel/trace/trace_branch.c`](https://github.com/torvalds/linux/blob/master/kernel/trace/trace_branch.c) 找到該函式的實作,至於程式碼的邏輯及目的目前還沒有很清楚 ==(先做標記)==
:::spoiler ftrace_likely_update()
```c
void ftrace_likely_update(struct ftrace_likely_data *f, int val,
int expect, int is_constant)
{
unsigned long flags = user_access_save();
/* A constant is always correct */
if (is_constant) {
f->constant++;
val = expect;
}
/*
* I would love to have a trace point here instead, but the
* trace point code is so inundated with unlikely and likely
* conditions that the recursive nightmare that exists is too
* much to try to get working. At least for now.
*/
trace_likely_condition(f, val, expect);
/* FIXME: Make this atomic! */
if (val == expect)
f->data.correct++;
else
f->data.incorrect++;
user_access_restore(flags);
}
EXPORT_SYMBOL(ftrace_likely_update);
```
:::
接著換第二種情況,第二種就單純很多,只有呼叫巨集函式 `__builtin_expect()` ,跟第一種實作的方式一樣
從以上的分析可以得到 `likely()` 主要的功能
:::success
1. `__builtin_expect(!!(x), 1)` 會告訴編譯器 `!!(x)` 為 `1` 的機率很高,因此編譯器可以根據這樣的訊息對程式碼做優化,加速執行速度
2. 換句話說 `!!(x)` 為 `1` , 表示 `x` 為非 `0` 的數字
3. 根據原始碼的邏輯,我們可以得知,如果找到了 `bits` 的最低位元的 clear bit 後, `bits` 仍不為 `0` ,則會進行 merge 的動作
:::
回到 `list_sort()` 原始碼,這邊則是依據前面提到的 `likely()` 決定是否要做 merge 的動作,而 merge 是利用 `a` 和 `b` 合併兩個 linked list 後面會有流程圖做更詳細的解釋
```c
if (likely(bits)) {
struct list_head *a = *tail, *b = a->prev;
a = merge(priv, cmp, b, a);
/* Install the merged result in place of the inputs */
a->prev = b->prev;
*tail = a;
}
```
接著在每一次的迭代都會增加一個節點到 pending list 裡,原始碼如下所示
```c
/* Move one element from input list to pending */
list->prev = pending;
pending = list;
list = list->next;
pending->next = NULL;
count++;
```
可以將上述的過程簡單的做個流程圖,每張圖表示 `do while` 一開始的時候
1. `count = 0`
初始狀態如下圖所示,這邊假設一共有 5 個節點,其中 `node1` 為 `head`
```graphviz
digraph list {
rankdir = LR;
node[shape = "record"]
node1[label = "<m>node1|{<p>prev|<n>next}"]
node2[label = "<m>node2|{<p>prev|<n>next}"]
node3[label = "<m>node3|{<p>prev|<n>next}"]
node4[label = "<m>node4|{<p>prev|<n>next}"]
node5[label = "<m>node5|{<p>prev|<n>next}"]
head[shape = plaintext, label = "head"]
pending[shape = plaintext, label = "pending"]
tail[shape = plaintext, label = "tail"]
list[shape = plaintext, label = "list"]
NULL[shape = plaintext, label = "NULL"]
node1:n -> node2:m
node2:n -> node3:m
node3:n -> node4:m
node4:n -> node5:m
node5:n -> NULL
head -> node1:m
tail -> pending -> NULL
list -> node2:m
}
```
2. `count = 1`
經過第一次的迭代,不會產生合併,只會將第二個節點 (`node2`) 加到 pending list ,如下圖表示, `node2` 已經被加到 pending list
```graphviz
digraph list {
rankdir = LR;
node[shape = "record"]
node1[label = "<m>node1|{<p>prev|<n>next}"]
node2[label = "<m>node2|{<p>prev|<n>next}"]
node3[label = "<m>node3|{<p>prev|<n>next}"]
node4[label = "<m>node4|{<p>prev|<n>next}"]
node5[label = "<m>node5|{<p>prev|<n>next}"]
head[shape = plaintext, label = "head"]
pending[shape = plaintext, label = "pending"]
tail[shape = plaintext, label = "tail"]
list[shape = plaintext, label = "list"]
NULL1[shape = plaintext, label = "NULL"]
NULL2[shape = plaintext, label = "NULL"]
NULL3[shape = plaintext, label = "NULL"]
node1:n -> node2:m
node2:n -> NULL3 [color=red]
node3:n -> node4:m
node4:n -> node5:m
node5:n -> NULL1
node2:p -> NULL2 [color=red]
node3:p -> node2:m
head -> node1:m
tail -> pending
pending -> node2:m [color=red]
list -> node3:m [color=red]
}
```
進入下列原始碼,由於 `count = 1` ,因此會進入一次迴圈,使 `tail` 指到 `node2` 的 `prev` ,如下圖所示
```c
/* Find the least-significant clear bit in count */
for (bits = count; bits & 1; bits >>= 1)
tail = &(*tail)->prev;
```
```graphviz
digraph list {
rankdir = LR;
node[shape = "record"]
node1[label = "<m>node1|{<p>prev|<n>next}"]
node2[label = "<m>node2|{<p>prev|<n>next}"]
node3[label = "<m>node3|{<p>prev|<n>next}"]
node4[label = "<m>node4|{<p>prev|<n>next}"]
node5[label = "<m>node5|{<p>prev|<n>next}"]
head[shape = plaintext, label = "head"]
pending[shape = plaintext, label = "pending"]
tail[shape = plaintext, label = "tail"]
list[shape = plaintext, label = "list"]
NULL1[shape = plaintext, label = "NULL"]
NULL2[shape = plaintext, label = "NULL"]
NULL3[shape = plaintext, label = "NULL"]
node1:n -> node2:m
node2:n -> NULL3
node3:n -> node4:m
node4:n -> node5:m
node5:n -> NULL1
node2:p -> NULL2
node3:p -> node2:m
head -> node1:m
tail -> node2:p [color=red]
pending -> node2:m
list -> node3:m
}
```
3. `count = 2`
經過 2 次的迭代,此時 pending list 會有兩個節點,如下圖所示, `node3` 已被加進 pending list
```graphviz
digraph list {
rankdir = LR;
node[shape = "record"]
node1[label = "<m>node1|{<p>prev|<n>next}"]
node2[label = "<m>node2|{<p>prev|<n>next}"]
node3[label = "<m>node3|{<p>prev|<n>next}"]
node4[label = "<m>node4|{<p>prev|<n>next}"]
node5[label = "<m>node5|{<p>prev|<n>next}"]
head[shape = plaintext, label = "head"]
pending[shape = plaintext, label = "pending"]
tail[shape = plaintext, label = "tail"]
list[shape = plaintext, label = "list"]
NULL1[shape = plaintext, label = "NULL"]
NULL2[shape = plaintext, label = "NULL"]
NULL3[shape = plaintext, label = "NULL"]
NULL4[shape = plaintext, label = "NULL"]
node1:n -> node2:m
node2:n -> NULL3
node3:n -> NULL4 [color=red]
node4:n -> node5:m
node5:n -> NULL1
node2:p -> NULL2
node3:p -> node2:m [color=red]
node4:p -> node3:m
head -> node1:m
tail -> node2:p
pending -> node3:m [color=red]
list -> node4:m [color=red]
}
```
接著由於一開始下列程式碼的影響,將原本指到 `node2->prev` 的 `tail` 改指回 `pending` 的位置,如下圖
```c
struct list_head **tail = &pending;
```
```graphviz
digraph list {
rankdir = LR;
node[shape = "record"]
node1[label = "<m>node1|{<p>prev|<n>next}"]
node2[label = "<m>node2|{<p>prev|<n>next}"]
node3[label = "<m>node3|{<p>prev|<n>next}"]
node4[label = "<m>node4|{<p>prev|<n>next}"]
node5[label = "<m>node5|{<p>prev|<n>next}"]
head[shape = plaintext, label = "head"]
pending[shape = plaintext, label = "pending"]
tail[shape = plaintext, label = "tail"]
list[shape = plaintext, label = "list"]
NULL1[shape = plaintext, label = "NULL"]
NULL2[shape = plaintext, label = "NULL"]
NULL3[shape = plaintext, label = "NULL"]
NULL4[shape = plaintext, label = "NULL"]
node1:n -> node2:m
node2:n -> NULL3
node3:n -> NULL4
node4:n -> node5:m
node5:n -> NULL1
node2:p -> NULL2
node3:p -> node2:m
node4:p -> node3:m
head -> node1:m
tail -> pending [color=red]
pending -> node3:m
list -> node4:m
}
```
一樣執行上述的迴圈,出迴圈後 `bits` 為 `2` ,這時接著執行 `if (likely(bits))` ,由於 `bits` 已經不為 `0` ,因此會執行 merge 的動作 (`a` 指向 `node3` ,且 `b` 指向 `node2`),下圖為合併結束的樣子 (假設位置不變), `node2` 已經接回 `node3`
```graphviz
digraph list {
rankdir = LR;
node[shape = "record"]
node1[label = "<m>node1|{<p>prev|<n>next}"]
node2[label = "<m>node2|{<p>prev|<n>next}"]
node3[label = "<m>node3|{<p>prev|<n>next}"]
node4[label = "<m>node4|{<p>prev|<n>next}"]
node5[label = "<m>node5|{<p>prev|<n>next}"]
head[shape = plaintext, label = "head"]
pending[shape = plaintext, label = "pending"]
tail[shape = plaintext, label = "tail"]
list[shape = plaintext, label = "list"]
NULL1[shape = plaintext, label = "NULL"]
NULL2[shape = plaintext, label = "NULL"]
NULL3[shape = plaintext, label = "NULL"]
node1:n -> node2:m
node2:n -> node3:m [color=red]
node3:n -> NULL3
node4:n -> node5:m
node5:n -> NULL1
node2:p -> NULL2
node3:p -> node2:m
node4:p -> node3:m
head -> node1:m
tail -> pending
pending -> node3:m
list -> node4:m
}
```
4. `count = 3`
經過了兩次加入 pending 及一次的 merge ,形成以下的圖,目前的 pending list 為 [2(node2,node3),1(node4)]
```graphviz
digraph list {
rankdir = LR;
node[shape = "record"]
node1[label = "<m>node1|{<p>prev|<n>next}"]
node2[label = "<m>node2|{<p>prev|<n>next}"]
node3[label = "<m>node3|{<p>prev|<n>next}"]
node4[label = "<m>node4|{<p>prev|<n>next}"]
node5[label = "<m>node5|{<p>prev|<n>next}"]
head[shape = plaintext, label = "head"]
pending[shape = plaintext, label = "pending"]
tail[shape = plaintext, label = "tail"]
list[shape = plaintext, label = "list"]
NULL1[shape = plaintext, label = "NULL"]
NULL2[shape = plaintext, label = "NULL"]
NULL3[shape = plaintext, label = "NULL"]
NULL4[shape = plaintext, label = "NULL"]
node1:n -> node2:m
node2:n -> node3:m
node3:n -> NULL3
node4:n -> NULL4 [color=red]
node5:n -> NULL1
node2:p -> NULL2
node3:p -> node2:m
node4:p -> node3:m [color=red]
node5:p -> node4:m
head -> node1:m
tail -> pending
pending -> node4:m [color=red]
list -> node5:m [color=red]
}
```
5. `count = 4`
在 `count = 3` 的最後將 `list` 移到了 `NULL` 的位置,因此此時已經離開迴圈,下圖為離開迴圈前的最後狀態
```graphviz
digraph list {
rankdir = LR;
node[shape = "record"]
node1[label = "<m>node1|{<p>prev|<n>next}"]
node2[label = "<m>node2|{<p>prev|<n>next}"]
node3[label = "<m>node3|{<p>prev|<n>next}"]
node4[label = "<m>node4|{<p>prev|<n>next}"]
node5[label = "<m>node5|{<p>prev|<n>next}"]
head[shape = plaintext, label = "head"]
pending[shape = plaintext, label = "pending"]
tail[shape = plaintext, label = "tail"]
list[shape = plaintext, label = "list"]
NULL1[shape = plaintext, label = "NULL"]
NULL2[shape = plaintext, label = "NULL"]
NULL3[shape = plaintext, label = "NULL"]
NULL4[shape = plaintext, label = "NULL"]
node1:n -> node2:m
node2:n -> node3:m
node3:n -> NULL3
node4:n -> NULL4
node5:n -> NULL1 [color=red]
node2:p -> NULL2
node3:p -> node2:m
node4:p -> node3:m
node5:p -> node4:m [color=red]
head -> node1:m
tail -> pending
pending -> node5:m [color=red]
list -> NULL1 [color=red]
}
```
由上述的流程圖可以得知所有的節點已經被加到 pending list ,接著的步驟就是將所有的 pending list 合併在一起,原始碼如下
```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, pending, list);
pending = next;
}
```
最後一個步驟也就是把已經斷掉的 `prev` 重新接回去
```c
/* The final merge, rebuilding prev links */
merge_final(priv, cmp, head, pending, list);
```
最後發現了一個巨集函式 `EXPORT_SYMBOL()` ,參考[Linux驅動開發——EXPORT_SYMBOL的使用](http://www.unixlinux.online/unixlinux/linuxjc/linuxjs/201703/91672.html),可以得知 `EXPORT_SYMBOL()` 的目的及使用方法
為了更清楚的說明了整個 linked list 合併的狀態,做出以下的表格
| state | merge | count | pending list 的狀態 (在迴圈一開始的地方) | pending list 的狀態 (在迴圈結束的地方) |
| ----- | ----- | ---------- | --------- | ------------|
| 0 | X | 0b0000(0) | NULL | [1] |
| 0 | X | 0b0001(1) | [1] | [1,1] |
| 1 | O | 0b0010(2) | [1,1] | [2,1] |
| 1 | X | 0b0011(3) | [2,1] | [2,1,1] |
| 2 | O | 0b0100(4) | [2,1,1] | [2,2,1] |
| 2 | O | 0b0101(5) | [2,2,1] | [4,1,1] |
| 3 | O | 0b0110(6) | [4,1,1] | [4,2,1] |
| 3 | X | 0b0111(7) | [4,2,1] | [4,2,1,1] |
| 4 | O | 0b1000(8) | [4,2,1,1] | [4,2,2,1] |
| 4 | O | 0b1001(9) | [4,2,2,1] | [4,4,1,1] |
| 5 | O | 0b1010(10) | [4,4,1,1] | [4,4,2,1] |
| 5 | O | 0b1011(11) | [4,4,2,1] | [8,2,1,1] |
| 2 | O. | 0b1100(12) | [8,2,1,1] | [8,2,2,1] |
| 2 | O | 0b1101(13) | [8,2,2,1] | [8,4,1,1] |
| 3 | O | 0b1110(14) | [8,4,1,1] | [8,4,2,1] |
| 3 | X | 0b1111(15) | [8,4,2,1] | [8,4,2,1,1] |
## merge()
這邊先附上整個 `merge()` 的程式碼
:::spoiler `merge()`
```c
__attribute__((nonnull(2,3,4)))
static struct list_head *merge(void *priv, list_cmp_func_t 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;
}
```
:::
`merge()` 的實作就比 `list_sort()` 單純很多,首先建立一個指向 `struct list_head` 的指標 `head` ,接著建立一個**指標的指標** `tail` ,指向 `head`
```c
struct list_head *head, **tail = &head;
```
接著進入無限迴圈,使用 `cmp` 來決定下一個節點要從 `a` 還是 `b` 取得
→ `cmp` 回傳 ≤ 時選擇 `a` ,回傳 > 時則選擇 `b`
```c
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;
}
}
```
當 linked list `a` 已經沒有節點時,直接接上 linked list `b` ,反之則接上 linked list `a`
```c
if (!a) {
*tail = b;
break;
}
if (!b) {
*tail = a;
break;
}
```
## merge_final()
這邊附上整個 `merge_final()` 的程式碼
:::spoiler `merge_final()`
```c
__attribute__((nonnull(2,3,4,5)))
static void merge_final(void *priv, list_cmp_func_t cmp, struct list_head *head,
struct list_head *a, struct list_head *b)
{
struct list_head *tail = head;
u8 count = 0;
for (;;) {
/* if equal, take 'a' -- important for sort stability */
if (cmp(priv, a, b) <= 0) {
tail->next = a;
a->prev = tail;
tail = a;
a = a->next;
if (!a)
break;
} else {
tail->next = b;
b->prev = tail;
tail = b;
b = b->next;
if (!b) {
b = a;
break;
}
}
}
/* Finish linking remainder of list b on to tail */
tail->next = b;
do {
/*
* If the merge is highly unbalanced (e.g. the input is
* already sorted), this loop may run many iterations.
* Continue callbacks to the client even though no
* element comparison is needed, so the client's cmp()
* routine can invoke cond_resched() periodically.
*/
if (unlikely(!++count))
cmp(priv, b, b);
b->prev = tail;
tail = b;
b = b->next;
} while (b);
/* And the final links to make a circular doubly-linked list */
tail->next = head;
head->prev = tail;
}
```
:::
將所有的 `prev` 接回前一個節點
```c
/* Finish linking remainder of list b on to tail */
tail->next = b;
do {
/*
* If the merge is highly unbalanced (e.g. the input is
* already sorted), this loop may run many iterations.
* Continue callbacks to the client even though no
* element comparison is needed, so the client's cmp()
* routine can invoke cond_resched() periodically.
*/
if (unlikely(!++count))
cmp(priv, b, b);
b->prev = tail;
tail = b;
b = b->next;
} while (b);
/* And the final links to make a circular doubly-linked list */
tail->next = head;
head->prev = tail;
```
## 參考資料
[list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c)
[研讀 Linux 核心的 lib/list_sort.c 原始程式碼](https://hackmd.io/@RinHizakura/HkEuhNwGO#list_sort)
[Standard Predefined Macros](https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html)
[Function Names as Strings](https://gcc.gnu.org/onlinedocs/gcc/Function-Names.html)
[Other Built-in Functions Provided by GCC](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)