# 2023q1 Homework7 (ktcp)
contributed by < `Korin777` >
## 開發環境
```
$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 39 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 20
On-line CPU(s) list: 0-19
Vendor ID: GenuineIntel
Model name: 12th Gen Intel(R) Core(TM) i7-12700H
CPU family: 6
Model: 154
Thread(s) per core: 2
Core(s) per socket: 14
Socket(s): 1
Stepping: 3
CPU max MHz: 4700.0000
CPU min MHz: 400.0000
BogoMIPS: 5376.00
Caches (sum of all):
L1d: 544 KiB (14 instances)
L1i: 704 KiB (14 instances)
L2: 11.5 MiB (8 instances)
L3: 24 MiB (1 instance)
NUMA:
NUMA node(s): 1
NUMA node0 CPU(s): 0-19
```
## CMWQ
使用者可以創建 workqueue 共享系統上的 worker-pool , 並根據設定的 flag 來使用對應的 worker-pool , woker-pool 會在需要時喚醒或動態增加 worker 來處理 work item , idle worker 不會被立刻釋放以減少建立 worker 的成本
## 在 khttpd 引入 CMWQ
[commit 0f89eac](https://github.com/Korin777/khttpd/commit/0f89eac2bededbd6bdd582b59d547dba9965bdae)
在原本的實做中,每當有新的連線需要自己創建新的 kernel thread 來處理,參考 [kecho](https://github.com/sysprog21/kecho) 引入 CMWQ 讓 worker-pool 上的 worker 來處理這些連線
```c
khttpd_wq = alloc_workqueue(KBUILD_MODNAME, WQ_UNBOUND, 0);
```
首先在 module 載入時建立 workqueue , `WQ_UNBOUND` 表示此為 unbound workqueue , 它會去使用 unbound worker-pool , 最後一個參數為每個 cpu 同時能處理的 work item 數量,這邊 0 代表預設的 256 個
* normal and high priority woker-pool: 每個 cpu 都有, worker 會 bound 在那個 cpu
* unbound worker-pool: worker 不會 bound 在特定的 cpu ,會犧牲 locality
```c
struct khttpd {
struct socket *sock;
struct list_head list;
struct work_struct khttpd_work;
};
struct http_service {
bool is_stopped;
struct list_head worker;
};
static struct work_struct *create_work(struct socket *sk)
{
struct khttpd *work;
if (!(work = kmalloc(sizeof(*work), GFP_KERNEL)))
return NULL;
work->sock = sk;
// set up a work item pointing to that function
INIT_WORK(&work->khttpd_work, http_server_worker);
list_add(&work->list, &daemon.worker);
return &work->khttpd_work;
}
static void free_work(void)
{
struct khttpd *l, *tar;
/* cppcheck-suppress uninitvar */
list_for_each_entry_safe (tar, l, &daemon.worker, list) {
kernel_sock_shutdown(tar->sock, SHUT_RDWR);
flush_work(&tar->khttpd_work);
sock_release(tar->sock);
kfree(tar);
}
}
```
每個連線會有各自的 `struct khttpd` 紀錄使用的 socket 及對應的 work item ,透過 [INIT_WORK](https://elixir.bootlin.com/linux/latest/source/include/linux/workqueue.h#L244) 來設定實際要處理的 function => http_server_worker
`struct http_service` 連接我們建立的 work item ,在卸載 module 時可以透過它去確保所有 work item 都執行完成並釋放資源
```c
queue_work(khttpd_wq, work);
```
最後把創建好的 work item 插入 workqueue 讓對應的 worker-pool 中的 worker 執行
| |Original|CMWQ|
|--|-- |-- |
|(requests/sec)|75470.673|136500.882|
透過 `./htstress -n 100000 -c 1 -t 4 http://localhost:8081/` 測量,每秒能處理的 requests 顯著增加
## 提供目錄檔案存取功能
## 引入 timer 主動關閉逾期的連線
[commit 7213049](https://github.com/Korin777/khttpd/commit/72130499cca9eb5b8fc69c6527a539be83c2cc71)
原本想參考 [sehttpd](https://github.com/sysprog21/sehttpd) 來實做 timer ,後來發現 kernel api 也有 timer 可以使用,便試著用他來關閉逾期的連線
```c
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct hlist_node entry;
unsigned long expires;
void (*function)(struct timer_list *);
u32 flags;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
```
`timer_list` 結構體定義於 [<linux/time.h>](https://elixir.bootlin.com/linux/latest/source/include/linux/timer.h#L11), timer 透過 `list_head` 的變形 `hlist_head` 連接, `expires` 為 timeout 的時間點,透過 `function` 來執行 timeout 後序該做的事
```c
void timer_callback(struct timer_list *arg) {
struct khttpd *worker = container_of(arg, struct khttpd, timer);
kernel_sock_shutdown(worker->sock, SHUT_RDWR);
}
```
首先定義 timer 的 callback function ,在連線逾時後主動關閉連線,這裡將 `timer_list` 嵌入 `khttpd` 結構體來取的連線對應的 `socket`
```c
timer_setup(&worker->timer, timer_callback, 0);
```
接著透過 [timer_setup](https://elixir.bootlin.com/linux/latest/source/include/linux/timer.h#L141) 來初始化 timer
```c
mod_timer(&worker->timer, jiffies + msecs_to_jiffies(5000));
```
透過 [mod_timer](https://elixir.bootlin.com/linux/latest/source/kernel/time/timer.c#L1188) 在每次收到新的 request 時重製 timer 逾時的時間點
```c
del_timer_sync(&worker->timer);
```
最後透過 [del_timer_sync](https://elixir.bootlin.com/linux/latest/source/include/linux/timer.h#L198) 在逾時或 client 關閉連線時將 timer 從 `hlist_head` 中移除
### 參考資料
[Concurrency Managed Workqueue](https://www.kernel.org/doc/html/latest/core-api/workqueue.html)
[蜗窝科技-CMWQ](http://www.wowotech.net/irq_subsystem/cmwq-intro.html)