# 2020q1 Homework4 (khttpd) contributed by < `fdgkhdkgh` > > [作業要求](https://hackmd.io/@sysprog/linux2020-khttpd) ## 開發紀錄 ### 與 fibdrv 結合 因為在上一次的作業 [fibdrv](https://github.com/fdgkhdkgh/fibdrv) 中,已經將老師的 [bignum](https://github.com/sysprog21/bignum) 以及 fast_doubling 移植到 kernel driver 中了。所以可以很簡易的將 fibdrv 的功能移植到 khttpd 上。 在 khttpd 中的 http_server_response 會取得使用者的請求,於是我在這裡解析使用者的請求,並回應相對應的 fibonacci 數值。 ### 使用 CMWQ 進行效能改進 原本 khttpd 在接收到使用者的請求後,會使用 kthread_run 去處理使用者的連線。後續我參考 [kecho](https://github.com/sysprog21/kecho) 的實作,將 kthread_run 改成 workqueue 的形式。當有新的連線進來時,使用 worker thread 來進行處理。 ### CMWQ 的效能評估 在評估 CMWQ 的實作,與原本的實作差異時,我簡單地修改 htstress.c 成 [my_htstress.c](https://github.com/fdgkhdkgh/khttpd/blob/master/my_htstress.c) 。 使其可以在每次發出請求時,隨機求取 fibonacci[0] ~ fibonacci[301] 的值(目前我的實作在頻繁求取超過 350 的數值時,電腦會當機,還在分析原因中。然而假如沒有頻繁求值的話,還是能求取 f[6000000] 以上的大請求。) 在使用前,可以先選擇這次產生亂數的種子。 只要種子相同,每次隨機請求的大小與順序都會相同,可以公平的檢測 原本 kthread_run 的實作與 CMWQ 的實作的效能差異 --- 原本的 khttpd 的效能 ``` ./my_htstress -n 100000 -c 100 -t 4 localhost:50000/ requests: 100000 good requests: 100000 [100%] bad requests: 0 [0%] socker errors: 0 [0%] seconds: 2.763 requests/sec: 36198.165 ``` --- 使用 CMWQ 改進過後的 khttpd 的效能 ``` ./my_htstress -n 100000 -c 100 -t 4 localhost:50000/ requests: 100000 good requests: 100000 [100%] bad requests: 0 [0%] socker errors: 0 [0%] seconds: 1.119 requests/sec: 89390.429 ``` --- 可以看見,使用 CMWQ 改進過後的 khttpd 有顯著的效能提升。 ## 改進 ### 改進 1. 撰寫驗證 fibonacci 正確性與效能的程式 確保 /fib/? 的正確性,以及計算回傳時間 TODO ### 改進 2. 改進安全疑慮 TODO ### 改進 3. 增加 fibonacci 的求值範圍 TODO ## 自我檢查清單 - [ ] 參照 fibdrv 作業說明 裡頭的「Linux 核心模組掛載機制」一節,解釋 $ sudo insmod khttpd.ko port=1999 這命令是如何讓 port=1999 傳遞到核心,作為核心模組初始化的參數呢? 在這裡,我使用一些方便的工具來看 insmod 的執行流程。 cirosantilli 的 [linux-kernel-module-cheat](https://github.com/cirosantilli/linux-kernel-module-cheat#your-first-kernel-module-hack) 專案可以快速地建立一個對 linux kernel 使用 qemu + gdbserver 進行除錯的環境。 我使用 gdbserver 在 do_init_module 下斷點並進行觀察。我是以 termernal_1 開啟 gdb ,並用 terminal_2 開啟 qemu 。 ``` # 範例 # terminal_1 # 載入 linux kernel 的符號表 gdb ~/linux-kernel-module-cheat/out/linux/default/x86_64/vmlinux --- # terminal_2 # 使用 qemu 開啟虛擬機,執行 linux kernel ,並開啟 gdbserver ,等待連線 ./run --gdb-wait --- # terminal_1 # 對感興趣的 function 下斷點 gdb> b do_init_module # 與 gdbserver 進行連接 gdb> target remote :45457 gdb> conti --- # terminal_2 # 使用 insmod 傳入參數 > insmod hello.ko para=100 para2=200 --- # terminal_1 此時會停在下的斷點, do_init_module # 可以印出感興趣的 structure 各個 member 的值 gdb-peda$ p *mod $1 = { state = MODULE_STATE_COMING, list = { next = 0xffffffff82048150 <modules>, prev = 0xffffffff82048150 <modules> }, name = "hello", '\000' <repeats 50 times>, mkobj = { kobj = { name = 0xffff88800e599b38 "hello", entry = { next = 0xffff88800f1a3060, prev = 0xffff88800f1c2c08 }, parent = 0xffff88800f1a3078, kset = 0xffff88800f1a3060, ktype = 0xffffffff8203af80 <module_ktype>, sd = 0xffff88800e3cf440, kref = { refcount = { refs = { counter = 0x3 } } }, state_initialized = 0x1, state_in_sysfs = 0x1, state_add_uevent_sent = 0x1, state_remove_uevent_sent = 0x0, uevent_suppress = 0x0 ..... # 印出 mod 所指向的 structure 的 members gdb-peda$ p *(mod->kp) $2 = { name = 0xffffffffc00010d8 "para2", mod = 0xffffffffc0002040, ops = 0xffffffff81c0a4e0 <param_ops_ushort>, perm = 0x124, level = 0xff, flags = 0x0, { arg = 0xffffffffc0002000, str = 0xffffffffc0002000, arr = 0xffffffffc0002000 } } ``` 在最一開始,先使用 strace 來看 insmod 執行了哪些系統呼叫 ``` strace -o log insmod hello.ko para=100 para2=200 ``` 可以看到其中有一行 ``` finit_module(3, "para=100 para2=200 ", 0) = 0 ``` 顯示了我所傳的參數,於是我決定從這個地方開始看。 查看 [finit_module 的程式碼](https://elixir.bootlin.com/linux/v4.18/source/kernel/module.c#L3855) 可以看到參數的變數名稱為 ```uargs``` 。 接下來稍作整理對參數的處理。 ``` finit_mould -> load_module -> /* 將字串複製給 mod->args */ mod->args = strndup_user(uargs, ~0UL >> 1); -> /* 將字串處理後,存入 mod->kp */ parse_args -> parse_one ``` 也稍微整理一下 structure 內的 member。 ``` struct module { struct kernel_param *kp; // kernel_param 陣列,裡面儲存了與參數有關的資訊 unsigned int num_kp; // kp 這個陣列的元素個數 char *args; // 以字串的形式儲存參數, // 以這次的例子,儲存的字串會是 "para\x00100\x00para2\x00200" } ``` ``` struct kernel_param { const char *name; // 參數名稱 struct module *mod; const struct kernel_param_ops *ops; // 該用什麼操作來設定/取用參數 // 例如型態是 ushort 就會是 <param_ops_ushort> const u16 perm; s8 level; u8 flags; union { void *arg; // 指向參數的值的位址 const struct kparam_string *str; const struct kparam_array *arr; }; }; ``` --- - [ ] 參照 CS:APP 第 11 章,給定的 kHTTPd 和書中的 web 伺服器有哪些流程是一致?又有什麼是你認為 kHTTPd 可改進的部分? (a): 給定的 kHTTPd 和書中的 web 伺服器有哪些流程是一致? ``` kHTTPd: getaddrinfo | socket main.c:sock_create() | bind main.c:kernel_bind() | listen main.c:kernel_listen | accept <---------| http_server.c:kernel_accept | | rio_readlineb | http_server.c:kernel_recvmsg | | rio_writen | http_server.c:kernel_sendmsg | | rio_readlineb ---| http_server.c:kernel_recvmsg | close main.c:kernel_sock_shutdown main.c:sock_release ``` --- (b):有什麼是你認為 kHTTPd 可改進的部分? TODO --- - [ ] htstress.c 用到 epoll 系統呼叫,其作用為何?這樣的 HTTP 效能分析工具原理為何? (a): epoll 系統呼叫的作用為何? [最平常的 server 實作方式,會是新增一個 process/thread 去處理新的連線。](https://kovyrin.net/2006/04/13/epoll-asynchronous-network-programming/)但其實也可以用單一一個 process/thread 去處理複數個連線。 每當 accept 一個連線後,就多一個需要監聽的 file descriptor 。而單一一個 process/thread 可以使用 poll/epoll 來監聽多個 file descriptor 。只要當某個使用者對 socket 傳輸資料,伺服器端就可以使用 poll/epoll 系統呼叫來監聽所有 socket (file descriptor) ,對需要回應的 file descriptor 進行回應。 (b): htstress.c 效能分析工具的原理為何? ``` Usage: htstress [options] [http://]hostname[:port]/path Options: -n : 總共要發送幾次請求 -c : 每個 worker 要同時發送多少個請求 -t : 在 client 端,要同時使用多少個 thread( worker ) ``` 在發送請求之前,會使用 start_time() 記錄開始送請求前的時間,在送出請求的數量滿足 n 個之後,就會使用 end_time() 來記錄結束的時間。最後會總結發送成功與否,以及花費的總時間。 ## Reference [1][Nagle's algorithm](https://en.wikipedia.org/wiki/Nagle%27s_algorithm) [2][IS_ERR 原理](https://www.jianshu.com/p/36ed811e9a1b) [3][linux-kernel-examples : kernel-threads.c](https://github.com/muratdemirtas/Linux-Kernel-Examples/blob/master/kernel-threads.c) [4][http parser](https://github.com/nodejs/http-parser) [5][htstress](https://github.com/arut/htstress) [6][khttpd](https://github.com/sysprog21/khttpd) [7][in-kernel HTTP server](https://hackmd.io/@sysprog/kernel-web-server) [8][CS:APP ch11 network programming 重點提示](https://hackmd.io/@sysprog/CSAPP-ch11?type=view) [9][nstack](https://hackmd.io/@jD9XFdyQS0iyAaFMPYi5Ww/ryfvFmZ0f?type=view) [10][server-framework](https://hackmd.io/@willychiu/HJY7Bzspf?type=view) [11][String Manipulation](https://www.cs.bham.ac.uk/~exr/teaching/lectures/systems/08_09/docs/kernelAPI/x1820.html) [12][Linux 效能分析工具: Perf](http://wiki.csie.ncku.edu.tw/embedded/perf-tutorial) [13][Linux kernel profiling with perf](https://perf.wiki.kernel.org/index.php/Tutorial#Sample_analysis_with_perf_report) [14][linux kernel module cheat - workqueue example](https://github.com/cirosantilli/linux-kernel-module-cheat/tree/ad077d3943f79c0f6481dab929970613c33c31a7) [15][kthread_run](https://elixir.bootlin.com/linux/latest/source/include/linux/kthread.h#L43) kthread_create + wake_up_process [16][CMWQ : 台部落](https://www.twblogs.net/a/5d7e2bb2bd9eee541c34617c) [17]linux kernel development 3rd.Edition ch8:Work Queues [18]linux kernel development 3rd.Edition ch2:Kernel Threads [19]CMWQ 系列文章 http://www.wowotech.net/irq_subsystem/workqueue.html http://www.wowotech.net/irq_subsystem/cmwq-intro.html http://www.wowotech.net/irq_subsystem/alloc_workqueue.html http://www.wowotech.net/irq_subsystem/queue_and_handle_work.html [20][linux kernel docs : CWMQ](https://www.kernel.org/doc/html/v4.15/core-api/workqueue.html) [21][sysprog21 : kecho](https://github.com/sysprog21/kecho) [22][alloc_workqueue](https://www.cnblogs.com/vedic/p/11069249.html) [23][Using epoll() For Asynchronous Network Programming ](https://kovyrin.net/2006/04/13/epoll-asynchronous-network-programming/) [24][cirosantilli/linux-kernel-module-cheat](https://github.com/cirosantilli/linux-kernel-module-cheat) [25][epoll man page](http://man7.org/linux/man-pages/man7/epoll.7.html) [26][poll man page](http://man7.org/linux/man-pages/man2/poll.2.html) ###### tags: `linux2020`