--- title: 2023 年 Linux 核心設計/實作課程作業 —— fibdrv image: https://i.imgur.com/KXUuZLV.png description: 引導學員開發 Linux 核心模組,預期知悉核心模組如何掛載進入 Linux 核心、Virtual File System (VFS) 概況、character device driver 撰寫,和準備對應的測試及效能分析工具 tags: linux2023 --- # L04: fibdrv :::warning :warning: 請務必詳閱作業描述 [(一)](/@sysprog/linux2023-fibdrv-a), [(二)](/@sysprog/linux2023-fibdrv-b), [(三)](/@sysprog/linux2023-fibdrv-c), [(四)](/@sysprog/linux2023-fibdrv-d), [(五)](/@sysprog/linux2023-fibdrv-e) 及 [(六)](/@sysprog/linux2023-fibdrv-f) ::: > 主講人: [jserv](http://wiki.csie.ncku.edu.tw/User/jserv) / 課程討論區: [2023 年系統軟體課程](https://www.facebook.com/groups/system.software2023/) :mega: 返回「[Linux 核心設計/實作](http://wiki.csie.ncku.edu.tw/linux/schedule)」課程進度表 ==[作業說明錄影](https://youtu.be/IfsldrCuX28)== / ==[Code Review 錄影](https://youtu.be/Fo-3MtrXr3E)== > :warning: 2023 年已變更作業要求 ## :memo: 預期目標 * 撰寫適用於 Linux 核心層級的程式 * 學習 `ktimer`, `copy_to_user` 一類的核心 API * 複習 C 語言 [數值系統](https://hackmd.io/@sysprog/c-numerics), [bitwise 操作](https://hackmd.io/@sysprog/c-bitwise), [List API](https://hackmd.io/@sysprog/c-linked-list), [alignment](https://hackmd.io/@sysprog/c-memory), [遞迴呼叫](https://hackmd.io/@sysprog/c-recursion) * 運算成本分析及其改進策略 * 初探 Linux VFS * 透過工具進行效能分析 * 初探[並行和多執行緒程式設計](https://hackmd.io/@sysprog/concurrency) ## :rocket: 費氏數列 參照以下材料理解 Fibonacci 數列和對應的計算手法: * [費氏數列分析](https://hackmd.io/@sysprog/fibonacci-number) * 「什麼是費氏數列」短片: [Part I](https://youtu.be/JWGCrICTars), [Part II](https://youtu.be/TA0Dpx0LOeY), [Part III](https://youtu.be/WyDn6wiwW74), [Part IV](https://youtu.be/iCSNHH45EeI) * TED: [The magic of Fibonacci numbers](https://youtu.be/SjSHVDfXHQ4) (有繁體中文字幕) 依據 [Fibonacci and Golden Ratio Formulae](http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibFormulae.html),考慮 Fibonacci 最初提到不會死亡的兔子繁衍總量問題,假設成年兔子為 `a` ,幼年兔子為 `b` ,我們可得到以下關係: $$ a_{n+1} = a_n + b_n \\ b_{n+1} = a_n $$ 將上述推導寫成矩陣的形式就會變成 $$ \begin{pmatrix} a_{n+1} \\ b_{n+1} \end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} \begin{pmatrix} a_n \\ b_n \end{pmatrix} $$ 則 $\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}$ 就是所謂的 ==Q-matrix==,可進一步改寫為: $$ Q = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} = \begin{pmatrix} F_2 & F_1 \\ F_1 & F_0 \end{pmatrix} \\ Q^n = \begin{pmatrix} F_{n+1} & F_n \\ F_n & F_{n-1} \end{pmatrix} $$ 解說短片: [The Fibonacci Q-matrix](https://youtu.be/lTHVwsHJrG0) 接著談及 [Fast Doubling](https://chunminchang.github.io/blog/post/calculating-fibonacci-numbers-by-fast-doubling) 手法,繼續整理: $$ \begin{split} \begin{bmatrix} F(2n+1) \\ F(2n) \end{bmatrix} &= \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}^{2n} \begin{bmatrix} F(1) \\ F(0) \end{bmatrix}\\ \\ &= \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}^n \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}^n \begin{bmatrix} F(1) \\ F(0) \end{bmatrix}\\ \\ &= \begin{bmatrix} F(n+1) & F(n) \\ F(n) & F(n-1) \end{bmatrix} \begin{bmatrix} F(n+1) & F(n) \\ F(n) & F(n-1) \end{bmatrix} \begin{bmatrix} 1 \\ 0 \end{bmatrix}\\ \\ &= \begin{bmatrix} F(n+1)^2 + F(n)^2\\ F(n)F(n+1) + F(n-1)F(n) \end{bmatrix} \end{split} $$ 因此可得: $$ \begin{split} F(2k) &= F(k)[2F(k+1) - F(k)] \\ F(2k+1) &= F(k+1)^2+F(k)^2 \end{split} $$ 根據 Fibonacci 數列原始定義 `F(0) = 0, F(1) = 1, F(2) = 1`我們就可用這三個值,依據上述公式推導出隨後的數值。 對應的虛擬碼如下: ```python Fast_Fib(n) a = 0; b = 1; // m = 0 for i = (number of binary digit in n) to 1 t1 = a*(2*b - a); t2 = b^2 + a^2; a = t1; b = t2; // m *= 2 if (current binary digit == 1) t1 = a + b; // m++ a = b; b = t1; return a; ``` 以 $F(6)$ 為例: * 第 1 次遞迴 ```graphviz strict digraph G { 1[label="F(6)"] 2[label="F(4)"] 3[label="F(5)"] 4[label="F(2)"] 5[label="F(3)"] 6[label="F(3)"] 7[label="F(4)"] 8[label="F(0)", style=filled] 9[label="F(1)", style=filled] 10[label="F(1)", style=filled] 11[label="F(2)"] 12[label="F(1)", style=filled] 13[label="F(2)"] 14[label="F(2)"] 15[label="F(3)"] 16[label="F(0)", style=filled] 17[label="F(1)", style=filled] 18[label="F(0)", style=filled] 19[label="F(1)", style=filled] 20[label="F(0)", style=filled] 21[label="F(1)", style=filled] 22[label="F(1)", style=filled] 23[label="F(2)", style=filled] 24[label="F(0)", style=filled] 25[label="F(1)", style=filled] 1 -> {2, 3} 2 -> {4, 5} 3 -> {6, 7} 4 -> {8, 9} 5 -> {10, 11} 6 -> {12, 13} 7 -> {14, 15} 11 -> {16, 17} 13 -> {18, 19} 14 -> {20, 21} 15 -> {22, 23} 23 -> {24, 25} } ``` * 第 2 次遞迴 ```graphviz strict digraph G { 1[label="F(6)"] 2[label="F(3)"] 3[label="F(4)"] 4[label="F(1)", style=filled] 5[label="F(2)", style=filled] 6[label="F(2)", style=filled] 7[label="F(3)"] 8[label="F(1)", style=filled] 9[label="F(2)", style=filled] 1 -> {2, 3} 2 -> {4, 5} 3 -> {6, 7} 7 -> {8, 9} } ``` 可見遞迴次數縮減,還能再更快嗎?再觀察到 $F(6)$ 被分成 $F(3)$ 和 $F(4)$ 兩個部分,其中 $F(4) = F(2)+F(3)$ ,可以利用 $F(3)$ 和遞迴 $F(3)$ 時所得到的 $F(2)$ 去計算 $F(4)$,這樣可以再次降低運算的次數,如下: * 第 2 次遞迴 ```graphviz strict digraph G { 1[label="F(6)"] 2[label="F(3)"] 3[label="F(4)"] 4[label="F(1)", style=filled] 5[label="F(2)", style=filled] 6[label=" " ,color=white] 7[label=" " ,color=white] {rank = same; 2;3;} {rank = same; 4;5;} 1 -> {2, 3} 2 -> {4, 5} 2 -> 3 [style=dashed; arrowhead=vee] 5 -> 3 [style=dashed; arrowhead=vee] 3 -> {6, 7} [color=white] } ``` 和最初的 Fibonacci 數列定義相比,可見相當大的差距。 示範案例: 求解 $F(10)$: 10~10~ = 1010~2~ | i | start | 4 | 3 | 2 | 1 | result | |---|-------|----------|----------|----------|---------|--------| | n | - | ==1==010 | 1==0==10 | 10==1==0 | 101==0== | - | |F(m) | F(0) | F(0*2+1) | F(1*2) | F(2*2+1) | F(5*2) | F(10) | | a | 0 | 1 | 1 | 5 | 55 | 55 | | b | 1 | 1 | 2 | 8 | 89 | - | 對照 $F(11)$ 11~10~ = 1011~2~ | | 1 | 0 |1|1| result | -------- | -------- | -------- |-------- |-------- |-------- | | F(n)| F(0*2+1)| F(1*2) |F(2*2+1)|F(5*2+1) |F(11) | a |1 |1|5|89|89 | b |1|2|8|144 ### 考慮到硬體加速 $F(n)$ 的手法 許多現代處理器提供 [clz / ctz](https://en.wikipedia.org/wiki/Find_first_set) 一類的指令,可搭配上述 [Fast Doubling](https://chunminchang.github.io/blog/post/calculating-fibonacci-numbers-by-fast-doubling) 手法運用: 1. 省略 $F(0)$,直接從 $F(1)$ 開始; 2. clz/[ffs](https://www.kernel.org/doc/htmldocs/kernel-api/API-ffs.html): 先去除數字 MSB 起算的開頭 0 位元,因為真正的數字不包含 leading 0s,所以計算它也沒用,因此需要 [clz](https://en.wikipedia.org/wiki/Find_first_set#CLZ) 計算有多少 leading 0s * 遇到 `0` $\rightarrow$ 進行 fast doubling,也就是求 $F(2n)$ 和 $F(2n+1)$ * 遇到 `1` $\rightarrow$ 進行 fast doubling,也就是先求 $F(2n)$ 和 $F(2n+1)$,再求 $F(2n+2)$ 可對照 [你所不知道的 C 語言: 遞迴呼叫篇](https://hackmd.io/@sysprog/c-recursion)。 ### Fibonacci 數的應用 電腦在計算亂數時,常以新產生出來的數值作為下一次計算的依據,這就是為什麼計算機隨機數大部分都會表示成遞迴定義的數列。 :::info 這裡只探討 [Pseudo-Random Number Generators](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) (PRNG) ::: > 這是一般的 Fibonacci 數列 ```cpp f[0] = 0; f[1] = 1; f[i] = f[i - 1] + f[i - 2]; ``` >數列呈現單調遞增 ``` 1, 1, 2, 3, 5, 8, 13, 21, ... ``` > 如果我們強迫 Fibonacci 數列的數值超出 100 之後折回來 ```cpp f[0] = 0; f[1] = 1; f[i] = (f[i - 1] + f[i - 2]) % 100; ``` > 一開始雖然還是看得到規則,不過整體趨勢已經不再是單調遞增,當數值折回之後規律變得不太明顯 ```cpp 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 44, 33, 77, 10, 87, 97, 84, 81, 65, 46, 11, 57, 68, 25, 93, 18, 11, 29, 40, ... ``` > 甚至可將 Fibonacci 的遞迴數列改成: ```cpp f[0] = 18; f[1] = 83; f[2] = 4; f[3] = 42; f[4] = 71; f[i] = (f[i - 2] + f[i - 5]) % 100; ``` > 這個數列的規則已不容易看穿 ```cpp 18, 83, 4, 42, 71, 60, 54, 64, 96, 35, 56, 89, 20, 85, 55, 41, 44, 61, 29, 16, 70, 60, 31, 89, 47, 59, 7, 90, 96, 37, ... ``` 這種產生隨機數的方法,稱為 [Lagged Fibonacci generator](https://en.wikipedia.org/wiki/Lagged_Fibonacci_generator) (LFG),是電腦產生隨機數的一種方法。 〈[Linux 核心的 hash table 實作](https://hackmd.io/@sysprog/linux-hashtable)〉提到,Linux 核心採用的雜湊函數稱為 Fibonacci hashing,其精神也依循上述,從而降低碰撞。 延伸閱讀: * [For The Love of Computing: The Lagged Fibonacci Generator — Where Nature Meet Random Numbers](https://medium.com/asecuritysite-when-bob-met-alice/for-the-love-of-computing-the-lagged-fibonacci-generator-where-nature-meet-random-numbers-f9fb5bd6c237) * [3blue1brown 的影片](https://youtu.be/bOXCLR3Wric?t=603)提到費氏數列也可寫成 Generating Function