freshLiver
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 2022q1 Homework3 (fibdrv) contributed by < `freshLiver` > ###### tags: `linux2022` `kernel module` `fibonacci` ## 準備工作 ### 測試環境 ```shell $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 39 bits physical, 48 bits virtual CPU(s): 12 On-line CPU(s) list: 0-11 Thread(s) per core: 2 Core(s) per socket: 6 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 167 Model name: 11th Gen Intel(R) Core(TM) i5-11400 @ 2.60GHz Stepping: 1 CPU MHz: 665.263 CPU max MHz: 4400.0000 CPU min MHz: 800.0000 BogoMIPS: 5184.00 Virtualization: VT-x L1d cache: 288 KiB L1i cache: 192 KiB L2 cache: 3 MiB L3 cache: 12 MiB NUMA node0 CPU(s): 0-11 ``` ### Kernel Headers ```json "includePath": [ "${default}", "/lib/modules/5.11.0-49-generic/build/arch/x86/include", "/lib/modules/5.11.0-49-generic/build/arch/x86/include/generated", "/lib/modules/5.11.0-49-generic/build/include", "/lib/modules/5.11.0-49-generic/build/include/generated" ], "defines": [ "MODULE", "__KERNEL__" ] ``` VS Code 相關的設定 ( `.vscode/c_cpp_properties.json` ),可以避免編輯器找不到 Kernel Header 而跳出錯誤。 ## fibdrv 實作 ### 現有缺陷 #### Variable-length Array 如同註解所示,Linux Kernel 中不應該使用 C99 新增的 VLA 來宣告陣列,可以改用大小為 2 的陣列搭配 `swap` 巨集改寫: ```c static long long fib_sequence(long long k) { long long f[2] = {0, 1}; for (int i = 1; i <= k; i++) { swap(f[0], f[1]); f[0] += f[1]; } return f[0]; } ``` 但這樣一樣會因為 64 位元的限制造成 `fib(93)` 後的結果仍是錯誤的。 ### 更快速的 Fibonacci 運算實作 - [x] [費氏數列分析](https://hackmd.io/@sysprog/fibonacci-number) - [ ] [Fibonacci and Golden Ratio Formulae](http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibFormulae.html) - [x] [The Fibonacci Q-matrix](https://youtu.be/lTHVwsHJrG0) - [x] [Fast Doubling](https://chunminchang.github.io/blog/post/calculating-fibonacci-numbers-by-fast-doubling) #### 實作 Fast Doubling 使用 Fast Doubling 的規則加上 Bitwise 的應用進行遞迴版的實作: ```c static inline uint64_t fast_doubling(uint32_t target) { if (target <= 2) return !!target; // fib(2n) = fib(n) * (2 * fib(n+1) − fib(n)) // fib(2n+1) = fib(n) * fib(n) + fib(n+1) * fib(n+1) uint64_t n = fast_doubling(target >> 1); uint64_t n1 = fast_doubling((target >> 1) + 1); // check 2n or 2n+1 if (target & 1) return n * n + n1 * n1; return n * ((n1 << 1) - n); } ``` 但雖然減少了不少計算,但還是會有重複計算的部份: ```graphviz digraph FIB { "3a" [label="3"; color="red"] "3b" [label="3"; color="red"] "2a" [label="2"] "2b" [label="2"] "2c" [label="2"] "1a" [label="1"] "1b" [label="1"] 6 -> "3a"; 6 -> 4; "3a" -> "2a"; "3a" -> "1a"; 4 -> "2b"; 4 -> "3b"; "3b" -> "2c"; "3b" -> "1b"; } ``` 而當 `target` 越大時重複的計算會造成的效能影響越大。 #### Bottom-up Fast Doubling 從一數的 2 進制表示可以發現該數是如何產生的,以 $87_{10}$ 為例: ```text 87 = 1010111 (87 >> i+1) i = 0 : 43 = (1010111 - 1) << 1 = 101011 i = 1 : 21 = ( 101011 - 1) << 1 = 10101 i = 2 : 10 = ( 10101 - 1) << 1 = 1010 i = 3 : 5 = ( 1010 - 0) << 1 = 101 i = 4 : 2 = ( 101 - 1) << 1 = 10 i = 5 : 1 = ( 10 - 0) << 1 = 1 i = 6 : 0 = ( 1 - 1) << 1 = 0 ^ 87 的第 i 個位元 ``` 若是進行移項並反過來看的話會變成: ```text (87 >> i+1) i = 6 : 1 = 0 << 1 + 1 = 1 i = 5 : 2 = 1 << 1 + 0 = 10 i = 4 : 5 = 10 << 1 + 1 = 101 i = 3 : 10 = 101 << 1 + 0 = 1010 i = 2 : 21 = 1010 << 1 + 1 = 10101 i = 1 : 43 = 10101 << 1 + 1 = 101011 i = 0 : 87 = 101011 << 1 + 1 = 1010111 ^ 87 的第 i 個位元 ``` 從 $n=0$ 開始看,可以發現每次位移後只要檢查目標數對應的位元,就可以知道下次應以 $fib(2n)$ 還是 $fib(2n+1)$ 為基礎進行右移。 若以文字表示流程的話可以寫成: 1. 從最高位元的 1 開始,此時 $n=1$,而: - $fib(n)=1$ - $fib(n+1)=1$ 2. 若下一個位元不存在的話跳到第 3 步,否則(假設目前為 $n=k$): - 透過 $fib(k)$ 以及 $fib(k+1)$ 計算 $fib(2k)$ 以及 $fib(2k+1)$ - 檢查下一個位元: - 0:$n = 2k$ - 1:$n = 2k + 1$,此時需要 $fib(n+1)$ 讓下一迭代能夠計算 $fib(2n)$ 以及 $fib(2n+1)$ - 回到第 2 步 3. 此時 $n$ 為目標數,回傳 $fib(n)$ 若寫成程式碼的話則會變成: ```c static inline uint64_t fast_doubling_iter(uint64_t target) { if (target <= 2) return !!target; // find first 1 uint8_t count = 63 - __builtin_clzll(target); uint64_t fib_n0 = 1, fib_n1 = 1; for (uint64_t i = count, fib_2n0, fib_2n1; i-- > 0;) { fib_2n0 = fib_n0 * ((fib_n1 << 1) - fib_n0); fib_2n1 = fib_n0 * fib_n0 + fib_n1 * fib_n1; if (target & (1UL << i)) { fib_n0 = fib_2n1; fib_n1 = fib_2n0 + fib_2n1; } else { fib_n0 = fib_2n0; fib_n1 = fib_2n1; } } return fib_n0; } ``` :::warning 由於 cppcheck 會偵測區域變數的 scope 是否能夠再縮減,因此這邊將 `i` 一併宣告成 64 位元的無號整數,以便將 `for` 迴圈需要用到的區域變數一併宣告。 ::: 而迴圈內 `if...else...` 的部份可以利用 `-!!(target & (1 << i))` 作為 mask 的技巧簡化成: ```c static inline uint64_t fast_doubling_iter(uint64_t target) { if (target <= 2) return !!target; // find first 1 uint8_t count = 63 - __builtin_clzll(target); uint64_t fib_n0 = 1, fib_n1 = 1; for (uint64_t i = count, fib_2n0, fib_2n1, mask; i-- > 0;) { fib_2n0 = fib_n0 * ((fib_n1 << 1) - fib_n0); fib_2n1 = fib_n0 * fib_n0 + fib_n1 * fib_n1; mask = -!!(target & (1UL << i)); fib_n0 = (fib_2n0 & ~mask) + (fib_2n1 & mask); fib_n1 = (fib_2n0 & mask) + fib_2n1; } return fib_n0; } ``` ### 更精確的 Fibonacci 運算實作 #### 基於 `list_head` 的大數運算 先在熟悉的 user space 進行實作來檢驗程式碼的正確性。 ##### 實作理念 ```c typedef struct { size_t len; struct list_head link; } bignum_head; typedef struct { uint64_t value; struct list_head link; } bignum_node; #define NEW_NODE(head, val) \ ({ \ bignum_node *node = malloc(sizeof(bignum_node)); \ if (node) { \ node->value = val; \ list_add_tail(&node->link, head); \ } \ }) ``` - 使用鏈結串列避免像字串操作須重複 `malloc` 以及 `free` 的成本 - 利用內建的 64 位元整數型別進行運算 - 減少解碼成本(僅須 `printf` 類的格式化輸出函式) ```c= static inline void bignum_add_to_smaller(struct list_head *lgr, struct list_head *slr) { struct list_head **l = &lgr->next, **s = &slr->next; for (bool carry = 0;; l = &(*l)->next, s = &(*s)->next) { if (*s == slr) { if (*l == lgr) { if (carry) NEW_NODE(slr, 1); break; } NEW_NODE(slr, 0); } bignum_node *lentry = list_entry(*l, bignum_node, link), *sentry = list_entry(*s, bignum_node, link); carry = FULL_ADDER_64(lentry->value, sentry->value, carry); } } ``` :::danger 程式碼中的 `l` 以及 `s` 兩個 indirect pointer 雖然看似多餘,但是其實不可被簡化,否則會影響答案,因為: 第 14 行的 `NEW_NODE` 會新增一節點並透過 `list_add_tail` 加到 `slr` 的尾端,因此若是單純使用指向 `list_head` 的指標的話,則需要在 `NEW_NODE` 後重新將 `s` 指向新的節點,否則第 18 行的 `list_entry` 相當於對串列的 head 使用 `list_entry`,造成存取到預期外位址。 因此使用 indirect pointer `*s` 來省略將 `s` 指向新的尾端節點的步驟。 ::: ```c #define MAX_DIGITS 18 #define BOUND64 1000000000000000000UL #define FULL_ADDER_64(a, b, carry) \ ({ \ uint64_t res = (a) + (b) + (carry); \ uint64_t overflow = -!!(res >= BOUND64); \ (b) = res - (BOUND64 & overflow); \ overflow; \ }) ``` 由於 64 位元無號整數的上限為 20 位數的 $18,446,744,073,709,551,615$,而為了減少解碼的成本,透過限制每個節點的上限值為 10 的冪,每當超過上限時就新增一節點儲存進位,而解碼時只要從最高位依序將每個節點以 10 進位的形式印出即可完成解碼。 :::success 為了盡可能的減少節點數量,原先是以 `UINT64_MAX` 作為上限,但發現在解碼時仍須進行大數運算,因此改以 10 的冪($10^{18} = 1000000000000000000$)作為上限值,來減少解碼成本。 而不選擇以 $10^{19}$ 作為上限的原因則是考慮到兩個 $10^{19}-1$ 相加時會超過 `UINT64_MAX`,造成需要額外判斷是否有 overflow 的發生,因此選擇少一位數的 $10^{18}$ 作為上限配合 $\geq$ 進行簡單的判斷。 ::: ##### 測試結果(最簡單實作的費氏數列運算) ```c int main(int argc, char const *argv[]) { struct list_head *h1 = bignum_new(1); struct list_head *h2 = bignum_new(0); for (int i = 0; i < atoi(argv[1]); ++i) { bignum_add_to_smaller(h1, h2); swap(h1, h2); } bignum_to_string(h2, NULL, 0); bignum_free(h1); bignum_free(h2); return 0; } ``` 由於目前只有實作加法的功能,因此先以最基本的遞迴呼叫測試: ```shell $ taskset -cp 1 pid 1's current affinity list: 0-9 ``` ```shell $ time taskset -c 10 ./a.out 100000 | wc 0 1 20899 taskset -c 10 ./a.out 100000 0.25s user 0.00s system 99% cpu 0.257 total wc 0.00s user 0.00s system 0% cpu 0.256 total ``` $fib(100000)$ 大約在 $\frac{1}{4}$ 秒左右計算完成,且與 [WolframAlpha 上的結果](https://www.wolframalpha.com/input?i=fib%28100000%29)相同。 ```shell $ time taskset -c 10 ./a.out 1000000 | wc 0 1 208988 taskset -c 10 ./a.out 1000000 29.70s user 0.00s system 99% cpu 29.707 total wc 0.00s user 0.00s system 0% cpu 29.707 total ``` 而 $fib(1000000)$ 大約在 30 秒內計算完成,但因位數過多無法與 WolframAlpha 上的結果相比。 ##### 實作 Fast Doubling 版本 :::warning TODO : 需要實作 in-place 乘法運算 ::: #### 以 Kernel API 改寫 ##### 動態記憶體配置 而由於 Linux Kernel 中不應使用 VLA,所以需要使用動態記憶體配置: > 參考資料:[LWN - Variable-length arrays and the max() mess](https://lwn.net/Articles/749064/) 但若使用熟悉的 `malloc` 的話,會發現連熟悉的函式庫都找不到,因此 `make all` 時就會發現編譯失敗: ```text /home/freshliver/Dropbox/Notes/_jserv/linux/labs/lab3-fibdrv/fibdrv.c:11:10: fatal error: stdlib.h: 沒有此一檔案或目錄 11 | #include <stdlib.h> ``` > Kernel Module 不能使用標準函式庫的原因參考了 [Stackoverflow 的說明](https://stackoverflow.com/questions/26455140/how-does-the-kernel-stop-you-using-malloc),但還需要查證其他第一手資料。 因此需要從 Linux Kernel 的 Header 中找出動態記憶體配置的函式,以及需要引入的標頭檔。而從 [Linux Kernel 文件的 Memory Allocation Guide](https://www.kernel.org/doc/html/latest/core-api/memory-allocation.html) 中有寫到: > Linux provides a variety of APIs for memory allocation. You can allocate small chunks using `kmalloc` or `kmem_cache_alloc` families, large virtually contiguous areas using `vmalloc` and its derivatives, or you can directly request pages from the page allocator with `alloc_pages`. It is also possible to use more specialized allocators, for instance `cma_alloc` or `zs_malloc`. 這段首先提示了幾個可用來動態記憶體配置的函式,而這些函式需要引入以下幾個標頭檔: ```c #include <linux/mm.h> #include <linux/slab.h> #include <linux/vmalloc.h> ``` 接下來則是要從[一堆看起來很像的記憶體配置函式](https://www.kernel.org/doc/html/latest/core-api/mm-api.html)中尋找符合須由的函式,而在下方的 [Selecting memory allocator](https://www.kernel.org/doc/html/latest/core-api/memory-allocation.html#selecting-memory-allocator) 部份則有提到選擇 Allocator 的建議以及限制: > The most straightforward way to allocate memory is to use a function from the kmalloc() family. And, to be on the safe side it’s best to use routines that set memory to zero, like kzalloc(). > The maximal size of a chunk that can be allocated with kmalloc is limited. The actual limit depends on the hardware and the kernel configuration, but it is a **good practice to use kmalloc for objects smaller than page size**. > For **large** allocations you can use vmalloc() and vzalloc(),(後略) 其中提到了 `kmalloc` 的限制,可以從 [Linux Manual Page 中 `sysconf` 的 POSIX.1 variables 部份](https://www.man7.org/linux/man-pages/man3/sysconf.3.html) 中找到 `PAGESIZE` 這個系統變數,而系統變數則可以透過 [`getconf`](https://www.man7.org/linux/man-pages/man1/getconf.1p.html) 取得(單位為位元組): ```shell $ getconf PAGESIZE 4096 ``` 由於在我電腦上的 `PAGESIZE` 設定是 4 KB,因此在這次實作中,只有能夠確保記憶體佔用小於 4 KB 的物件才使用 `kmalloc` 進行配置,例如:`list_head` 以及 `bignum_head` 等簡單的結構體物件,並使用 `kfree` 釋放對應的記憶體哭間。 而無法確保大小的物件,例如 $fib(k)$ 的結果字串,則使用 `vmalloc` 相關的函式進行配置,並搭配 `vfree` 釋放記憶體空間。 ##### 改寫記憶體配置的部份 在建立節點以及刪除串列的部份比較單純,直接將 `malloc` 以及 `free` 分別改成 `kmalloc` 以及 `kfree` 即可: :::warning GFP ::: ```c #define NEW_NODE(head, val) \ ({ \ bignum_node *node = kmalloc(sizeof(bignum_node), GFP_KERNEL); \ if (node) { \ node->value = val; \ list_entry(head, bignum_head, link)->len++; \ list_add_tail(&node->link, head); \ } \ }) static inline void bignum_free(struct list_head *head) { bignum_node *ptr, *next; list_for_each_entry_safe (ptr, next, head, link) kfree(ptr); kfree(list_entry(head, bignum_head, link)); } ``` 而解碼的部份則是使用 `vzalloc` 來省去 `memset` 的步驟: ```c static inline char *bignum_to_string(struct list_head *head) { // UINT64 < BOUND64 (10^18) size_t digits = list_entry(head, bignum_head, link)->len * MAX_DIGITS + 1; // DMA for result string char *res = vzalloc(sizeof(char) * digits), *pres = res; if (!res) return NULL; // decode from Most Significant Node if (head == head->next) { vfree(res); return NULL; } uint64_t node_result = 0; struct list_head *p = head->prev; // Most Significant Node node_result = list_entry(p, bignum_node, link)->value; snprintf(pres, MAX_DIGITS + 1, "%" U64_FMT, node_result); // other nodes for (p = p->prev; p != head; p = p->prev) { size_t pos = strlen(res); node_result = list_entry(p, bignum_node, link)->value; snprintf(&res[pos], MAX_DIGITS + 1, "%018" U64_FMT, node_result); } return res; } ``` ##### 改寫 `fibdrv.c` ```c static long long fib_sequence_big(uint64_t target, char *buf, size_t size) { struct list_head *lgr = bignum_new(1), *slr = bignum_new(0); for (uint64_t i = 0; i < target; ++i) { bignum_add_to_smaller(lgr, slr); swap(lgr, slr); } char *result = bignum_to_string(slr); size_t failed = copy_to_user(buf, result, size); vfree(result); bignum_free(lgr); bignum_free(slr); return size - failed; } ``` 在 `client.c` 中新增一個函式來進行最基本的 `fibonacci` 運算,並將運算結果透過 `copy_to_user` 複製到在 user space 中透過 `malloc` 分配的記憶體,最後再釋放記憶體空間。 ```c static ssize_t fib_read(struct file *file, char *buf, size_t size, loff_t *offset) { return (ssize_t) fib_sequence_big(*offset, buf, size); } ``` 接著則須改寫 `fib_read` 的部份,傳入 `buf` 以及 `size` 讓 `client` 能夠取得運算結果。 ##### 改寫 `client.c` ```c // fib(n) length ~= 0.2090n size_t length_pred = offset / 4 + 1; char *result = calloc(length_pred, 1); for (int i = 0; i <= offset; i++) { lseek(fd, i, SEEK_SET); sz = read(fd, result, length_pred); printf("Reading from " FIB_DEV " at offset %d, returned the sequence " "%s.\n", i, result); } for (int i = offset; i >= 0; i--) { lseek(fd, i, SEEK_SET); sz = read(fd, result, length_pred); printf("Reading from " FIB_DEV " at offset %d, returned the sequence " "%s.\n", i, result); } free(result); ``` 而 `client.c` 則須修改兩個包含 `read` 的 `for` 迴圈的內容,為了確保答案能夠全部存放到 `buffer` 中,這邊使用了大於 [$fib(n)$ 位數預測值 $n \times 0.2090$](https://en.wikipedia.org/wiki/Fibonacci_number#Magnitude) 的 $n \times 0.25$ 作為 `buffer` 大小,並使用此 `buffer` 作為 `read` 的參數儲存結果,最後再將 `printf` 改用 `result` 輸出答案。 ##### 移除 `MAX_LENGTH` 限制 在完成上面的步驟後,如果直接執行 `make check` 會發現 `fibdrv` 的輸出在 $fib(92)$ 後與預期的數值不同,這是因為 `fibdrv.c` 原先用來回傳答案的型別 `sszie_t` 無法表示 $fib(93)$ 以及之後的數值,因此有在 `seek` 的部份另外進行限制: ```c #define MAX_LENGTH 92 static loff_t fib_device_lseek(struct file *file, loff_t offset, int orig) { loff_t new_pos = 0; switch (orig) { case 0: /* SEEK_SET: */ new_pos = offset; break; case 1: /* SEEK_CUR: */ new_pos = file->f_pos + offset; break; case 2: /* SEEK_END: */ new_pos = MAX_LENGTH - offset; break; } if (new_pos > MAX_LENGTH) new_pos = MAX_LENGTH; // max case if (new_pos < 0) new_pos = 0; // min case file->f_pos = new_pos; // This is what we'll use now return new_pos; } ``` 這會讓 `seek` 無法設定成大於 92 的數,因此 `fib_read` 參數的 `offset` 最大只會是 92,造成在 `client.c` 中呼叫的 $fib(k), \forall k \geq 93$ 都以 $fib(92)$ 替代,使得 `fibdrv` 在 $fib(92)$ 後的輸出皆相同,因此只要將 `MAX_LENGTH` 改成 `100` 即可。 ##### 修改正確答案 ```diff Reading from /dev/fibonacci at offset 90, returned the sequence 2880067194370816120. Reading from /dev/fibonacci at offset 91, returned the sequence 4660046610375530309. Reading from /dev/fibonacci at offset 92, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 93, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 94, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 95, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 96, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 97, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 98, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 99, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 100, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 100, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 99, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 98, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 97, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 96, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 95, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 94, returned the sequence 7540113804746346429. -Reading from /dev/fibonacci at offset 93, returned the sequence 7540113804746346429. +Reading from /dev/fibonacci at offset 93, returned the sequence 12200160415121876738. +Reading from /dev/fibonacci at offset 94, returned the sequence 19740274219868223167. +Reading from /dev/fibonacci at offset 95, returned the sequence 31940434634990099905. +Reading from /dev/fibonacci at offset 96, returned the sequence 51680708854858323072. +Reading from /dev/fibonacci at offset 97, returned the sequence 83621143489848422977. +Reading from /dev/fibonacci at offset 98, returned the sequence 135301852344706746049. +Reading from /dev/fibonacci at offset 99, returned the sequence 218922995834555169026. +Reading from /dev/fibonacci at offset 100, returned the sequence 354224848179261915075. +Reading from /dev/fibonacci at offset 100, returned the sequence 354224848179261915075. +Reading from /dev/fibonacci at offset 99, returned the sequence 218922995834555169026. +Reading from /dev/fibonacci at offset 98, returned the sequence 135301852344706746049. +Reading from /dev/fibonacci at offset 97, returned the sequence 83621143489848422977. +Reading from /dev/fibonacci at offset 96, returned the sequence 51680708854858323072. +Reading from /dev/fibonacci at offset 95, returned the sequence 31940434634990099905. +Reading from /dev/fibonacci at offset 94, returned the sequence 19740274219868223167. +Reading from /dev/fibonacci at offset 93, returned the sequence 12200160415121876738. Reading from /dev/fibonacci at offset 92, returned the sequence 7540113804746346429. Reading from /dev/fibonacci at offset 91, returned the sequence 4660046610375530309. Reading from /dev/fibonacci at offset 90, returned the sequence 2880067194370816120. ``` ### 透過寫入使用不同的實作求值 ```c static enum FIB_MODES { FIB_MODE_BASIC_64 = 0, FIB_MODE_BASIC_BIG = 1, FIB_MODE_FAST_DOUBLING_64 = 2, FIB_MODE_FAST_DOUBLING_BIG = -1, } mode = FIB_MODE_BASIC_64; ``` 透過建立一個包含數種費氏數列計算實作的 `enum`,並修改 `fib_write` 讓程式能夠透過對 `/dev/fibonacci` 進行 `write` 來修改模式: ```c static ssize_t fib_write(struct file *file, const char *buf, size_t size, loff_t *offset) { int val, err = kstrtoint_from_user(buf, size, 10U, &val); if (err == ERANGE || err == EINVAL) pr_err("OVERFLOW OR NOT A NUMBER STRING.\n"); else switch (val) { case FIB_MODE_BASIC_64: pr_info("SET MODE : BASIC_64.\n"); mode = FIB_MODE_BASIC_64; return FIB_MODE_BASIC_64; case FIB_MODE_FAST_DOUBLING_64: pr_info("SET MODE : FAST_64.\n"); mode = FIB_MODE_FAST_DOUBLING_64; return FIB_MODE_FAST_DOUBLING_64; case FIB_MODE_BASIC_BIG: pr_info("SET MODE : BASIC_BIG.\n"); mode = FIB_MODE_BASIC_BIG; return FIB_MODE_BASIC_BIG; case FIB_MODE_FAST_DOUBLING_BIG: default: pr_warn("TO BE IMPLEMENTED.\n"); break; } return 1; } ``` 這邊是選擇使用整數作為各種實作模式的編號,在 `fib_write` 中會先透過 `kstrtoint_from_user` 嘗試將 `buf` 中的字串轉為整數,接著再根據結果設定模式。 :::warning `printk` 或是 `pr_info` 等 [Message Logging Functions](https://www.kernel.org/doc/html/latest/core-api/printk-basics.html) 都會輸出到 `/var/log/syslog` 中,可以透過 `tail` 或是 `cat` 等命令檢查程式是否有照預期運作。 ```shell Mar 16 00:02:24 freshliver-K21 kernel: [78008.798135] MODE = BASIC_64. Mar 16 00:02:27 freshliver-K21 kernel: [78011.276084] UNKNOWN MODE. Mar 16 00:02:27 freshliver-K21 kernel: [78011.276092] MODE = BASIC_64. Mar 16 00:02:31 freshliver-K21 kernel: [78015.190769] MODE = BASIC_64. Mar 16 00:02:33 freshliver-K21 kernel: [78017.419772] MODE = BASIC_64. Mar 16 00:02:36 freshliver-K21 kernel: [78019.993990] SET MODE : BASIC_BIG. Mar 16 00:02:36 freshliver-K21 kernel: [78019.993994] MODE = BASIC_BIG. Mar 16 00:02:45 freshliver-K21 kernel: [78028.973371] SET MODE : BASIC_BIG. Mar 16 00:02:45 freshliver-K21 kernel: [78028.973374] MODE = BASIC_BIG. ``` ::: 而 `fib_read` 中則須根據選擇的模式呼叫對應的實作函式來進行計算: ```c static ssize_t fib_read(struct file *file, char *buf, size_t size, loff_t *offset) { switch (mode) { case FIB_MODE_BASIC_64: printk(KERN_INFO "MODE = BASIC_64.\n"); return (ssize_t) fib_basic_64(*offset, buf, size); case FIB_MODE_FAST_DOUBLING_64: printk(KERN_INFO "MODE = FAST_DOUBLING_64.\n"); return (ssize_t) fib_fast_64(*offset, buf, size); case FIB_MODE_BASIC_BIG: printk(KERN_INFO "MODE = BASIC_BIG.\n"); return (ssize_t) fib_basic_big(*offset, buf, size); case FIB_MODE_FAST_DOUBLING_BIG: default: return copy_to_user(buf, "TO BE IMPLEMENTED.", size); } } ``` ## 評測及分析 在評測及分析的部份參考了 [KYG-yaya573142 的報告](https://hackmd.io/@KYWeng/rkGdultSU)中所使用到的技巧以及觀念。 ### 準備 #### 減少干擾因素 為了進可能減少干擾因素,在測試時做了以下處理: - 關閉其他可能影響測量的程式 - 修改 GRUB 參數讓部份 CPU 不被使用並使用 `taskset` 設定 Affinity ```shell $ cat /etc/default/grub | grep "isolcpu" GRUB_CMDLINE_LINUX_DEFAULT="isolcpus=10,11" ``` - 關閉 Intel Turbo `sudo sh -c "echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo"` - 關閉 ASLR `sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"` - 設定 scaling_governor 為 performance `sudo sh -c "echo performance > /sys/devices/system/cpu/cpu$CPU_ID/cpufreq/scaling_governor"` - 重複測量並排除 95% 信賴區間外的數據 - 小範圍 ($fib(0)$ 到 $fib(92)$) 的測試重複 1000 次 - 大範圍 ($fib(0)$ 到 $fib(2000)$) 的測試則重複 100 次 :::danger ```bash ISOLATED_CPU_ID=11 ISOLATED_CPU_AFFINITY_MASK=7ff for irq in $(find /proc/irq/ -name "smp_affinity"); do AND_MSK="$(( 0x$(cat $irq) & 0x$ISOLATED_CPU_AFFINITY_MASK ))" AND_MSK="$(printf \"%03x\" $AND_MSK )" sudo sh -c "echo $AND_MSK > $irq"; done sudo sh -c "echo $ISOLATED_CPU_AFFINITY_MASK > /proc/irq/default_smp_affinity" ``` 修改部份 IRQ 的 `smp_affinity` 時會出現 `sh: 1: echo: echo: I/O error` 的錯誤訊息: ```shell $ ISOLATED_CPU_AFFINITY_MASK=7ff $ for irq in $(find /proc/irq/ -name "smp_affinity"); do AND_MSK="$(( 0x$(cat $irq) & 0x$ISOLATED_CPU_AFFINITY_MASK ))" AND_MSK="$(printf \"%03x\" $AND_MSK )" sudo sh -c "echo $AND_MSK > $irq"; done sh: 1: echo: echo: I/O error sh: 1: echo: echo: I/O error sh: 1: echo: echo: I/O error ... ``` ::: #### 透過 Shell Script 進行自動測試以及格式化輸出 為了方便重複測量執行時間以及處理數據,另外[寫了一個 Shell Script](https://github.com/freshLiver/fibdrv/commit/4e1e729c243c4c1c23502a9f06a402c6d4ee1190) 來處理前一部份提到的處理以及重複測量相關的,這會將 [experiments/read_perf.c](https://github.com/freshLiver/fibdrv/commit/35dc8cbc008814fe61a7fc174a549e12b2ce40fc) 的結果以 JSON 的格式輸出,最後再交給 [Python Script](https://github.com/freshLiver/fibdrv/blob/4e1e729c243c4c1c23502a9f06a402c6d4ee1190/experiments/plot_result.ipynb) 進行信賴區間的處理以及繪製圖表。 ### 64 位元整數版本 #### VLA (原始版本) #### 無 VLA ![](https://i.imgur.com/LNGHxJW.png) :::warning 原先即使關閉了 Turbo、ASLR 也將 cpufreq 設定成 PERFORMANCE 模式,並透過 taskset 將測試程式指定在其中一個已設定 isolcpu 的 cpu 上測試了,但似乎仍會受到其他因素影響、畫出像下方一樣有明顯抖動的結果。 ![](https://i.imgur.com/0QVUad5.png) 結果透過 htop 發現 dropbox 有明顯佔用其他 cpu,而將 dropbox 關閉後就沒有明顯的抖動了。 ::: #### Fast Doubling ##### Top-down (Recursive) ##### Bottom-up (Iterative) ![](https://i.imgur.com/23V5952.png) ##### `ctz`/`clz` 類的指令對費氏數列的效益 在 Bottom-up 的策略中可以看到 `__builtin_clz` 的使用,以便快速的找到最高位的 1 :::warning TODO : 與自定義的 `clz` 進行比較 ::: ### 大數運算版本 - `bignum` ![](https://i.imgur.com/QPYwpzB.png) ![](https://i.imgur.com/660giGy.png) #### --- // #### 加速大數運算與縮減記憶體操作成本的措施 - [ ] [KYG-yaya573142 的報告](https://hackmd.io/@KYWeng/rkGdultSU) ### `fibdrv.c` 中的 MUTEX > 注意到 `fibdrv.c` 存在著 `DEFINE_MUTEX`, `mutex_trylock`, `mutex_init`, `mutex_unlock`, `mutex_destroy` 等字樣,什麼場景中會需要呢?撰寫多執行緒的 userspace 程式來測試,觀察 Linux 核心模組若沒用到 mutex,到底會發生什麼問題。嘗試撰寫使用 POSIX Thread 的程式碼來確認。 ## 其他 ### 為何不希望在虛擬機器中進行實驗

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully