# 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
:::
## 參考資料