# 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`