freshLiver
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    --- 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` 並開始對核心模組進行初始化。

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully