--- title: 2020 年春季 Linux 核心設計課程作業 —— khttpd image: https://repository-images.githubusercontent.com/249636967/4e4e8900-6e20-11ea-9abf-57f8123e9c66 description: 檢驗學員對浮點數和定點數運算的認知,連同 Linux 核心對於浮點數運算的限制,並透過 livepatch 達到動態變更核心模組行為 --- # H08: khttpd ###### tags: `linux2020` > 主講人: [jserv](http://wiki.csie.ncku.edu.tw/User/jserv) / 課程討論區: [2020 年系統軟體課程](https://www.facebook.com/groups/system.software2020/) :mega: 返回「[Linux 核心設計](http://wiki.csie.ncku.edu.tw/linux/schedule)」課程進度表 ## :memo: 預期目標 * 學習 Linux 核心的 kernel thread 處理機制 * 預習電腦網路原理 * 整合 [fibdrv](https://hackmd.io/@sysprog/linux2020-fibdrv) 開發成果 * 學習 [Concurrency Managed Workqueue](https://www.kernel.org/doc/html/v4.15/core-api/workqueue.html) (cmwq) ## :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, &param, 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: 自我檢查清單 - [ ] 參照 [fibdrv 作業說明](https://hackmd.io/@sysprog/linux2020-fibdrv) 裡頭的「Linux 核心模組掛載機制」一節,解釋 `$ sudo insmod khttpd.ko port=1999` 這命令是如何讓 `port=1999` 傳遞到核心,作為核心模組初始化的參數呢? - [ ] 參照 [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 效能分析工具原理為何? ## :penguin: 作業要求 * 回答上述「自我檢查清單」的所有問題,需要附上對應的參考資料和必要的程式碼,以第一手材料 (包含自己設計的實驗) 為佳 :::warning :warning: 如果你在 2020 年 3 月 27 日前,已從 GitHub [sysprog21/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 ::: * 在 GitHub 上 fork [khttpd](https://github.com/sysprog21/khttpd),目標是將 [fibdrv 作業](https://hackmd.io/@sysprog/linux2020-fibdrv)的成果整合進 kHTTPd。過程中應一併完成以下: 1. 當來自 web 客戶端 (即網頁瀏覽器或類似的軟體) 請求的網址為 `/fib/N` 時 ($N$ 是自然數,最大可到 $2^{31} - 1$),例如 `$ wget http://localhost:8081/fib/10` 應該要讓 web 客戶端得到 $Fibonacci(10)$ 即 `55` 這個字串,這個實作需要考慮到大數運算 $\to$ 不要小看這個需求,由於大數運算無可避免會用到更大的記憶體空間和更長的執行時間,你要考慮手動安插 `reschedule()`, `preempt_enable()`, `preempt_disable()`; 2. 自 [Facebook 討論串](https://www.facebook.com/groups/system.software2020/permalink/345124729755066/) 找出合適的 Fibonacci 運算檢驗程式,用以比對上述 (1) 的運算結果,並且要能詳實記錄和分析處理時間 (從 HTTP 請求到 kHTTPd 回應); 3. 指出 kHTTPd 實作的缺失 (特別是安全疑慮) 並予以改正; 4. 引入 [Concurrency Managed Workqueue](https://www.kernel.org/doc/html/v4.15/core-api/workqueue.html) (cmwq),改寫 kHTTPd,分析效能表現和提出改進方案,可參考 [kecho](https://github.com/sysprog21/kecho); 5. 參照 [in-kernel HTTP server](https://hackmd.io/@sysprog/kernel-web-server),學習其中分析和實作手法,從而改進 kHTTPd; ## 繳交方式 編輯 [Homework4 作業區共筆](https://hackmd.io/@sysprog/linux2020-homework4),將你的觀察、上述要求的解說、應用場合探討,以及各式效能改善過程,善用 gnuplot 製圖,紀錄於新建立的共筆 ## 截止日期 * Apr 18, 2020 (含) 之前 > 越早在 GitHub 上有動態、越早接受 code review,評分越高