--- tags: 作業 --- # 2019q1 Homework2 (fibdrv) contributed by < `ab37695543xs` > ## 開發環境 ``` $ lscpu 架構: x86_64 CPU 作業模式: 32-bit, 64-bit Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 每核心執行緒數: 2 每通訊端核心數: 4 Socket(s): 1 NUMA 節點: 1 供應商識別號: GenuineIntel CPU 家族: 6 型號: 142 Model name: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz 製程: 10 CPU MHz: 2099.944 CPU max MHz: 3400.0000 CPU min MHz: 400.0000 BogoMIPS: 3600.00 虛擬: VT-x L1d 快取: 32K L1i 快取: 32K L2 快取: 256K L3 快取: 6144K NUMA node0 CPU(s): 0-7 ``` ``` $ uname -r 4.18.0-16-generic ``` ``` $ gcc --version gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 ``` ``` $ busybox | head -1 BusyBox v1.27.2 (Ubuntu 1:1.27.2-2ubuntu3.1) multi-call binary. ``` ## 作業目標 - [ ] 撰寫可適用於使用者和核心層級的程式 - [ ] 自動測試機制 - [ ] 透過工具進行效能分析 ## 自我檢查 ### 1. 檔案 `fibdrv.c` 裡頭的 `MODULE_LICENSE`,`MODULE_AUTHOR`,`MODULE_DESCRIPTION`,`MODULE_VERSION` 等巨集做了什麼事,可以讓核心知曉呢?`insmod` 這命令背後,對應 Linux 核心內部有什麼操作呢?請舉出相關 Linux 核心原始碼並解讀 #### [linux/module.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/module.h) * 單純以宣告時的註解來看 * `LICENSE`:提供不同的敘述像 `GPL`、`Dual BSD/GPL` 等等來說明給予何種授權 * `AUTHOR`:最好以姓名和電子郵件來說明作者資訊 * `DESCRIPTION`:簡單說明模組的功能 * `VERSION`:以格式 `[<epoch:>]<version>[-<extra-version>]` 表示,例如 `1:2.0` * `epoch`:可以想像為大更新,沒提及的話預設為 0,數值愈大愈新版 * `version`:可以使用英數,以 `.` 區隔,一樣依數值排序 * `extra-version`:前面加上 `-`,主要為額外說明或區別 * 以宣告來看,可以猜想是傳入資訊給 `MODULE_INFO` 並記錄 ```cpp #define MODULE_LICENSE(_license) MODULE_INFO(license, _license) #define MODULE_AUTHOR(_author) MODULE_INFO(author, _author) #define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description) #define MODULE_VERSION(_version) MODULE_INFO(version, _version) ``` ```cpp #define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info) ``` #### [linux/moduleparam.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/moduleparam.h#L21) 追溯下去發現又多了好幾個 macro ```cpp #define __MODULE_INFO(tag, name, info) \ static const char __UNIQUE_ID(name)[] \ __used __attribute__((section(".modinfo"), unused, aligned(1))) \ = __stringify(tag) "=" info ``` #### [linux/compiler.h](https://elixir.bootlin.com/linux/v4.18.16/source/includelinux/compiler.h#L166) 先觀察第一個 macro `__UNIQUE_ID` ```cpp # define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __LINE__) ``` #### [linux/compiler_types.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/compiler_types.h#L53) 這邊兩個井字號的 [`a##b`](https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html#Concatenation) 實際上是做文字連接,將兩邊的 token 合併為一個 ```cpp #define ___PASTE(a,b) a##b #define __PASTE(a,b) ___PASTE(a,b) ``` 所以可以簡化為 `__UNIQUE_ID(prefix) = ((__UNIQUE_ID_ + prefix) + __LINE__)` #### [linux/compiler-gcc.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/compiler-gcc.h#L183) * GNU 提供 [attribute](https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html#Attribute-Syntax) 功能讓 [function](https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes)、[variable](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes) 等等能夠添加屬性 * `attribute(used)` 表示即使物件未參照到,仍要將其放入 ELF[^[1]^](http://sp1.wikidot.com/elfobjfile)[^[2]^](http://www.study-area.org/cyril/opentools/opentools/x909.html),通常用於 inline * 變數的部份有提到如果使用 `used`,需要一併使用 `static` ```cpp #define __used __attribute__((__used__)) ``` #### [linux/moduleparam.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/moduleparam.h#L21) * 與 `__used` 一樣,用來修飾前面的 `__UNIQUE_ID` * `section` 編譯器一般會將 code 放在 ELF 的 `.text` 區段,這樣可以指定存放的區段 * `unused` 表示不一定會用到,即使沒使用到編譯也不會跳警告 * `aligned` 指定最小的 alignment (byte),數值必須為 2 的次方 ```cpp __attribute__((section(".modinfo"), unused, aligned(1))) ``` #### [linux/stringify.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/stringify.h#L10) * 最後的 macro `__stringify`,這邊的 [`#x`](https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing) 實際上是將傳入的引數轉成 const string * 這邊引數的 `...` 是 [variadic macros](https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html),表示可以有多個引數 * 不寫成一個 macro 主要是為了讓引數也能用 macro,參考 [argument prescan](https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html) * 考慮先 `#define foo bar` * `__stringify(foo)` 等同 `__stringify(bar)`,得到 `bar` * 如果只用 `#x`,會直接轉 macro,得到 `foo` ```cpp #define __stringify_1(x...) #x #define __stringify(x...) __stringify_1(x) ``` * 所以可以簡化為 `tag "=" info`,整個在這邊會是一個字串 * 歸納來看 `__MODULE_INFO` 可以視為 `const char <name>[] = "<tag> = <info>"` 考慮 `MODULE_AUTHOR(CSIE)`,實際流程如下 1. `MODULE_INFO(author, CSIE)` 2. `__MODULE_INFO(author, author, CSIE)` 3. `const author[] = "author = CSIE"` 4. 寫入 ELF 的 `.modinfo` 區段 `insmod` 的實際操作併入第二問一起解釋 ### 2. 當我們透過 `insmod` 去載入一個核心模組時,為何 `module_init` 所設定的函式得以執行呢?Linux 核心做了什麼事呢? #### [linux/module.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/module.h#L86) 作為模組初始化的進入點,依照模組的種類,會使用不同的 `module_init()`,一種是給 builtin,執行於 kernel boot;另一種是給 module,執行於 module insertion,下面都會再詳述 ```cpp #ifndef MODULE ... #define module_init(x) __initcall(x); ... #else ... #define module_init(initfn) ... ``` #### 第一種 builtin module(靜態編譯) #### [linux/init.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/init.h#L206) 第一種 `module_init(x)` 的 `__initcall(x)` 會等同 `__define_inicall(x, 6)` ```cpp #define device_initcall(fn) __define_initcall(fn, 6) ... #define __initcall(fn) device_initcall(fn) ``` * 等同宣告一個型態為 `initcall_t` 的函式指標 `__inicall_fn6`,函式回傳值為 int * 引數 `id` 使得複數的 initcall 可以指向同一個函式 handler,而不會造成重複 symbol 的問題 * 使用 `__used` 強制放入 ELF * `__attribute__((__section__(".initcall" #id ".init")))` 表示放入 ELF 名為 `.initcall6.init` 的區段 * 將函式指標指向引數 `fn` ```cpp typedef int (*initcall_t)(void); ... #define __define_initcall(fn, id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" #id ".init"))) = fn; ``` 在 kernal boost 過程中會執行 `do_one_initcall`,前面的 `id` 在此便代表執行的優先順序 ```cpp extern int do_one_initcall(initcall_t fn); ``` 由於這部份非本次重點,有興趣可以詳見 [linux内核驱动之 module_init 解析](https://blog.csdn.net/Richard_LiuJH/article/details/45669207),只是要注意該 `vmlinux.lds.S` 是 arm 架構的,Ubuntu 的可以參考 [Where is vmlinux](https://superuser.com/questions/62575/where-is-vmlinux-on-my-ubuntu-installation) #### 第二種 external module(動態編譯) #### [linux/module.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/module.h#L129) * 透過 [`insmod`](http://www.skrenta.com/rt/man/insmod.1.html) 添加的模組肯定是需要 module insertion,所以適用第二種 * 觀察 inline 函式[^[1]^](http://www.greenend.org.uk/rjk/tech/inline.html)[^[2]^](http://gnitnaw.github.io/程式語言/2016/06/14/C_inline.html),主要作為編譯器最佳化的方式,直接在呼叫函式的地方改寫,減少呼叫的 overhead * GNU 和 C99 的 static inline 規則是一樣的 ```cpp #define module_init(initfn) \ static inline initcall_t __maybe_unused __inittest(void) \ { return initfn; } \ int init_module(void) __attribute__((alias(#initfn))); ``` #### [linux/compiler-gcc.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/compiler-gcc.h#L147) `__maybe_unused` 和前面 `__used` 類似,參考 [Linux 2007 的 commit](https://github.com/torvalds/linux/commit/0d7ebbbc6eaa5539f78ab20ed6ff1725a4e332ef),表示可能不會被參照,不要產生警告 ```cpp #define __maybe_unused __attribute__((unused)) ``` * `inline __inittest()` 可以簡化為單純傳回引數 `initfn` * 用途是為了檢查傳入的 `initfn` 是否對應 `initcall_t` 格式,否則會在編譯時出錯,可參考 [the advantage of inittest](https://stackoverflow.com/questions/26118635/what-is-the-advantage-of-inittest-in-kernel) #### [linux/module.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/module.h#L129) * inline 後面定義了一個函式 `int init_module(void)`,宣告是可被外部參照的 * [function attribute](https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes) 提到 `alias` 用於定義 `alias` 後面函式的別名,所以後面的函式一定要存在 * `#initfn` 用來將 `initfn` 轉為字串 ```cpp extern void init_module(void); ... int init_module(void) __attribute__((alias(#initfn))); ``` 可以簡化為呼叫 `init_module()` 會等同執行 `initfn` 而不需額外定義內容,所以 inline 的檢查相當重要 ==後續 insmod 何時呼叫此 init_module?== #### [busybox/modutils/insmod.c](https://elixir.bootlin.com/busybox/1.27.2/source/modutils/insmod.c#L49) * `insmod` 會呼叫 `bb_init_module()`,並傳入模組名稱與後綴的選項 * `filename` 用 `*++argv` 取得第二個引數,對應到 `$ insmod fibdrv` ```cpp= int insmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int insmod_main(int argc UNUSED_PARAM, char **argv) { char *filename; int rc; filename = *++argv; ... rc = bb_init_module(filename, parse_cmdline_module_options(argv, /*quote_spaces:*/ 0)); if (rc) bb_error_msg("can't insert '%s': %s", filename, moderror(rc)); return rc; } ``` #### [busybox/modutils/modutils.c](https://elixir.bootlin.com/busybox/1.27.2/source/modutils/modutils.c#L194) 註解提到在初始化模組時有兩種 syscall,會優先嘗試使用 file descriptor 的 `finit_module()`,如果不行才用一般的 `init_module()`,詳細可參考 [Michael Kerrisk 的說明](https://lwn.net/Articles/519010/) * 首先看下半部的 `init_module()` * `image_size` 為模組的配置大小,`INT_MAX - 4095` 可以改寫為 2147483647 (2^31^-1) - 4096 + 1 * 試著執行 `$ getconf PAGE_SIZE`,可以發現 4096 剛好等於 page size,關於數字來源可以參考 [page size of Linux (x86) 4KB](https://stackoverflow.com/questions/11543748/why-is-the-page-size-of-linux-x86-4-kb-how-is-that-calculated) ==為何要這樣初始化?== * 接著經由三步驟 * 開啟 ELF 檔 * 使用 `mmap()` 將檔案內容放入記憶體 * 呼叫 `init_module()` 此 syscall * 引入 `finit_module` 的考量 * 因為從 image 獲取 fd 與 module loading 是分開的,使得 OS 失去驗證原始檔案信賴度的能力 * 與其透過 `NULL` 繼續延用 `init_module()`,透過新增一個 syscall 來傳遞 fd 反而更好 ```cpp= int FAST_FUNC bb_init_module(const char *filename, const char *options) { size_t image_size; char *image; int rc; bool mmaped; if (!options) options = ""; ... /* * First we try finit_module if available. Some kernels are configured * to only allow loading of modules off of secure storage (like a read- * only rootfs) which needs the finit_module call. If it fails, we fall * back to normal module loading to support compressed modules. */ # ifdef __NR_finit_module { int fd = open(filename, O_RDONLY | O_CLOEXEC); if (fd >= 0) { rc = finit_module(fd, options, 0) != 0; close(fd); if (rc == 0) return rc; } } # endif image_size = INT_MAX - 4095; mmaped = 0; image = try_to_mmap_module(filename, &image_size); if (image) { mmaped = 1; } else { errno = ENOMEM; /* may be changed by e.g. open errors below */ image = xmalloc_open_zipped_read_close(filename, &image_size); if (!image) return -errno; } errno = 0; init_module(image, image_size, options); rc = errno; if (mmaped) munmap(image, image_size); else free(image); return rc; } ``` 原始碼上面定義了 [`finit_module() `](https://linux.die.net/man/2/finit_module) 與 [`init_module()`](http://man7.org/linux/man-pages/man2/init_module.2.html),由於使用到 [`syscall()`](http://man7.org/linux/man-pages/man2/syscall.2.html),所以需要權限才能執行,也就是 `sudo insmod` ```cpp #define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts) #if defined(__NR_finit_module) # define finit_module(fd, uargs, flags) syscall(__NR_finit_module, fd, uargs, flags) #endif ``` #### [sys/syscall.h](https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/sys/syscall.h.html) * `asm/unistd.h` 定義了 system call `__NR_<name>` 所對應的編號,[一說](https://stackoverflow.com/questions/8306202/what-does-nr-stand-for-in-system-call-number-that-is-usually-used-as-suffix) NR 表示 numeric reference * 而 `bits/syscall.h` 主要是為了兼容舊系統使用 `SYS_<name>`,另外將 `__NR` 轉換 ```cpp /* This file should list the numbers of the system calls the system knows. But instead of duplicating this we use the information available from the kernel sources. */ #include <asm/unistd.h> #ifndef _LIBC /* The Linux kernel header file defines macros `__NR_<name>', but some programs expect the traditional form `SYS_<name>'. So in building libc we scan the kernel's list and produce <bits/syscall.h> with macros for all the `SYS_' names. */ # include <bits/syscall.h> ``` #### [asm/unistd_64.h](https://code.woboq.org/userspace/include/asm/unistd_64.h.html#317) * 這數字表示對應 system call table 裡的 syscall `__x64_sys_finit_module` * 可參考 [x86 64 Linux system call table](http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/),或是查看最新版本的 [`arch/x86/entry/syscalls/syscall_64.tbl`](https://elixir.bootlin.com/linux/v4.18.16/source/arch/x86/entry/syscalls/syscall_64.tbl) ```cpp #define __NR_finit_module 313 ``` #### [bits/syscall.h](https://code.woboq.org/gcc/include/bits/syscall.h.html) 轉換只是重新定義 ```cpp #ifdef __NR_finit_module # define SYS_finit_module __NR_finit_module #endif ``` 可以簡化為呼叫 `finit_module()` 等同呼叫 `syscall(__x64_sys_finit_module, fd, uargs, flags)` #### [linux/syscalls.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/syscalls.h#L566) * 這邊宣告了許多 syscall handler 的原型,實作定義在 `kernel/module.c` * `asmlinkage` 是 gcc 的標籤,表示函式的參數要從 stack 拿而非 register (x64 常見),可以參考[什麼是 "asmlinkage"?](http://www.jollen.org/blog/2006/10/_asmlinkage.html) * `__user` 表示從用戶空間取得 ```cpp /* kernel/module.c */ asmlinkage long sys_finit_module(int fd, const char __user *uargs, int flags); ``` * 原始碼上面也定義了 syscall 對應的 socket * 也提到實作是透過 `__do_sys_*()` 的函式來執行 * [Linux 系统调用 (syscall) 原理](http://gityuan.com/2016/05/21/syscall/) * [Linux 系统调用之 SYSCALL_DEFINE](https://blog.csdn.net/hxmhyp/article/details/22699669) ```cpp= ``` 考慮 `insmod fibdrv.ko` 實際流程如下 * `bb_init_module("fibdrv.ko", parse_cmdline_module_options())` * `finit_module(image, image_size, options)` * `syscall(__x64_sys_finit_module, image, image_size, options)` ?? [arch/x86/kernel/vmlinux.lds.S](https://elixir.bootlin.com/linux/v4.18.16/source/arch/x86/kernel/vmlinux.lds.S) [linux 模块机制浅析](https://blog.csdn.net/xiayulewa/article/details/45769017) ``` finit_module(3, "", 0) = 0 ``` #### [kernel/module.c](https://elixir.bootlin.com/linux/v4.18.16/source/kernel/module.c#L3435) 查看 init_module 的實作 ```cpp static noinline int do_init_module(struct module *mod) { ... /* Start the module */ if (mod->init != NULL) ret = do_one_initcall(mod->init); if (ret < 0) { goto fail_free_freeinit; } if (ret > 0) { pr_warn("%s: '%s'->init suspiciously returned %d, it should " "follow 0/-E convention\n" "%s: loading module anyway...\n", __func__, mod->name, ret, __func__); dump_stack(); } ... } ``` #### [kernel/module.c](https://elixir.bootlin.com/linux/v4.18.16/source/kernel/module.c#L3835) ```cpp SYSCALL_DEFINE3(init_module, void __user *, umod, unsigned long, len, const char __user *, uargs) { int err; struct load_info info = { }; err = may_init_module(); if (err) return err; pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n", umod, len, uargs); err = copy_module_from_user(umod, len, &info); if (err) return err; return load_module(&info, uargs, 0); } ``` ```cpp static int load_module(struct load_info *info, const char __user *uargs, int flags) { ... } ``` ### 試著執行 `$ readelf -a fibdrv.ko`, 觀察裡頭的資訊和原始程式碼及 `modinfo` 的關聯,搭配上述提問,解釋像 `fibdrv.ko` 這樣的 ELF 執行檔案是如何「植入」到 Linux 核心 #### `$ modinfo readelf -a fibdrv.ko` ```= filename: /home/ab37695543xs/hw2/fibdrv/fibdrv.ko version: 0.1 description: Fibonacci engine driver author: National Cheng Kung University, Taiwan license: Dual MIT/GPL srcversion: 24DC5FB7E7608AF16B0CC1F depends: retpoline: Y name: fibdrv vermagic: 4.18.0-16-generic SMP mod_unload ``` 第 2 到 5 行對應到了 `fibdrv.c` 的 `MODULE_LICENSE`、`MODULE_AUTHOR` 等輸出 ```cpp MODULE_LICENSE("Dual MIT/GPL"); MODULE_AUTHOR("National Cheng Kung University, Taiwan"); MODULE_DESCRIPTION("Fibonacci engine driver"); MODULE_VERSION("0.1"); ``` 其餘則是定義在編譯產生出的 `fibdrv.mod.c`,`MODULE_INFO` 的功能與上面大同小異,只是多了一個 `tag` 引數要傳入 ```cpp MODULE_INFO(vermagic, VERMAGIC_STRING); MODULE_INFO(name, KBUILD_MODNAME); ... #ifdef RETPOLINE MODULE_INFO(retpoline, "Y"); #endif ... MODULE_INFO(srcversion, "24DC5FB7E7608AF16B0CC1F"); ``` * 定義了一個字串 `__module_depends = "depends="` * `__used` 搭配 `static` 同前面所述,強迫變數放入 ELF * `__attribute__((section(".modinfo")))` 表示放入 ELF 名為 `.modinfo` 的區段,與前面追溯下來的目的地相同 ```cpp static const char __module_depends[] __used __attribute__((section(".modinfo"))) = "depends="; ``` * 定義了一個結構 `__this_module = {...}` * `__visible` 讓此結構能被外部參照,詳見下面 * `__attribute__((section(".gnu.linkonce.this_module")))` 表示放入 ELF 名為 `.gnu.linkonce.this_module` 的區段 * `.name` 使用 `KBUILD_MODNAME` 取得模組名稱 * `.init` 設定 [`init_module`](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/module.h#L132) 為初始化的函式 * `.exit` 只有在編譯 `unload` 時才會定義,設定 `cleanup_module` 為卸載時的函式 ```cpp __visible struct module __this_module __attribute__((section(".gnu.linkonce.this_module"))) = { .name = KBUILD_MODNAME, .init = init_module, #ifdef CONFIG_MODULE_UNLOAD .exit = cleanup_module, #endif .arch = MODULE_ARCH_INIT, }; ``` #### [linux/compiler-gcc.h](https://elixir.bootlin.com/linux/v4.18.16/source/include/linux/compiler-gcc.h#L275) * [`externally_visible`](https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes) 主要是讓物件在當前的編譯個體,也能被外部使用 * 和 gcc 最佳化的指令 [`-fwhole-program`](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Optimize-Options) 相反,這會將除了 `main` 和 `externally_visible` 的部份都做較激進的最佳化 ```cpp #define __visible __attribute__((externally_visible)) ``` #### 使用 [objdump](https://sourceware.org/binutils/docs/binutils/objdump.html) 檢查 object file * 使用 `-x` 列出所有標頭資訊 * section 即區段部份可以看到最後一個為 `.modinfo` * 查看 symbol table,可以看到上面 `MODULE_INFO` 的部份都已轉成 `__UNIQUE_ID` 的型式放在 `.modinfo` 區段 * 上半部訊息定義在 `fibdrv.c`,下半部則定義在編譯出的 `fibdrv.mod.c` ``` $ objdump -x fibdrv.ko 區段: Idx Name Size VMA LMA File off Algn 0 .note.gnu.build-id 00000024 0000000000000000 0000000000000000 00000040 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA ... 8 .modinfo 000000ec 0000000000000000 0000000000000000 00000560 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA SYMBOL TABLE: ... 0000000000000000 l O .modinfo 000000000000000c __UNIQUE_ID_version26 000000000000000c l O .modinfo 0000000000000024 __UNIQUE_ID_description25 0000000000000030 l O .modinfo 000000000000002e __UNIQUE_ID_author24 000000000000005e l O .modinfo 0000000000000015 __UNIQUE_ID_license23 0000000000000000 l df *ABS* 0000000000000000 fibdrv.mod.c 0000000000000078 l O .modinfo 0000000000000023 __UNIQUE_ID_srcversion16 00000000000000a0 l O .modinfo 0000000000000009 __module_depends 00000000000000a9 l O .modinfo 000000000000000c __UNIQUE_ID_retpoline15 00000000000000b5 l O .modinfo 000000000000000c __UNIQUE_ID_name14 00000000000000c1 l O .modinfo 000000000000002b __UNIQUE_ID_vermagic13 ``` 使用 `-s` 查看所有內容,可以發現資訊都正確地紀錄在 `.modinfo` ``` $ objdump -s fibdrv.ko ... 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 6f6e3d32 34444335 46423745 37363038 on=24DC5FB7E7608 0090 41463136 42304343 31460000 00000000 AF16B0CC1F...... 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 2d31362d 67656e65 72696320 534d5020 -16-generic SMP 00e0 6d6f645f 756e6c6f 61642000 mod_unload . ``` 放入區段的過程與 linker 與 linker script 有關,可以詳見 * [GNU ld 的 linker script 簡介](https://www.slideshare.net/zzz00072/gnu-ldlinker-script) * [Linker Script 初探](http://wen00072.github.io/blog/2014/03/14/study-on-the-linker-script/) * [史丹佛大學的 ld.info](http://www.slac.stanford.edu/comp/unix/package/rtems/doc/html/ld/ld.info.Scripts.html) * [預設 linker script](https://gist.github.com/csukuangfj/c4bd4f406912850efcbedd2367ac5f33) (`$ ld --verbose`) * [Linux 二進位檔中的 section](https://alittleresearcher.blogspot.com/2015/02/linux-section.html) ### 這個 `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 核心模組範例 ### 注意到 `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) 運算嗎?請列出關鍵程式碼並解說