---
tags: linux kernel
---
# 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。
```c
listener = socket(PF_INET, SOCK_STREAM, 0);
```
2. 將剛建立的 socket 與 localhost 綁定
```c
bind(listener, (struct sockaddr*) &addr, sizeof(addr));
```
3. 再來我們讓 socket 開始監聽,並將 socket 的 file descriptor 用 epoll_ctl 加入 epoll 監控的列表之中,以對使用者透過 socket 建立連線時可以進行處理。
```c
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 陣列的位置依序執行,這樣的作法有可能導致處理速度被拖慢,進而造成測量時間大幅增加。
![](https://i.imgur.com/1sz6vii.png)
- kecho 考慮到 concurrency 議題,使用 CMWQ 將任務分配給空閒的執行緒執行,並且因為在 kernel 執行所以,所以執行的效率比 `user-echo-server` 好許多。
![](https://i.imgur.com/L5XegoC.png)
- 因為 I/O 執行需要很多時間,所以我嘗試將 `user-echo-server` 的所有 `print` 操作刪除之後再做一次 `bench`,可以發現執行的速度確實有很大幅度的提升。
![](https://i.imgur.com/nMnlxK2.png)
- 同樣,也把 `kecho` 的所有 `print` 操作刪除,並且重新執行 `bench` ,可以看出 `kecho` 在無需 `print` 的情況下同時接受多連線時受到的影響較小。
![](https://i.imgur.com/Z8VrXS0.png)
## drop-tcp-socket 原理
### TIME_WAIT socket
![](https://i.imgur.com/Z9KPjkb.png)
- 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`
```c
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, ¶m, 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 的資料。
:::warning
因為 `http_server_daemon` 是一個無窮迴圈,因此我們需要利用 kthread 的 `kthread_should_stop` 判斷是否應該結束此 daemon function,並且執行 `kthread_stop` 來終止執行。
:::
```c
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` 進行相對應的反應。
:::success
其中 `http_parser_settings` 設定了各時期應該執行的相對應 function。
:::
```c
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;
}
```