--- tags: linux2022 --- # 2022q1 Homework6 (ktcp) contributed by < [Eddielin0926](https://github.com/Eddielin0926) > > [K08: ktcp 作業說明](https://hackmd.io/@sysprog/linux2022-ktcp) > [Homework6 作業區](https://hackmd.io/@sysprog/linux2022-homework6) ## :gear: 環境 ```shell $ uname -r 5.13.0-39-generic $ gcc --version gcc (Ubuntu 9.4.0-1ubuntu1~20.04) 9.4.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian Address sizes: 48 bits physical, 48 bits virtual CPU(s): 6 On-line CPU(s) list: 0-5 Thread(s) per core: 2 Core(s) per socket: 3 Socket(s): 1 NUMA node(s): 1 Vendor ID: AuthenticAMD CPU family: 21 Model: 2 Model name: AMD FX(tm)-6300 Six-Core Processor Stepping: 0 Frequency boost: enabled CPU MHz: 1400.000 CPU max MHz: 3500.0000 CPU min MHz: 1400.0000 BogoMIPS: 7033.04 Virtualization: AMD-V L1d cache: 48 KiB L1i cache: 192 KiB L2 cache: 6 MiB L3 cache: 8 MiB NUMA node0 CPU(s): 0-5 ``` ### 無法打出 '^]' 因為我使用是 windows 系統的電腦,然後用 SSH 連到 Ubuntu 來操作,這時會碰到一個問題,微軟的輸入法在中文鍵盤的設定下,切到英文輸入按 `Ctrl + ]` 會變成`】`,導致無法跳出 telnet 連線,解決的辦法就新增一個 US 鍵盤就可以避免組合鍵自動轉換成中文了。 ## :checkered_flag: 自我檢查清單 - [x] 參照 [Linux 核心模組掛載機制](https://hackmd.io/@sysprog/linux-kernel-module),解釋 `$ sudo insmod khttpd.ko port=1999` 這命令是如何讓 `port=1999` 傳遞到核心,作為核心模組初始化的參數呢? > 過程中也會參照到 [你所不知道的 C 語言:連結器和執行檔資訊](https://hackmd.io/@sysprog/c-linker-loader) - [ ] 參照 [CS:APP 第 11 章](https://hackmd.io/s/ByPlLNaTG),給定的 kHTTPd 和書中的 web 伺服器有哪些流程是一致?又有什麼是你認為 kHTTPd 可改進的部分? - [ ] `htstress.c` 用到 [epoll](http://man7.org/linux/man-pages/man7/epoll.7.html) 系統呼叫,其作用為何?這樣的 HTTP 效能分析工具原理為何? - [ ] 給定的 `kecho` 已使用 CMWQ,請陳述其優勢和用法 - [ ] 核心文件 [Concurrency Managed Workqueue (cmwq)](https://www.kernel.org/doc/html/latest/core-api/workqueue.html) 提到 "The original create_`*`workqueue() functions are deprecated and scheduled for removal",請參閱 Linux 核心的 git log (不要用 Google 搜尋!),揣摩 Linux 核心開發者的考量 - [ ] 解釋 `user-echo-server` 運作原理,特別是 [epoll](http://man7.org/linux/man-pages/man7/epoll.7.html) 系統呼叫的使用 - [ ] 是否理解 `bench` 原理,能否比較 `kecho` 和 `user-echo-server` 表現?佐以製圖 - [ ] 解釋 `drop-tcp-socket` 核心模組運作原理。`TIME-WAIT` sockets 又是什麼? ### 核心模組初始化的參數 在閱讀[Linux 核心模組掛載機制](https://hackmd.io/@sysprog/linux-kernel-module#Linux-核心模組掛載機制)後,我們可以得知 `.ko` 會被 [kernel/module.c](https://elixir.bootlin.com/linux/v4.18/source/kernel/module.c) 中的 `load_module` 函式所讀取,簡單的看過函式內容後,可以發現到參數會被傳入到 `uargs` 中並放到 `mod`。 ```c /* Allocate and load the module: note that size of section 0 is always zero, and we rely on this for optional sections. */ static int load_module(struct load_info *info, const char __user *uargs, int flags) { ... /* Now copy in args */ mod->args = strndup_user(uargs, ~0UL >> 1); if (IS_ERR(mod->args)) { err = PTR_ERR(mod->args); goto free_arch_cleanup; } ... /* Module is ready to execute: parsing args may do that. */ after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp, -32768, 32767, mod, unknown_module_param_cb); ... } ``` 模組在準備好後就會將剛剛的參數給 `parse_args`,這個函式定義在 [kernel/params.c](https://elixir.bootlin.com/linux/v4.18/source/kernel/params.c#L164),會將 `args` 一個一個交給 `parse_one` 分析,如果遇到非預期的參數就會交由 `unknown_module_param_cb` 來處理。 ```c static int unknown_module_param_cb(char *param, char *val, const char *modname, void *arg) { struct module *mod = arg; int ret; if (strcmp(param, "async_probe") == 0) { mod->async_probe_requested = true; return 0; } /* Check for magic 'dyndbg' arg */ ret = ddebug_dyndbg_module_param_cb(param, val, modname); if (ret != 0) pr_warn("%s: unknown parameter '%s' ignored\n", modname, param); return 0; } ``` 當參數無法被解析會在 `pr_warn` 被印出來,這邊做一個小實驗看 `mod->args` 是不是放的是我們給的參數,以 [hello](https://hackmd.io/@sysprog/linux-kernel-module#程式碼準備) 這個模組為例。因為要看到 `WARNING` 層級的紀錄,我們先調整 `dmesg`,將 console_loglevel 設置為將 KERN_WARNING (4) 的消息列印到主控台。 ```shell $ sudo dmesg -n 4 ``` 接下來在掛載模組時加入任意參數。 ```shell $ sudo insmod hello.ko test=1234 ``` 執行 `dmesg` 後可以看到 `test` 確實被放入 `mod->args`,然後因為無法解析而被印了出來。 ```shell [78697.937988] hello: unknown parameter 'test' ignored [78697.938031] Hello, world ``` 接下來觀察 kecho 是如何定義參數的,以 `port` 為例。 ```c module_param(port, ushort, S_IRUGO); ``` 在 [include/linux/moduleparam.h](https://elixir.bootlin.com/linux/v4.18/source/include/linux/moduleparam.h#L221) 中可以看到 `module_param` 的定義。 ```c #define module_param(name, type, perm) \ module_param_named(name, name, type, perm) #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) #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 } } ``` 從上面的的 `__section("__param")` 可以得知模組參數的資訊會放在 `__param` 區段 (section)。 我們回頭看一下剛剛的 `module_load`,可以看到模組的資訊會經由 `find_module_sections` 放到 `mod` 中,而 `__param` 的資訊會被放到 `mod->kp`。 ```c /* Allocate and load the module: note that size of section 0 is always zero, and we rely on this for optional sections. */ static int load_module(struct load_info *info, const char __user *uargs, int flags) { ... /* Now we've got everything in the final locations, we can * find optional sections. */ err = find_module_sections(mod, info); ... } static int find_module_sections(struct module *mod, struct load_info *info) { mod->kp = section_objs(info, "__param", sizeof(*mod->kp), &mod->num_kp); ... } ``` 最後 `parse_args` 所做的事情就是去檢查 `mod->args` 的內容是否有對應到 `mod->kp`。 ## Concurrency Managed Workqueue (CMWQ) ### 什麼是 CMWQ [Concurrency Managed Workqueue (CMWQ)](https://www.kernel.org/doc/html/v4.10/core-api/workqueue.html) 是一個佇列用來管理並行程式的工作任務。當程式在需要異步執行的情況,任務會被放入佇列中等候,再由各個執行緒從佇列中提取任務去處理。 ### 為什麼需要 CMWQ 原始的工作佇列實作中有兩種不同的類型 — **多執行緒的工作佇列**(MTWQ)和**單執行緒的工作佇列**(STWQ)。 - 多執行緒的工作佇列: 每個 CPU 有各自的工作執行緒,因此單一個 MTWQ 會需要保持大約與 CPU 數量相同的工作執行緒。 - 單執行緒的工作佇列: 系統內只有一個工作執行緒