# 2020q1 Homework4 (khttpd) contributed by < `jwang0306` > > [作業要求](https://hackmd.io/@sysprog/linux2020-khttpd?fbclid=IwAR1MiaCf9QKEqmWxePL3JMqZjnsIJkV_HCs0stIMLQoBPe0qz-51B3nhZNY#-%E4%BD%9C%E6%A5%AD%E8%A6%81%E6%B1%82) > [GitHub](https://github.com/jwang0306/khttpd) ## 自我檢查清單 - [ ] 參照 [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 效能分析工具原理為何? --- ## Concurrency Managed Workqueue ### 修改前 透過 htstress 測試原本每秒所能處理的請求: ```shell $ make check ``` 輸出結果為大約 15000 requests/sec: ```shell requests: 100000 good requests: 100000 [100%] bad requests: 0 [0%] socker errors: 0 [0%] seconds: 7.078 requests/sec: 14128.297 Complete ``` ### 引入 CMWQ 以下實做參考了 [sysprog21/kecho](https://github.com/sysprog21/kecho) 。與該實做的不同點是,我沒有去自己建立並維護一個全域的 list ,目的只是為了在清理 workqueue 的時候依循著該 list 將資源釋放。我覺得只要在每個 work 執行的 function (這裡為 `http_server_worker`)最後釋放就好,每完成一個 work 就釋放一個。而且 workqueue 裡面的 [`struct worker_pool`](https://elixir.bootlin.com/linux/latest/source/kernel/workqueue.c#L147) 本來就有一個 worklist 用以維護 [`queue_work()`](https://elixir.bootlin.com/linux/latest/source/kernel/workqueue.c#L1389) 所插入的 works 了,所以我認為不需要再額外去維護一個全域的 list 。 - #### khttpd_work_data - `sock` :每個 connection 都有一個 socket - `khttpd_work` :每一個 request 都代表一個 work ```cpp struct khttpd_work_data { struct socket *sock; struct work_struct khttpd_work; }; ``` - #### create_work - 用以初始化 `khttpd_work_data` - 執行 `INIT_WORK` 的時候,會將此 function 賦予 work `(__work)->func = func` ```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_worker - 每個 work 所執行的 function - 需將回傳型態改為 `void` - 於 function 最後將 work data 給釋放 ```diff - static int http_server_worker(void *arg) + static void http_server_worker(struct work_struct *work) { ... - struct socket *socket = (struct socket *) arg; + struct khttpd_work_data *wdata = + container_of(work, struct khttpd_work_data, khttpd_work); ... - request.socket = socket; + request.socket = wdata->sock; ... - while (!kthread_should_stop()) { - int ret = http_server_recv(socket, buf, RECV_BUFFER_SIZE - 1); + while (!daemon.is_stopped) { + int ret = http_server_recv(wdata->sock, buf, RECV_BUFFER_SIZE - 1); ... - kernel_sock_shutdown(socket, SHUT_RDWR); - sock_release(socket); + kernel_sock_shutdown(wdata->sock, SHUT_RDWR); + sock_release(wdata->sock); + kfree(wdata); ... } ``` - #### http_server_daemon - 建立 work - 將 work 放進 workqueue , `queue_work(khttpd_wq, work)` ```diff int http_server_daemon(void *arg) { - struct task_struct *worker; + struct work_struct *work; ... while (!kthread_should_stop()) { ... - worker = kthread_run(http_server_worker, socket, KBUILD_MODNAME); - if (IS_ERR(worker)) { + 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; } ``` - #### main.c - 初始化 module 時建立 workqueue - `alloc_workqueue(char *name, unsigned int flags, int max_active)` - name :名稱 - flags :種類 - `WQ_MEM_RECLAIM` (default) - `WQ_UNBOUND` - `WQ_HIGHPRI` - ... - max_active :per-cpu 上所能執行的最大 work 數量 - 預設 256 (default ,設定為 0) - 最大 512 - 在離開 module 時清掉 workqueue - `flush_workqueue(struct workqueue_struct *wq)` - `destroy_workqueue(struct workqueue_struct *wq)` ```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); } ``` ### 修改後 將 concurrent connections 數目調大: ```shell $ ./htstress -n 100000 -c 4 -t 4 http://localhost:8081/ ``` 輸出結果為大約 33000 requests/sec ,翻了一倍左右: ```shell requests: 100000 good requests: 100000 [100%] bad requests: 0 [0%] socker errors: 0 [0%] seconds: 2.975 requests/sec: 33615.378 ``` --- ## 回傳 Fibonacci ### 想法與實做 由於我不確定整合到底是整合到什麼地步,因此首先就用最直接簡單的方法達到了要求,目前沒有其他的想法。 - 修改 `Makefile` 將 bignum fibonacci 引入 - 暴力 hard coded 切割 `request_url` ,拿出數字 - 將計算結果塞進 HTTP response ,最後由 `http_server_response()` 回傳至 client 端 ```cpp static char *http_fib_response(char *request_url) { char *resp_buf = kmalloc(RESP_BUFFER_SIZE, GFP_KERNEL); memset(resp_buf, 0, RESP_BUFFER_SIZE); char *tmp = kmalloc(MAX_DIGITS, GFP_KERNEL); memset(tmp, 0, MAX_DIGITS); char *cpy = kmalloc(strlen(request_url) + 1, GFP_KERNEL); snprintf(cpy, strlen(request_url) + 1, "%s", request_url); cpy[strlen(request_url)] = '\0'; char *n = NULL; strsep(&cpy, "/"); strsep(&cpy, "/"); n = strsep(&cpy, "/"); long int k; kstrtol(n, 10, &k); bn_t result = fib_double_clz((long long) k); for (int i = 0, index = 0; i < result.num_digits; ++i) index += snprintf(&tmp[index], MAX_DIGITS - index, "%d", result.digits[result.num_digits - i - 1]); snprintf(resp_buf, RESP_BUFFER_SIZE, "" "HTTP/1.1 200 OK" CRLF "Server: " KBUILD_MODNAME CRLF "Content-Type: text/plain" CRLF "Content-Length: %ld" CRLF "Connection: Keep-Alive" CRLF CRLF "%s", strlen(tmp), tmp); kfree(tmp); kfree(cpy); return resp_buf; } ``` - 是個非常死的寫法,為了測試用,決定先求有再求好 ```cpp static int http_server_response(struct http_request *request, int keep_alive) { char *response; pr_info("requested_url = %s\n", request->request_url); if (request->method != HTTP_GET) response = keep_alive ? HTTP_RESPONSE_501_KEEPALIVE : HTTP_RESPONSE_501; else response = keep_alive ? http_fib_response(request->request_url) : HTTP_RESPONSE_200_DUMMY; http_server_send(request->socket, response, strlen(response)); return 0; } ``` ### 執行結果 - 由於 bignum 實做上的鄙陋,由於陣列最大只有開到 32 (digit) ,因此十分容易 <s>segmentation fault</s> ,最大只能算到 $fib(154)$ - 重新實做 bignum 是必須的 ![](https://i.imgur.com/O52SeAQ.png) :::danger 1. 真正的問題不是 "segmentation fault",請你查明之後再正確描述 2. 如果在核心中計算 Fibonacci 數要耗費相當多時間,對其他任務 (process/thread) 會有什麼衝擊?是否需要 [reschedule](http://man7.org/linux/man-pages/man7/sched.7.html)?又,從其他任務切換回來後,原有的 Fibonacci 數計算能否繼續並維持 TCP 通訊? 這就是你需要釐清且準備好解決方案的議題。 :notes: jserv ::: :::warning TODO - 使 server 在計算 fibonacci 時不會被 block 住 - 重新實做 bignum 以算得更大的數字 ::: --- ## 改進 kHTTPd --- ## 參考資料: - [in-kernel HTTP server](https://hackmd.io/@sysprog/kernel-web-server) - [sysprog21/kecho](https://github.com/sysprog21/kecho) - [Linux 的并发可管理工作队列机制探讨](https://www.ibm.com/developerworks/cn/linux/l-cn-cncrrc-mngd-wkq/index.html) - [Concurrency Managed Workqueue之(一):workqueue的基本概念](http://www.wowotech.net/irq_subsystem/workqueue.html) - [Concurrency Managed Workqueue之(二):CMWQ概述](http://www.wowotech.net/irq_subsystem/cmwq-intro.html) - [Concurrency Managed Workqueue之(三):创建workqueue代码分析](http://www.wowotech.net/irq_subsystem/alloc_workqueue.html)