網路系統是Linux中設計最複雜,功能最強大的子系統。本篇文章要陳述的並非正統的Linux網路系統,而是僅僅聚焦在鏈路層上,也就是所謂的網卡驅動。
TCP/IP協議族,每層都分配了不同的功能,針對不同的協議和應用情境設計,L2負責將網路層的報文發送到物理介質上。網卡驅動是構成L2的主要部分。
Linux核心本身提供了針對網路裝置的驅動架構開發架構netops,核心裡也整合了大量主流網卡廠商的驅動程式。但這類標準架構下開發出來的驅動程式,雖然可以很好得契合Linux網路子系統設計,但在某些情境的應用下,則顯得力不從心。核心問題是,效能。
筆者所在的行業,存在大量資料面或轉寄面的設計,在資料面或轉寄面系統中,對需要支援的協議棧要求並不高,UDP + IP的組合基本足夠了,甚至可以更簡單。而對網卡轉寄效能的要求則非常高,要求達到linerate。在當前,行業內10G網卡早就是標配了,40G甚至100G的也不缺乏。從長期的測試資料來看,基於傳統Linux核心態驅動的網路裝置很難滿足效能上的需求。先來看看核心態驅動的瓶頸所在,不一一給出理論上的分析了,直接給出結論。
1) 存在記憶體拷貝。從核心態mbuf到使用者態之間,有一次記憶體拷貝
2)動態buffer管理。記憶體buffer動態申請釋放
3)核心態使用者態切換存在環境切換的時延
4)中斷驅動收包的方式,會進一步增大時延
5)龐大複雜的核心協議棧處理
針對上述問題,筆者總結兩類解決方案,並試著陳述其設計概要。
第一類為使用者態驅動方式,設計概要如下:
1)將外設IO映射到使用者態,讓使用者態程式可直接存取外設。 這樣就避免了核心協議棧的幹擾,以及核心態使用者態間的記憶體拷貝開銷
2)改中斷方式收包圍查詢式收包。當然也可權衡採用一次中斷多次查詢的方式,在效能和cpu佔用率間作出一定的平衡
3)預分配buffer。在使用者態實現一個buffer cache池,用於填充到硬體。如果硬體支援自釋放buffer,此處的設計會更簡單
4)線程綁定到特定核,避免線程調度帶來的時延以及開銷。
使用者態驅動的效能相比核心態驅動可以提升一大截,基本可滿足10G甚至更高的要求。但這類方案也存在一定的弊端:
1)使用者態需要實現一套簡單的協議棧,某種程度上會加大工作量
2)系統健壯性及安全性有一定的降低。使用者可直接存取並控制外設。系統入侵成本太低,因此並不適合一些開放式的應用情境。
第二類為基於核心態驅動的最佳化改造,比較典型的為pf_ring及netmap方案。此處不展開將這兩個開源項目的細節,只從設計思想進行概括。以netmap為例
1)實現靜態預留buffer,避免動態申請釋放buffer記憶體
2)採用查詢式收報(由使用者態發起查詢,核心態配合完成)
3)採用批處理的方式,一次查詢完成多個收包處理,分攤查詢所帶來系統調用的開銷。如系統調用開銷為100ms,若一次收10個包,則分攤到每個包的開銷為10ms。
4)實現使用者態和核心態的貢獻記憶體,作為buffer。這樣只需要在核心態跟使用者態間傳遞指標或位移量即可,無須拷貝記憶體。
同時使用者態驅動所實現的線程綁定技術也需要應用到這裡來。這類方案比使用者態驅動的優勢在於:
1)使用者態無法訪問外設,增加了安全性
2)效能遠高於傳統核心態驅動,略低於使用者態驅動,但可滿足線速的需求
3)某些時候可借用核心的協議棧。可在效能和功能完整性間比較靈活地切換。
關於netmap的實現,後續有時間的話打算寫個專題。此處不再展開