# 2025q1 Homework5 (assessment) contribute by < `ginsengAttack` > :::danger 注意書寫規範! ::: ## 閱讀〈因為自動飲料機而延畢的那一年〉 作者以解決具體問題出發,試圖製造出自動飲料機,但是問題之所以存在,就是因為它暫時沒有解法。 在尋找解方的過程中,一定會遇到很多出發前難以想像的困難,但這也是一個問題值得被解決的證明。 想要獲得回報,就要有相應的付出,從飲料機的每一個細節,再到為了它而延畢,都是一種取捨,一種等價交換。 同時,細節也是決定成敗的關鍵,材質、結構、流速,一切都環環相扣,我在初步接觸核心的程式設計後也稍有體悟,程式出錯,系統是真的會壞給你看,溢位、race condition、資源釋放,不真的經歷很難切身的體會這些細節。 ## 目前投入 ktcp 產出不多,但是再不準備期末專題就來不及了,所以暫緩進度。 ## 疑問 KXO這個專案和裝置驅動有關聯性嗎? 在看驅動相關的專案的時候,我發現系統和硬體溝通的方式,也是透過註冊操作,提供上下層通訊的媒介,和 KXO 中核心和使用者層級的互動方式有些相似。 甚至在註冊操作的時候,也是註冊裝置號碼? - [ ] 查過後,發現這個東西叫 Character Device Driver ,也是驅動程式的一種。 ktcp 具體在什麼部份會需要進行 IO 的處理? - [ ] 接收連線,傳輸數據都是 如何在 ktcp 中更好的應用驅動程式相關知識? ktcp 可以運行在樹莓派上面嗎? 根據 [linux 核心網路:第一章](https://hackmd.io/@rickywu0421/linux_networking_1) ![圖片](https://hackmd.io/_uploads/r1N_qTLGex.png) 傳遞一個封包需要層層遞進的系統呼叫,老師在上課的時候提到, ktcp 直接使用 `kernel_recvmsg` 和 `kernel_sendmsg` 在核心層級發收資料,但為什麼不使用更下層的系統呼叫? 這段描述是我的理解,這樣理解有錯嗎? > 原本架構 CPU 和 thread 綁定,並且一組 thread 為一個 workqueue。CMWQ 一樣 CPU 和 thread 綁定,但是 workqueue 變成更上層的概念,我們定義的 workitem 不再嚴格對應到具體某個 CPU 上面的某個 thread。 ## 期末專題 ktcp 筆記 (紀錄一下讀教材遇到的問題,然後都先讀老師的材料,比較好吸收) (純筆記,實做放在另一個頁面) ### I/O 模型演化: 事件驅動伺服器:原理和實例 [連結](https://hackmd.io/@sysprog/linux-io-model/https%3A%2F%2Fhackmd.io%2F%40sysprog%2Fevent-driven-server) #### I/O 模型概述 雙工分為兩種: 全雙工 和 半雙工 半雙工就是允許雙向通訊,但是同時只有一方可以進行傳輸,像對講機一樣。 後來引入了 `select` 系統呼叫,能一次監控多個檔案描述子, 'kxo' 就使用了這種技術,同時去監聽鍵盤事件和來自核心的資料,這邊的監聽是"通知哪個檔案有資料",後續我們再自行讀取。 #### Non-blocking I/O 這邊提到可以透過將檔案描述子設置為 `O_NONBLOCK` 來實現非阻塞。 同時提到 lwan 也是採用這種技術,但是我實際上在自己的電腦上實驗的時候,發現我採用這種方法,會致使程式碼以 busy waitting 的方式運行,而如果是採用阻塞式的就不會有這個問題? #### I/O Multiplexing 這種機制就是透過 `epoll`、`select`一類 api 去做出事件監聽,本身是阻塞式的,我們可以透過回傳值來判定具體是哪一個檔案子有資料傳入。 但是以 ktcp 的架構,貌似不適合這種結構?還是可以用這個機制,一次監聽多個 port? 原來是一次監聽多種類型的操作。 然後我還想問, `accept` 算不算一種 `select` 的機制? #### Asynchronous I/O 難以理解,這部份是必須作業系統支援? #### 綜合解讀 第一次看不太懂,稍微研究過 ktcp 後回來看這個例子說明,很具體。 #### 事件驅動式伺服器 簡單來說,就是將連線處理和實際服務分開,由一個 thread 去負責接收連線,反應到 ktcp 裡面就是背景程式,實際服務則由其他的thread 提供。 #### 案例探討:NGINX 由 master 行程負責初始化任務,worker 則專門處理用戶端請求,比較特別的是它會將每個 worker 固定綁定至特定 CPU。 kxo 裡面也有類似的機制,印象中是透過禁用中斷達成的,今天上課有提到,但是我沒有聽到最後,之後會查看同學筆記來確認。 然後會用 `epoll` 去監聽不同種類的事件,是後面會提到的 Reactor Pattern 架構。 #### Thread Pool 透過將工作交由 thread 執行,以作業系統的輔助提高同步能力,由一個thread loop 做為 consumer ,變相達成一種 aio。 ### I/O 模型演化: 高效 Web 伺服器開發 #### TCP 伺服器的實作挑戰 一般的 tcp 伺服器,會先監聽某個 port ,等待連線,然後有連線進入就讀取該 fd 的資料,`tcp echo` 就是如此, 但是這種程式碼構造簡單,直接回傳訊息即可,如果是建立在其上的 web 伺服器,就必須要有解析 HTTP 的和處理通訊協定的功能,這種結構就沒有辦法負荷多個請求的同時處理。 有一種方法是 fork,讓子行程去運行,親代行程繼續監聽連線。可是這意味著每個連線都會需要一個完整的行程,花費大量的資源,就算引入 thread 機制也不會有太大的不同,資源的使用一樣很高。 所以可以引入 thread pool,後面會提到的 CMWQ 就是採用這個機制。 可是執行緒的數量也有上限,同樣會在過多連線的情況下遇到無法滿足並行的困境。 這個問題的根源是 read 為阻塞式, 這邊我看不懂,是指用事件驅動可以克服這個問題? 但是根據: > 若進一步增加廚房人手(類比為 thread pool 中的 worker thread),即可提升同時處理訂單的能力(throughput)。但當工作人員數量持續增加,瓶頸會轉移至實體空間、烤箱數量等資源限制(即系統容量)。 這種模型不是也有上限嗎? #### Reactor Pattern Reactor pattern 是個設計模式 (design pattern),針對同時處理多個或單個服務需求。服務處理任務 (service handler) 將傳入的需求拆解,然後分派給相關的請求端處理任務 (request handler)。 #### Edge vs. Level Triggering 一個是電位改變時觸發,一個是處於某個電位就一直觸發。 分別對應到對資料處理流程的不同處理,`select` 和 `poll` ,要自行將資料讀取完成,所以有資料就要一直讀,但是 `Asynchronous I/O` 則是將資料交給作業系統處理,所以觸發一次就好。 #### 實做考量點 :question: 將 fd 設定為 non-blocking 後... :這個問題我不懂,我能理解 tcp 傳輸可能會切分封包,讓上層的資料被分段傳輸,可是這一定要用狀態機模式的解析器才可以解決嗎?老師的其中一項要求是將解析器換成更加輕量的解析器,不就又會遇到這個問題嗎? ### I/O uring 無法理解,還要花時間再看 ### 透過 timerfd 處理週期性任務 #### timerfd 把 timer 當成 file descriptor,這樣就可以透過上述的 I/O Multiplexor 去監聽。 之前提到伺服器可以透過 `epoll` 去監聽不同的事件,這就是其中一種,可以用一致的邏輯去處理。 但是我看 ktcp 的教材,它好像不是用這種方法,而是維護一個 queue 來處理所有連線的 timer? ### kecho/khttpd ### 程式碼導讀: #### 減少 printk 的使用 這個 `pr_info` 會對效能產生影響,甚至印出太多還會塞滿記憶體,教材建議以統計的方式,最後一次印出。 #### HTTP keep-alive 模式 這個伺服器是自行解析 http 請求,實際上下層依賴 tcp 建立連線,然後tcp 建立連線的時候需要三路交握,是一個比較大的開銷,使用 keep-alive 機制就能透過 persistent connection 的方式維持一個 tcp 的連線,而不是每次重新進行交握。 #### Linux 核心如何處理傳遞到核心模組的參數 使用 `module_param` 巨集傳遞參數,但是為什麼不用字節驅動? 這邊不太理解,還要再看。 ### Ktcp #### CMWQ workqueue 是一種機制,使用者將預要執行的工作項目包裝成結構體,並交由核心進行處理。 原有的 MT workqueue 會為每個 CPU 配置一個 worker thread,讓工作在複數個處理器上執行,可是一來,多個 CPU 可能會連帶導致大量的 worker thread 耗盡 PID。 (這邊我有個疑問,教材的描述是:"若建立 100 個 workqueue 且系統有 64 個 CPU,就會產生多達 6400 條 worker threads。",但這個架構有那麼不彈性嗎?我不能每個 CPU 數量不同?) 而且 worker thread 綁定單一 CPU,且各 CPU 間的 worker 無法共享工作項目。 所以引入 CMWQ,捨棄每個 workqueue 綁定一組 worker threads 的設計,改為所有 workqueue 共享一組 per-CPU worker pool,然後動態併發控制:CMWQ 內部根據工作負載與系統狀態,自動調整 worker threads 的並行程度與綁定,避免資源浪費。 有三個元素: 1. workqueue 2. worker pool:由核心維護的 worker thread 池,每個 CPU 有一組對應的 pool 3. worker 我的理解是: 原本架構 CPU 和 thread 綁定,並且一組 thread 為一個 workqueue。 CMWQ 一樣 CPU 和 thread 綁定,但是 workqueue 變成更上層的概念,我們定義的 workitem 不再嚴格對應到具體某個 CPU 上面的某個 thread。 :::danger 注意用語! ::: 這個方法讓核心幫我們動態的管理執行緒的<s>調用</s>,對應到 ktcp 中,就是把 `kthread_run` 取代,讓我們不用每次為了處理新的連線就要創建一個新的 thread ,而且除了基本的記憶體配置與 kthread 結構,idle 狀態下的 worker thread 並不會額外耗用系統資源。 #### 使用 Ftrace 觀察 kHTTPd 按照教材要求,輸入: ```c sudo ls /sys/kernel/debug/tracing //獲得: available_events options stack_trace_filter available_filter_functions osnoise synthetic_events available_filter_functions_addrs per_cpu timestamp_mode ... ``` 然後接著輸入: ```c sudo cat /sys/kernel/debug/tracing/available_filter_functions | grep khttpd ``` 然後我一開始還沒有打 `/sys/kernel/debug/tracing/` 然後想說我的電腦有問題,有點蠢。 然後我將輸入改為: ```c # clear echo 0 | sudo tee $TRACE_DIR/tracing_on sudo truncate -s 0 $TRACE_DIR/set_graph_function # 明確清空文件 sudo truncate -s 0 $TRACE_DIR/set_ftrace_filter # 明確清空文件 echo nop | sudo tee $TRACE_DIR/current_tracer # setting echo function_graph | sudo tee $TRACE_DIR/current_tracer echo 3 | sudo tee $TRACE_DIR/max_graph_depth echo http_server_worker | sudo tee $TRACE_DIR/set_graph_function # execute echo 1 | sudo tee $TRACE_DIR/tracing_on ./htstress localhost:8081 -n 2000 echo 0 | sudo tee $TRACE_DIR/tracing_on ``` 然後最終:`sudo cat /sys/kernel/debug/tracing/trace` `http_parser_execute()` 為效能瓶頸,該函式採用狀態機模型,針對輸入資料 buf 進行逐字元處理,其實後面破梗了,可以用更輕量化的函式庫來實現,例如 `picohttpparser`。 #### 實作 content cache `iterate_dir()` 是另一個效能瓶頸,它會走訪檔案目錄裡面所有檔案,即使我只放一個檔案,他的影響也很大,遑論更大的目錄。 教材的方法很直觀,既然我們必須傳目錄資料,但是走訪又很花時間,那就先將常用資料緩存,說起來簡單,但是做起來很難。 具體實做之後跟著一起做。 #### scatter-gather I/O 傳送資料 可以提高吞吐量,一次把多個 buf 的資料傳出,就不用每傳一段資料,就要一次系統呼叫。 這個我還沒寫,也沒辦法測。 #### 支援 HTTP 壓縮 降低傳輸開銷,Linux 核心就有提供壓縮 API將檔案內容經壓縮後透過 HTTP 回應傳送給客戶端,藉此達成減少網路傳輸負擔與提升效能的目的。 這個我自己實做過,但是要注意,非純文字檔案例如圖檔,會被壓壞。 #### 引入 timer 以主動關閉逾期的連線 為何不用前述的 `timerfd` 搭配 `epoll` 機制? #### 藉由 RCU 在並行環境中釋放系統資源 在針對效能的分析中,讀取檔案的操作佔用了大量的時間,所以可以通過 cache 的機制降低這方面的成本。 這個所謂的 content cache 讀多寫少,主要是用來應付來自網路中的請求,少數時候需要更改內容,所以使用普遍的 lock 不現實,卻是使用 RCU 的理想環境。 這個機制會使用 `rcu_read_lock` 和 `rcu_read_unlock` 保護讀取操作,但不同的是,他不會禁止其他人進行存取。 這個特別的鎖,搭配相應的存取操作,能保證資料在讀的資料不會受到破壞,但是新的讀取操作又能讀取到更新後的資料。 最後透過 `synchronize_rcu()` 產生一段 grace period 也就是寬限期,讓舊資料的刪除操作延後到所有正在讀取者結束讀取。 像是清潔廁所一樣,放一個"清潔中"的牌子到廁所外面,後來的去其他廁所,但是已經在裡面的人也不會被趕走,直到所有人使用完才真的開始清潔。 ## 期末專題想法 我想要聚焦在韌體跟驅動程式,最好是和電腦網路相關的領域。 之所以會選擇裝置驅動這個主題,是因為老師上課的時候提過,在台灣學 linux 往往會去寫 device driver ,並且很多優秀人才是不屑寫這個的,這是一個值得把握的機會。 ### 開發 vETH 驅動程式 :::danger 注意書寫規範:中英文間用一個半形空白字元區隔 ::: 參考[ vwifi 虛擬無線網路裝置驅動程式和實驗環境](https://hackmd.io/@rickywu0421/FinalProject) 此專題撰寫了針對虛擬網路設備的驅動程式,提供模擬無線網路的實驗環境。 :::danger call 是「呼叫」,而非「調用」,尊重你所在的傳統文化 ::: `cfg80211` 是 linux 管理無線網路的框架,會通過<s>調用</s> api 跟下層的設備進行互動,而 vwifi 同樣提供了 api 給 cfg80211 進行使用,並同時模擬了設備的行為。 虛擬的網路設備讓專題的發想有很大的空間,不會受限於硬體。 而我想針對乙太網路的有線環境進行學習,仿照 vwifi 的形式,透過軟體模擬實體網路卡,在虛擬機器之間透過虛擬網卡建立乙太網路環境。 乙太網路不像 wifi 一樣在核心中有專門的子系統,而是透過通用的子系統進行管理,並且比起連線與通訊的議題,更重視封包的傳輸與處理。