lib/list_sort.c
研讀筆記list_sort.c
src// SPDX-License-Identifier: GPL-2.0
SPDX(Software Package Data Exchange)是一種標準化的方式,用來標示軟體的授權許可資訊。GPL-2.0
則代表採用 GNU 通用公共授權(GNU General Public License)第 2 版 進行授權。這是一種開源許可證,允許使用者自由地使用、修改和分發代碼,但前提是必須遵守 GPL-2.0 的條款,例如分發修改後的代碼時也必須保持開源並提供原始碼。這行註解清楚地告訴開發者這段代碼的法律授權狀態,避免了冗長的版權聲明文字。
#include <linux/compiler.h>
: 它確保代碼在不同環境下都能正確編譯和運行
#include <linux/export.h>
: 此定義了用於導出符號的巨集,例如 EXPORT_SYMBOL
。在 Linux 核心中,有些函數或變數需要被其他核心模組(module)使用,這時就需要透過這些巨集將它們標記為可導出,如果有函數需要被其他模組調用,就會依賴這個頭檔案提供的功能
list_sort
用於對linux kernel中的doubly linekd list進行排序。排序部分採用的是穩定的 merge sort algorithm,並支援自定義比較邏輯。
實作方面 list_sort
仰賴兩個輔助函式 : merge
和 merge_final
merge
此註解為針對 merge
函式的作用 :
此函式回傳的中間格式架構有以下三特點 :
此是GCC編譯器提供的一個擴展屬性,用於告訴編譯器某些函數參數不應該為 NULL。具體來說,這裡的 (2,3,4) 表示函數的第 2、第 3 和第 4 個參數必須是非空的ptr
引數 :
void *priv
: 為一私有泛型資料指標,目的是讓使用者能設置比較邏輯 cmp
的對象list_cmp_func_t cmp
: 是一個自訂的比較函數,用來決定兩個串列節點 a 和 b 的相對順序
list_cmp_func_t
定義在 <linux/list.h>
中struct list_head *a
: a 是第一個sorted的linked list headstruct list_head *b
: b 是第二個sorted的linked list head邏輯 :
用 *head
作為新的list之head,再用 **tail
根據 a、b list中element內容比較去選擇將該 element 串入新的list(若相等則取a保證stable)。若 a、b 誰先取完,就將另一個list串到新list後面
merge_final
作用:最終合併兩個子列表,並恢復雙向鏈表的 prev
指針,形成循環結構。
src :
邏輯 :
前面部分與 merge
相同,新的部分為多維護一個 count
與後面使用 do-while 迴圈處理剩餘節點,同時監控迴圈次數(count
)以避免過長執行。最後閉合形成循環
unlikely()
是一個巨集(macro),作為編譯器的提示,告訴編譯器這個條件(!++count)
不太可能發生,優化程式執行效率。count
為 u8 ,值為255時再執行 ++count
後,會溢出,count
變為 0cmp(priv, b, b)
為一 no-op ,通常包含一些副作用(side effect),觸發某些系統檢查點。這些副作用給予系統一個機會,讓操作系統的調度器(scheduler)介入,檢查是否需要切換到其他任務。list_sort
:src :
@priv
:私有資料,對 list_sort 來說是不透明的指標,會直接傳遞給比較函數 @cmp。這允許 @cmp 在比較元素時使用額外的上下文資料。@head
:要排序的linked list的頭部,指標指向串列的起始節點。@cmp
:自訂的比較函數,由使用者提供,用來定義兩個節點之間的順序關係。描述比較函數 @cmp 的行為
大於 0:表示 @a 應排在 @b 後面。例如,若要升序排序,則 @a > @b 時返回大於 0。
小於等於 0:表示 @a 應排在 @b 前面,或兩者相等時保持原始順序。list_sort
是一個穩定排序(stable sort),即相等元素的相對順序不會改變。@cmp 總是以輸入串列中較早出現的元素作為 @a,因此無需明確區分 @a < @b 和 @a == @b 的情況,只需返回 <= 0 即可。
描述@cmp所需的數學屬性要求
@cmp的兩種style
strcmp
。block/blk-mq.c
中的 plug_ctx_cmp
就採用此@cmp 多欄位比較範例
假設節點有 high、middle、low 三個欄位,依序比較。
先比較 high,若不同,根據 high 的值決定順序;若相同,再比較 middle,最後比較 low。這種方式實現了字典序(lexicographical order),常用於多條件排序。
說明此list_sort的eager體現在條件允許時盡早合併子串列,以免最後很長一條merge一小條不balanced的case。且實施的準則為 2:1 balanced merges
,當有兩個大小為 2^k 的子串列,且後續有 2^k 個元素時,將這兩個子串列合併成一個大小為 2^(k+1) 的串列,旨在先merge小。
只要快取能容納 32^k 個元素,就能避免cache thrashing,相較於fully-eager bottom-up
mergesort,這種方法減少了約 0.2n 次比較。
描述 count
的角色為表示待處理串列中的元素總數,用來決定何時進行合併。且描述了運作機制 :
位元操作:
每次 count 增加時,設置某個位元(bit k),並清除低於 k 的位元(k-1 至 0)。
合併觸發:
當 count 達到 2^k 的奇數倍(除了第一次達到 2^k),觸發兩個大小為 2^k 的串列合併,生成一個 2^(k+1) 的串列。
描述 count
機制的merge時機 : 當 count
為 2^k 的奇數倍時,表示有 2^k 個元素在較小的待處理串列中,此時可以安全合併兩個 2^k 大小的串列。後續合併時,生成兩個 2^(k+1) 的串列,接著它們會被合併成 2^(k+2),確保待處理串列數量不超過兩個。
描述pending list的狀態,大小為 2^k 的待處理串列數量取決於:
count
的第 k 位狀態。count
>= 2^(k+1))。描述6種states
x
表示任意位元,y
表示非零高位。
0:00x
- 無 2^k 大小的串列,x 個小於 2^k 的串列。
1:01x
- 無 2^k ,2^(k-1) + x 個小於 2^k。
2:x10x
- 無 2^k ,2^k + x 個小於 2^k。
3:x11x
- 1 個 2^k ,2^(k-1) + x 個小於 2^k。
4:y00x
- 1 個 2^k ,2^k + x 個小於 2^k。
5:y01x
- 2 個 2^k, 2^(k-1) + x 個小於 2^k,觸發合併後回到狀態 2。
生成 : 2->3 和 4->5:當 bit k-1 為 1 且高位非零時,生成 2^k 大小的串列(state 5)。
合併 : 5->2:合併兩個 2^k 的串列,生成 2^(k+1),並回到state 2
最終合併:處理完所有輸入後,將所有待處理串列從小到大合併,完成排序。
總結 : list_sort
透過 count
控制合併時機,實現 2:1 balanced merge。它優化了cache使用(需容納 32^k 元素)和比較次數(減少 0.2n 次),特別適合資料能放入 L1 快取的情況。使用者可透過 @cmp 和 @priv 靈活定義排序邏輯
函式流程
*pending
(待合併子列表)為 NULL,count
為 0。count
達到某個 2^k 的奇數倍時,觸發大小為 2^k 的子列表合併)
count
的二進位表示,找到最低位的 0(記為 bits)。pending
的 prev
指針前進 bits
步,定位需要合併的子列表。bits
!= 0,取出 pending
中的兩個子列表 a 和 b,使用 merge 合併,並更新 pending
。list
中取出一個元素,作為大小為 1 的子列表,加入 pending
。count
加 1。pending
開始,逐對合併子列表,使用 mergelist_sort
優點特性