---
title: 2022 年 Linux 核心設計/實作課程作業 —— kecho + khttpd
image: https://repository-images.githubusercontent.com/181623502/5a221200-560c-11ea-8a63-53e08f8c367c
description: 檢驗學員對 Linux 核心 kthread 和 workqueue 處理機制的認知
tags: linux2022
---
# K08: ktcp
> 主講人: [jserv](http://wiki.csie.ncku.edu.tw/User/jserv) / 課程討論區: [2022 年系統軟體課程](https://www.facebook.com/groups/system.software2022/)
:mega: 返回「[Linux 核心設計](http://wiki.csie.ncku.edu.tw/linux/schedule)」課程進度表
==[解說錄影](https://youtu.be/XMKNPiIYVCE)==
## :memo: 預期目標
* 學習 [Linux 核心設計: 針對事件驅動的 I/O 模型演化](https://hackmd.io/@sysprog/linux-io-model)
* 探討 TCP 伺服器開發議題
* 學習 Linux 核心的 kernel thread 和 workqueue 處理機制
* 學習 [Concurrency Managed Workqueue](https://www.kernel.org/doc/html/latest/core-api/workqueue.html) (cmwq)
* 預習電腦網路原理
## :rocket: `kecho`: 執行在 Linux 核心模式的 TCP 伺服器
取得 kecho 原始程式碼並編譯:
```shell
$ git clone https://github.com/sysprog21/kecho
$ cd kecho
$ make
```
預期會見到以下:
* 執行檔: `bench` 及 `user-echo-server`
* 核心模組 `kecho.ko` 及 `drop-tcp-socket.ko`
接著可進行測試:
```shell
$ make check
```
參考輸出:
```
Preparing...
Send message via telnet
Progress : [########################################] 100%
Complete
```
該操作由以下動作組成:
```shell
$ sudo insmod kecho.ko
$ telnet localhost 12345
```
會出現以下輸出:
```
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
```
可輸入任何字元 (記得按下 Enter),然後就會看到 telnet 回應你剛才輸入的字元。
按下 `Ctrl` 和 `]` 組合鍵,之後按下 `q`,即可離開 telnet 畫面。接著可以試著在 `$ telnet localhost 12345` 時不要輸入任何字元,只是等待,會看到以下的 kernel 訊息 (可用 `$ dmesg` 觀察):
```
cope
le: 4404 kB
RssShmem: 0 kB
VmData: 880 kB
VmStk: 132 kB
VmExe: 136 kB
VmLib: 6336 kB
VmPTE: 212 kB
VmSwap: 0 kB
HugetlbPages: 0 kB
CoreDumping: 0
Threads: 1
SigQ: 0/31543
```
kecho 掛載時可指定 port 號碼: (預設是 `port=12345`)
```shell
$ sudo insmod kecho.ko port=1999
```
修改或測試 kecho 的過程,可能因為 `TIME-WAIT` sockets 持續佔用,導致 `rmmod` 無法成功,這時可透過給定的 `drop-tcp-socket` 核心模組來剔除特定的 TCP 連線。請詳細閱讀 [kecho](https://github.com/sysprog21/kecho) 以得知必要的設定和準備工作。
## :house: `user-echo-server`: 執行於使用者層級的 TCP 伺服器
`user-echo-server` 是 `kecho` 的使用者層級的實作,可對照功能和比較效能,運用 [epoll](http://man7.org/linux/man-pages/man7/epoll.7.html) 系統呼叫,會傾聽 port 12345。
不管是 `user-echo-server` 抑或 `kecho`,都可搭配給定的 `bench` 程式來分析效能。請詳細閱讀 [kecho](https://github.com/sysprog21/kecho) 以得知必要的設定和準備工作。
## :icecream: 電腦網路概論
預習 [CS:APP 第 11 章](https://hackmd.io/s/ByPlLNaTG): Network Programming,搭配閱讀:
* [nstack 開發紀錄 (1)](https://hackmd.io/s/ryfvFmZ0f)
* [nstack 開發紀錄 (2)](https://hackmd.io/s/r1PUn3KGV)
## :rocket: kHTTPd: 執行在 Linux 核心模式的 Web 伺服器
取得 kHTTPd 原始程式碼並編譯:
```shell
$ git clone https://github.com/sysprog21/khttpd
$ cd khttpd
$ make
```
預期會見到執行檔 `htstress` 和核心模組 `khttpd.ko`。接著可進行測試:
```shell
$ make check
```
參考輸出:
```
0 requests
10000 requests
20000 requests
30000 requests
40000 requests
50000 requests
60000 requests
70000 requests
80000 requests
90000 requests
requests: 100000
good requests: 100000 [100%]
bad requests: 0 [0%]
socker errors: 0 [0%]
seconds: 4.722
requests/sec: 21177.090
```
這台電腦的實驗結果顯示,我們的 kHTTPd 每秒可處理超過 20K 個 HTTP 請求。上述實驗透過修改過的 [htstress](https://github.com/arut/htstress) 工具得到,我們可拿來對 `http://www.google.com` 網址進行測試:
```shell
$ ./htstress -n 1000 -c 1 -t 4 http://www.google.com/
```
參考輸出:
```
requests: 1000
good requests: 1000 [100%]
bad requests: 0 [0%]
socker errors: 0 [0%]
seconds: 17.539
requests/sec: 57.015
```
kHTTPd 掛載時可指定 port 號碼: (預設是 `port=8081`)
```shell
$ sudo insmod khttpd.ko port=1999
```
除了用網頁瀏覽器開啟,也可用 `wget` 工具:
```shell
$ wget localhost:1999
```
參考 `wget` 執行輸出:
```
Resolving localhost (localhost)... ::1, 127.0.0.1
Connecting to localhost (localhost)|::1|:1999... failed: Connection refused.
Connecting to localhost (localhost)|127.0.0.1|:1999... connected.
HTTP request sent, awaiting response... 200 OK
Length: 12 [text/plain]
Saving to: 'index.html'
```
得到的 `index.html` 內容就是 `Hello World!` 字串。
下方命令可追蹤 kHTTPd 傾聽的 port 狀況:
```shell
$ sudo netstat -apn | grep 8081
```
注意,在多次透過網頁瀏覽器存取 kHTTPd 所建立的連線後,可能在 module unload 時,看到 `dmesg` 輸出以下:
```
CPU: 37 PID: 78277 Comm: khttpd Tainted: G D WC OE K 4.15.0-91-generic #92-Ubuntu
Hardware name: System manufacturer System Product Name/ROG STRIX X399-E GAMING, BIOS 0808 10/12/2018
RIP: 0010:0xffffffffc121b845
RSP: 0018:ffffabfc9cf47d68 EFLAGS: 00010282
RAX: 0000000000000000 RBX: ffff8f1bc898a000 RCX: 0000000000000218
RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffffffff84c5107f
RBP: ffffabfc9cf47dd8 R08: ffff8f14c7e34e00 R09: 0000000180400024
R10: ffff8f152697cfc0 R11: 000499a097a8e100 R12: ffff8f152697cfc0
R13: ffffabfc9a6afe10 R14: ffff8f152697cfc0 R15: ffff8f1d4a5f8000
FS: 0000000000000000(0000) GS:ffff8f154f540000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffffffffc121b845 CR3: 000000138b00a000 CR4: 00000000003406e0
Call Trace:
? __schedule+0x256/0x880
? kthread+0x121/0x140
? kthread_create_worker_on_cpu+0x70/0x70
? ret_from_fork+0x22/0x40
Code: Bad RIP value.
RIP: 0xffffffffc121b845 RSP: ffffabfc9cf47d68
CR2: ffffffffc121b845
---[ end trace 76d6d2ce81c97c71 ]---
```
### 核心 API
許多 Linux 裝置驅動程式或子系統會透過 kernel threads(簡稱`kthread`),在背景執行提供特定服務,然後等待特定 events 的發生。等待的過程中,kthread 會進入 sleep 狀態,當 events 發生時,kthread 會被喚醒執行一些耗時的工作,如此一來,可防止 main thread 被 blocked。
使用示範: [kernel-threads.c](https://github.com/muratdemirtas/Linux-Kernel-Examples/blob/master/kernel-threads.c)
`kthread_run` 巨集在 Linux v5.5 的定義 [include/linux/kthread.h](https://elixir.bootlin.com/linux/v5.5/source/include/linux/kthread.h#L43) :
```cpp
#define kthread_run(threadfn, data, namefmt, ...) \
({ \
struct task_struct *__k \
= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
if (!IS_ERR(__k)) \
wake_up_process(__k); \
__k; \
})
```
可見到 `kthread_create` 成功時直接 `wake_up_process`,回傳值為 `task_struct`。
下方命令可查閱系統上的 kthread:
```shell
$ ps -ef
```
預期可見:
```
root 2 0 0 Feb17 ? 00:00:01 [kthreadd]
```
PPID 為 `2` 的都屬於 kthread,而 `$ ps auxf` 可見樹狀結構。
參考輸出結果:
```
0:01 /usr/sbin/sshd -D
0:00 \_ sshd: jserv [priv]
0:05 | \_ sshd: jserv@pts/11
0:03 | \_ -bash
0:00 | \_ ps auxf
0:00 | \_ less
```
尋找剛才載入的 khttpd 核心模組:
```shell
$ ps -ef | grep khttpd
```
預期可見以下:
```
root 18147 2 0 14:13 ? 00:00:00 [khttpd]
```
* `kthread_stop()`
通知 kthread 準備關閉(將 `kernel_should_stop` 旗標設為 true),並等待直到 `threadfn` 結束(由此可知 `threadfn` 中需要有一直驗證停止旗標的實做)。其中會把 `kthread->kthread_should_stop` 設為 true。此函式也用於喚醒 kthread。
* `schedule()` 運作方式
首先使用 `set_current_state()` 將 kthread 的 state 改成 `TASK_INTERRUPTIBLE`,使得稍後可被移出 run queue (排程器排程時會從 run queue 挑出任務),接著呼叫 `schedule()`,告知排程器可切換到其他任務,即讓出 CPU 資源,至此該 kthread 即開始睡眠。假如要喚醒這個 kthread,我們可用 `wake_up_process()` (帶入參數為目標 kthread 的 `task_struct *`),這個函式會將 kthread 的 state 切換回 `TASK_RUNNING` ,並將其放回 run queue,接著就等待排程器下一次排程。
* TCP backlog
在 `accept()` 成功前 queue 中最大能存放的連線要求。
* `sock_create(PF_INET, SOCK_STREAM, IPPROTO_TCP, &sock)`
* PF_INET(protocol family) : 使用 IPv4
* SOCK_STREAM : 提供一個序列化以及可靠的 byte stream
* IPPROTO_TCP : 指定使用 TCP 協定
* `kernel_setsockopt(sock, SOL_TCP, TCP_NODELAY, (char *) &opt, sizeof(opt))`
* SOL_TCP : 使用 TCP 協定的 API
* TCP_NODELAY : 關閉 TCP 資料處理的 [Nagle's Algorithm](https://en.wikipedia.org/wiki/Nagle%27s_algorithm)
參考 [TCP 參數對延遲和傳輸量的影響](https://medium.com/fcamels-notes/tcp-%E5%8F%83%E6%95%B8%E5%B0%8D%E5%BB%B6%E9%81%B2%E5%92%8C%E5%82%B3%E8%BC%B8%E9%87%8F%E7%9A%84%E5%BD%B1%E9%9F%BF-12910f8d82bd)
* `kernel_bind(sock, (struct sockaddr *) &addr, sizeof(addr))`
* 綁定一個網路位址
* `kernel_listen(sock, DEFAULT_BACKLOG)`
* DEFAULT_BACKLOG : 紀錄 pending connection 資訊(因為 client 端有可能在 server 呼叫 accept() 之前就呼叫 connect())
* `kthread_run(echo_server_daemon, ¶m, MODULE_NAME)`
* 建立一個 kernel thread 並執行它
* `kernel_accept(param->listen_sock, &sock, 0)`
* 等待 client 端請求一個連線
![](https://i.imgur.com/n37T1hM.png)
> 出處: [CS:APP 第 11 章](https://hackmd.io/s/ByPlLNaTG)
## :checkered_flag: 自我檢查清單
- [ ] 參照 [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 又是什麼?
## :penguin: 作業要求
* 回答上述「自我檢查清單」的所有問題,需要附上對應的參考資料和必要的程式碼,以第一手材料 (包含自己設計的實驗) 為佳
:::warning
:warning: 如果你在 2022 年 4 月 19 日前,已從 GitHub [sysprog21/kecho](https://github.com/sysprog21/kecho) 或 [khttpd](https://github.com/sysprog21/khttpd) 進行 fork,請依據 [Alternatives to forking into the same account](https://github.community/t5/Support-Protips/Alternatives-to-forking-into-the-same-account/ba-p/7428) 一文,對舊的 repository 做對應處置,然後重新 fork
:warning: 2022 年的作業要求和 2020 年不同,請留意!
:::
* 在 GitHub 上 fork [kecho](https://github.com/sysprog21/kecho),目標是修正 `kecho` 的執行時期的缺失,提升效能和穩健度 (robustness)
* 若使用者層級的程式頻繁傳遞過長的字串給 `kecho` 核心模組,會發生什麼事?
* 參照 [kecho pull request #1](https://github.com/sysprog21/kecho/pull/1),嘗試比較 kthread 為基礎的實作和 CMWQ,指出兩者效能的落差並解釋
* 如果使用者層級的程式建立與 `kecho` 核心模組的連線後,就長期等待,會導致什麼問題?
* 研讀 [Linux Applications Performance: Introduction](https://unixism.net/2019/04/linux-applications-performance-introduction/),嘗試將上述實作列入考量,比較多種 TCP 伺服器實作手法的效能表現
* 在 GitHub 上 fork [khttpd](https://github.com/sysprog21/khttpd),目標是提供檔案存取功能和修正 `khttpd` 的執行時期缺失。過程中應一併完成以下:
* 指出 kHTTPd 實作的缺失 (特別是安全疑慮) 並予以改正
* 引入 [Concurrency Managed Workqueue](https://www.kernel.org/doc/html/v4.15/core-api/workqueue.html) (cmwq),改寫 kHTTPd,分析效能表現和提出改進方案,可參考 [kecho](https://github.com/sysprog21/kecho)
* 實作 [HTTP 1.1 keep-alive](https://en.wikipedia.org/wiki/HTTP_persistent_connection),並提供基本的 [directory listing](https://cwiki.apache.org/confluence/display/httpd/DirectoryListings) 功能
- 可由 Linux 核心模組的參數指定 `WWWROOT`,例如 [httpd](https://github.com/sysprog21/concurrent-programs/tree/master/httpd)
## 繳交方式
編輯 [Homework6 作業區共筆](https://hackmd.io/@sysprog/linux2022-homework6),將你的觀察、上述要求的解說、應用場合探討,以及各式效能改善過程,善用 gnuplot 製圖,紀錄於新建立的共筆
## 截止日期
* May 10, 2022 (含) 之前
> 越早在 GitHub 上有動態、越早接受 code review,評分越高
## 作業觀摩
* [eecheng87/kecho](https://hackmd.io/@eecheng/Sy9XsgjOI)
* [OscarShiang/kecho](https://hackmd.io/@oscarshiang/linux_kecho)
* [bakudr18/khttpd](https://hackmd.io/@bakudr18/SkFA8Favu)
* [AndybnA/khttpd](https://hackmd.io/@AndybnA/khttpd)
* [eecheng/khttpd](https://hackmd.io/@eecheng/SkyvhMqwU)
* [haogroot/khttpd](https://hackmd.io/@haogroot/2020q1linux-khttpd)
* [KYG-yaya573142/khttpd](https://hackmd.io/@KYWeng/H1OBDQdKL)