依據 ktcp 的指示,持續改進 sysprog21/khttpd 的程式碼,打造出高效且穩定的網頁伺服器。
依據 ktcp 的指示,在 sysprog21/khttpd 的基礎之上,利用 CMWQ 一類的機制,打造出高效且穩定的網頁伺服器,需要處理 lock-free 的資源管理議題 (如 RCU)。
搭配閱讀: 〈RCU Usage In the Linux Kernel: One Decade Later〉
An application can change the IP options on a per-socket basis by calling sys_setsockopt,
which eventually causes the kernel to call setsockopt.
setsockopt sets the new IP options, then uses call_
rcu to asynchronously free the memory storing the old IP
options. Usingcall_rcu
ensures all threads that might
be manipulating the old options will have released their
reference by exiting the RCU critical section.
以 ftrace 檢視 kHTTPd 運作的細節,檢視 I/O 模型並尋求效能改進空間
參考 kecho 把所有 work 使用一個鏈結串列做管理,用 is_stopped
判斷是否結束連線
原本的 kthread-based 實作是每個 request 都會執行一次 kthread_run
,改成 CMWQ 後改為呼叫 create_work
來建立 work,並把建立好的 work 放入 queue 中,server 結束時呼叫 free_work
釋放管理 work 的串列
配置完空間後,呼叫 INIT_WORK
建立新的 work,並初始化再把 work 加進串列中,當 work 被執行時會呼叫 http_server_worker
來完成任務
執行命令 ./htstress http://localhost:8081 -t 3 -c 20 -n 200000
測試
kthread | CMWQ | |
---|---|---|
throughput | 40223.732 | 59425.669 |
使用 linux/fs.h 中的 dir_context
、filp_open
、iterate_dir
函式
呼叫 http_server_send
送出 header 與 html,若目錄無法開啟則回傳 404,若可以則回傳 200 OK ,在執行 tracedir
時把目錄裡每個檔案寫在 html table 中並傳送到 client 讓網頁顯示所有的檔案
傳送每個檔案的檔名與檔案或子目錄的連結,並用 html 的 table 包起來,並發送到 request->socket
結果:
原本的實作只會顯示目錄中的所有檔案,新增判斷開啟的是目錄或檔案來回傳檔案內容
把 MIME type 加入 hashtable 中做查詢
hashtable 結構:
把 MIME type 跟對應的副檔名加入 hashtable 中,在 moudle 初始化時呼叫
查詢 hashtable 得到 MIME type
根據不同副檔名傳送不同 content-type
可以開啟並顯示圖片
可以發現 iterate_dir()
花最多時間,其次是 http_server_send
(),參考 Jerejere0808 的開發紀錄使用 content cache 來加快 iterate_dir()
的執行時間
為了避免每次請求時都執行 iterate_dir()
或 kernel_read()
,可以將回應快取下來,以便在下次有相同的請求時不需要再次讀取。
使用 kernel 的 hashtable API 來實作快取,並且由於檔案系統符合資料讀取頻繁,資料寫入較少的情況,加入 Read-Copy Update (RCU) 同步機制,以支援多個讀取端同時存取快取的需求。
快取項目結構:
每次 request 先根據 request->url
查詢 hashtable,若有就直接傳送,若無則跟原本一樣判斷 request 是檔案還是目錄,讀取完後再插入到 hashtable 中
參考 sehttpd、作業說明使用 min heap 實作 timer
timer 與 queue 的結構
在將 response content 插入 cache 時呼叫 cache_add_timer
把 timer 插入 queue 中
設定好 timer_node
後呼叫 prio_queue_insert()
確認節點在 min heap 中的位置
在每次 request 檢查有沒有 timer expired,因為是 min heap 所以只要檢查 heap 中的第一個即可
用 do-while 來確保沒有被 min heap 沒有被同時修改,並把第一個節點跟最後一個有效的節點互換,交換完成之後把有效節點 nalloc 減一,再呼叫 prio_queue_sink
把被交換的節點下沉到正確的位置,最後再呼叫 hash_del_rcu
來把快取的內容移除,移除完之後呼叫 synchronize_rcu
來同步讀者
並在有相同 request 時延長 timer 的時間
執行 ./htstress http://localhost:8081 -n 20000
測試
因為兩萬次 request 都是相同的,所以只有第一次需要讀檔,requests/sec 從 7474.638 提升到 18254.904