# 2023q1 Homework6 (ktcp)
###### tags: `linux2023`
contributed by < `JoshuaLee0321` >
## 自我檢查清單
- [x] 給定的 `kecho` 已使用 CMWQ,請陳述其優勢和用法
:::success
我想要先提到一個在 `workqueue.h` 中的笑話,在 `linux-hwe-5.19-headers-5.19.0-41/include/linux/workqueue.h` 的第 343 行,有一個定義了 `WQ_MAX_ACTIVE = 512` 註解上這樣打: `/* I like 512, better ideas? */` 相當的有幽默感。
### CMWQ 用法
使用方法其實沒有到很難
```graphviz
digraph G {
rankdir=LR
node[shape=rectangle]
"wq = alloc_workqueue()" -> "queue_work(wq, job)" -> "destroy_workqueue(wq)"
"queue_work(wq, job)" -> "queue_work(wq, job)" [label="while working"]
}
```
根據上面的流程圖可以看到,先行宣告一個 `struct workqueue_struct *wq`,在 `alloc_workqueu()` 時設定好此 `wq` 的行為模式,接下來只需要給他工作即可
#### CMWQ FLAGS
根據 `workqueue.h` 中有一個 `enum` 對於各個 `FLAG` 的定義以及它的名稱,不難猜出要這些參數有甚麼用處。以下來一一介紹:
```c
WQ_UNBOUND
/* 各工作不會被綁定在特定 CPU 上執行 */
WQ_FREEZABLE
/* 跟電源相關的 flag ,當系統進入冬眠時,有下這個 flag 的 WQ 會完成當前所有 work 並且不啟動新的 work 直到系統解除冬眠 */
WQ_MEM_RECLAIM
/* 若有這個參數,系統會創建 rescuer thread,當檢測到會產生死結時,出手相助 */
WQ_HIGHPRI
/* 此 wq 有高優先權 */
WQ_CPU_INTENSIVE
/* 此 wq 會特別消耗 CPU ,在 2 中提到:
* Runnable CPU intensive work items will not prevent
* other work items in the same worker-pool from starting
* execution
* 也就是說,他可以算是優先權相對較低的工作 */
WQ_SYSFS
/* 在 linux file system 中可以看到此 wq */
WQ_POWER_EFFICIENT
/* 當此 work 很吃資源的時候,想省電可以下這個參數 */
```
參考資料:
1. [CMWQ概述](http://www.wowotech.net/irq_subsystem/cmwq-intro.html)
2. [為甚麼 WQ_CPU_INTENSIVE 對 unbound 工作隊獵沒有意義](https://tjtech.me/why-cpu-intensive-work-is-meaningless-for-unbound-wq.html)
3. [sysfs](https://man7.org/linux/man-pages/man5/sysfs.5.html)
### CMWQ 優勢
相比於其他自行定義的 queue,如果程式設計者創造出太多 thread,這些 thread 每一個都有一個自己獨立的 PID,有機會導致 kernel space 中的 process 數量太多,而 user space 根本沒有辦法調度任何 process
另外在很多情況下,queue 中的 process 都是需要被[異步處理](https://en.wikipedia.org/wiki/Asynchronous_I/O),而通常自己設計的 queue 無法達成這個效果
***********
相比之下,CMWQ明確的劃分了前後端的實作
![](https://i.imgur.com/ocd9tIB.png)
讓用戶不需要關心如何分配每個 thread 在任何時間上的需不需要被執行,並且提出 `thread pool` 機制,讓所有進程都可以存取到共享的 `thread`
:::
- [ ] 搭配閱讀《Demystifying the Linux CPU Scheduler》第 1 章和第 2 章,描述 CPU 排程器和 workqueue/CMWQ 的互動,應探究相關 Linux 核心原始程式碼
- [ ] 研讀〈[Linux 核心設計: RCU 同步機制](https://hackmd.io/@sysprog/linux-rcu)〉並測試相關 Linux 核心模組以理解其用法
- [ ] 如何測試網頁伺服器的效能,針對多核處理器場景調整
- [ ] 如何利用 [Ftrace](https://docs.kernel.org/trace/ftrace.html) 找出 `khttpd` 核心模組的效能瓶頸,該如何設計相關實驗學習。搭配閱讀《Demystifying the Linux CPU Scheduler》第 6 章
- [x] 解釋 `drop-tcp-socket` 核心模組運作原理。`TIME-WAIT` sockets 又是什麼?
:::success
### 讀了許久無法理解,想要請問老師該怎麼去理解這份 kernel module
#### 參考資料
1. [what is net_generic function](https://stackoverflow.com/questions/20189858/what-is-net-generic-function-in-linux-include-net-net-namespace-h)
2. [`char array[0] 1`](https://blog.csdn.net/zhinanpolang/article/details/110433515)
3. [`char array[0] 2`](https://blog.csdn.net/weixin_30335575/article/details/99294723?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-99294723-blog-110433515.235%5Ev32%5Epc_relevant_increate_t0_download_v2_base&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-99294723-blog-110433515.235%5Ev32%5Epc_relevant_increate_t0_download_v2_base&utm_relevant_index=2)
:::
- [ ] 研讀 [透過 eBPF 觀察作業系統行為](https://hackmd.io/@sysprog/linux-ebpf),如何用 eBPF 測量 kthread / CMWQ 關鍵操作的執行成本?
> 參照 [eBPF 教程](https://github.com/eunomia-bpf/bpf-developer-tutorial)
- [ ] 參照〈[測試 Linux 核心的虛擬化環境](https://hackmd.io/@sysprog/linux-virtme)〉和〈[建構 User-Mode Linux 的實驗環境](https://hackmd.io/@sysprog/user-mode-linux-env)〉,在原生的 Linux 系統中,利用 UML 或 `virtme` 建構虛擬化執行環境,搭配 GDB 追蹤 `khttpd` 核心模組
## 在 khttp 中引入 cmwq
參照 [kecho](https://hackmd.io/@sysprog/linux2020-kecho) 的程式碼,可以發現為了在核心中有效管理每一個 thread,可以看到在 `echo_server.h` 中存在
```c
struct echo_service {
bool is_stopped;
struct list_head worker;
};
```
同理,我也在 `http_server.h` 中新增了同樣的結構方便管理每一個 caller
```c
struct khttp_service {
bool is_stopped;
struct list_head worker;
}
```
再來是修改原本的 `http_server_worker.c` 在原本的 `kecho` 當中必須要有幾個制式的流程:
1. alloc_work()
2. create_work()
3. `process listhead of daemon`
4. queue_work()
5. free_work()
所以我們必須要照著上面的流程更改原本的程式碼。
```c=
struct workqueue_struct *khttp_wq;
...
static int __init khttpd_init(void)
{
...
khttp_wq = alloc_workqueue(MODULE_NAME, 0, 0);
/* workqueue allocated */
http_server = kthread_run(http_server_daemon, ¶m, KBUILD_MODNAME);
...
}
```
首先更改 `main.c` 中的全域變數 `khttp_wq` 當作我們的佇列 (由以上第 1 行紀錄)
再來在第六行的地方初始化剛剛宣告的佇列。
> 由於我覺得在 `http_server.c` 又有一個新的結構體,這樣導致我要更改每一個結構體時必須要到不同的檔案中修改,於是我把所有有關結構體的宣告都移動到 `http_server.h` 中
```c=
...
struct http_request {
struct socket *socket;
enum http_method method;
char request_url[128];
int complete;
struct list_head worker;
struct work_struct http_work;
};
...
```
* `http_server.c` 中的 `struct http_request` 移動到 `http_server.h` 並且在第 6, 7 行新增了鏈結串列節點以及 CMWQ 的結構。
接下來更改 `http_server.c`,首先,先實作 `create_work` 以及 `free_work`,兩者皆參考 [`kecho-echo_server.c`](https://github.com/sysprog21/kecho/blob/master/echo_server.c) 中的對應函式實作
```c
static struct work_struct *create_work(struct socket *sk)
{
struct http_request *work;
if( !(work = kmalloc(sizeof(struct http_request), GFP_KERNEL)))
return NULL;
work->socket = sk;
/* initalize work */
INIT_WORK(&work->http_work, http_server_worker);
/* add work->node into daemon list*/
list_add(&work->worker, &daemon.head);
/* return the specific work */
return &work->http_work;
}
static void free_work(void)
{
struct http_request *l, *tar;
list_for_each_entry_safe(tar, l, &daemon.head, worker) {
kernel_sock_shutdown(tar->socket, SHUT_RDWR);
flush_work(&tar->http_work);
sock_release(tar->socket);
kfree(tar);
}
}
```
再來根據 [`kecho-echo_server.c`](https://github.com/sysprog21/kecho/blob/master/echo_server.c) 中可以發現,其傳遞參數的方法是由 [`container_of`](https://hackmd.io/@sysprog/linux-macro-containerof) 的方法把 `struct work_struct` 當作參數放入 `khttp_worker` 中,所以在 `khttp` 中也必須要做一樣的事情,並且把相對傳遞參數的方法改掉
```c=
/* change void *arg into struct work_struct*/
static void http_server_worker(struct work_struct *work)
{
struct http_request *worker =
container_of(work, struct http_request, http_work);
...
realloc:
buf = kzalloc(RECV_BUFFER_SIZE, GFP_KERNEL);
if (!buf) {
pr_err("can't allocate memory!\n");
goto realloc;
}
http_parser_init(&parser, HTTP_REQUEST);
parser.data = &worker->socket;
while (!kthread_should_stop()) {
int ret = http_server_recv(worker->socket, buf, RECV_BUFFER_SIZE - 1);
if (ret <= 0) {
if (ret)
pr_err("recv error: %d\n", ret);
break;
}
if (!http_parser_execute(&parser, &setting, buf, ret))
continue;
if (worker->complete && !http_should_keep_alive(&parser))
break;
memset(buf, 0, RECV_BUFFER_SIZE);
}
kernel_sock_shutdown(worker->socket, SHUT_RDWR);
kfree(buf);
}
```
可以看到在第 7 行增加了 `realloc` 的標籤,由於先前的函數在失敗之後就直接回傳 -1 當作失敗,並且重複使用 thread,在這邊因為沒有辦法回傳,所以我選擇的方法為重複 `kzalloc` 直到成功為止。另外可以看到第 4 行已經利用 `container_of` 取出結構體,並且在第 15, 18, 32 行取代成 `worker->socket`。
經過以下的改動,我已經把 CMWQ 實作在 `khttp` 中了,以下為兩者之間用 [`hstress.c`](https://github.com/sysprog21/khttpd/blob/master/htstress.c) 的比較
> 以下數據皆使用 `./htstress localhost:8081 -n 100000 -c 10` 來做區別
* `khttpd without CMWQ`
![](https://hackmd.io/_uploads/rkaIBJOVn.png)
* `khttpd with CMWQ`
![](https://hackmd.io/_uploads/Hk89Hk_E3.png)
可以看到,在實作了 `CMWQ` 之後,速度從原本的 4.6 秒降低至 4.01 秒。
## 提供目錄檔案存取功能,提供基本的 directory listing 功能
### 利用額外的終端機監控資訊
* 在實作 khttpd 時,時常不清楚內部回傳值到底如何,尤其當我想要觀察 `http_server_worker` 時,我總是不知道裡面有哪些參數是可以使用的
此時我發現可以利用額外的一個終端機不停監控內核中的資訊,使用以下 command
```bash
sudo dmesg -wH
```
另外在修改了 `http_server_worker` 中的一小部分:
```diff
static void http_server_worker(struct work_struct *work)
{
...
while (!kthread_should_stop()) {
...
+ pr_info("buf = %s", buf);
...
}
...
}
```
這樣一來就可以隨時得知 `buf` 中的資訊
![](https://hackmd.io/_uploads/ByDhkmfHn.png)
:::info
##### 題外話 `favicon.ico`
* 在把 buf 列印出來後,我發現路徑中一直出現 `favicon.ico` 這個關鍵字,查了之後才發現許多瀏覽器都會發送此類型的靜態 `request`
參考資料: [**favicon.ico**](https://www.cisco.com/c/zh_tw/support/docs/security/web-security-appliance/117995-qna-wsa-00.pdf)
:::
# 問題
- [ ] 目前怎麼找都沒有找到一個好的方法把 directory 內的東西列出來,想要請問老師有甚麼關鍵字可以搜尋的
:::success
在 kernel 中並不存在 process 的概念,如果要做出 directory listing 的功能,必須要給每一個連線者專屬的記憶體空間,讓他可以去訪問當前她想要的東西
:::
- [ ]