# 2019q1 Homework2 (fibdrv)
contributed by < `tfliao` >
## Environment
```shell
$ uname -a
Linux debian-nk 4.19.0-2-amd64 #1 SMP Debian 4.19.16-1 (2019-01-17) x86_64 GNU/Linux
$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux buster/sid
Release: testing
Codename: buster
```
## 自我檢查清單
- [x] MODULE_XXX 巨集做了什麼事
[`MODULE_LICENSE(_license)`](https://elixir.bootlin.com/linux/v4.19.16/source/include/linux/module.h#L200) -> `MODULE_INFO(tag, info)` -> [`__MODULE_INFO(tag, name, info)`](https://elixir.bootlin.com/linux/v4.19.16/source/include/linux/moduleparam.h#L21)
以`MODULE_LICENSE("Dual MIT/GPL")`作為例子,MACRO最後會變成`__MODULE_INFO("license", "license", "Dual MIT/GPL")`,並且展開為下列程式碼
```
static const char __UNIQUE_ID(name)[] \
__used __attribute__((section(".modinfo"), unused, aligned(1))) \
= __stringify(tag) "=" info
```
概念上就是
1. 宣告一個 static 的常數字串 (`static const char[]`)
2. 字串內容是 "license=Dual MIT/GPL" (`__stringify(tag) "=" info`)
3. 放在.modinfo 這個 section (`__attribute__((section(".modinfo")))`)
4. 變數名稱不重要所以請 compiler 隨便長一個 (`__UNIQUE_ID(name)`)
5. 為了避免 compiler 浪費空間去做 padding ,所以請他對齊一個位元(`__attribute__((aligned(1)))`)
6. 最後是跟 linker 說不要把這個變數當成沒用的東西 (`__attribute__((unused))` 和 [`__used`](https://elixir.bootlin.com/linux/v4.19.16/source/tools/include/linux/compiler.h#L54)
可使用`readelf` dump `.modinfo` 內的值
```
$ readelf fibdrv.ko -p .modinfo
String dump of section '.modinfo':
[ 0] version=0.1
[ c] description=Fibonacci engine driver
[ 30] author=National Cheng Kung University, Taiwan
[ 5e] license=Dual MIT/GPL
[ 78] srcversion=24DC5FB7E7608AF16B0CC1F
[ a0] depends=
[ a9] retpoline=Y
[ b5] name=fibdrv
[ c1] vermagic=4.19.0-2-amd64 SMP mod_unload modversions
```
- [ ] insmod
遇到問題先找source code,在 `man insmod` 的輸出左下角,可以知道 `insmod` 放在 `kmod`這個 package 內,從 [這裡](http://snapshot.debian.org/package/kmod/25-2/) 可以找到原始碼,在 `tools/insmod.c` 裡面,可以看到 `insmod` 主要就做幾件事
```
135 ctx = kmod_new(NULL, &null_config);
136 if (!ctx) {
137 ERR("kmod_new() failed!\n");
138 free(opts);
139 return EXIT_FAILURE;
140 }
141
142 err = kmod_module_new_from_path(ctx, filename, &mod);
143 if (err < 0) {
144 ERR("could not load module %s: %s\n", filename,
145 strerror(-err));
146 goto end;
147 }
148
149 err = kmod_module_insert_module(mod, flags, opts);
150 if (err < 0) {
151 ERR("could not insert module %s: %s\n", filename,
152 mod_strerror(-err));
153 }
```
* `kmod_new`
* 建立 `struct kmod_ctx`,做為後續操做 `kmod` 系列函數用的物件
* `kmod_module_new_from_path`
* 把路徑轉換為module name,檢查是否已經存在於kernel中,若都沒問題則用 `kmod_module_new` 建立 `struct kmod_module`,並記錄 name 和 path
* `kmod_module_insert_module`
* 利用前面產生 `struct kmod_module` 內的路徑,把 elf 檔案讀進 memory 中 (同樣存放在 `kmod_module` 結構內)
* 這邊有個有趣的點,kernel module在編譯的時候,會把參照的kernel版本以vermagic的形式存起來 (e.g. 前面 dump 出來的 `vermagic=4.19.0-2-amd64 SMP mod_unload modversions`),在 insert 時檢查是否與現在的 kernel 相符,避免插錯 module 把系統搞壞;如果有下 `KMOD_INSERT_FORCE_VERMAGIC` 這個 flag,在 insert 之前會把 `vermagic` 移除來繞過 kernel 的檢查
* 最後從 elf 檔案中取得檔案大小,透過 `init_module(module_image, size, param)` 請 kernel 把 module_image 載入到 kernel 中
- [ ] init_module
> 終於回到kernel space了 感動
`init_module` 是一個 system call,其實作定義在[kernel/module.c](https://elixir.bootlin.com/linux/v4.19.26/source/kernel/module.c#L3842),在確認當下的執行的 kernel 有支援 module 能力後,把 module_image 用 `copy_module_from_user` 複製到 kernel space,最後透過 `load_module` 載入 module_image
[`load_module`](https://elixir.bootlin.com/linux/v4.19.26/source/kernel/module.c#L3644) 開始會先跑一堆檢查,然後把 module imge 載入到 kernel 內,最後在 call do_init_module 執行 module 設定的進入點
* `elf_header_check`
* 檢查elf的格式
* `setup_load_info`
* 讀取 .modinfo section 內的值,把 mod 指到 data 內對應的位置 ... etc
* `blacklisted`
* 檢查這個 module 是不是在黑名單內,有的話就不要 load
* `module_sig_check`
* 檢查 module 的 signature
* `rewrite_section_headers`
* 把 elf 的 section header 指到 kernel space 的 address
* 如果 kernel 不想支援 module unload,把 .exit 幹掉 XDDD
* `check_modstruct_version`
* 檢查 modversion
* `layout_and_allocate`
* 調整 section/ symbol table 的 layout,並且把 module 移動到新的記憶體位置 (而非 info 的一部份)
* `add_unformed_module`
* 檢查是不是有同名的 module,若沒有則把這個 module 串到 list 中
* `add_taint_module`
* 若 module 的 signature 不正確,會把 kernel 標記成 (taint),基本上 tainted kernel 出現的 bug 會被社群無視,就是個誰知道你的 kernel 被幹過什麼事情的概念 XD
* `module_unload_init`
* 設定 refcount 以及追蹤 `struct module_use` 的 linked-list
* `find_module_sections`
* 把各個 section 的位置存到 `struct module` 結構的變數內
* `check_module_license_and_versions`
* 檢查 module 使用的 license,必要時標上taint
* `setup_modinfo`
* 把用 MODULE_XXX 寫入的資訊存到 `struct module` 結構的變數內
* `simplify_symbols`
* 試圖解出 symbol table 內的 undefine,若有 undefine 則會 load 失敗
* 若從其他 module 中找到 symbol,會產生 `struct module_use` 紀錄 module 之間的dependency
* `apply_relocations`
* 根據 section type,調整存放在記憶體內的位置
* `post_relocation`
* 調整完位置後,把 module 內的 symbol 加入 kallsym
* `flush_module_icache`
* flush module 使用的 instruction cache
* `strndup_user`
* 把 module arguments 複製到 kernel space
* `complete_formation`
* 檢查重複的 symbol,狀態標成 COMING
* `mod_sysfs_setup`
* 產生 module 對應的 sysfs 目錄
* `do_init_module`
* 到這裡就會呼叫 `struct module` 內的 init 函數
但是奇怪的地方是,在 kernel 裡面沒有人去設定 `struct module` 中的 `init` 函數,把 [`module_init`](https://elixir.bootlin.com/linux/v4.19.26/source/include/linux/module.h#L130) 解開也只是增加一個叫 `init_module` 的 alias 指向我們的 `init_dib_dev`,最後在編譯過程產生的 `fibdrv.mod.c` 找到答案
> 這部分是參考 cjwind 大大的[共筆](https://hackmd.io/s/SkW25-2UV),才知道要去找fibdrv.mod.c
```
11 __visible struct module __this_module
12 __attribute__((section(".gnu.linkonce.this_module"))) = {
13 .name = KBUILD_MODNAME,
14 .init = init_module,
15 #ifdef CONFIG_MODULE_UNLOAD
16 .exit = cleanup_module,
17 #endif
18 .arch = MODULE_ARCH_INIT,
19 };
```
這裡宣告一個 `struct module` 的變數,並且把 `init_module` 存到 `init` 的欄位內,配上 `module_init` 設定的 alias ,我們就建立好一個 `struct module` ,內含的 `init` 直接指向 `init_fib_drv`
再回頭看 [`setup_load_info`](https://elixir.bootlin.com/linux/v4.19.26/source/kernel/module.c#L2947),有一段尋找 `.gnu.linkonce.this_module` 的 section,並且 assign 到 `info->mod` 的邏輯,在這裡就已經把 `init` 設定到我們的 `init_fib_drv` 上了
- [ ] module (?)
- [ ] ktime
- [ ] clock_gettime
- [ ] VFS
- [ ] mutex
測試前先猜測, `mutex` 僅有開檔的時候被 lock,關檔(或是module unload)的時候被 unlock ,這確保了同時僅會有一個 userspace 的程序在操作 `/dev/fibonacci`,並且這個 module 也只有這一個介面,等於一次僅會有一個程序在使用這個 module。
==猜測==
若是把 `mutex` 移除,以這個 module 來說應該不會炸裂 XD
在多執行緒的情況下,假設每個執行緒不共用開啟的檔案,則各自開啟的檔案應有獨立的 fd ,各自的,不該出現問題;但若是公用開啟的檔案則在 seek 和 read 之間,pos可能被改變,造成 read 到的值不是期待的結果,這個問題就算沒有移除 mutex 也會發生。
若是多個程序同時開啟檔案,狀況應該與單一程序多執行緒的結果相同
==結果==
修改程式碼於 [remove_mutex](https://github.com/tfliao/fibdrv/tree/remove_mutex)
同時執行10個client,根據 `dmesg` 的訊息,可以看到 `/dev/fibonacci` 被同時開啟多次,並且十個 client 的 log 完全相符
* 此處可修改 client 測試是否為獨立的 `struct file` (但是懶得弄XD)
```
$ for i in `seq 1 10`; do sudo ./client > /tmp/log.$i & done
[305276.901764] fibonacci opened by 19263
[305276.902706] fibonacci closed by 19263
[305276.910405] fibonacci opened by 19267
[305276.912089] fibonacci closed by 19267
[305276.915322] fibonacci opened by 19270
[305276.916426] fibonacci closed by 19270
[305276.917330] fibonacci opened by 19268
[305276.918249] fibonacci closed by 19268
[305276.920695] fibonacci opened by 19273
[305276.921104] fibonacci opened by 19272
[305276.921658] fibonacci closed by 19273
[305276.922018] fibonacci closed by 19272
[305276.932219] fibonacci opened by 19275
[305276.933983] fibonacci opened by 19274
[305276.935930] fibonacci opened by 19277
[305276.936539] fibonacci opened by 19276
[305276.936893] fibonacci closed by 19277
[305276.937461] fibonacci closed by 19276
[305276.938267] fibonacci closed by 19275
[305276.938791] fibonacci closed by 19274
$ for i in `seq 2 10`; do diff /tmp/log.1 /tmp/log.$i ; done
```
==結論==
這個 module 擋在開檔根本沒什麼用XD
但也是因為這是教學用途的 module,功能較為單純,若這個 module 會把計算數列的時間存在 module 內,或是透過 write 決定要計算的fib,這時就比較需要 lock 來避免中途被改內容。
- [ ] ffs
## fibdrv 修改
### 使用 ktime 執行時間
[`ktime`](https://www.kernel.org/doc/html/latest/core-api/timekeeping.html) 系列 API 在最基礎的使用上相當直覺,在需要測量程式碼前後用 `ktime_get` 取得當下的時間,相減就可以知道這過程中經過多少時間
```
static ssize_t fib_read(struct file *file,
char *buf,
size_t size,
loff_t *offset)
{
ssize_t r;
#ifdef ENABLE_STAT
ktime_t begin = ktime_get();
#endif /* ENABLE_STAT */
r = fib_sequence(*offset);
#ifdef ENABLE_STAT
struct timespec64 ts = ktime_to_timespec64(ktime_get() - begin);
printk(KERN_INFO "fib_sequence(%lld) takes %lld.%09ld seconds\n",
(long long) *offset, (long long) ts.tv_sec, (long) ts.tv_nsec);
#endif /* ENABLE_STAT */
return r;
}
```
用 `printk` 把算出來的結果直接丟到 console 輸出
```
$ sudo ./client
[93220.012408] fib_sequence(0) takes 0.000000053 seconds
[93220.012419] fib_sequence(1) takes 0.000000044 seconds
[93220.012427] fib_sequence(2) takes 0.000000060 seconds
[93220.012436] fib_sequence(3) takes 0.000000056 seconds
[93220.012445] fib_sequence(4) takes 0.000000058 seconds
[93220.012454] fib_sequence(5) takes 0.000000073 seconds
[93220.012462] fib_sequence(6) takes 0.000000064 seconds
[93220.012474] fib_sequence(7) takes 0.000000071 seconds
[93220.012484] fib_sequence(8) takes 0.000000068 seconds
[93220.012492] fib_sequence(9) takes 0.000000072 seconds
[93220.012501] fib_sequence(10) takes 0.000000071 seconds
[93220.012510] fib_sequence(11) takes 0.000000074 seconds
...
```
### 使用 sysfs
用 `printk` 固然方便,但執行時間容易因為各種因素而不穩定,通常我們會執行多次來觀察各種統計數值,此時用 `printk` 作為介面就不會是個好選擇。
此時我們可以使用 kernel 的 sysfs 介面,透過 sysfs 介面,我們可以宣告 `show` 和 `store` 介面來提供讀寫操作。要產生一個 sysfs entry 還挺多東西要寫的,好在 kernel 有提供 [`sample`](https://elixir.bootlin.com/linux/v4.19.26/source/samples/kobject/kobject-example.c) ,幫我們省掉去 stack overflow 找哪段 code 可以抄的時間。
我建立了兩個介面, `result` 用來讀取目前的統計資訊 (為了之後用其他成切資料方便,輸出格式為: `n : ns / cnt`), `reset` 則是清空當下的統計,修改的程式碼在 [commit](https://github.com/tfliao/fibdrv/commit/bdd47fa3a76280fe1cecbca807d00515012dcf19) 。
```
$ ll /sys/kernel/fibonacci/*
-rw-r--r-- 1 root root 4096 Mar 17 01:05 /sys/kernel/fibonacci/reset
-r--r--r-- 1 root root 4096 Mar 17 01:05 /sys/kernel/fibonacci/result
$ sudo ./client > /dev/null
$ cat /sys/kernel/fibonacci/result
0: 88 / 2
1: 74 / 2
2: 95 / 2
3: 100 / 2
4: 99 / 2
5: 109 / 2
6: 123 / 2
7: 118 / 2
8: 124 / 2
9: 129 / 2
10: 131 / 2
...
92: 3428 / 18
```
## CHECK LIST
- [ ] 回答上述「自我檢查清單」的所有問題,需要附上對應的參考資料和必要的程式碼,以第一手材料 (包含自己設計的實驗) 為佳
- [ ] 在 GitHub 上 fork [fibdrv](https://github.com/sysprog21/fibdrv),目標是改善 `fibdrv` 計算 Fibinacci 數列的執行效率,過程中需要量化執行時間,可在 Linux 核心模組和使用層級去測量
- [ ] 在 Linux 核心模組中,可用 ktime 系列的 API
- [ ] 在 userspace 可用 [clock_gettime](https://linux.die.net/man/2/clock_gettime) 相關 API
- [ ] 分別用 gnuplot 製圖,分析 Fibonacci 數列在核心計算和傳遞到 userspace 的時間開銷,單位需要用 us 或 ns (自行斟酌)
- [ ] 嘗試解讀上述時間分佈,特別是隨著 Fibonacci 數列增長後,對於 Linux 核心的影響
- [ ] 原本的程式碼只能列出到 Fibonacci(100),請修改程式碼,列出後方更多數值 (注意: 檢查正確性和數值系統的使用)
- [ ] 逐步最佳化 Fibonacci 的執行時間,引入 [費氏數列](https://hackmd.io/s/BJPZlyDSV) 提到的策略,並善用 [clz / ctz](https://en.wikipedia.org/wiki/Find_first_set) 一類的指令,過程中都要充分量化
==自我檢查清單==
- [x] 檔案 `fibdrv.c` 裡頭的 `MODULE_LICENSE`, `MODULE_AUTHOR`, `MODULE_DESCRIPTION`, `MODULE_VERSION` 等巨集做了什麼事,可以讓核心知曉呢?
- [x]`insmod` 這命令背後,對應 Linux 核心內部有什麼操作呢?請舉出相關 Linux 核心原始碼並解讀
- [x] 當我們透過 `insmod` 去載入一個核心模組時,為何 `module_init` 所設定的函式得以執行呢?Linux 核心做了什麼事呢?
- [x] 試著執行 `$ readelf -a fibdrv.ko`, 觀察裡頭的資訊和原始程式碼及 `modinfo` 的關聯,搭配上述提問,解釋像 `fibdrv.ko` 這樣的 ELF 執行檔案是如何「植入」到 Linux 核心
- [ ] 這個 `fibdrv` 名稱取自 Fibonacci driver 的簡稱,儘管在這裡顯然是為了展示和教學用途而存在,但針對若干關鍵的應用場景,特別去撰寫 Linux 核心模組,仍有其意義,請找出 Linux 核心的案例並解讀。提示: 可參閱 [Random numbers from CPU execution time jitter](https://lwn.net/Articles/642166/)
- [ ] 查閱 [ktime 相關的 API](https://www.kernel.org/doc/html/latest/core-api/timekeeping.html),並找出使用案例 (需要有核心模組和簡化的程式碼來解說)
- [ ] [clock_gettime](https://linux.die.net/man/2/clock_gettime) 和 [High Resolution TImers (HRT)](https://elinux.org/High_Resolution_Timers) 的關聯為何?請參閱 POSIX 文件並搭配程式碼解說
- [ ] `fibdrv` 如何透過 [Linux Virtual File System](https://www.win.tue.nl/~aeb/linux/lk/lk-8.html) 介面,讓計算出來的 Fibonacci 數列得以讓 userspace (使用者層級) 程式 (本例就是 `client.c` 程式) 得以存取呢?解釋原理,並撰寫或找出相似的 Linux 核心模組範例
- [x] 注意到 `fibdrv.c` 存在著 `DEFINE_MUTEX`, `mutex_trylock`, `mutex_init`, `mutex_unlock`, `mutex_destroy` 等字樣,什麼場景中會需要呢?撰寫多執行緒的 userspace 程式來測試,觀察 Linux 核心模組若沒用到 mutex,到底會發生什麼問題
- [ ] 許多現代處理器提供了 [clz / ctz](https://en.wikipedia.org/wiki/Find_first_set) 一類的指令,你知道如何透過演算法的調整,去加速 [費氏數列](https://hackmd.io/s/BJPZlyDSV) 運算嗎?請列出關鍵程式碼並解說