# 2020q1 Homework5 (khttpd) contributed by < `jeeeff` > ## 實驗環境 作業系統 ```shell $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 18.04.4 LTS Release: 18.04 Codename: bionic ``` 編譯器 ```shell $ gcc --version gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0 Copyright (C) 2017 Free Software Foundation, Inc. ``` ## 作業要求 - [ ] 參照 [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 效能分析工具原理為何? - [x] 當來自 web 客戶端 (即網頁瀏覽器或類似的軟體) 請求的網址為 `/fib/N` 時 ($N$ 是自然數,最大可到 $2^{31} - 1$),例如 `$ wget http://localhost:8081/fib/10` 應該要讓 web 客戶端得到 $Fibonacci(10)$ 即 `55` 這個字串,這個實作需要考慮到大數運算 - [x] 自 [Facebook 討論串](https://www.facebook.com/groups/system.software2020/permalink/345124729755066/) 找出合適的 Fibonacci 運算檢驗程式,用以比對上述 (1) 的運算結果,並且要能詳實記錄和分析處理時間 (從 HTTP 請求到 kHTTPd 回應); - [ ] 指出 kHTTPd 實作的缺失 (特別是安全疑慮) 並予以改正; - [x] 引入 [Concurrency Managed Workqueue](https://www.kernel.org/doc/html/v4.15/core-api/workqueue.html) (cmwq),改寫 kHTTPd,分析效能表現和提出改進方案,可參考 [kecho](https://github.com/sysprog21/kecho); - [ ] 參照 [in-kernel HTTP server](https://hackmd.io/@sysprog/kernel-web-server),學習其中分析和實作手法,從而改進 kHTTPd; ## Fibonacci() * 因為這次作業要求要做大數的運算,所以新增了 bignum.h 和 bignum.c 兩個檔案,然後找到處理回應的副程式:http_server_response 將要回傳的 response 值改為費氏係數,運算回傳值的副程式:parse_url 是沿用 <`eecheng87`> 的實做: * 這段程式碼會將 url 拆開判斷兩個 '/' 之間是否為 fib ,是的話就該做費氏係數的運算,並將答案經由 fib_res 放進 res 做回傳到前面提到的副程式 http_server_response 的 response 變數 ```cpp static char *parse_url(char *url, int keep_alive) { size_t size; char const *del = "/"; char *token, *tmp = url, *fib_res = NULL, *res; char *msg = keep_alive ? HTTP_RESPONSE_200_KEEPALIVE_DUMMY : HTTP_RESPONSE_200_DUMMY; tmp++; token = strsep(&tmp, del); if (strcmp(token, "fib") == 0) { long k; kstrtol(tmp, 10, &k); fib_res = fib((unsigned long long) k); } if (!fib_res) return NULL; size = strlen(msg) + strlen(fib_res) + _log10(strlen(fib_res)) - 4; res = kmalloc(size, GFP_KERNEL); snprintf(res, size, msg, strlen(fib_res), fib_res); return res; } ``` * 最後輸出的關鍵是上面 msg 的值也要做相對應的更改,不然網頁只會印出 Hello World! ```cpp #define HTTP_RESPONSE_200_DUMMY \ "" \ "HTTP/1.1 200 OK" CRLF "Server: " KBUILD_MODNAME CRLF \ "Content-Type: text/plain" CRLF "Content-Length: %d" CRLF \ "Connection: Close" CRLF CRLF "%s" CRLF #define HTTP_RESPONSE_200_KEEPALIVE_DUMMY \ "" \ "HTTP/1.1 200 OK" CRLF "Server: " KBUILD_MODNAME CRLF \ "Content-Type: text/plain" CRLF "Content-Length: %d" CRLF \ "Connection: Keep-Alive" CRLF CRLF "%s" CRLF ``` ## Fibonacci 運算檢驗程式 * 我使用陳建豪同學的腳本,然後稍微修改 DATA 變數,改成 a= curl localhost:1999/fib/$ORDER * Makefike 增加: ``` verify: ./fibcheck.sh 1 10 ./fibcheck.sh 100 10 ./fibcheck.sh 1000 10 ./fibcheck.sh 10000 10 ``` ## 引入 Concurrency Managed Workqueue ### 改寫 kHTTPd * http_server.h * 引入 header `<linux/workqueue.h>` * .is_stopped 初值為 false ,當 server 端 [khread_should_stop](https:// "www.fsl.cs.sunysb.edu/kernel-api/re68.html") 回傳值為 1, .is_stopped 就會為 true,使 http_server_daemon 副程式跳出 while loop 結束, client 端也不在產生 kthread * 每個 request 的 socket 都會收到 worker->sock 的值 ```cpp #include <linux/workqueue.h> ... struct khttpd_service { bool is_stopped; }; struct khttpd_work_data { struct socket *sock; struct work_struct khttpd_work; }; ``` * http_server.c - 1 * 因為是用物件,所以 http_server_worker 副程式回傳值改為 void ,副程式內變數 socket 都改成 woker->sock ,最後要結束時再 kfree 掉 worker * 撰寫 create_work 做 http_server_daemon 裡面 worker 的初始化 ```cpp struct work_struct *create_work(struct socket *socket) { struct khttpd_work_data *wdata; if (!(wdata = kmalloc(sizeof(struct khttpd_work_data), GFP_KERNEL))) return NULL; wdata->sock = socket; INIT_WORK(&wdata->khttpd_work, http_server_worker); return &wdata->khttpd_work; } ``` * http_server.c - 2 * 在 http_server_daemon 裡如果 create_work (原本的 kthread_run) 啟動失敗,一樣要印出警告,否則就可以進入排程(queue_work) ```cpp int http_server_daemon(void *arg) { ... if (unlikely(!(work = create_work(socket)))) { pr_err("can't create more worker process\n"); kernel_sock_shutdown(socket, SHUT_RDWR); sock_release(socket); continue; } queue_work(khttpd_wq, work); } daemon.is_stopped = true; return 0; } ``` * main.c * 加入啟動 module 時加入 workqueue 和離開 module 時清掉 workqueue 的指令 ```cpp struct workqueue_struct *khttpd_wq; ... static int __init khttpd_init(void) { ... khttpd_wq = alloc_workqueue(KBUILD_MODNAME, 0, 0); ... } static void __exit khttpd_exit(void) { ... flush_workqueue(khttpd_wq); destroy_workqueue(khttpd_wq); } ``` ### 引入 CMWQ 的效能比較 ``` $ make check ``` 引入 CMWQ 之前的效能是 13962.342 requests/sec ``` requests: 100000 good requests: 100000 [100%] bad requests: 0 [0%] socker errors: 0 [0%] seconds: 7.162 requests/sec: 13962.342 Complete ``` 引入之後效能是之前的 2.67 倍 ``` requests: 100000 good requests: 100000 [100%] bad requests: 0 [0%] socker errors: 0 [0%] seconds: 2.686 requests/sec: 37236.112 Complete ``` :::danger 尚未分析 `/fib/?` 連線要求對應的反應時間,以及同時數個連線的影響 :notes: jserv ::: ## 參考資料