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