contributed by < ranvd
>
以下內容為 Linux 核心版本 5.15,其他版本的檔案位置與內容可以有些出入
在過去的教材中有提到 LKM 在 Linux kernel 中是透過鍊結串列維護。在 include/linux/module.h 中可以看到 module 結構體,其中可以看到有一項熟悉的結構體 struct list_head
,根據註解可以猜測 Linux 核心中的 modules 是透過鍊結串列的方式儲存,同時也使用 enum 型態紀錄目前 module 的狀態。
為了驗證 Linux Module 的載入方式如上述提到的鍊結串列,首先透過 strace 追蹤 insmod 命令在載入 LKM 時使用了哪些系統呼叫。從下面 strace insmod
的輸出結果可以看到,在第 1 行時將 hello.ko 打開,此時打開的 fd = 3,接著在第 6 行可以看到呼叫了 finit_module
這個系統呼叫。
在 kernel/module.c 中可以看到以下的程式碼。finit_module
首先是透過 kernel_read_file_from_fd
將整個 Linux 核心模組讀取到 kernel space 後呼叫了 load_module
將 Linux 核心模組載入。
接著透過 load_module
的程式碼可以看到 load_module
透過 layout_and_allocate
配置了一個 module 的記憶體空間,接著再透過 add_unformed_module
將該核心模組加入鍊結串列中。最後透過 do_init_module
將載入的核心模組初始化。
最後,為了驗證上述的結果,透過第第七周的教材 建構 User-Mode Linux 的實驗環境,使用 GDB 對 UML 的行為進行觀測。首先是設立中斷點在 finit_module
系統呼叫,不過從上面系統呼叫的宣告方式,可以知道 finit_module
這並不是真正系統呼叫函式的名稱,在對著程式碼『舉燭』過後,並結合過去教材提供的連結 How does the Linux kernel handle a system call 得知這些系統呼叫的名稱是透過腳本與 .tbl 在編譯時自動生成的。因此根據 syscall_64.tbl 可以輕易得知正確的系統呼叫名稱為 sys_finit_module
。
接著就進行 debug,從 GDB 的訊息中可以看到每個模組之間確實適用開頭提到的鍊結串列維護。
Heapsort 的方式如下,先透過 bottom-up 的方式建立 heap,接著再透過不斷將根節點與未排序的最後一個節點交換,並維持 heap 的特性。直到所有的節點都排序好即可。複雜度為 。
HEAPSORT(input: a, len: n):
for (i = n/2 to 1):
reheap(n, i)
for (m = n to 2):
interchange a[1] and a[m]
in m != 2 then reheap(m-1, 1)
在 Linux 核心早期的 lib/sort.c (1da177e) 以下稱這種方式為 Standard heapsort。heapify 的方式為由上而下尋找最小的節點,因此每層都會需要 2 次比較。從 22a241c 開始,就採用與現在較為相近的 heapsort 方式 Bottom-up heapsort,由於程式碼將 Heapify 與 Sorting 兩個部分寫在同一個 for 迴圈中,導致我花了一些時間才看懂他的實作如何對照 BOTTOM-UP-HEAPSORT。以下稱作 Bottom-up heapsort。Bottom-up heapsort 與傳統 heapsort 最主要的差別在於減少 reheap 時的比較次數。
Bottom-up heapsort 將原本 reheap 的部份改成 bottom-up-reheap
bottom-up-reheap(m, i):
j = leaf-search(m, i)
j = bottom-up-search(i, j)
interchange(i, j)
首先會由上至下比較左右兩個子節點的大小,選擇較小的方向走。如下演算法,由於只需要比較左右子節點,因此只需要做一次比較,所以 leaf-search 最多只會需要做 d 次比較,d 是樹的深度。
leaf-search(m, i):
j = i
while 2j < m:
if (a[2j] < a[2j+1]):
j = 2j
else:
j = 2j + 1
if (2j == m):
j = m
return j
上述找到的 j 稱作 special leaf,過程中經過的路徑稱作 special path。接著我們要順著這個路徑由下至上尋找 reheap 中的 i 應該要放的正確位置。由於 heap 是 complete binary tree,因此尋找父節點可以透過簡單的 n/2 得到正確的位置。如下演算法。
bottom-up-search(i, j):
while (j AND a[i] < a[j]):
j = j/2
return j
最後,找到正確的位置後,就可以將資料從 j 開始不斷向上做交換直到根節點。
interchange(i, j):
l = log(j/i), x=a[j], a[j]=a[i]
while (j > i):
interchange a[j/2] and x
j = j/2
建立 Heap 時 leaf-search 的比較次數
首先,先考慮建立 heap 時的 leaf-search 比較次數,假設 heap 是 full binary tree。則我們會有 個節點。從 full binary tree 的特性我們可以知道深度 k 的節點會有 個節點。而越靠進葉節點的 inner nodes 需要的比較次是越少,因此可以列出需要比較的次數 介於 。
從上面可以得知在 full binary tree 中需要的總比較次為
接著令 可知 ,接著可知 接著透過等比數列和公式可知 ,將 代回公式可知
接著考慮非 full binary tree 的情況。令 ,其中 代表節點的總數, 代表上一層的 full binary tree。因此 代表多出來的節點個數。由於每多 個節點就會增加 個內部節點。而每個內部節點都須增加 1 個比較次數,因此最多只有可能增加 次比較。
以下提供簡單的思考方式說明為什麼 。由於在多出來的 之上的二元樹狀結構中,最多會有 個內部節點會被更新,接著離開樹狀結構後,需要一路更新內部節點到根節點,最多會有 個節點。因此最差的情況為 。從前述資訊可知道在非 full binary tree 的情況下的總比較次數為
建立 Heap 時 leaf-search 的比較次數
接著,考慮建立 heap 時的 bottom-up-search 比較次數。令 為傳統 Heapsort 須使用的比較次數的一半、 為 Bottom-up-search 所需的比較次數。
當 j <