---
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, ¶m_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 數量相同的工作執行緒。
- 單執行緒的工作佇列: 系統內只有一個工作執行緒