# 2019q1 Homework2 (fibdrv) contributed by < `Laurora` > ### Reviewed by `rebvivi` - 在標題 `fibdrv 計算 Fibonacci 數列` 的第一張圖,可以在 `client.c` 中嘗試用不同的輸入數值: * 原本老師給的檔案是用 `for (i = 0; i <= offset; i++)`當作 input,輸入數值是漸進遞增的 ,所以畫出來的圖是 best case 的情況 * 可以嘗試把 `fib_sequence(n)` 的輸入數值 n 改成固定區間的 random input ## 自我檢查清單 - [ ] 檔案 fibdrv.c 裡頭的 MODULE_LICENSE, MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_VERSION 等巨集做了什麼事,可以讓核心知曉呢? insmod 這命令背後,對應 Linux 核心內部有什麼操作呢?請舉出相關 Linux 核心原始碼並解讀 `MODULE_LICENSE`: 讓 kernel 知道 device driver 的授權方式 * 如果一個 module 没有授權,那麼很多需要授權的函式會因此不能使用 * 如果 module 授權錯誤,會導致 module 在 run 或是 load 錯誤 `MODULE_AUTHOR`: module 的作者 `MODULE_DESCRIPTION`: module 的功用跟簡介 `MODULE_VERSION`: module 的版本 `insmod`: 用於將給定的 module 載入到 kernel 中 * Linux 有許多功能是通過 module 的方式,在需要時才載入 kernel,可以讓 kernel較為精簡,進而提高效率 * module 載入的時候會呼叫 `init_fib_dev` 當使用 `insmod fibdrv.ko` 時,會自動的呼叫到 `init_fib_dev` 這個 function ```clike module_init(init_fib_dev); ``` --- - [ ] 當我們透過 insmod 去載入一個核心模組時,為何 module_init 所設定的函式得以執行呢?Linux 核心做了什麼事呢? 我們在`fibdrv.c`載入函式庫 `<linux/init.h>` ```clike #include <linux/init.h> ``` 在 `<linux/init.h>` 中定義如下: ```clike #ifndef MODULE #define module_init(x) __initcall(x); #else #define module_init(initfn) \ int init_module(void) __attribute__((alias(#initfn))); #endif ``` * `__attribute__`: 讓我們定義函數的行為,以便告訴 gcc 在編譯時期對此函式做一些特殊的處理或檢查動作 * `alias` : 定義 `init_module` 爲函數 initfn 的別名 ```clike module_init(init_fib_dev); ``` * `module_init(init_fib_dev)` 的作用就是定義一個變量名 `init_module`,其地址和 `init_fib_dev` 一樣 * 在寫 module 的時候有兩個特殊的函數,分別是 `init_module` 和 `cleanup_module` ,這兩個函數分別在 `insmod` 的時候和 `rmmod` 的時候調用,並且 `insmod` 和 `rmmod` 只識別這兩個特殊的函數 * 當我們給的 `initfn (init_fib_dev)` 定義一個別名 `init_module`,這樣 `insmod` 就能夠執行我們的 `initfn (init_fib_dev)` --- - [ ] 試著執行 $ readelf -a fibdrv.ko, 觀察裡頭的資訊和原始程式碼及 modinfo 的關聯,搭配上述提問,解釋像 fibdrv.ko 這樣的 ELF 執行檔案是如何「植入」到 Linux 核心 當我們在終端機輸入 `$ modinfo fibdrv.ko` ,可以得到以下資訊 ``` version: 0.1 description: Fibonacci engine driver author: National Cheng Kung University, Taiwan license: Dual MIT/GPL srcversion: CCEDBC56685E857565A460A depends: retpoline: Y name: fibdrv vermagic: 4.18.0-15-generic SMP mod_unload ``` 而當我們輸入 ` $ readelf -a fibdrv.ko` 會列出,可以在 `Section Headers:` 中找到 `.modinfo` ,利用 `$ sudo objdump --section=.modinfo -s fibdrv.ko` 可以查看 `.modinfo` 的內容,發現與上方結果一致。 ``` Contents of section .modinfo: 0000 76657273 696f6e3d 302e3100 64657363 version=0.1.desc 0010 72697074 696f6e3d 4669626f 6e616363 ription=Fibonacc 0020 6920656e 67696e65 20647269 76657200 i engine driver. 0030 61757468 6f723d4e 6174696f 6e616c20 author=National 0040 4368656e 67204b75 6e672055 6e697665 Cheng Kung Unive 0050 72736974 792c2054 61697761 6e006c69 rsity, Taiwan.li 0060 63656e73 653d4475 616c204d 49542f47 cense=Dual MIT/G 0070 504c0000 00000000 73726376 65727369 PL......srcversi 0080 6f6e3d43 43454442 43353636 38354538 on=CCEDBC56685E8 0090 35373536 35413436 30410000 00000000 57565A460A...... 00a0 64657065 6e64733d 00726574 706f6c69 depends=.retpoli 00b0 6e653d59 006e616d 653d6669 62647276 ne=Y.name=fibdrv 00c0 00766572 6d616769 633d342e 31382e30 .vermagic=4.18.0 00d0 2d31352d 67656e65 72696320 534d5020 -15-generic SMP 00e0 6d6f645f 756e6c6f 61642000 mod_unload . ``` 同樣利用 `objdump` 查詢 `.gnu.linkonce.this_module` 即列出此模組的名字 `fibdrv` * `modinfo` 指令用來顯示核心模組的資訊( section `.modinfo` ),此 section 的資料不會載入到 kernel address space 。 * 而 `.gnu.linkonce.this_module` section 含有一個 module 的結構,其中包含模組名及其他欄位,但在這次的作業中只看的到模組名。插入模組 (`insmod`) 時,`module_init()` 會從這個 section 將該 module 結構讀入動態記憶體。 > The `.modinfo` section is used by the `modinfo` command to show information about the kernel module. The data stored in the section is not loaded in the kernel address space. > The `.gnu.linkonce.this_module` special section includes a module structure which contains, among other fields, the module's name. When inserting a module, the `init_module()` system call reads the module structure from this special section into an area of dynamic memory. >--- source from [Special sections in Linux binaries](https://lwn.net/Articles/531148/) * linux 模組化後, `.ko` 檔可透過 insmod 載入 linux 核心,由於 `.ko` 檔環境須與系統環境一致,因此在開發之前的準備工作需要確認 `linux-headers` 是否正確安裝於開發環境。 * makefile 中 ```$(MAKE) -C $(KDIR) M=$(PWD) modules``` 即是將 `linux-headers` include 進來。 --- - [ ] 這個 `fibdrv` 名稱取自 Fibonacci driver 的簡稱,儘管在這裡顯然是為了展示和教學用途而存在,但針對若干關鍵的應用場景,特別去撰寫 Linux 核心模組,仍有其意義,請找出 Linux 核心的案例並解讀。 --- - [ ] 查閱 `ktime` 相關的 API,並找出使用案例 (需要有核心模組和簡化的程式碼來解說) --- - [ ] `clock_gettime` 和 High Resolution TImers (HRT) 的關聯為何?請參閱 POSIX 文件並搭配程式碼解說 * `clock_gettime`: 用來取得程式的 CPU time,是 POSIX 的 High Resolution Timer 之一,精確度可以達到 nanosecond,其所回傳的時間是用 `timespec` 這個 struct 所儲存 第一個 parameter `clock_id` 可以設定成 `clockid_t` 型態 `CLOCK_REALTIME`: `clockid_t` 型態的一種,是系統實際的時間 ```clike int clock_gettime (clockid_t clock_id, struct timespec *tp); ``` * 函式會將計時器的值儲存成 `timespec` 的型態,會將時間分為 second 及 nanosecond ```clike struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; ``` --- - [ ] `fibdrv` 如何透過 Linux Virtual File System 介面,讓計算出來的 Fibonacci 數列得以讓 userspace (使用者層級) 程式 (本例就是 `client.c` 程式) 得以存取呢?解釋原理,並撰寫或找出相似的 Linux 核心模組範例 * Linux Virtual File System --- - [ ] 注意到 `fibdrv.c` 存在著 `DEFINE_MUTEX`, `mutex_trylock`, `mutex_init`, `mutex_unlock`, `mutex_destroy` 等字樣,什麼場景中會需要呢?撰寫多執行緒的 userspace 程式來測試,觀察 Linux 核心模組若沒用到 mutex,到底會發生什麼問題 * mutex 的存在是為了讓 kernel 保持同步,防止多核心處理器同時訪問及修改某段 code ,或者對 driver 做 critical section 的 protection。 * `DEFINE_MUTEX`: 靜態初始化 mutex ,一般宣告在 global * `mutex_init`: 初始化已經 allocate 的 mutex ,It is used for per-object mutexes, when mutex is just a field in a heap-allocated object. * `mutex_trylock`: 不暫停目前執行的程式,嘗試鎖定 mutex ,成功返回非零,否則返回 0 。若沒有 sleeping ,此功能無法安全地在硬體或軟體中斷環境中使用。 * `mutex_unlock`: 將 mutex 解鎖。 * `mutex_destroy`: 可以 destory mutex pointer 所指向的與 mutex lock 相關的任何狀態。用來儲存 mutex lock 的空間不會釋放。 * userspace 沒有 mutex 的情況下測試多執行緒 ( [測試程式](https://github.com/Laurora/test_pthread) ) 預期結果 ``` This is a pthread1. This is a pthread1. This is a pthread1. This is a pthread2. This is a pthread2. This is a pthread2. This is the main process. This is the main process. This is the main process. ``` 但是在沒有 mutex 的情況可能會變成 ``` This is a pthread1. This is a pthread1. This is a pthread1. This is the main process. This is the main process. This is a pthread2. This is a pthread2. This is a pthread2. This is the main process. ``` 或是其他可能。顯示出 main process 和 thread 之間會競爭先後順序,若 process 和 thread 之間有共用變數或是資料傳遞,沒有 mutex 會導致計算結果與預期結果不同。 - [ ] 許多現代處理器提供了 `clz / ctz` 一類的指令,你知道如何透過演算法的調整,去加速費氏數列運算嗎?請列出關鍵程式碼並解說 ## fibdrv 計算 Fibonacci 數列 * 原來使用一維陣列儲存 fib ![](https://i.imgur.com/qWfwmah.png) * 更改算法,預期結果為 $O(logN)$ ```clike static long long fib_sequence(long long n) { long long i,h,j,k,t; i=h=1; j=k=0; while(n>0) { if(n%2==1){ t=j*h; j=i*h + j*k +t; i=i*k + t; } t=h*h; h=2*k*h + t; k=k*k + t; n=(long long) n/2; } return j; } ``` 但此圖仍看不出 $O(logN)$ 的趨勢,可能是迭代次數不夠多的關係。 ![](https://i.imgur.com/GIPGzaI.png)