Try   HackMD

2020q1 Homework4 (khttpd)

contributed by < jwang0306 >

作業要求
GitHub

自我檢查清單

  • 參照 fibdrv 作業說明 裡頭的「Linux 核心模組掛載機制」一節,解釋 $ sudo insmod khttpd.ko port=1999 這命令是如何讓 port=1999 傳遞到核心,作為核心模組初始化的參數呢?
  • 參照 CS:APP 第 11 章,給定的 kHTTPd 和書中的 web 伺服器有哪些流程是一致?又有什麼是你認為 kHTTPd 可改進的部分?
  • htstress.c 用到 epoll 系統呼叫,其作用為何?這樣的 HTTP 效能分析工具原理為何?

Concurrency Managed Workqueue

修改前

透過 htstress 測試原本每秒所能處理的請求:

$ make check

輸出結果為大約 15000 requests/sec:

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 。與該實做的不同點是,我沒有去自己建立並維護一個全域的 list ,目的只是為了在清理 workqueue 的時候依循著該 list 將資源釋放。我覺得只要在每個 work 執行的 function (這裡為 http_server_worker)最後釋放就好,每完成一個 work 就釋放一個。而且 workqueue 裡面的 struct worker_pool 本來就有一個 worklist 用以維護 queue_work() 所插入的 works 了,所以我認為不需要再額外去維護一個全域的 list 。

  • khttpd_work_data

    • sock :每個 connection 都有一個 socket
    • khttpd_work :每一個 request 都代表一個 work
struct khttpd_work_data {
    struct socket *sock;
    struct work_struct khttpd_work;
};
  • create_work

    • 用以初始化 khttpd_work_data
    • 執行 INIT_WORK 的時候,會將此 function 賦予 work (__work)->func = func
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 給釋放
- 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)
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)
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 數目調大:

$ ./htstress -n 100000 -c 4 -t 4 http://localhost:8081/

輸出結果為大約 33000 requests/sec ,翻了一倍左右:

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 端
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;
}
  • 是個非常死的寫法,為了測試用,決定先求有再求好
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) ,因此十分容易 segmentation fault ,最大只能算到
    fib(154)
  • 重新實做 bignum 是必須的

  1. 真正的問題不是 "segmentation fault",請你查明之後再正確描述
  2. 如果在核心中計算 Fibonacci 數要耗費相當多時間,對其他任務 (process/thread) 會有什麼衝擊?是否需要 reschedule?又,從其他任務切換回來後,原有的 Fibonacci 數計算能否繼續並維持 TCP 通訊?

這就是你需要釐清且準備好解決方案的議題。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
jserv

TODO

  • 使 server 在計算 fibonacci 時不會被 block 住
  • 重新實做 bignum 以算得更大的數字

改進 kHTTPd


參考資料: