# 2019q1 Homework5 (daemon) contributed by < `yiwei01` > ###### tags: `sysprog2019` ## 取得 kecho 程式碼編譯後測試: ``` $ git clone https://github.com/yiwei/kecho $ cd kecho $ make $ sudo insmod fastecho.ko $ telnet localhost 12345 ``` ## 閱讀程式碼 ### fastecho_module.c : ```clike= #include <linux/errno.h> #include <linux/in.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/kthread.h> #include <linux/module.h> #include <linux/sched.h> #include <linux/sched/signal.h> #include <linux/signal.h> #include <linux/tcp.h> #include <linux/types.h> #include <net/sock.h> #include "fastecho.h" #define DEFAULT_PORT 12345 #define DEFAULT_BACKLOG 128 MODULE_LICENSE("Dual MIT/GPL"); MODULE_AUTHOR("National Cheng Kung University, Taiwan"); MODULE_DESCRIPTION("Fast echo server in kernel"); MODULE_VERSION("0.1"); static ushort port = DEFAULT_PORT; static ushort backlog = DEFAULT_BACKLOG; module_param(port, ushort, S_IRUGO); module_param(backlog, ushort, S_IRUGO); struct echo_server_param param; struct socket *listen_sock; struct task_struct *echo_server; static int open_listen(struct socket **); static void close_listen(struct socket *); static int fastecho_init_module(void) { int error = open_listen(&listen_sock); if (error < 0) { printk(KERN_ERR MODULE_NAME ": listen socket open error\n"); return error; } param.listen_sock = listen_sock; echo_server = kthread_run(echo_server_daemon, &param, MODULE_NAME); if (IS_ERR(echo_server)) { printk(KERN_ERR MODULE_NAME ": cannot start server daemon\n"); close_listen(listen_sock); } return 0; } static void fastecho_cleanup_module(void) { send_sig(SIGTERM, echo_server, 1); kthread_stop(echo_server); close_listen(listen_sock); } static int open_listen(struct socket **result) { struct socket *sock; struct sockaddr_in addr; int error; int opt = 1; /* using IPv4, TCP/IP */ error = sock_create(PF_INET, SOCK_STREAM, IPPROTO_TCP, &sock); if (error < 0) { printk(KERN_ERR MODULE_NAME ": socket create error = %d\n", error); return error; } printk(MODULE_NAME ": socket create ok....\n"); /* set tcp_nodelay */ error = kernel_setsockopt(sock, SOL_TCP, TCP_NODELAY, (char *) &opt, sizeof(opt)); if (error < 0) { printk(KERN_ERR MODULE_NAME ": setsockopt tcp_nodelay setting error = %d\n", error); sock_release(sock); return error; } printk(MODULE_NAME ": setsockopt ok....\n"); /* set sockaddr_in */ memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(DEFAULT_PORT); /* bind */ error = kernel_bind(sock, (struct sockaddr *) &addr, sizeof(addr)); if (error < 0) { printk(KERN_ERR MODULE_NAME ": socket bind error = %d\n", error); sock_release(sock); return error; } printk(MODULE_NAME ": socket bind ok....\n"); /* listen */ error = kernel_listen(sock, DEFAULT_BACKLOG); if (error < 0) { printk(KERN_ERR MODULE_NAME ": socket listen error = %d\n", error); sock_release(sock); return error; } printk(MODULE_NAME ": socket listen ok....\n"); *result = sock; return 0; } static void close_listen(struct socket *sock) { kernel_sock_shutdown(sock, SHUT_RDWR); sock_release(sock); } module_init(fastecho_init_module); module_exit(fastecho_cleanup_module); ``` 在 `module_init(fastecho_init_module);` 後呼叫 `open_listen` 建立一個 socket,並在第 16 行可看到 `DEFAULT_PORT 12345` (因此 `$ telnet localhost 12345` 便可執行 fastecho 這支程式)。接著展開 [kthread_run](https://www.fsl.cs.sunysb.edu/kernel-api/re67.html) 這個 macro ,執行 `echo_server_daemon` 這個 function。另外附上 `kthread_run` 在 Linux 5.1 版的 [source code](https://elixir.bootlin.com/linux/latest/source/include/linux/kthread.h#L44) : ```clike= #define kthread_run(threadfn, data, namefmt, ...) \ ({ \ struct task_struct *__k \ = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \ if (!IS_ERR(__k)) \ wake_up_process(__k); \ __k; \ }) ``` 其實就是 `kthread_create` 成功時直接 `wake_up_process`。再來可以看到 **kthread_run(echo_server_daemon, &param, MODULE_NAME);** 中的 `echo_server_daemon` 便定義在 echo_server.c 裡。 ### echo_server.c ```clike= #include <linux/kernel.h> #include <linux/kthread.h> #include <linux/sched/signal.h> #include <linux/tcp.h> #include "fastecho.h" #define BUF_SIZE 4096 static int get_request(struct socket *sock, unsigned char *buf, size_t size) { struct msghdr msg; struct kvec vec; int length; /* kvec setting */ vec.iov_len = size; vec.iov_base = buf; /* msghdr setting */ msg.msg_name = 0; msg.msg_namelen = 0; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_flags = 0; printk(MODULE_NAME ": start get response\n"); /* get msg */ length = kernel_recvmsg(sock, &msg, &vec, size, size, msg.msg_flags); printk(MODULE_NAME ": get request = %s\n", buf); return length; } static int send_request(struct socket *sock, unsigned char *buf, size_t size) { int length; struct kvec vec; struct msghdr msg; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_flags = 0; vec.iov_base = buf; vec.iov_len = strlen(buf); printk(MODULE_NAME ": start send request.\n"); length = kernel_sendmsg(sock, &msg, &vec, 1, strlen(buf) - 1); printk(MODULE_NAME ": send request = %s\n", buf); return length; } static int echo_server_worker(void *arg) { struct socket *sock; unsigned char *buf; int res; sock = (struct socket *) arg; allow_signal(SIGKILL); allow_signal(SIGTERM); buf = kmalloc(BUF_SIZE, GFP_KERNEL); if (!buf) { printk(KERN_ERR MODULE_NAME ": kmalloc error....\n"); return -1; } while (!kthread_should_stop()) { res = get_request(sock, buf, BUF_SIZE - 1); if (res <= 0) { if (res) { printk(KERN_ERR MODULE_NAME ": get request error = %d\n", res); } break; } res = send_request(sock, buf, strlen(buf)); if (res < 0) { printk(KERN_ERR MODULE_NAME ": send request error = %d\n", res); break; } } res = get_request(sock, buf, BUF_SIZE - 1); res = send_request(sock, buf, strlen(buf)); kernel_sock_shutdown(sock, SHUT_RDWR); sock_release(sock); kfree(buf); return 0; } int echo_server_daemon(void *arg) { struct echo_server_param *param = arg; struct socket *sock; struct task_struct *thread; int error; allow_signal(SIGKILL); allow_signal(SIGTERM); while (!kthread_should_stop()) { /* using blocking I/O */ error = kernel_accept(param->listen_sock, &sock, 0); if (error < 0) { if (signal_pending(current)) break; printk(KERN_ERR MODULE_NAME ": socket accept error = %d\n", error); continue; } /* start server worker */ thread = kthread_run(echo_server_worker, sock, MODULE_NAME); if (IS_ERR(thread)) { printk(KERN_ERR MODULE_NAME ": create worker thread error = %d\n", error); continue; } } return 0; } ``` - [kthread_should_stop](https://www.fsl.cs.sunysb.edu/kernel-api/re68.html) : 當 kthread 接收到 [kthread_stop](https://www.fsl.cs.sunysb.edu/kernel-api/re71.html) 時,`kthread_should_stop` 會 return true,否則 return false - [signal_pending(current)](http://welkinchen.pixnet.net/blog/post/49649996--signal_pending%28current%29-%E7%94%A8%E6%B3%95) : 目前只知道當此 function 的 return value 不為 0 的時候表示有訊號產生,這邊先猜測 `if (signal_pending(current)` 這個檢查是為了處理在 `kernel_accept` 時接收到 signal 時,要 break 出 while loop,待驗證。 - `echo_server_worker` : 這個 function 主要藉由呼叫 `get_request` 和 `send_request` 來負責訊息在 socket 的傳送。 - `get_request` : 1. 先了解 `msghdr` 這個 structure : 在 [Linux 5.1](https://elixir.bootlin.com/linux/latest/source/include/linux/socket.h#L48) 定義如下 : ```clike= struct msghdr { void *msg_name; /* ptr to socket address structure */ int msg_namelen; /* size of socket address structure */ struct iov_iter msg_iter; /* data */ void *msg_control; /* ancillary data */ __kernel_size_t msg_controllen; /* ancillary data buffer length */ unsigned int msg_flags; /* flags on received message */ struct kiocb *msg_iocb; /* ptr to iocb for async requests */ }; ``` 2. 再來看到 `kvec` 這個 structure : 在 [Linux 5.1](https://elixir.bootlin.com/linux/v5.1.1/source/include/linux/uio.h#L20) 定義如下 : ```clike= struct kvec { void *iov_base; /* and that should *never* hold a userland pointer */ size_t iov_len; }; ``` 3. 最後看到 `kernel_recvmsg` 在 [Linux 5.1](https://elixir.bootlin.com/linux/latest/source/net/socket.c#L908) 定義如下 : ```clike= int kernel_recvmsg(struct socket *sock, struct msghdr *msg, struct kvec *vec, size_t num, size_t size, int flags) { mm_segment_t oldfs = get_fs(); int result; iov_iter_kvec(&msg->msg_iter, READ, vec, num, size); set_fs(KERNEL_DS); result = sock_recvmsg(sock, msg, flags); set_fs(oldfs); return result; } ``` 先透過 `iov_iter_kvec` 將 msg->msg_iter 做設定 ```clike= void iov_iter_kvec(struct iov_iter *i, unsigned int direction, const struct kvec *kvec, unsigned long nr_segs, size_t count) { WARN_ON(direction & ~(READ | WRITE)); i->type = ITER_KVEC | (direction & (READ | WRITE)); i->kvec = kvec; i->nr_segs = nr_segs; i->iov_offset = 0; i->count = count; } ``` 最後再透過 `sock_recvmsg` 來取得資料 - `send_request` : 與 `get_request` 相似。 ## kecho 缺失 執行未修改過的 kecho,會發現字串會有亂碼,因此第一直覺是字串沒有 null-charater 或是未初始化的問題。 ``` yiwei@yiwei-VirtualBox:~/tmp/kecho$ telnet localhost 12345 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. banana banana ��������������������������������������������������������������������������������������������������������������������������l�l�l������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ ``` **dmesg** 下亦顯示亂碼 ``` [ 3091.132582] fastecho: start get response [ 3096.924543] fastecho: get request = banana \xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xffl\xe2l\xffl\xe2l\xffl\xe2l\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf6\xf6\xf6\xff\xf6\xf6\xf6\xff\xf6\xf6\xf6\xff\xf6\xf6\xf6\xff\xf6\xf6\xf6\xff\xf6\xf6\xf6\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7\xff\xf7\xf7\xf7 [ 3096.924553] fastecho: start send request. ``` ## 修改過程 1. 在 `echo_server_worker` 第 18 行加上 ` memset(buf, '\0', BUF_SIZE);` ,故在每次 `get_request` 之前都會將 buf 初始化為 null-character。 2. 在 `send_request` 時 ,將第 18 行的 `length = kernel_sendmsg(sock, &msg, &vec, 1, strlen(buf) - 1); ` 改成 `length = kernel_sendmsg(sock, &msg, &vec, 1, strlen(buf));` 用意是將 null-character 一併傳送。 ### 修改後的 `echo_server_worker` ```clike= static int echo_server_worker(void *arg) { struct socket *sock; unsigned char *buf; int res; sock = (struct socket *) arg; allow_signal(SIGKILL); allow_signal(SIGTERM); buf = kmalloc(BUF_SIZE, GFP_KERNEL); if (!buf) { printk(KERN_ERR MODULE_NAME ": kmalloc error....\n"); return -1; } while (!kthread_should_stop()) { memset(buf, '\0', BUF_SIZE); // initialize buf with null-character res = get_request(sock, buf, BUF_SIZE - 1); if (res <= 0) { if (res) { printk(KERN_ERR MODULE_NAME ": get request error = %d\n", res); } break; } res = send_request(sock, buf, strlen(buf)); if (res < 0) { printk(KERN_ERR MODULE_NAME ": send request error = %d\n", res); break; } } res = get_request(sock, buf, BUF_SIZE - 1); res = send_request(sock, buf, strlen(buf)); kernel_sock_shutdown(sock, SHUT_RDWR); sock_release(sock); kfree(buf); return 0; } ``` ### 修改後的 `send_request` ```clike= static int send_request(struct socket *sock, unsigned char *buf, size_t size) { int length; struct kvec vec; struct msghdr msg; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_flags = 0; vec.iov_base = buf; vec.iov_len = strlen(buf); printk(MODULE_NAME ": start send request.\n"); length = kernel_sendmsg(sock, &msg, &vec, 1, strlen(buf)); // send with null-character printk(MODULE_NAME ": send request = %s\n", buf); return length; } ``` 上述的修改能使得訊息正常傳送,但透過 dmesg 能觀察到, 每次在 telnet 按下 `ctrl + ]` 後,總是會多出 2 次 get 和 1 次 send。 ``` [ 1483.493954] fastecho: start get response [ 1484.710412] fastecho: get request = asdwed [ 1484.710413] fastecho: start send request. [ 1484.710431] fastecho: send request = asdwed [ 1484.710433] fastecho: start get response [ 1487.606301] fastecho: get request = [ 1487.606302] fastecho: start get response [ 1487.606305] fastecho: get request = [ 1487.606306] fastecho: start send request. [ 1487.606309] fastecho: send request = ``` 修正 : 將 `echo_server_worker` 第 35、36 行的 `get_request` 以及 `send_request` 註解掉,如此一來當按下 `ctrl + ]` 後,便只後留下一次 `get_request`。 ### 修改後的 `echo_server_worker` ```clike= static int echo_server_worker(void *arg) { struct socket *sock; unsigned char *buf; int res; sock = (struct socket *) arg; allow_signal(SIGKILL); allow_signal(SIGTERM); buf = kmalloc(BUF_SIZE, GFP_KERNEL); if (!buf) { printk(KERN_ERR MODULE_NAME ": kmalloc error....\n"); return -1; } while (!kthread_should_stop()) { memset(buf, '\0', BUF_SIZE); // initialize buf with null-character res = get_request(sock, buf, BUF_SIZE - 1); if (res <= 0) { if (res) { printk(KERN_ERR MODULE_NAME ": get request error = %d\n", res); } break; } res = send_request(sock, buf, strlen(buf)); if (res < 0) { printk(KERN_ERR MODULE_NAME ": send request error = %d\n", res); break; } } /* remove unnecessary statement */ // res = get_request(sock, buf, BUF_SIZE - 1); // res = send_request(sock, buf, strlen(buf)); kernel_sock_shutdown(sock, SHUT_RDWR); sock_release(sock); kfree(buf); return 0; } ``` ``` [ 1808.119523] fastecho: start get response [ 1811.627405] fastecho: get request = sklc [ 1811.627407] fastecho: start send request. [ 1811.627424] fastecho: send request = sklc [ 1811.627426] fastecho: start get response [ 1814.378337] fastecho: get request = ``` 而最後剩下的這個 `[ 1814.378337] fastecho: get request = ` ,是 user 按下 `ctrl + ]` 後,因為沒有送出 message , 所以在離開 `get_request` function 前印出 buf 的內容便是空的 (全是 null-character) 。