# 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)