--- tags: linux2022, kernel module --- # 在 `insmod` 時使用 `port=` 初始化核心模組的機制 contributed by < `freshLiver` > > 參照 Linux 核心模組掛載機制,解釋 `$ sudo insmod khttpd.ko port=1999` 這命令是如何讓 port=1999 傳遞到核心,作為核心模組初始化的參數呢? ## 呼叫 `insmod` 首先從 [`insmod` 的 man page](https://www.man7.org/linux/man-pages/man8/insmod.8.html) 中的 SYNOPSIS 的部份可以看到: ```shell insmod [filename] [module options...] ``` `port` 並不是 `insmod` 提供的參數,而是 `kecho` 核心模組接受的參數。而從 The Linux Kernel Module Programming Guide 中 [4.5 Passing Command Line Arguments to a Module](https://sysprog21.github.io/lkmpg/#passing-command-line-arguments-to-a-module) 的部份則可找到關於核心模組能透過 `module_param()` 提供參數: > To allow arguments to be passed to your module, declare the variables that will take the values of the command line arguments as global and then use the `module_param()` macro, (defined in [`include/linux/moduleparam.h`](https://github.com/torvalds/linux/blob/master/include/linux/moduleparam.h)) to set the mechanism up. At runtime, `insmod` will fill the variables with any command line arguments that are given, like `insmod mymodule.ko myvariable=5` . The variable declarations and macros should be placed at the beginning of the module for clarity. 因此在 `insmod` 時會將 `port=???` 傳給使用 `module_param()` 巨集指定的變數,而若嘗試展開這個巨集的話會得到以下結果: ```c /** * module_param - typesafe helper for a module/cmdline parameter * @name: the variable to alter, and exposed parameter name. * @type: the type of the parameter * @perm: visibility in sysfs. * * @name becomes the module parameter, or (prefixed by KBUILD_MODNAME and a * ".") the kernel commandline parameter. Note that - is changed to _, so * the user can use "foo-bar=1" even for variable "foo_bar". * * @perm is 0 if the variable is not to appear in sysfs, or 0444 * for world-readable, 0644 for root-writable, etc. Note that if it * is writable, you may need to use kernel_param_lock() around * accesses (esp. charp, which can be kfreed when it changes). * * The @type is simply pasted to refer to a param_ops_##type and a * param_check_##type: for convenience many standard types are provided but * you can create your own by defining those variables. * * Standard types are: * byte, hexint, short, ushort, int, uint, long, ulong * charp: a character pointer * bool: a bool, values 0/1, y/n, Y/N. * invbool: the above, only sense-reversed (N = true). */ #define module_param(name, type, perm) \ module_param_named(name, name, type, perm) ``` :::warning 可以注意的部份是當 commandline 呼叫時指定參數名稱中若包含 `-` 字元時,`-` 會被轉換成 `_` 並對應對應到轉換後的變數名稱。 ::: `module_param` 是定義在 `include/linux/moduleparam.h` 中的一個巨集,而它展開成相同標頭檔中的另一個巨集 `module_param_named`: ```c /** * module_param_named - typesafe helper for a renamed module/cmdline parameter * @name: a valid C identifier which is the parameter name. * @value: the actual lvalue to alter. * @type: the type of the parameter * @perm: visibility in sysfs. * * Usually it's a good idea to have variable names and user-exposed names the * same, but that's harder if the variable must be non-static or is inside a * structure. This allows exposure under a different name. */ #define module_param_named(name, value, type, perm) \ param_check_##type(name, &(value)); \ module_param_cb(name, &param_ops_##type, &value, perm); \ __MODULE_PARM_TYPE(name, #type) ``` 而 `module_param_named` 巨集包含了三個部份: ## 1. 檢查核心模組參數 首先會透過 `param_check_##type` 呼叫對應的型別檢查的巨集,在同標頭檔中定義了 Standard Types 的型別檢查巨集,而那些巨集又都會對應到 `__param_check` 這個巨集: ```c /* All the helper functions */ /* The macros to do compile-time type checking stolen from Jakub Jelinek, who IIRC came up with this idea for the 2.4 module init code. */ #define __param_check(name, p, type) \ static inline type __always_unused *__check_##name(void) { return(p); } ``` 這個巨集會根據給予的 `name` 建立一個回傳型別為 `type*` 的函式,而函式的定一則會回傳另一個參數 `p`,若是從 `module_param` 巨集展開的話,`p` 就相當於 `&(name)`,因此 `__param_check` 能夠在**編譯時期**達成以下作用: - `&(name)` 檢查 `name` 是已經被宣告過得變數 - 回傳 `p` 以確保 `name` 的型別與 `type` 相容 - 函式名稱可以避免對相同變數使用 `module_param` 造成函式被重複定義 :::info 其中 `__always_unused` 定義在 [`include/linux/compiler_attributes.h`](https://github.com/torvalds/linux/blob/master/include/linux/compiler_attributes.h) 中: ```shell $ grep -ir "define __always_unused" include/linux/compiler_attributes.h:#define __always_unused __attribute__((__unused__)) ``` 展開後會對應到 GCC C Extension 中 [Common Function Attributes](https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-unused-function-attribute) 的 `unused`: > This attribute, attached to a function, means that the function is meant to be possibly unused. GCC will not produce a warning for this function. 效果是讓編譯器不產生關於此函式的相關警告訊息,因此可以避免編譯器產生 `module_param_named` 巨集中定義的函式未被使用的**警告**訊息。 ::: ## 2. 建立存取核心模組參數所需的結構體物件 接著則是呼叫同樣是定義在 `include/linux/moduleparam.h` 的另一個巨集 `module_param_cb`,其定義如下: ```c /** * module_param_cb - general callback for a module/cmdline parameter * @name: a valid C identifier which is the parameter name. * @ops: the set & get operations for this parameter. * @arg: args for @ops * @perm: visibility in sysfs. * * The ops can have NULL set or get functions. */ #define module_param_cb(name, ops, arg, perm) \ __module_param_call(MODULE_PARAM_PREFIX, name, ops, arg, perm, -1, 0) /* This is the fundamental function for registering boot/module parameters. */ #define __module_param_call(prefix, name, ops, arg, perm, level, flags) \ /* Default value instead of permissions? */ \ static const char __param_str_##name[] = prefix #name; \ static struct kernel_param __moduleparam_const __param_##name __used \ __section("__param") __aligned(__alignof__(struct kernel_param)) = { \ __param_str_##name, \ THIS_MODULE, \ ops, \ VERIFY_OCTAL_PERMISSIONS(perm), \ level, \ flags, \ {arg}} ``` 而巨集 `module_param_cb` 可以再被展開成 `__module_param_call` 巨集,接著就可以看到熟悉的變數宣告語法: 1. 宣告一個 static const 字串變數,並初始化其值為 `prefix #name` 2. 宣告一個 `kernel_param` 結構體的 static 物件 `__param_##name` ```c struct kernel_param { const char *name; struct module *mod; const struct kernel_param_ops *ops; const u16 perm; s8 level; u8 flags; union { void *arg; const struct kparam_string *str; const struct kparam_array *arr; }; }; ``` 若是從 `module_param` 一路展開來的話,這個物件中的各個成員會被初始化成 : - `char *name` 會被初始化為 `__param_str_##name`,也就是傳入的變數名稱加上 `__param_str_` 的前綴字串。 - `struct module *mod` 則是對應定義在被 `kernel/params.c` 使用到的 `include/linux/module.h` 所包含的標頭檔 `include/linux/export.h` 中的巨集 `THIS_MODULE` : ```c // include/linux/export.h #ifndef __ASSEMBLY__ #ifdef MODULE extern struct module __this_module; #define THIS_MODULE (&__this_module) #else #define THIS_MODULE ((struct module *)0) #endif ``` 若是 `MODULE` 有被定義的話,則會在連結時期從外部取得 `module` 結構體的物件 `__this_module` 的位址作為 `THIS_MODULE`;若 `MODULE` 沒被定義的話則會是一指向 `0` 的指標。 在 `insmod` 過程中的 `load_module` 時會從 `.gnu.linkonce.this_module` 區段中取得當前操作的核心模組的物件。 :::danger 有使用 `$ grep -r "this_module"` 尋找定義 `this_module` 的地方,但仍沒有找到實際定義的地方,用 `$ objdump -s kecho.ko` 也只有在 `.gnu.linkonce.this_module` 區段中看到 `kecho` 以及一堆 0,不明白 `__this_module` 是怎麼取得當前的核心模組物件,也還沒找到是在哪邊被指定放在 `.gnu.linkonce.this_module` 區段。 ::: - `struct kernel_param_ops *ops` : ```c struct kernel_param_ops { /* How the ops should behave */ unsigned int flags; /* Returns 0, or -errno. arg is in kp->arg. */ int (*set)(const char *val, const struct kernel_param *kp); /* Returns length written or -errno. Buffer is 4k (ie. be short!) */ int (*get)(char *buffer, const struct kernel_param *kp); /* Optional function to free kp->arg when module unloaded. */ void (*free)(void *arg); }; ``` 這個結構體中包含了三個 function pointer,會在解析核心模組參數時被使用到,作用如其函式名稱以及註解。除了三個 function pointer 之外還包含了一個 flag 變數,可能的值同樣列舉在相同標頭檔下: ```c /* * Flags available for kernel_param_ops * * NOARG - the parameter allows for no argument (foo instead of foo=1) */ enum { KERNEL_PARAM_OPS_FL_NOARG = (1 << 0) }; ``` 但只有一個值,用來表示當參數為 0 時是否仍要存取,會在讀取模組參數時使用到。 而在這邊是將 `ops` 初始化成 `&param_ops_##type`,以 `port` 這個核心模組的參數為例的話就是 `param_ops_ushort` 這個 `kernel_param_ops` 結構體的物件的位址,若是非 Standard Type 的話也可以自行定義相關的 `kernel_param_ops` 結構體的物件來處理自訂型別的參數存取,以便在後面會提到的 `parse_one` 中**使用統一界面處理參數**。 :::warning 部份 Standard Type 對應的 `kernel_param_ops` 是[以巨集 `STANDARD_PARAM_DEF` 定義在 `kernel/params.c`](https://github.com/torvalds/linux/blob/672c0c5173427e6b3e2a9bbb7be51ceeec78093a/kernel/params.c#L217) 中: ```c #define STANDARD_PARAM_DEF(name, type, format, strtolfn) \ int param_set_##name(const char *val, const struct kernel_param *kp) \ { \ return strtolfn(val, 0, (type *)kp->arg); \ } \ int param_get_##name(char *buffer, const struct kernel_param *kp) \ { \ return scnprintf(buffer, PAGE_SIZE, format "\n", \ *((type *)kp->arg)); \ } \ const struct kernel_param_ops param_ops_##name = { \ .set = param_set_##name, \ .get = param_get_##name, \ }; \ EXPORT_SYMBOL(param_set_##name); \ EXPORT_SYMBOL(param_get_##name); \ EXPORT_SYMBOL(param_ops_##name) ``` 由於傳入核心模組的參數是以字串形式處理,因此解析參數時需要根據參數的型別調用不同的處理函式 `strtolfn`,以 `int` 為例的話就是 [`kstrtoint`](https://github.com/torvalds/linux/blob/672c0c5173427e6b3e2a9bbb7be51ceeec78093a/lib/kstrtox.c#L259) 這個定義在 [`lib/kstrtox.c`](https://github.com/torvalds/linux/blob/master/lib/kstrtox.c) 中的函式,然後再與後面會說明的 `arg`(即程式碼中的 `kp->arg`)進行互動,對實際傳入 `module_param` 的核心模組的參數進行存取。 ::: - `u16 perm` :::danger 核心模組的參數實際上會被以檔案的形式存在 `/sys/module/*/parameter/` 下,因此 `perm` 是用來設定相關的檔案權限。 ::: 而相關的權限 flag 定義在 [`include/uapi/linux/stat.h`](https://github.com/torvalds/linux/blob/master/include/uapi/linux/stat.h) 中,而各種模式的說明則在 [](https://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html) 文件中。而在這邊使用的權限 flag 是 `S_IRUGO`,可以展開成: ```c #define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH) ``` 因此根據 [`S_IRUSR`](https://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html#index-S_005fIRUSR), [`S_IRGRP`](https://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html#index-S_005fIRGRP) 以及 [`S_IROTH`](https://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html#index-S_005fIROTH) 的說明可以知道他的權限會被設定為成「所有人皆有讀取權限(0444)」的狀態,而 `VERIFY_OCTAL_PERMISSIONS` 則是會透過 `BUILD_BUG_ON_ZERO` 在編譯時期檢查檔案權限的數值是否合法。 :::danger 用途 ::: - `s8 level` :::danger 能夠設定的值域為 $[-128,127]$,在這邊被初始化成 -1,在 `parse_one` 中會檢查 `params[i].level < min_level || params[i].level > max_level` 是否成立,但在 `load_module` 中的 `parse_args` 傳送的 level 上下界分別是 -32768 與 32767,因此這個條件不會成立,不清楚這個 `level` 的作用。 ::: - `u8 flags` 在 `include/linux/moduleparam.h` 下列舉了兩個 `kernel_param` 結構體成員 `flag` 的值: ```c /* * Flags available for kernel_param * * UNSAFE - the parameter is dangerous and setting it will taint the kernel * HWPARAM - Hardware param not permitted in lockdown mode */ enum { KERNEL_PARAM_FL_UNSAFE = (1 << 0), KERNEL_PARAM_FL_HWPARAM = (1 << 1), }; ``` `KERNEL_PARAM_FL_UNSAFE` 這個 flag 會在使用 `module_param_unsafe` 巨集時被作為 flag 傳給 `__module_param_call`,而 `KERNEL_PARAM_FL_HWPARAM` 則是會在使用 `module_param_hw_named` 時作為 flag 的一部分傳入,但這邊使用的是 `module_param`,因此 flag 部份為 0。 - `union` - `void *arg` - `struct kparam_string *str` - `struct kparam_array *arr` 這三個成員是用來儲存,但陣列以及字串的比較特別,需要傳遞資料結構的相關資訊,例如字串需要傳遞字串長度,因此使用 union 儲存不同參數的位址。並利用了 [Anonymous union](https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html) 的技巧,讓 union 中的成員可以直接透過 `structure.unoin_member` 存取。 在這邊設定是將 `arg` 設為 `&(name)`,也就是傳給 `module_param` 的變數的位址,所以在後面能夠透過前面提到的 `ops` 對核心模組參數對應的變數進行 `set`、`get`、`free` 等操作。 除了宣告及初始化變數之外,需要注意部份還有幾個: - `__param_##name` 有透過 GCC C Extension 中 [Variable Attribute 的 `section`](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-section-variable-attribute) 指定這個變數要儲存在 `__param` 這個區段中。 - `__param_##name` 有透過 GCC C Extension 中 [Variable Attribute 的 `used`](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-used-variable-attribute),所以即使 `__param_##name` 以 `static` 宣告、且並未在其他地方被 reference,這個變數也一樣會被保證出現在 Symbol Table 中(emitted)。 簡單實驗驗證差異,[參考自此](https://stackoverflow.com/questions/61255179/setting-attribute-used-to-c-variable-constant-has-no-effect): ```sh $ cat decl.c && gcc -c decl.c && readelf -Ws decl.o // decl.c const static int unref_var_with_used __attribute__((used)) = 2; const static int unref_var_without_used = 3; Symbol table '.symtab' contains 4 entries: 編號: 值 大小 類型 約束 版本 索引名稱 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS decl.c 2: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 unref_var_with_used 3: 0000000000000004 4 OBJECT LOCAL DEFAULT 4 unref_var_without_used $ cat decl.c && gcc -O3 -c decl.c && readelf -Ws decl.o // decl.c const static int unref_var_with_used __attribute__((used)) = 2; const static int unref_var_without_used = 3; Symbol table '.symtab' contains 3 entries: 編號: 值 大小 類型 約束 版本 索引名稱 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS decl.c 2: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 unref_var_with_used ``` 會發現雖然在未開啟最佳化時兩個 static 變數都會出現在 Symbol Table 中,但若開啟最佳化的話,則只有有使用 `used` 的 `unref_var_with_used` 才會出現在 Symbol Table 中。 與前一項合起來看的話,可以知道這是為了確保 `__param_##name` 在之後 `insmod` 時能夠從 `__param` 區段中讀取資訊,並配合傳入的核心模組參數(後面會提到的 `mod->args`)對對應的變數(例如 `__param_port` 對應到 `port`)進行存取。 - `__param_##name` 有透過 GCC C Extension 中 [Variable Attribute 的 `aligned`](https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-aligned-variable-attribute) 確保其中的各個成員都是以 `__alignof__(struct kernel_param)` 個位元組為單位進行對齊。 :::danger 不知道為什麼需要特別指定對齊大小 ::: - `__moduleparam_const` ```c /* On alpha, ia64 and ppc64 relocations to global data cannot go into read-only sections (which is part of respective UNIX ABI on these platforms). So 'const' makes no sense and even causes compile failures with some compilers. */ #if defined(CONFIG_ALPHA) || defined(CONFIG_IA64) || defined(CONFIG_PPC64) #define __moduleparam_const #else #define __moduleparam_const const #endif ``` ## 3. 儲存剛剛建立的結構體物件到 `modinfo` 區段中 而在完成 `module_param_cb` 後則會繼續呼叫另一個巨集 `__MODULE_PARM_TYPE`: ```c #define __MODULE_PARM_TYPE(name, _type) \ __MODULE_INFO(parmtype, name##type, #name ":" _type) #define __MODULE_INFO(tag, name, info) \ static const char __UNIQUE_ID(name)[] \ __used __section(".modinfo") __aligned(1) \ = __MODULE_INFO_PREFIX __stringify(tag) "=" info ``` 而這個巨集最終可以展開成一個宣告變數的形式,將一個以 1 位元組對齊的 static const 字串的變數 `__UNIQUE_ID_##name##__COUNTER__` 儲存在 `.modinfo` 區段。 而在這邊的用途是將 以便在之後 `insmod` 呼叫到 `setup_load_info()` 時能夠找到這個變數。 - 字串內容為 `__MODULE_INFO_PREFIX __stringify(tag) "=" info` ```c static const char __UNIQUE_ID(name)[] __used __section(".modinfo") __aligned(1) = __MODULE_INFO_PREFIX __stringify(tag) "=" info ``` :::warning 待補 ::: ## 實際傳遞核心模組參數的流程 在 Linux 核心模組運作原理的 [Linux 核心模組掛載機制](https://hackmd.io/@sysprog/linux-kernel-module#Linux-%E6%A0%B8%E5%BF%83%E6%A8%A1%E7%B5%84%E6%8E%9B%E8%BC%89%E6%A9%9F%E5%88%B6)的部份有說明了 `insmod` 到 `module_init` 的流程了,但在文章中沒有說明有核心模組參數時的行為,因此嘗試加上參數後透過 strace 追蹤 `insmod` 的執行流程: ```shell $ sudo strace insmod kecho.ko port=22222 bench=1 execve("/usr/sbin/insmod", ["insmod", "kecho.ko", "port=22222", "bench=1"], 0x7fffeb8025a8 /* 17 vars */) = 0 ... openat(AT_FDCWD, "/home/freshliver/Dropbox/Notes/_jserv/linux/labs/kecho/kecho.ko", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1", 6) = 6 lseek(3, 0, SEEK_SET) = 0 newfstatat(3, "", {st_mode=S_IFREG|0664, st_size=834024, ...}, AT_EMPTY_PATH) = 0 mmap(NULL, 834024, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f43d5cf5000 finit_module(3, "port=22222 bench=1", 0) = 0 munmap(0x7f43d5cf5000, 834024) = 0 close(3) = 0 exit_group(0) = ? +++ exited with 0 +++ ``` 會發現在在呼叫 `finit_module` 時的第二個參數就是 `insmod` 時輸入的核心參數,接著再跟著掛載流程嘗試找出實際傳遞核心模組參數的地方: 1. 呼叫 `insmod` 掛載目標核心模組 2. 透過 `openat` 取得核心模組的 File Descriptor,並將其以及指定的核心模組參數作為參數呼叫 `finit_module` 3. 在 `finit_module` 最後呼叫 `load_module` 並以模組的參數 `uargs` 及資訊 `info` 作為參數 - 核心模組資訊是 user mode 的資料,因此透過 `kernel_read_file_from_fd` 讀取 FD 對應的核心模組內容,並存在儲存核心模組內容的結構體 `load_info` 的物件 buffer 中。 - 透過定義在 [`kernel/module_decompress.c` 中的 `module_decompress`](https://github.com/torvalds/linux/blob/b6b2648911bbc13c59def22fd7b4b7c511a4eb92/kernel/module_decompress.c#L204) 函式讀取核心模組檔案中的資訊。 :::warning 然後就會發現不小心看到了 quiz3 中沒完成的延伸要求 -- [DIVIDE-ROUND-UP 相關巨集在 Linux 核心中的實作以及應用](https://hackmd.io/EYJVpB8fSK6ceyNdKbKFBw#%E7%AC%AC%E5%8D%81%E9%A1%8C---DIVIDE_ROUND_CLOSEST),是時候該來補上了: ```c // include/linux/math.h #define DIV_ROUND_UP __KERNEL_DIV_ROUND_UP // include/uapi/linux/const.h #define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) ``` 這個巨集的實作很單純,因為要無條件進位,所以先將目標數字加上除數 `d`,但又要避開 `n` 恰為 `d` 的整數倍的情況,所以另外減了 1,最後再除以除數 `d` 即可。 而在這邊的用途是用來計算需要的 PAGE 數量,然後再**乘二**確保解壓縮後的資料有足夠的記憶體空間能夠儲存。 ::: 然後再透過對應的壓縮策略進行解壓縮,並儲存到 `info` 中,從巨集的定義可以看到核心模組的壓縮策略應該包含 [gzip](https://en.wikipedia.org/wiki/Gzip) 以及 [xz](https://en.wikipedia.org/wiki/XZ_Utils) 兩種: ```c ... #ifdef CONFIG_MODULE_COMPRESS_GZIP #include <linux/zlib.h> #define MODULE_COMPRESSION gzip #define MODULE_DECOMPRESS_FN module_gzip_decompress ... #elif CONFIG_MODULE_COMPRESS_XZ #include <linux/xz.h> #define MODULE_COMPRESSION xz #define MODULE_DECOMPRESS_FN module_xz_decompress ... #else #error "Unexpected configuration for CONFIG_MODULE_DECOMPRESS" #endif ``` 4. 接著進入到 `load_module` 中讀取並檢查核心模組相關資訊 - 先透過 `setup_load_info` 讀取核心模組的 `.modinfo` 區段、`this_module` 以及 String Table 的位址等基本資訊。 - 透過 `find_module_sections` 讀取其他區段的資訊,包含 Symbol Table 的起始位址以及[1-2. 建立存取核心模組參數所需的結構體物件](#1-2-%E5%BB%BA%E7%AB%8B%E5%AD%98%E5%8F%96%E6%A0%B8%E5%BF%83%E6%A8%A1%E7%B5%84%E5%8F%83%E6%95%B8%E6%89%80%E9%9C%80%E7%9A%84%E7%B5%90%E6%A7%8B%E9%AB%94%E7%89%A9%E4%BB%B6)中宣告並指定儲存在 `__param` 區段中 `kernel_param` 結構體的物件 `__param_##name`,並將這些物件存在 `mod->kp` 中。 - 接下來則開始解析核心模組的參數 - 由於傳給 `load_module` 的參數是 user mode 中的 `uargs`,所以要先透過 `strndup_user` 複製 `uargs` 到 kernel mode 中對應的物件中(核心模組結構體 `module` 物件中儲存核心模組參數字串的部份 `mod->args`)。 - 然後呼叫定義在 `kernel/params.c` 中的 `parse_args` 以及 `parse_one` 依序解析核心模組參數字串 `mod->args`,並將對應的核心模組參數設定成傳入的參數。 ```c // kernel/module.c - load_module() parse_args(mod->name, mod->args, mod->kp, mod->num_kp, -32768, 32767, mod, unknown_module_param_cb); ``` ```c // kernel/params.c - parse_args() char *parse_args(const char *doing, char *args, const struct kernel_param *params, unsigned num, s16 min_level, s16 max_level, void *arg, int (*unknown)(char *param, char *val, const char *doing, void *arg)) { ... while (*args) { ... ret = parse_one(param, val, doing, params, num, min_level, max_level, arg, unknown); ... } ... } ``` - 而在 `parse_one` 中會依序檢查傳入的核心模組參數 `mod->args` 是否有在實際的模組參數 `mod->kp` 中,若是有的話就會更新成使用者傳入的值,否則則保持預設值。 ```c static int parse_one(char *param, char *val, const char *doing, const struct kernel_param *params, unsigned num_params, s16 min_level, s16 max_level, void *arg, int (*handle_unknown)(char *param, char *val, const char *doing, void *arg)) { for (i = 0; i < num_params; i++) { if (parameq(param, params[i].name)) { if (params[i].level < min_level || params[i].level > max_level) return 0; ... if (param_check_unsafe(&params[i])) err = params[i].ops->set(val, &params[i]); ... } } ... } ``` :::warning 而在 `parse_one` 中用來更新核心模組參數的 `param[i].ops` 就是前面 [1-2. 建立存取核心模組參數所需的結構體物件](#1-2-%E5%BB%BA%E7%AB%8B%E5%AD%98%E5%8F%96%E6%A0%B8%E5%BF%83%E6%A8%A1%E7%B5%84%E5%8F%83%E6%95%B8%E6%89%80%E9%9C%80%E7%9A%84%E7%B5%90%E6%A7%8B%E9%AB%94%E7%89%A9%E4%BB%B6)中宣告的,包含 `get`、`set`、`free` 等 function pointer 的 `kernel_param_ops` 結構體的物件 `&param_ops_##type`。 ::: 5. 呼叫 `do_init_module` 開始建立初始化核心模組 - 在核心模組中有的 `module_init` 巨集會給予實際要執行的初始化函式(在此 `kecho` 中為 `kecho_init_module` )一個別名 `init_module`,而在 `do_init_module` 中呼叫 `do_one_initcall` 時傳遞的參數 `mod->init` 就是這個 `init_module`,也就是對應到實際定義在核心模組的初始化函式。 :::danger 但還沒找到 `mod->init` 在何時指派 ::: - 以 `mod->init` 為參數呼叫定義在 `init/main.c` 中的 `do_one_initcall` 函式,並在函式中呼叫 `mod->init` 對應的參數 `fn` 並開始對核心模組進行初始化。