# 2019q1 Homework2 (fibdrv) contributed by < `njjack` > ## 環境 ## 自我檢查清單 - [ ] 檔案 fibdrv.c 裡頭的 MODULE_LICENSE, MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_VERSION 等巨集做了什麼事,可以讓核心知曉呢? insmod 這命令背後,對應 Linux 核心內部有什麼操作呢?請舉出相關 Linux 核心原始碼並解讀 `MODULE_LICENSE`, `MODULE_AUTHOR`, `MODULE_DESCRIPTION`, `MODULE_VERSION` 四個 macro 在 [include/linux/module.h](https://elixir.bootlin.com/linux/v4.16/source/include/linux/module.h) 中定義,展開成 macro `MODULE_INFO` 而`MODULE_INFO` 也在同一份header file 中定義,展開成 macro `__MODULE_INFO` ```clike=199 #define MODULE_LICENSE(_license) MODULE_INFO(license, _license) ``` ```clike=205 #define MODULE_AUTHOR(_author) MODULE_INFO(author, _author) ``` ```clike=208 #define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description) ``` ```clike=237 #define MODULE_VERSION(_version) MODULE_INFO(version, _version) ``` ```clike=161 #define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info) ``` `__MODULE_INFO` 在 [include/linux/moduleparam.h](https://elixir.bootlin.com/linux/v4.16/source/include/linux/moduleparam.h#L21) 中定義如下 ```clike=20 #ifdef MODULE #define __MODULE_INFO(tag, name, info) \ static const char __UNIQUE_ID(name)[] \ __used __attribute__((section(".modinfo"), unused, aligned(1))) \ = __stringify(tag) "=" info #else /* !MODULE */ /* This struct is here for syntactic coherency, it is not used */ #define __MODULE_INFO(tag, name, info) \ struct __UNIQUE_ID(name) {} #endif ``` __UNIQUE_ID 在 [include/linux/compiler.h](https://elixir.bootlin.com/linux/v4.16/source/include/linux/compiler.h#L166) 中定義 ```clike=166 # define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __LINE__) ``` __PASTE 在 [include/linux/compiler_types.h](https://elixir.bootlin.com/linux/v4.16/source/include/linux/compiler_types.h#L53) 中定義 ```clike= #define ___PASTE(a,b) a##b #define __PASTE(a,b) ___PASTE(a,b) ``` 至此可以理解 `__UNIQUE_ID()` 作用為串接字串得到一字元陣列之變數名,而此字元陣列的值則由 `__stringify` 將參數 `tag` 轉成字串後和 "=" 以及參數 `info` 合併所得 __stringify 在 [include/linux/stringify.h](https://elixir.bootlin.com/linux/v4.16/source/include/linux/stringify.h#L10) 中定義 ```clike=9 #define __stringify_1(x...) #x #define __stringify(x...) __stringify_1(x) ``` 接著看 `__attribute__` 告訴編譯器的訊息, `section` 表示要寫入指定的 section ; `unused` 表示可能不被程式用到,編譯時不產生 warning ; `aligned(1)` 表示以 `1 byte` 為單位對齊 可簡單結論, `MODULE_LICENSE` , `MODULE_AUTHOR` , `MODULE_DESCRIPTION` , `MODULE_VERSION` 作用為將 module 資訊以字元陣列儲存(且字元陣列的變數名稱及內容都有一定格式),並寫入 `.modinfo` section 參考同學 `cjwind` 之[共筆](https://hackmd.io/s/SkW25-2UV) ,使用觀察 ELF 文件之工具 `readelf` , 可以看到 module 的資訊被寫入 `.modinfo` section ``` $ readelf -x .modinfo fibdrv.o 「.modinfo」區段的十六進位傾印: 0x00000000 76657273 696f6e3d 302e3100 64657363 version=0.1.desc 0x00000010 72697074 696f6e3d 4669626f 6e616363 ription=Fibonacc 0x00000020 6920656e 67696e65 20647269 76657200 i engine driver. 0x00000030 61757468 6f723d4e 6174696f 6e616c20 author=National 0x00000040 4368656e 67204b75 6e672055 6e697665 Cheng Kung Unive 0x00000050 72736974 792c2054 61697761 6e006c69 rsity, Taiwan.li 0x00000060 63656e73 653d4475 616c204d 49542f47 cense=Dual MIT/G 0x00000070 504c00 PL. ``` 進一步觀察 symbol table 可以看到下方列出的四個 symbol name 即是 `__UNIQUE_ID()` 作用所得到的變數名稱。其中 symbol name 尾端的數字(23~20)對應到的是 `__UNIQUE_ID()` macro 定義中的 `__LINE__` 。原以為 `__LINE__` 是一個常數,但這裡可以發現在上述每一層操作中並沒有更改 `__LINE__` 的值,卻出現四個不一樣的數字。 參考 [Standard Predefined Macros](https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html) , `__LINE__` 是 gcc 預先定義的 macro ,展開得到的是 source code 中目前 input line 的 line number。 ``` $ readelf -s fibdrv.o 符號表「.symtab」含有 51 個條目: 編號: 值 大小 類型 約束 版本 索引名稱 ... 25: 0000000000000000 12 OBJECT LOCAL DEFAULT 15 __UNIQUE_ID_version23 26: 000000000000000c 36 OBJECT LOCAL DEFAULT 15 __UNIQUE_ID_description22 27: 0000000000000030 46 OBJECT LOCAL DEFAULT 15 __UNIQUE_ID_author21 28: 000000000000005e 21 OBJECT LOCAL DEFAULT 15 __UNIQUE_ID_license20 ... ``` - [ ] 當我們透過 insmod 去載入一個核心模組時,為何 module_init 所設定的函式得以執行呢?Linux 核心做了什麼事呢? 先來看 `module_init` 實際做了什麼事 在 [include/linux/module.h](https://elixir.bootlin.com/linux/v4.16/source/include/linux/module.h#L86) 中定義如下 ```clike=86 #define module_init(x) __initcall(x); ``` 接著依序可在 [include/linux/init.h](https://elixir.bootlin.com/linux/v4.16/source/include/linux/init.h#L1) 找到下列每一層 macro 定義 ```clike=206 #define __initcall(fn) device_initcall(fn) ``` ```clike=201 #define device_initcall(fn) __define_initcall(fn, 6) ``` ```clike=170 #define __define_initcall(fn, id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" #id ".init"))) = fn; ``` 同一個 header file 中 可以找到 `initcall_t` 定義為一個 function pointer ```clike=116 typedef int (*initcall_t)(void); ``` 直接以 `fibdrv.c` 為例, `module_init(init_fib_dev);` 展開後得到名字為 `__initcall_init_fib_dev6` 的 function pointer ,且指向 `fibdrv.c` 中定義的函式 `init_fib_dev` 回頭考慮 `insmod` 做了什麼事情,使 `module_init` 設定的函式被執行。查看 `insmod` 執行時的 system call ,可以發現並猜測和相關的函式呼叫為 `finit_module(3, "", 0)` ``` $ sudo strace insmod fibdrv.ko ... finit_module(3, "", 0) = 0 munmap(0x7f60e5a5d000, 8368) = 0 close(3) = 0 exit_group(0) = ? +++ exited with 0 +++ ``` 在 `finit_module` 的 man page 中可以找到下方敘述,呼叫 `finit_module` 或 `init_module` ,會執行 module 的 init function ,可以猜測前面討論 `module_init` 所設定的函式即是此 init function ,但實際需再研究 `finit_module` 函式內容,以理解如何 call init function 及 `module_init` 如何具有設定 init function 之意義 ``` init_module() loads an ELF image into kernel space, performs any neces‐ sary symbol relocations, initializes module parameters to values pro‐ vided by the caller, and then runs the module's init function. ``` ``` The finit_module() system call is like init_module(), but reads the module to be loaded from the file descriptor fd. ``` - [ ] fibdrv 如何透過 Linux Virtual File System 介面,讓計算出來的 Fibonacci 數列得以讓 userspace (使用者層級) 程式 (本例就是 client.c 程式) 得以存取呢?解釋原理,並撰寫或找出相似的 Linux 核心模組範例 Virtual file system(VFS)是 actual file system 之上的一層界面,user application 可以使用定義好的界面對 file 操作,呼叫對應的 system call,而不用考慮不同 file system 以及底層如何實作 依序呼叫下列函式來完成 device 註冊 1.`alloc_chrdev_region()`:取得 driver 專屬的 major number。 2.`cdev_init()`: 初始化 cdev 結構,包含system call 的 handler 3.`cdev_add()`: 向 kernel 登記 cdev_init() 設定好的 device 資訊 傳入 `cdev_init()` 的一個參數 `fib_fops` 在 `fibdrv.c` 有如下定義,即是 file operation 和 system call 的對應關係 ``` const struct file_operations fib_fops = { .owner = THIS_MODULE, .read = fib_read, .write = fib_write, .open = fib_open, .release = fib_release, .llseek = fib_device_lseek, }; ``` 接著呼叫下列函式創建 device file 1.`class_create()`:在 sysfs 下創建一個 class (/sys/class/<name>) 2.`device_create()`:在 /dev 下創建相應的 device file (/dev/<device name>) 本例中,上列函式在 init function `init_fib_dev` 中呼叫,即在 `insmod` 後 userspace 程式即可透過對 device file `fibnacci` 作 file operation 呼叫對應的 system call ,且其回傳值是 system call 的回傳值 ## fibdrv 執行時間分析 原始版本 ![](https://i.imgur.com/ZQrtFLl.png) 參考[你所不知道的C語言:遞迴呼叫篇](https://hackmd.io/s/rJ8BOjGGl),使用其中 fast doubling 演算法程式碼 ![](https://i.imgur.com/EhzfXnh.png)