執行人: yqt2000
解說影片
依據 ktcp 作業規範,開發高效網頁伺服器 (針對靜態)。
需要附上對應的參考資料和必要的程式碼,以第一手材料 (包含自己設計的實驗) 為佳
khttpd
核心模組的效能瓶頸,該如何設計相關實驗學習。搭配閱讀《Demystifying the Linux CPU Scheduler》第 6 章drop-tcp-socket
核心模組運作原理。TIME-WAIT
sockets 又是什麼?待整理 eric88525, zoanana990 筆記內容
參照 eBPF 教程
virtme
建構虛擬化執行環境,搭配 GDB 追蹤 khttpd
核心模組隨著電腦硬體逐漸提供 atomic 指令後,mutex 或稱為 lock 的機制被列入作業系統的實作考量:
簡言之,要搶資源時用 mutex,要互相通知時用 semaphore。
上方說法過於武斷,避免這樣的「簡言之」。
工程人員說話要精準。
mutex 與 semaphore 的差別在於:
RCU 適用於頻繁的讀取 (即多個 reader)、但資料寫入 (即少量的 updater/writer) 卻不頻繁的情境,例如檔案系統,經常需要搜尋特定目錄,但對目錄的修改卻相對少,這就是 RCU 理想應用場景。
RCU 藉由 lock-free 程式設計滿足以下場景的同步需求:
即使存取舊的資料,不會影響最終行為的正確,這樣的情境就適合 RCU,對其他網路操作也有相似的考量。
改寫 kHTTPd,分析效能表現和提出改進方案,可參考 kecho
CMWQ 是對 Linux 傳統 Workqueue 機制的改進和重新實現。它保持了原有 API 的兼容 性,同時引入了更智能的並行管理和資源利用策略。
注意用語!
一般 workqueue:
CMWQ架構圖:
CMWQ | 一般workqueue | |
---|---|---|
執行緒管理 | 使用統一的 Worker pools,由所有 workqueue 共享,更有效利用資源 | Multi Thread 模式下每個 CPU 有一個 worker,Single Thread 模式下整個系統只有一個 worker |
並行處理 | 可以動態調整並行級別,更靈活的進行管理 | Multi Thread 可能因 I/O blocking 導致其他 worker 等待;Single Thread 可能造成死鎖 |
資源利用 | 通過共享 worker pools 和動態調整,大幅減少資源浪費 | 容易造成系統資源浪費,特別是在 Multi Thread 模式下 |
引入了 work item 的概念,為一個簡單的結構包含函式指標指向非同步任務運作的函式,執行時建立一個 work item 和(放入) workqueue。而處理這些任務的執行緒為 worker threads 用於一個接一個處理這些任務,而在任務結束後 thread 會變為 idle 狀態,而 worker-pools 就是用來管理這些 threads
兩種 worker-pools 類型:
分離 Workqueue 和 Worker-pools:使用者只需關注將任務放入 queue,不需考慮執行細節。
注意用語!
務必詳閱 https://hackmd.io/@sysprog/it-vocabulary 和 https://hackmd.io/@l10n-tw/glossaries
為了在核心模組中引入 CMWQ,我們會需要使用到 <linux/workqueue.h>
中的這些函式:
參考了 kecho 的作法,以及 fatcatorange,YiChianLin 的筆記
這部份我的 commit: fde4de, e593125
程式碼註解不該出現中文!總是用美式英語書寫。
改進 git commit message,參照第一次作業的規範。
main.c
宣告 workqueue ,並在使用 workqueue 相關 API 時 include workqueue.h
http_server.h
khttpd_service 採用雙向鏈結串列作為 workqueue 管理 work
http_server.c
請求 - 以 http_request 作為其結構體,嵌入 list_head 進行管理,而
work_struct 為真正要作的任務,可被 INIT_WORK
函式初始化,去運行客製化的函式( e.g. 此專案中處理請求的函式 http_server_worker
)
注意書寫規範:
注意書寫規範:
create_work()
& free_work()
在 create_work 中,根據傳入的 socket 建立一個 work,為每一個連線請求進行 kernel space (kmalloc) 的動態記憶體配置,並進行初始化,再透過 list_add 加入到 workqueue 當中
注意書寫規範:
使用 workqueue 時,程式執行時就是傳入一個 struct work_struct ,因此可透過 container_of 取得請求的 socket
注意書寫規範:
100000 requests with 500 requests at a time(concurrency)
ab -n 100000 -c 500 -k http://127.0.0.1:8081/
Requests per second | Time per request (mean, across all concurrent requests) | |
---|---|---|
khttpd | 185278.85 #/sec | 0.005 ms |
kecho | 136125.17 #/sec | 0.007 ms |
cserv | 42542.40 #/sec | 0.024 ms |
make check 運行腳本進行測試,htstress 作為客戶端發送請求給伺服器,比較單純使用 kthread_run 和引入 CMWQ 後伺服器處理 200000 筆的請求的表現。
./htstress http://localhost:8081 -t 3 -c 20 -n 200000
引入 CMWQ 之前 | 引入 CMWQ 之後 | |
---|---|---|
requests: | 200000 | 200000 |
good requests: | 200000 [100%] | 200000 [100%] |
bad requests: | 0 [0%] | 0 [0%] |
socket errors: | 0 [0%] | 0 [0%] |
seconds: | 3.354 | 2.003 |
requests/sec: | 59624.124 | 99860.744 |
可以看到處理 200000 筆請求的整體時間降低了不少,另外每秒可處理的請求個數也提昇了約 1.6倍。
按照作業 kecho + khttpd 實作 directory listening 的步驟 以及比照 fatcatorange同學筆記內容 修正缺失的方法去實作該功能,以下為整理兩者筆記並融合我在實作遇上的問題和解決方法。
http_server_response
要加入這個功能,要修改 http_server_response
,原本的 http_server_response
只會檢查是不是用 get ,是的話回傳一個 HTTP_RESPONSE_200 (代表成功) ,內容是 hello world。
而這個函式被使用在 http_server.c/http_parser_callback_message_complete()
:
上述函式被綁定在 http_server.c/http_server_worker()
:
當 http_server_worker()
執行 http_parser_execute()
時,就會根據解析執行對應的函式,以這個例子來說,解析完整個 http 請求時執行 http_parser_callback_message_complete()
正式修改 http_server.c/http_server_response()
:
http_server.c/handle_directory()
handle_directory()
對於不同的 case 做出回應,主要執行以下步驟:
其中迭代目錄(tracedir
, iterate_dir
)為該方法的精隨,於下面內容繼續介紹
iterate_dir(struct file *, struct dir_context *);
定義於 <linux/fs.h> 可對目錄struct file*
進行迭代並運行 struct dir_context *
設置的 callback function。
因此首先須在 struct http_request
中加入 struct dir_context
而 http_server.c/trace_dir()
可以回傳給客戶端當前目錄下的資料夾連結或檔案連結,並將其印出顯示在 localhost 的頁面上, 使得使用者可以藉由點擊目錄進入更深層的目錄或存取檔案
main.c
http_server.h
即可透過 module_param ,在載入模組時指定路徑,e.g.
http_server.c
中的 send_http_header()
& send_http_content()
& catstr()
& read_file()
解釋行為落差