---
tags: linux2025
---
# [2025q1](https://wiki.csie.ncku.edu.tw/linux/schedule) 第 8 週測驗題
:::info
目的: 檢驗學員對 LKMPG 及 CPU 排程器的認知
:::
==[作答表單: 測驗 `1`](https://docs.google.com/forms/d/e/1FAIpQLScs0R8eQr-YTrSjj8cV956Ese2OMA2wAZWjw4zuHEmJMq5LoA/viewform?usp=dialog)==
### 測驗 `1`
[第一份作業](https://hackmd.io/@sysprog/linux2025-lab0)提及[修改過的 `tiny-web-server`](https://hackmd.io/@sysprog/linux2025-lab0/%2F%40sysprog%2Flinux2025-lab0-c),讓學員及早理解電腦網路通訊、[客戶—伺服器通訊模型](https://en.wikipedia.org/wiki/Client%E2%80%93server_model),還有相關的 socket 程式設計 (對照 [CS:APP 第 11 章](https://hackmd.io/@sysprog/CSAPP-ch11))。為了檢驗學員對於 [LKMPG](https://sysprog21.github.io/lkmpg/) 的認知,我們開發一套 Linux 核心模式的網頁伺服器 (in-kernel web server),命名為 `kweb`,展示如何在 Linux 核心內部直接建立 TCP 伺服器。
`kweb` 在指定的埠號(port number,預設 `9999`)進行監聽,並在接收到連線後提供簡易的 HTTP 回應。其主要流程如下:
1. 建立與監聽 socket
- 使用 `__sock_create()`(或 `sock_create_kern()`)在指定網路名稱空間(net namespace)中建立 TCP socket
- 綁定(bind)到指定的 IP 和埠號,並呼叫 `kernel_listen()` 進行監聽
- 於 socket 收到新連線時,由 `sk_data_ready` 函式觸發 [work item](https://docs.kernel.org/core-api/workqueue.html),負責呼叫 `kernel_accept()` 來接受連線
2. 接受新連線
- 藉由 `kernel_accept()` 取得新的 `client_sock`,並將其包裝在一個工作結構(`struct client_work_queue`)中
- 使用 `schedule_work()` 將實際處理客戶端的流程排入系統工作佇列,使每個連線皆可在獨立的 work context 處理
3. 回應客戶端
- 在 `client_handler()` 中,`kweb` 使用 `sock_sendmsg()` 或 `kernel_sendpage()` 傳送固定的 HTTP 回應給客戶端
- 該回應包含簡單的標頭(`HTTP/1.1 200 OK`)以及測試訊息;另外也加入了 `Content-Length` 等標頭,使其符合標準的 HTTP 格式
4. 狀態控制 (藉由 sysfs)
- `kweb` 會在 `/sys/kernel/webserver/net/data/status` 建立一個屬性檔 (attribute),並以讀寫檔案的方式來啟用或停用伺服器。
- 停用伺服器時,會先取消(cancel)與清空(flush)正在等待的接受連線工作,以避免 race condition;然後才真正釋放監聽中的 socket
5. 多網路[名稱空間](https://man7.org/linux/man-pages/man7/namespaces.7.html) (netns) 支援
- 以 `netns_subsys_ops` 配合 Linux 的 per-net 機制,在不同的網路名稱空間中維持獨立的伺服器實例 (instance)
- 從使用者端看來,可以藉由 `unshare -t net` 建立新 netns,並在該空間中掛載 sysfs 後,使用同樣的啟停方式來管理伺服器。
以下為使用 kweb 的範例操作方式,並以繁體中文進行說明。此範例以預設的埠號 9999 舉例,您可在載入模組時透過參數改成其他埠號。
考慮以下 `Makefile` 內容: (注意 tab)
```
MODNAME := kweb
obj-m += $(MODNAME).o
KVER ?= $(shell uname -r)
KSRC ?= /lib/modules/$(KVER)/build
all:
make -C $(KSRC) M=$(PWD) modules
clean:
make -C $(KSRC) M=$(PWD) clean
```
編譯核心模組: `make`,完成後會產生一個名為 `kweb.ko` 的可載入模組檔 (LKM)。隨後使用以下命令將 `kweb` 模組載入核心:
```shell
$ sudo insmod kweb.ko [host=0.0.0.0] [port=9999]
```
- `host`:要綁定的 IP 位址(若不指定,預設為 `0.0.0.0`,表示在所有網卡監聽)
- `port`:要監聽的 TCP 埠號(若不指定,預設為 9999)
載入後,`kweb` 會在 `/sys/kernel/webserver/net/data` 中建立名為 `status` 的屬性檔案,用來控制伺服器的啟用與停用。
* 啟用伺服器: `echo 1 | sudo tee /sys/kernel/webserver/net/data/status`
一旦啟用,kweb 會在指定(或預設)的 IP:9999 上開始監聽。
* 測試連線: 可用 `nc`(netcat)或 `curl` 進行測試。例如 `nc 127.0.0.1 9999`。若成功連線,應可看到包含 `HTTP/1.1 200 OK` 與訊息內容的回應
* 停用伺服器: `echo 0 | sudo tee /sys/kernel/webserver/net/data/status`
Namespace 使用方式:
1. 建立與進入新網路名稱空間。利用以下命令產生並進入一個新的 netns(只影響該 shell):
```
$ sudo unshare -t net sh
```
進入後,可以執行 `ip a` 查看新 netns 裡可用的網路介面。如果還沒啟用 lo(127.0.0.1),可以:
```
$ ip addr add 127.0.0.1/8 dev lo
$ ip link set lo up
```
2. 在新 netns 中掛載 sysfs。為了操作 kweb 的 sysfs 介面,需要在新 netns 中掛載 sysfs:
```
mkdir /mnt/sysfs
mount -t sysfs sysfs /mnt/sysfs
```
之後即可在 `/mnt/sysfs/kernel/webserver/net/data/status` 找到相同的 `status` 屬性檔。
3. 啟用伺服器(新 netns
與前述方式相同:
```
echo 1 | sudo tee /mnt/sysfs/kernel/webserver/net/data/status
```
此時只會在新 netns 中開啟一個獨立的伺服器埠(同一個 IP:Port),與原本的 netns 互不影響。
4. 回到舊 netns
- 離開該 shell 即可回到原先 netns(或在另一個終端視窗操作原本的 netns)
- 新 netns 內啟用的伺服器依然存在,直到您將模組移除或在該 netns 中將其停用
當您不再需要此網頁伺服器時,可用以下命令將模組從核心卸載,並同時停止所有網路名稱空間中的 `kweb` 伺服器:
```shell
$ sudo rmmod kweb
```
此動作也會移除 `/sys/kernel/webserver` 相關路徑。
`kweb.c` 的主要程式碼可見 [kweb.c](https://gist.github.com/jserv/8f6b00ef84b72a6f17a8bbf0827e24c9)。作答規範:
* `AAAA`, `BBBB`, `CCCC`, `DDDD` 皆為有效的 C 語言表示式,且該依循一致的程式碼風格 (注意空白字元!)
* `AAAA` (第 158 行) 和 `DDDD` (第 208 行) 包含 `container_of` 巨集
* `BBBB` 和 `CCCC` 該使用 Linux [workqueue](https://docs.kernel.org/core-api/workqueue.html) 提供的函式呼叫
* 均不包含分號 (即 `;`) 並以最精簡的形式書寫
:::success
延伸問題:
1. 解釋上述程式碼運作原理
2. 以上述 kweb 為基礎,借助[第 6 份作業](https://hackmd.io/@sysprog/linux2025-ktcp)的基礎,改善程式碼的效能和並行能力
:::