# 2020q1 Homework4 (khttpd) contributed by < `nickyanggg` > ###### tags: `linux2020` ## [作業要求](https://hackmd.io/@sysprog/linux2020-khttpd#-%E4%BD%9C%E6%A5%AD%E8%A6%81%E6%B1%82) - [自我檢查清單](https://hackmd.io/@sysprog/linux2020-khttpd#-%E8%87%AA%E6%88%91%E6%AA%A2%E6%9F%A5%E6%B8%85%E5%96%AE) - 將 [fibdrv 作業](https://hackmd.io/@nickyanggg/fibdrv) 整合進來 ## 實驗環境 ```shell $ uname -a Linux nickyanggg-ubuntu18 5.3.0-42-generic #34~18.04.1-Ubuntu SMP Fri Feb 28 13:42:26 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux $ gcc --version gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 ``` ## Kernel module 的參數傳遞 觀察 `main.c`,可發現預設的 port 為 8081,而透過掛載的時候加上 `port=1999` 可將參數傳入,推測是透過 `module_param`,於是就追進去看。 ```cpp #define DEFAULT_PORT 8081 static ushort port = DEFAULT_PORT; module_param(port, ushort, S_IRUGO); static int __init khttpd_init(void) { int err = open_listen_socket(port, backlog, &listen_socket); if (err < 0) { pr_err("can't open listen socket\n"); return err; } param.listen_socket = listen_socket; http_server = kthread_run(http_server_daemon, &param, KBUILD_MODNAME); if (IS_ERR(http_server)) { pr_err("can't start http server daemon\n"); close_listen_socket(listen_socket); return PTR_ERR(http_server); } return 0; } ``` 參考 [Linux drivers module param](https://b8807053.pixnet.net/blog/post/339227792-linux-driver-modules-param),發現 `module_param` 為 [<linux/moduleparam.h>](https://github.com/torvalds/linux/blob/master/include/linux/moduleparam.h) 中所定義的巨集,透過它可以在啟動系統或模組裝載時指定參數值。 - `name`:使用者看到的參數名,也是模組內接受參數的變數。 - `type`:參數的類型。 - `perm`:sysfs 中相應檔案的存取權限。 ```cpp /** * module_param - typesafe helper for a module/cmdline parameter * @name: the variable to alter, and exposed parameter name. * @type: the type of the parameter * @perm: visibility in sysfs. * * @name becomes the module parameter, or (prefixed by KBUILD_MODNAME and a * ".") the kernel commandline parameter. Note that - is changed to _, so * the user can use "foo-bar=1" even for variable "foo_bar". * * @perm is 0 if the the variable is not to appear in sysfs, or 0444 * for world-readable, 0644 for root-writable, etc. Note that if it * is writable, you may need to use kernel_param_lock() around * accesses (esp. charp, which can be kfreed when it changes). * * The @type is simply pasted to refer to a param_ops_##type and a * param_check_##type: for convenience many standard types are provided but * you can create your own by defining those variables. * * Standard types are: * byte, short, ushort, int, uint, long, ulong * charp: a character pointer * bool: a bool, values 0/1, y/n, Y/N. * invbool: the above, only sense-reversed (N = true). */ #define module_param(name, type, perm) \ module_param_named(name, name, type, perm) ``` ## kHTTPd web 伺服器 ![](https://i.imgur.com/n37T1hM.png) > 出處:[CS:APP 第 11 章](https://hackmd.io/s/ByPlLNaTG) `khttpd_init` 中的 `open_listen_socket` 基本上做的事就是開一個 socket、bind 到一個 port 上並開始 listen,和圖中的 `open_listenfd` 做的事一樣。接著會透過 kernel thread 去執行 `http_server_daemon`,當中的 `kernel_accept` 相當於圖中的 `accept` 是用來等待 client 端的連線。在處理 client 的 request 時,會另外開一條 thread 去執行 `http_server_worker`,讓新開的 socket 去和 client 連線接收 request 封包。因此原本的 socket 只要負責 listen 即可,一旦有 request 則透過新建的 socket 去處理。 ```cpp int http_server_daemon(void *arg) { struct socket *socket; struct task_struct *worker; struct http_server_param *param = (struct http_server_param *) arg; allow_signal(SIGKILL); allow_signal(SIGTERM); while (!kthread_should_stop()) { int err = kernel_accept(param->listen_socket, &socket, 0); if (err < 0) { if (signal_pending(current)) break; pr_err("kernel_accept() error: %d\n", err); continue; } worker = kthread_run(http_server_worker, socket, KBUILD_MODNAME); if (IS_ERR(worker)) { pr_err("can't create more worker process\n"); continue; } } return 0; } ``` ## 整合 [fibdrv 作業](https://hackmd.io/OWwxp6sWRbKnbR-LZO0WOg) 進 kHTTPd - include [<bignum.h>](https://github.com/nickyanggg/khttpd/blob/master/bignum.h) - 將原本寫死的 `Hello world` 以及字串長度改寫成 `%d`、`%s`。 - 將 `http_server_response` 回傳成功的部分改寫成呼叫 `fib_url`。 - `fib_url` 會檢查是否為正確格式,並且呼叫 `fib_sequence` (fast doubling) 來計算應當回傳的數字。 - 因為 `bignum` 實作的方式,目前只會去計算 fib(4500) 以內的數。 - `fib_sequence` 的回傳內容會是反向的,所以要將它轉向。 - malloc 的空間要多加 `length * 2` 是因為除了 `%s` (fib 所得到的結果) 的長度之外,還要再加上 `%d` 的長度,那這邊就直接取一個確定大等於它的數。 - 最後 response 完之後要釋放回傳內容所用到的空間。 ```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 static char *fib_url(char *url, int keep_alive) { char *response = keep_alive ? HTTP_RESPONSE_200_KEEPALIVE_DUMMY : HTTP_RESPONSE_200_DUMMY; char *ret, *token, *tmp = url, result[MAX_DIGIT]; int size, length, i; strsep(&tmp, "/"); token = strsep(&tmp, "/"); if (!strcmp(token, "fib")) { token = strsep(&tmp, "/"); long num; kstrtol(token, 10, &num); // bignum max digit = 1000, fib(4500) digit = 941 if (num > 4500 || num < 0) return NULL; char *rev_fib = fib_sequence(num)->decimal; length = strlen(rev_fib); for (i = 0; i < length; i++) result[i] = rev_fib[length - 1 - i]; result[length] = '\0'; } else return NULL; size = strlen(response) + length * 2; ret = kmalloc(size, GFP_KERNEL); snprintf(ret, size, response, length, result); return ret; } 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 = fib_url(request->request_url, keep_alive); http_server_send(request->socket, response, strlen(response)); kfree(response); return 0; } ``` 參考[黃鈺盛](https://gist.github.com/wilson6405/c8b86f71ce680efee26850298c9c237c)所提供的驗證程式,並改寫 `test.sh` 來驗證 $fib(1)$ ~ $fib(4500)$。 ```=shell #!/usr/bin/env bash KHTTPD_MOD=khttpd.ko if [ "$EUID" -eq 0 ] then echo "Don't run this script as root" exit fi # load kHTTPd sudo rmmod -f khttpd 2>/dev/null sleep 1 sudo insmod $KHTTPD_MOD # check fibonacci 0 ~ 4500 for i in {1..4500}; do python scripts/test.py -p 8081 -i $i done # epilogue sudo rmmod khttpd echo "Complete" ``` 執行結果皆正確。 ```shell $ bash scripts/test.sh Pass Pass Pass ... Pass Pass Pass ```