Try   HackMD

2022q1 Homework6 (ktcp)

contributed by < huang-me >

kecho

給定的 kecho 已使用 CMWQ,請陳述其優勢和用法

  • 優勢:
    1. CMWQ 利用的 thread pool 的機制,大幅減少了創建 thread 的 overhead。
    2. 使用 unbounded thread 機制使得 thread 可以切換到 idle 的 CPU 上,增加系統資源的使用率。
    3. 引入 scheduling 機制資源不會被需要長時間的 thread 佔用。
  • 用法:
    1. alloc_workqueue : 在初始化模組時用來建立一個 workqueue
    2. destroy_workqueue : 用來釋放 workqueue
    3. queue_work : 將任務放入 workqueue 中排程
    4. INIT_WORK : 用以初始化任務

user-echo-server 運作原理

  1. 先建立一個 socket 的 file descriptor,之後透過它來操作 socket。
    ​​​​listener = socket(PF_INET, SOCK_STREAM, 0);
    
  2. 將剛建立的 socket 與 localhost 綁定
    ​​​​bind(listener, (struct sockaddr*) &addr, sizeof(addr));
    
  3. 再來我們讓 socket 開始監聽,並將 socket 的 file descriptor 用 epoll_ctl 加入 epoll 監控的列表之中,以對使用者透過 socket 建立連線時可以進行處理。
    ​​​​if (listen(listener, 128) < 0)
    ​​​​    server_err("Fail to listen", &list);
    
    ​​​​int epoll_fd;
    ​​​​if ((epoll_fd = epoll_create(EPOLL_SIZE)) < 0)
    ​​​​    server_err("Fail to create epoll", &list);
    
    ​​​​static struct epoll_event ev = {.events = EPOLLIN | EPOLLET};
    ​​​​ev.data.fd = listener;
    ​​​​if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listener, &ev) < 0)
    ​​​​    server_err("Fail to control epoll", &list);
    ​​​​printf("Listener (fd=%d) was added to epoll.\n", epoll_fd);
    

kecho、user-echo-server 效能比較

  • 因為 bench 所記錄的時間是從 client 送出資料到收到 server 端資料回傳的時間,而且因為 user-echo-server 並不是一個並行的 server,所以在 epoll 同時收到多個 client 的事件時,會依據 file descriptor 位於 epoll_ev 陣列的位置依序執行,這樣的作法有可能導致處理速度被拖慢,進而造成測量時間大幅增加。
    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 →
  • kecho 考慮到 concurrency 議題,使用 CMWQ 將任務分配給空閒的執行緒執行,並且因為在 kernel 執行所以,所以執行的效率比 user-echo-server 好許多。
    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 →
  • 因為 I/O 執行需要很多時間,所以我嘗試將 user-echo-server 的所有 print 操作刪除之後再做一次 bench,可以發現執行的速度確實有很大幅度的提升。
    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 →
  • 同樣,也把 kecho 的所有 print 操作刪除,並且重新執行 bench ,可以看出 kecho 在無需 print 的情況下同時接受多連線時受到的影響較小。
    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 →

drop-tcp-socket 原理

TIME_WAIT socket

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 →

  • tcp socket 在結束連線時會進行一系列的溝通,以確保雙方都關閉 socket 以免佔用。而 server 端的最後一個狀態即為 TIME_WAIT 並且會停留在這個狀態 2MLS (2 x Maximum Segment Lifetime),以避免資料遺失。
  • 如果有很多 TIME_WAIT 狀態的 socket 佔用著 port 會使得新建立的連線難以找到一個可以使用的 port,但一下狀況使得我們還是需要 TIME_WAIT 機制
    1. 若有 A、B 兩次連線都連線到相同的 port,且沒有任何 TIME_WAIT 機制,若是要傳送給 A 的資料在 A 關閉連線且 B 馬上連上此 port,則 B 將誤以為是要傳送給自己的訊息。
    2. 為了確保 TCP full-duplex 終止連線的可靠性。假設 client 終止連線的最後一個 ACK (由 server 傳來)在傳輸過程丟了,則 client 會再傳一次 FIN,若此時沒有 TIME-WAIT 的機制,client 將會收到傳送錯誤的訊息 RST,因為 server 已經關閉了。

kHTTPd

khttp 運作原理

khttp_init 運作原理

  • kernel module 在被 insert 到 kernel 後,執行以下 function,其內容主要做以下事情
    1. 先建立新的 socket 的 file descriptor,並且將其與 localhost:port bind 在一起。
    2. 建立一個 thread 執行 http_server_daemon
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;
}

http_server_daemon 運作原理

  • 執行一個無窮迴圈,在接受到新的資料時,將接收到的資料傳遞給新的 socket 並且建立一個新的 worker 處理此 socket 的資料。

因為 http_server_daemon 是一個無窮迴圈,因此我們需要利用 kthread 的 kthread_should_stop 判斷是否應該結束此 daemon function,並且執行 kthread_stop 來終止執行。

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;
}

http_server_worker 運作原理

  • 執行一個無限迴圈,接收此 socket 的所有資料並且藉由 http_parser 進行相對應的反應。

其中 http_parser_settings 設定了各時期應該執行的相對應 function。

static int http_server_worker(void *arg)
{
    char *buf;
    struct http_parser parser;
    struct http_parser_settings setting = {
        .on_message_begin = http_parser_callback_message_begin,
        .on_url = http_parser_callback_request_url,
        .on_header_field = http_parser_callback_header_field,
        .on_header_value = http_parser_callback_header_value,
        .on_headers_complete = http_parser_callback_headers_complete,
        .on_body = http_parser_callback_body,
        .on_message_complete = http_parser_callback_message_complete};
    struct http_request request;
    struct socket *socket = (struct socket *) arg;

    allow_signal(SIGKILL);
    allow_signal(SIGTERM);

    buf = kmalloc(RECV_BUFFER_SIZE, GFP_KERNEL);
    if (!buf) {
        pr_err("can't allocate memory!\n");
        return -1;
    }

    request.socket = socket;
    http_parser_init(&parser, HTTP_REQUEST);
    parser.data = &request;
    while (!kthread_should_stop()) {
        int ret = http_server_recv(socket, buf, RECV_BUFFER_SIZE - 1);
        if (ret <= 0) {
            if (ret)
                pr_err("recv error: %d\n", ret);
            break;
        }
        http_parser_execute(&parser, &setting, buf, ret);
        if (request.complete && !http_should_keep_alive(&parser))
            break;
    }
    kernel_sock_shutdown(socket, SHUT_RDWR);
    sock_release(socket);
    kfree(buf);
    return 0;
}