owned this note
owned this note
Published
Linked with GitHub
---
tags: linux2022
---
# 2022q1 Homework6 (ktcp)
contributed by < `sternacht` >
> [作業要求](https://hackmd.io/@sysprog/linux2022-ktcp#-作業要求)
## :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,請陳述其優勢和用法
- [x] 核心文件 [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 又是什麼?
### 解釋 `$ sudo insmod khttpd.ko port=1999` 這命令是如何讓 `port=1999` 傳遞到核心,作為核心模組初始化的參數呢?
首先看到 [main.c](https://github.com/sysprog21/khttpd/blob/master/main.c) ,裡面對傳入的 port 是如何使用
```c
#define DEFAULT_PORT 8081
static ushort port = DEFAULT_PORT;
module_param(port, ushort, S_IRUGO);
```
參考[這份文件](https://tldp.org/LDP/lkmpg/2.6/html/x323.html)可得知,不同於一般程式執行時讀取參數是使用 `argc` 和 `*argv` ,module 讀取 command line argument 的方式是透過 `module_param` 巨集來將參數傳入 module
定義在 [linux/moduleparam.h](https://github.com/torvalds/linux/blob/master/include/linux/moduleparam.h) ,將 `module_param` 展開
```c
#define module_param(name, type, perm) \
module_param_named(name, name, type, perm)
```
再展開 `module_param_named`
```c
#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)
```
這個部分利用在 [你所不知道的 C 語言:前置處理器應用篇](https://hackmd.io/@sysprog/c-preprocessor?type=view) 中提到的 `##` 將字串鏈結,來給不同 type 相對應的函式呼叫,以及 `#` 將文字轉成字串。
`module_param_named` 分成三個部分,第一部分先對傳入的 `type` 與 `name` 進行檢查,確認兩變數為同一種型態。
```c
#define __param_check(name, p, type) \
static inline type __always_unused *__check_##name(void) { return(p); }
#define param_check_ushort(name, p) __param_check(name, p, unsigned short)
```
在回傳型態為 `type` 的函式上寫上 `return p`,可以在編譯時期去確認型態是否符合。此外,加上 `__always_unused` 相當於 `__attribute__((unused))`,使編譯器不會對該未使用的函式警告
第二部分 `module_param_cb` 展開
```c
#define module_param_cb(name, ops, arg, perm) \
__module_param_call(MODULE_PARAM_PREFIX, name, ops, arg, perm, -1, 0)
#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 } }
```
看起來相當複雜,根據 [linux/moduleparam.h](https://github.com/torvalds/linux/blob/master/include/linux/moduleparam.h) 的註解
> module_param_cb - general callback for a module/cmdline parameter
可以得知這一段就是從 command line 中取得 param 的作用。
最後 `__MODULE_PARM_TYPE` 則是一個 `__MODULE_INFO` 的 macro,可以利用 `modinfo khttpd.ko` 命令來觀察到這個巨集所做的事
```c
#define __MODULE_PARM_TYPE(name, _type) \
__MODULE_INFO(parmtype, name##type, #name ":" _type)
```
回到 `module_param` 上,至此,大部份的參數都已經有了解釋,剩下 `S_IRUGO` ,查閱了一下這個參數的作用([定義1](https://github.com/torvalds/linux/blob/master/include/uapi/linux/stat.h), [定義2](https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/tools/perf/trace/beauty/mode_t.c)),是限制檔案的權限,讀過[鳥哥的文章](https://linux.vbird.org/linux_basic/centos7/0210filepermission.php)應該對這個很有印象,這邊不多做討論, `S_IRUGO` 表示所有使用者都有 read 的權限。
:::info
Q: `S_IRUGO` 在整個過程中只用到一次,而且僅是用在確認傳入的權限是否符合規定,這麼做的用意為何?
:::
### CMWQ 的優勢和用法
[參考1](https://www.kernel.org/doc/html/v4.10/core-api/workqueue.html)、[參考2](http://www.wowotech.net/irq_subsystem/cmwq-intro.html)
過去的 workqueue 機制,分成 ST(single thread) 及 MT(multi thread) 兩種,差別在workqueue 是執行在一個執行緒或是多個執行緒上(數量根據有多少 CPU 核決定),並且 task 的分派與執行都是在同一個執行緒上,這些機制容易出現幾個問題。
- workqueue 彼此獨立存在,管理各自的 `workers-pool` ,而 MT workqueue 更會建立同 CPU 數量個 workers ( threads ),因此若是浮濫的建立 workqueue,加上超多核 CPU 的環境, thread pid 很容易就達到上限,即使可以靠調整上限暫時解決問題,同時太多 task 也會影響系統效能。
- concurrency 效果不佳,在 ST workqueue 下,由於執行是按照順序,因此一個需要執行較長時間的 task 會導致其他 task 只能等待,在 MT workqueue 下雖然相較之下較不嚴重,若有多個 work 在同一個 CPU 中執行,而其他 CPU 處於 idle 的狀態,此時因為缺乏 work 轉移的機制,仍會導致分配不均勻的問題,影響 concurrency 效率。
- deadlock 的問題,假設有兩個 task A 及 task B,並且 A 的執行過程中會需要 B 的執行結果,但依照 (A, B) 的順序排在 ST workqueue 時,明顯會發生 deadlock ,即使用 MT workqueue 也無法完全避免這樣的狀況,只是發生的機率降低而已。
**CMWQ** 的設計相較過去有幾個重點
- 首先 CMWQ 引入了一個新的概念 `work item`,實際上是一個 struct 持有一個 pointer 指向要執行的 function ,當系統要執行某個函式的時候,就要建立一個 `work item` 指向 要執行的 function,再把 `work item` 排進 workqueue 中。
- workers pool 不獨立屬於某個 workqueue ,而是所有 workqueue 共用,每個 CPU 都綁定兩個 `workers-pool` ,分別處理普通以及高優先權的任務,另外還有一個額外的 `workers-pool` (unbound)
- 分開前後端,subsystem 及 drivers 可以在前端透過 CMWQ API 建立指定的 `work item` 並放入 workqueue 中,後端則是負責管理 `workers-pool`,當有 `work item` 進到 queue 中,系統會根據 `work item` 中的參數去分配給指定的 CPU 。
過多 task 的問題,在 CMWQ 中靠著 dynamic thread pool 的方式獲得解決,前述提到 `work item` 會依據參數被分派給 CPU,接著會在對應的 `workers-pool` 裡找一個狀態為 idle 的 worker 去執行,若是沒有則會新創一個來處理任務,並且 idle 一段時間之後, worker 會被系統銷毀。
對於 concurrency 不佳的問題而言, CMWQ 並不積極的去實現,而是保持足夠的 concurrency 並最大化系統的資源,但是當類似 deadlock 的情況發生時, CMWQ 仍然有一套解決方法。例如 task C, task D 被分配在同一個 `workers-pool` ,當 C 因為某些原因進入等待的狀態時, `workers-pool` 會得知此情況,並將 D 分配到其他的 worker 上去執行。
### 核心文件 [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 核心開發者的考量
`create_*workqueue()` 就是前面提過的,早期的 workqueue 機制,存在許多問題。
自 CMWQ 引入 linux kernel ,系統會建立預設的 workqueue 稱作 `system_wq` ,並且 `system_wq` 能夠提供足夠的 concurrency ,即使是排在同一個 CPU 上, deadlock 發生的機率也越來越小,逐漸不再需要另外建立 workqueue 來解決 concurrency 不足的情況發生。
> 引述自 Bhaktipriya Shridhar 於 2016 的 commit
> Unlike a dedicated per-cpu workqueue created with create_workqueue(),
system_wq allows multiple work items to overlap executions even on
the same CPU; however, a per-cpu workqueue doesn't have any CPU
locality or global ordering guarantees unless the target CPU is
explicitly specified and thus the increase of local concurrency
shouldn't make any difference.