# 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, &param, 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 的功能,必須要給每一個連線者專屬的記憶體空間,讓他可以去訪問當前她想要的東西 ::: - [ ]