# 2019q1 Homework2 (fibdrv) contributed by < `Shengyuu` > ## Enviroment ```shell 4.15.0-46-generic ``` ## 自我檢查清單 ### 1. 檔案 fibdrv.c 裡頭的 MODULE_LICENSE, MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_VERSION 等巨集做了什麼事,可以讓核心知曉呢? insmod 這命令背後,對應 Linux 核心內部有什麼操作呢?請舉出相關 Linux 核心原始碼並解讀 這幾個巨集並非用在 Kernel 的本身,而是在告訴其他人這個 kernel module 的基本資訊,包括作者、版權、描述、版本。我們可以在 linux/module.h 裡面找到這些巨集的定義 ```c #define MODULE_LICENSE(_license) MODULE_INFO(license, _license) #define MODULE_AUTHOR(_author) MODULE_INFO(author, _author) #define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description) ``` 從上述程式碼可看到這幾個巨集都使用了 MODLUE_INFO 這個巨集,而 MODULE_INFO 也是定義在 linux/modlue.h 的 ```c #define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info) ``` MODULE_INFO 的定義右使用了巨集 __MODULE_INFO,在找一下會發現它被定義在 /linux/moduleparam.h 中,我們再來看看這個巨集又做了什麼事 ```c #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 ``` 先不看中間 `__used` 和 `__attribute__` 的修飾,其實這個巨集就是在宣告一個字元陣列並給予特定字串,可簡化成以下形式比較好理解 ```c static const char __UNIQUE_ID(name)[] = __stringify(tag) "=" info ``` ## 什麼是 `__attribute__` 、 `section` 在詳細探討這個聚集之前要先來了解什麼是 `__attribute__` 以及 `section` - `__attribute__` 主要用於改變所聲明或定義的函數或資料的特徵,它有很多用於改變不同特徵的子向,例如程式碼中的 `section` 、`unused` 、`aligned` 等等 - **`unused` :** 告訴 GCC 這個變數可能不會被用到,GCC 就不會對此提出 warming ```c #include<stdio.h> int main() { static int a = 1; static int b __attribute__((unused)) = 2; return 0; } ``` ``` $ gcc -o att att.c -Wall att.c: In function ‘main’: att.c:5:14: warning: unused variable ‘a’ [-Wunused-variable] static int a = 1; ^ At top level: att.c:5:14: warning: ‘a’ defined but not used [-Wunused-variable] ``` - **`used`:** 給予 static variable `used` 會強迫該變數被 emitted 即使這個變數沒有被 referenced >used This attribute, attached to a variable with static storage, means that the variable must be emitted even if it appears that the variable is not referenced. 節錄 [Common Variable Attributes](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes) > 參考 [cjwind同學](https://hackmd.io/s/SkW25-2UV) 做的實驗 ```c #include<stdio.h> static int a = 3; int main() { return 0; } ``` 用 nm 指令可以列出目標文件(.o)的符號清單 ``` $ gcc -O1 -c used.c -Wall $ nm used.o 0000000000000000 T main ``` 將 static int 變數宣告的地方加上 `__attribute((used))` 之後再用 `nm` 觀察一次 ```c #include<stdio.h> static int a = 3; int main() { return 0; } ``` ``` $ gcc -O1 -c used.c -Wall $ nm used.o 0000000000000000 d a 0000000000000000 T main ``` - **`section`:** 可以在 object file 上加上一個 section,要注意的是 section 只能設定在 global variable 上 >Sometimes you need additional sections, or you need certain particular variables to appear in special sections. 節錄 [Common Variable Attributes](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes) > 來個小範例 ```c #include<stdio.h> int a __attribute__((section ("test_test"))); int main() { return 0; } ``` ``` $ gcc -O1 -c section.c -Wall $ readelf -a used.o Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 0000000000000000 00000040 0000000000000006 0000000000000000 AX 0 0 1 [ 2] .data PROGBITS 0000000000000000 00000046 0000000000000000 0000000000000000 WA 0 0 1 [ 3] .bss NOBITS 0000000000000000 00000046 0000000000000000 0000000000000000 WA 0 0 1 [ 4] test_test PROGBITS 0000000000000000 00000048 0000000000000004 0000000000000000 WA 0 0 4 ``` - **aligned:** 可用來設定變數或 struct 以多少 bytes 對齊,對齊的長度必須是 2 的整數次方 >The aligned attribute specifies a minimum alignment for the variable or structure field, measured in bytes. When specified, alignment must be an integer constant power of 2. 節錄 [Common Variable Attributes](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes) > 小範例 ```c #include<stdio.h> struct p { int a; char b; char c; int d; } pp; struct q { int a; char b; char c; int d; }__attribute__((aligned(1024))) qq; int main() { printf("pp = %d\n qq = %d \n", sizeof(pp), sizeof(qq)); return 0; } ``` ``` $ gcc -o aligned aligned.c -Wall $ ./aligned pp = 12 qq = 1024 ``` > `__attribute__` 語法說明參考[Attribute Syntax](https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html) 對變數使用 `__attribute__` 參考[Variable Attribute](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#Common-Variable-Attributes) > [color=#77FF00] ## 深入__module_info 再來詳細探討該巨集對這個字元陣列做了什麼 - `__used` 定義在 [/ linux / compiler_attributes.h ](https://github.com/torvalds/linux/blob/64c0133eb88a3b0c11c42580a520fe78b71b3932/include/linux/compiler_attributes.h) ```c #define __used __attribute__((__used__)) ``` 這個巨集的作用就是將變數或函式的屬性設成 `__used__`,好讓目標檔會產生該變數或寒是的 symbol - `__stringnify(tag)__` 定義在[/ linux / stringify.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/stringify.h) ```c /* SPDX-License-Identifier: GPL-2.0 */ #ifndef __LINUX_STRINGIFY_H #define __LINUX_STRINGIFY_H /* Indirect stringification. Doing two levels allows the parameter to be a * macro itself. For example, compile with -DFOO=bar, __stringify(FOO) * converts to "bar". */ #define __stringify_1(x...) #x #define __stringify(x...) __stringify_1(x) #endif /* !__LINUX_STRINGIFY_H */ ``` >這裡用兩層 macro 是為了讓 macro 可以使用變數傳入,可以 參考 [cjwind同學](https://hackmd.io/s/SkW25-2UV)詳細的解說 > 了解完這些 macro 的定義後我們可以發現 `__module_info` 這個巨集會在目標檔(.o)中 .modinfo 的 section 內加入一個字串,而這個字串就是 `<tag>=<info>` ,也就是 license、author 等資訊 ### 2. 當我們透過 insmod 去載入一個核心模組時,為何 module_init 所設定的函式得以執行呢?Linux 核心做了什麼事呢? 我們先來看看 module_init 在 /linux/module.h 內的定義 ```c #ifndef MODULE /** * module_init() - driver initialization entry point * @x: function to be run at kernel boot time or module insertion * * module_init() will either be called during do_initcalls() (if * builtin) or at module insertion time (if a module). There can only * be one per module. */ #define module_init(x) __initcall(x); .......... ....... ... #else /* MODULE */ /* * In most cases loadable modules do not need custom * initcall levels. There are still some valid cases where * a driver may be needed early if built in, and does not * matter when built as a loadable module. Like bus * snooping debug drivers. */ #define module_init(initfn) \ static inline initcall_t __maybe_unused __inittest(void) \ { return initfn; } \ int init_module(void) __copy(initfn) __attribute__((alias(#initfn))); ``` 從標頭檔的定義中我們可以看到 module_init 的宣告分為有 define module 和沒有 define module 兩個版本。前者代表這個 device driver 是 Loadable kernel module(LKM); 而後者代表 driver 是 Built-in kernel modules **參考 [Difference between Linux Loadable and built-in modules](https://stackoverflow.com/questions/22929065/difference-between-linux-loadable-and-built-in-modules)** ## 剖析 module_init (LKM) 再來詳細看看這次作業用到的 module_init (LKM) 做了什麼 ```c #define module_init(initfn) \ static inline initcall_t __maybe_unused __inittest(void) \ { return initfn; } \ int init_module(void) __copy(initfn) __attribute__((alias(#initfn))); ``` - 首先來看看 `initcall_t` ,它是一個被定義在 [/linux/init.h](https://github.com/torvalds/linux/blob/master/include/linux/init.h) 內的一個 **pointer to function** ```c typedef int (*initcall_t)(void); ``` 再回頭看看 `static inline ...` ,看起來是在檢查傳入的函式 `initfn` 的回傳型態有沒有和 `initcall_t` 定義的一樣。試著把 `fibdrv.c` 裡頭 `init_fib_dev` 的回傳型態改為 `double` 看看會發生什麼事 ```c static double __init init_fib_dev(void) double rc = 0; ``` ``` $ make /home/yao/Linux_Class/fibdrv/fibdrv.c: In function ‘__inittest’: /home/yao/Linux_Class/fibdrv/fibdrv.c:171:168: error: return from incompatible pointer type [-Werror=incompatible-pointer-types] module_init(init_fib_dev); ``` 從 error 的提示看出傳入 `module_init` 的函式回傳的 type 一定要符合 `initcall_t` 定義的型態否則就會發生 error - macro 中的第二段其實就是將 `initfn` 取個別名,這樣使用 `init_module` 系統呼叫的時候就會呼叫到我們定義的 initial function >之後補完整 :::warning 在做這部份的時候有個疑惑 就是在 `fibdrv.c` 中倒數第二行使用 `module_init` macro 的時候,傳入的函式並沒有加括號 ```c module_init(init_fib_dev); ``` 這樣的寫法應該會得到 `init_fib_dev` 函式的記憶體位置,如此一來 macro 中函式 `inittest` return 的值和 `init_fib_dev` 這個函式回傳的值好像就沒有什麼關係了 做個小測試: ```c #include<stdio.h> #define module_init(initfn) \ initcall_t inittest(void) \ {return initfn;} \ printf("%d",inittest()); typedef int (*initcall_t)(void); static double fun(void) { return 3.0; } int main() { module_init(fun); return 0; } ``` ``` $ gcc -o init init.c init.c:6:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘initcall_t {aka int (*)(void)}’ [-Wformat=] printf("%d",inittest()); $ ./init 92538538 ``` 從結果可以看出來,即使 `inittest` 函式回傳型態是 double ,編譯一樣不會有 error ,而且 return 的值真的是 `fun` 的記憶體位置 後來又是著按照 [afcidk](https://hackmd.io/s/r1ginnn8N) 同學的方法,去看 `fibdrv.c` 經過預處理過後的樣子 ```c static inline __attribute__((unused)) __attribute__((no_instrument_function)) __attribute__((gnu_inline)) initcall_t __attribute__((unused)) __inittest(void) { return init_fib_dev; } int init_module(void) __attribute__((alias("init_fib_dev")));; ``` 確定 `init_fib_dev` 後面沒有接著括號 但是如果直接更改 `fibdrv.c` 中的 `init_fib_dev` 函式的回傳型態又會發生 error ::: ### 3. 試著執行 $ readelf -a fibdrv.ko, 觀察裡頭的資訊和原始程式碼及 modinfo 的關聯,搭配上述提問,解釋像 fibdrv.ko 這樣的 ELF 執行檔案是如何「植入」到 Linux 核心 ### 4. 這個 fibdrv 名稱取自 Fibonacci driver 的簡稱,儘管在這裡顯然是為了展示和教學用途而存在,但針對若干關鍵的應用場景,特別去撰寫 Linux 核心模組,仍有其意義,請找出 Linux 核心的案例並解讀。提示: 可參閱 Random numbers from CPU execution time jitter ### 5. 查閱 ktime 相關的 API,並找出使用案例 (需要有核心模組和簡化的程式碼來解說) ### 6. clock_gettime 和 High Resolution TImers (HRT) 的關聯為何?請參閱 POSIX 文件並搭配程式碼解說 ### 7. fibdrv 如何透過 Linux Virtual File System 介面,讓計算出來的 Fibonacci 數列得以讓 userspace (使用者層級) 程式 (本例就是 client.c 程式) 得以存取呢?解釋原理,並撰寫或找出相似的 Linux 核心模組範例 ### 8. 注意到 fibdrv.c 存在著 DEFINE_MUTEX, mutex_trylock, mutex_init, mutex_unlock, mutex_destroy 等字樣,什麼場景中會需要呢?撰寫多執行緒的 userspace 程式來測試,觀察 Linux 核心模組若沒用到 mutex,到底會發生什麼問題 ### 9. 許多現代處理器提供了 clz / ctz 一類的指令,你知道如何透過演算法的調整,去加速 費氏數列 運算嗎?請列出關鍵程式碼並解說 ## 測試給定的 fibdrv 模組 - 在最後編譯並測試輸入```$ make check``` 產生了 ``` insmod: ERROR: could not insert module fibdrv.ko: Required key not available ``` >爬文的過程中找到這篇文章[Linux 内核模块](https://jin-yang.github.io/post/kernel-modules.html)問題出在 Modlue signature verification ,如果 UEFI Secure Boot 啟動,那麼內核所有模塊都必須使用 UEFI Secure key 簽名,去 BIOS 關閉 UEFI Secure Boot 才節決這個文題 > :::warning 還不是很了結文中提到的 Modlue signature verification 之後有時間再回來看一下 ::: - 解決上面的問題之後再輸入一次```$ make check```還是有錯誤 ``` insmod: error inserting 'fibdrv.ko': -1 Invalid module format ``` >這次的問題是因為 make 時使用的核心版本和執行中的核心版本不一致 > 查看系統核心版本 ``` $ uname -r 4.15.0-46-generic ``` :::danger 尊重台灣科技前輩的篳路藍縷,使用台灣慣用術語 :notes: jserv ::: 查看 make 使用的核心版本 ``` $ modinfo fibdrv.ko 4.15.0-45-generic SMP mod_unload ``` >輸入以下命令重新編譯 module 後問題就解決了 ```shell $ make clean $ make all $ make check ``` ## 參考資料 [解析Linux 內核可裝載模塊的版本檢查機制](https://www.ibm.com/developerworks/cn/linux/l-cn-kernelmodules/index.html) [C/C++ 的預處理定義: #, #@, ##](https://blog.xuite.net/jesonchung/scienceview/93554778-C%2FC%2B%2B+%E7%9A%84%E9%A0%90%E8%99%95%E7%90%86%E5%AE%9A%E7%BE%A9+%3A+%23+%2C++%23%40+%2C+%23%23) ###### tags: `Linux 核心設計 2019`