Try   HackMD

2025q1 第 8 週測驗題

目的: 檢驗學員對 LKMPG 及 CPU 排程器的認知

作答表單: 測驗 1

測驗 1

第一份作業提及修改過的 tiny-web-server,讓學員及早理解電腦網路通訊、客戶—伺服器通訊模型,還有相關的 socket 程式設計 (對照 CS:APP 第 11 章)。為了檢驗學員對於 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,負責呼叫 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. 多網路名稱空間 (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 模組載入核心:

$ 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 伺服器:

$ sudo rmmod kweb

此動作也會移除 /sys/kernel/webserver 相關路徑。

kweb.c 的主要程式碼可見 kweb.c。作答規範:

  • AAAA, BBBB, CCCC, DDDD 皆為有效的 C 語言表示式,且該依循一致的程式碼風格 (注意空白字元!)
  • AAAA (第 158 行) 和 DDDD (第 208 行) 包含 container_of 巨集
  • BBBBCCCC 該使用 Linux workqueue 提供的函式呼叫
  • 均不包含分號 (即 ;) 並以最精簡的形式書寫

延伸問題:

  1. 解釋上述程式碼運作原理
  2. 以上述 kweb 為基礎,借助第 6 份作業的基礎,改善程式碼的效能和並行能力