Linux轉(zhuǎn)發(fā)性能評估與優(yōu)化(轉(zhuǎn)發(fā)瓶頸分析與解決方案)
線速問題
很多人對這個線速概念存在誤解。認(rèn)為所謂線速能力就是路由器/交換機(jī)就像一根網(wǎng)線一樣。而這,是不可能的。應(yīng)該考慮到的一個概念就是延遲。數(shù)據(jù)包進(jìn)入路由器或者交換機(jī),存在一個核心延遲操作,這就是選路,對于路由器而言,就是路由查找,對于交換機(jī)而言,就是查詢MAC/端口映射表,這個延遲是無法避開的,這個操作需要大量的計算機(jī)資源,所以不管是路由器還是交換機(jī),數(shù)據(jù)包在內(nèi)部是不可能像在線纜上那樣近光速傳輸?shù)?。類比一下你?jīng)過十字街頭的時候,是不是要左顧右盼呢?
那么,設(shè)備的線速能力怎么衡量呢?如果一個數(shù)據(jù)包經(jīng)過一個路由器,那么延遲必覽無疑,可是設(shè)備都是有隊列或者緩沖區(qū)的,那么試想一個數(shù)據(jù)包緊接一個數(shù)據(jù)包從輸入端口進(jìn)入設(shè)備,然后一個數(shù)據(jù)包緊接一個數(shù)據(jù)包從輸出端口發(fā)出,這是可以做到的,我們對數(shù)據(jù)包不予編號,因此你也就無法判斷出來的數(shù)據(jù)包是不是剛剛進(jìn)去的那個了,這就是線速。
我們可以用電容來理解轉(zhuǎn)發(fā)設(shè)備。有人可能會覺得電容具有通高頻阻低頻的功效,我說的不是這個,所以咱不考慮低頻,僅以高頻為例,電容具有存儲電荷的功能,這就類似存儲轉(zhuǎn)發(fā),電容充電的過程類似于數(shù)據(jù)包進(jìn)入輸入隊列緩沖區(qū),電容放電的過程類似于數(shù)據(jù)包從輸出緩沖區(qū)輸出,我們可以看到,在電流經(jīng)過電容的前后,其速度是不變的,然而針對具體的電荷而言,從電容放出的電荷絕不是剛剛在在另一側(cè)充電的那個電荷,電容的充電放電擁有固有延遲。
我們回到轉(zhuǎn)發(fā)設(shè)備。對于交換機(jī)和路由器而言,衡量標(biāo)準(zhǔn)是不同的。
對于交換機(jī)而言,線速能力是背板總帶寬,因為它的查表操作導(dǎo)致的延遲并不大,大量的操作都在數(shù)據(jù)包通過交換矩陣的過程,因此背板帶寬直接導(dǎo)致了轉(zhuǎn)發(fā)效率。而對于路由器,衡量標(biāo)準(zhǔn)則是一個端口每秒輸入輸出最小數(shù)據(jù)包的數(shù)量,假設(shè)數(shù)據(jù)包以每秒100個進(jìn)入,每秒100個流出,那么其線速就是100pps。
本文針對路由器而不針對交換機(jī)。路由器的核心延遲在路由查找,而這個查找不會受到數(shù)據(jù)包長度的影響,因此決定路由器線速能力的核心就在數(shù)據(jù)包輸出的效率,注意,不是數(shù)據(jù)包輸入的效率,因為只要隊列足夠長,緩存足夠大,輸入總是線速的。但是輸入操作就涉及到了如何調(diào)度的問題。這也就說明了為何很多路由器都支持輸出流量控制而不是輸入流量控制的原因,因為輸入流控即使完美完成,它也會受到路由器輸出端口自身輸出效率的影響,流控結(jié)果將不再準(zhǔn)確。
在寫這個方案的前晚,有一個故事。我最近聯(lián)系到了初中時一起玩搖滾玩音響的超級鐵的朋友,他現(xiàn)在搞舞臺設(shè)計,燈光音響之類的。我問他在大型舞臺上,音箱擺放的位置不同,距離后級,前置,音源也不同,怎么做到不同聲道或者相同聲道的聲音同步的,要知道,好的耳朵可以聽出來毫秒級的音差...他告訴我要統(tǒng)一到達(dá)時間,即統(tǒng)一音頻流到達(dá)各個箱子的時間,而這要做的就是調(diào)延遲,要求不同位置的箱子路徑上要有不同的延遲。這對我的設(shè)計方案的幫助是多么地大啊。
然后,在第二天,我就開始整理這個令人悲傷最終心碎的Linux轉(zhuǎn)發(fā)優(yōu)化方案。
聲明:本文只是一篇普通文章,記錄這個方案的點點滴滴,并不是一個完整的方案,請勿在格式上較真,內(nèi)容上也只是寫了些我認(rèn)為重要且有意思的。完整的方案是不便于以博文的形式發(fā)出來的。見諒。
問題綜述
Linux內(nèi)核協(xié)議棧作為一種軟路由運行時,和其它通用操作系統(tǒng)自帶的協(xié)議棧相比,其效率并非如下文所說的那樣非常低。然而基于工業(yè)路由器的評判標(biāo)準(zhǔn),確實是低了。?市面上各種基于Linux內(nèi)核協(xié)議棧的路由器產(chǎn)品,甚至網(wǎng)上也有大量的此類文章,比如什么將Linux變成路由器之類的,無非就是打開ip_forward,加幾條iptables規(guī)則,搞個配置起來比較方便的WEB界面...我想說這些太低級了,甚至超級低級。我很想談一下關(guān)于專業(yè)路由器的我的觀點,但是今天是小小的生日,玩了一天,就不寫了。只是把我的方案整理出來吧。
Linux的轉(zhuǎn)發(fā)效率到底低在哪兒?如何優(yōu)化?這是本文要解釋的問題。依然如故,本文可以隨意轉(zhuǎn)載并基于這個思路實現(xiàn)編碼,但是一旦用于商業(yè)目的,不保證沒有個人或組織追責(zé),因此文中我盡量采用盡可能模糊的方式闡述細(xì)節(jié)。
瓶頸分析概述
1.DMA和內(nèi)存操作我們考慮一下一個數(shù)據(jù)包轉(zhuǎn)發(fā)流程中需要的內(nèi)存操作,暫時不考慮DMA。*)數(shù)據(jù)包從網(wǎng)卡拷貝到內(nèi)存*)CPU訪問內(nèi)存讀取數(shù)據(jù)包元數(shù)據(jù)*)三層報頭修改,如TTL*)轉(zhuǎn)發(fā)到二層后封裝MAC頭*)數(shù)據(jù)包從內(nèi)存拷貝到輸出網(wǎng)卡這幾個典型的內(nèi)存操作為什么慢?為什么我們總是對內(nèi)存操作有這么大的意見?因為訪問內(nèi)存需要經(jīng)過總線,首先總線競爭(特別在SMP和DMA下)就是一個打群架的過程,另外因為內(nèi)存自身的速度和CPU相比差了幾個數(shù)量級,這么玩下去,肯定會慢??!所以一般都是盡可能地使用CPU的cache,而這需要一定的針對局部性的數(shù)據(jù)布局,對于數(shù)據(jù)包接收以及其它IO操作而言,由于數(shù)據(jù)來自外部,和進(jìn)程執(zhí)行時的局部性利用沒法比。所以必須采用類似Intel I/OAT的技術(shù)才能改善。
1.1.Linux作為服務(wù)器時采用標(biāo)準(zhǔn)零拷貝map技術(shù)完全勝任。這是因為,運行于Linux的服務(wù)器和線速轉(zhuǎn)發(fā)相比就是個蝸牛,服務(wù)器在處理客戶端請求時消耗的時間是一個硬性時間,無法優(yōu)化,這是代償原理。Linux服務(wù)唯一需要的就是能快速取到客戶端的數(shù)據(jù)包,而這可以通過DMA快速做到。本文不再具體討論作為服務(wù)器運行的零拷貝問題,自己百度吧。
1.2.Linux作為轉(zhuǎn)發(fā)設(shè)備時需要采用DMA映射交換的技術(shù)才能實現(xiàn)零拷貝。這是Linux轉(zhuǎn)發(fā)性能低下的根本。由于輸入端口的輸入隊列和輸出端口的輸出隊列互不相識,導(dǎo)致了不能更好的利用系統(tǒng)資源以及多端口數(shù)據(jù)路由到單端口輸出隊列時的隊列鎖開銷過大,總線爭搶太嚴(yán)重。DMA影射交換需要超級棒的數(shù)據(jù)包隊列管理設(shè)施,它用來調(diào)度數(shù)據(jù)包從輸入端口隊列到輸出端口隊列,而Linux幾乎沒有這樣的設(shè)施。雖然近年在路由器領(lǐng)域有人提出了輸入隊列管理,但是這項技術(shù)對于Linux而言就是另一個世界,而我,把它引入了Linux世界。
2.網(wǎng)卡對數(shù)據(jù)包隊列Buff管理在Linux內(nèi)核中,幾乎對于所有數(shù)據(jù)結(jié)構(gòu),都是需要時alloc,完畢后free,即使是kmem_cache,效果也一般,特別是對于高速線速設(shè)備而言(skb內(nèi)存拷貝,若不采用DMA,則會頻繁拷貝,即便采用DMA,在很多情況下也不是零拷貝)。即使是高端網(wǎng)卡在skb的buffer管理方面,也沒有使用完全意義上的預(yù)分配內(nèi)存池,因此會由于頻繁的內(nèi)存分配,釋放造成內(nèi)存顛簸,眾所周知,內(nèi)存操作是問題的根本,因為它涉及到CPU Cache,總線爭搶,原子鎖等,實際上,內(nèi)存管理才是根本中的根本,這里面道道太多,它直接影響CPU cache,后者又會影響總線...從哪里分配內(nèi)存,分配多少,何時釋放,何時可以重用,這就牽扯到了內(nèi)存區(qū)域著色等技術(shù)。通過分析Intel千兆網(wǎng)卡驅(qū)動,在我看來,Linux并沒有做好這一點。
3.路由查找以及其它查找操作Linux不區(qū)分對待路由表和轉(zhuǎn)發(fā)表,每次都要最長前綴查找,雖然海量路由表時trie算法比hash算法好,但是在路由分布畸形的情況下依然會使trie結(jié)構(gòu)退化,或者頻繁回溯。路由cache效率不高(查詢代價太大,不固定大小,僅有弱智的老化算法,導(dǎo)致海量地址訪問時,路由cache沖突鏈過長),最終在內(nèi)核協(xié)議棧中下課。如果沒有一個好的轉(zhuǎn)發(fā)表,那么Linux協(xié)議棧在海量路由存在時對于線速能力就是一個瓶頸,這是一個可擴(kuò)展性問題。另外,很多的查詢結(jié)果都是可以被在一個地方緩存的,但是Linux協(xié)議棧沒有這種緩存。比如,路由查詢結(jié)果就是下一跳,而下一跳和輸出網(wǎng)卡關(guān)聯(lián),而輸出網(wǎng)卡又和下一跳的MAC地址以及將要封裝的源MAC地址關(guān)聯(lián),這些本應(yīng)該被緩存在一個表項,即轉(zhuǎn)發(fā)表項內(nèi),然而Linux協(xié)議棧沒有這么做。
4.不合理的鎖為何要加鎖,因為SMP。然而Linux內(nèi)核幾乎是對稱的加鎖,也就是說,比如每次查路由表時都要加鎖,為何?因為怕在查詢的期間路由表改變了...然而你仔細(xì)想想,在高速轉(zhuǎn)發(fā)情景下,查找操作和修改操作在單位時間的比率是多少呢?不要以為你用讀寫鎖就好了,讀寫鎖不也有關(guān)搶占的操作嗎(雖然我們已經(jīng)建議關(guān)閉了搶占)?起碼也浪費了幾個指令周期。這些時間幾率不對稱操作的加鎖是不必要的。你只需要保證內(nèi)核本身不會崩掉即可,至于說IP轉(zhuǎn)發(fā)的錯誤,不管也罷,按照IP協(xié)議,它本身就是一個盡力而為的協(xié)議。
5.中斷與軟中斷調(diào)度Linux的中斷分為上半部和下半部,動態(tài)調(diào)度下半部,它可以在中斷上下文中運行,也可以在獨立的內(nèi)核線程上下文中運行,因此對于實時需求的環(huán)境,在軟中斷中處理的協(xié)議棧處理的運行時機(jī)是不可預(yù)知的。Linux原生內(nèi)核并沒有實現(xiàn)Solaris,Windows那樣的中斷優(yōu)先級化,在某些情況下,Linux靠著自己動態(tài)的且及其優(yōu)秀的調(diào)度方案可以達(dá)到極高的性能,然而對于固定的任務(wù),Linux的調(diào)度機(jī)制卻明顯不足。而我需要做的,就是讓不固定的東西固定化。
6.通用操作系統(tǒng)內(nèi)核協(xié)議棧的通病作為一個通用操作系統(tǒng)內(nèi)核,Linux內(nèi)核并非僅僅處理網(wǎng)絡(luò)數(shù)據(jù),它還有很多別的子系統(tǒng),比如各種文件系統(tǒng),各種IPC等,它能做的只是可用,簡單,易擴(kuò)展。Linux原生協(xié)議棧完全未經(jīng)網(wǎng)絡(luò)優(yōu)化,且基本裝機(jī)在硬件同樣也未經(jīng)優(yōu)化的通用架構(gòu)上,網(wǎng)卡接口在PCI-E總線上,如果DMA管理不善,總線的占用和爭搶帶來的性能開銷將會抵消掉DMA本意帶來的好處(事實上對于轉(zhuǎn)發(fā)而言并沒有帶來什么好處,它僅僅對于作為服務(wù)器運行的Linux有好處,因為它只涉及到一塊網(wǎng)卡)[ 注意,我認(rèn)為內(nèi)核處理路徑并非瓶頸,這是分層協(xié)議棧決定的,瓶頸在各層中的某些操作,比如內(nèi)存操作(固有開銷)以及查表操作(算法不好導(dǎo)致的開銷)]
綜述:Linux轉(zhuǎn)發(fā)效率受到以下幾大因素影響
IO/輸入輸出的隊列管理/內(nèi)存修改拷貝 (重新設(shè)計類似crossbar的隊列管理實現(xiàn)DMA ring交換)各種表查詢操作,特別是最長前綴匹配,諸多本身唯一確定的查詢操作之間的關(guān)聯(lián)沒有建立SMP下處理器同步(鎖開銷)(使用大讀鎖以及RCU鎖)以及cache利用率中斷以及軟中斷調(diào)度
Linux轉(zhuǎn)發(fā)性能提升方案
概述此方案的思路來自基于crossbar的新一代硬件路由器。設(shè)計要點:
1.重新設(shè)計的DMA包管理隊列( 思路來自Linux O(1)調(diào)度器,crossbar陣列以及VOQ[虛擬輸出隊列])2.重新設(shè)計的基于定位而非最長前綴查找的轉(zhuǎn)發(fā)表3.長線程處理(中斷線程化,處理流水線化,增加CPU親和)4.數(shù)據(jù)結(jié)構(gòu)無鎖化(基于線程局部數(shù)據(jù)結(jié)構(gòu))5.實現(xiàn)方式5.1.驅(qū)動以及內(nèi)核協(xié)議棧修改5.2.完全的用戶態(tài)協(xié)議棧5.3.評估:用戶態(tài)協(xié)議棧靈活,但是在某些平臺要處理空間切換導(dǎo)致的cache/tlb/mmu表的flush問題
內(nèi)核協(xié)議棧方案優(yōu)化框架0.例行優(yōu)化1).網(wǎng)卡多隊列綁定特定CPU核心(利用RSS特性分別處理TX和RX)[ 可以參見《Effective Gigabit Ethernet Adapters-Intel千兆網(wǎng)卡8257X性能調(diào)優(yōu)》]2).按照包大小統(tǒng)計動態(tài)開關(guān)積壓延遲中斷ThrottleRate以及中斷Delay(對于Intel千兆卡而言)3).禁用內(nèi)核搶占,減少時鐘HZ,由中斷粒度驅(qū)動(見上面)4).如果不準(zhǔn)備優(yōu)化Netfilter,編譯內(nèi)核時禁用Netfilter,節(jié)省指令5).編譯選項去掉DEBUG和TRACE,節(jié)省指令周期6).開啟網(wǎng)卡的硬件卸載開關(guān)(如果有的話)7).最小化用戶態(tài)進(jìn)程的數(shù)量,降低其優(yōu)先級8).原生網(wǎng)絡(luò)協(xié)議棧優(yōu)化? ? 由于不再作為通用OS,可以讓除了RX softirq的task適當(dāng)饑餓? ? *CPU分組(考慮Linux的cgroup機(jī)制),劃一組CPU為數(shù)據(jù)面CPU,每一個CPU綁定一個RX softirq或者? ? *增加rx softirq一次執(zhí)行的netdev_budget以及time limit,或者? ? *只要有包即處理,每一個。控制面/管理面的task可以綁在別的CPU上。
宗旨:原生協(xié)議棧的最優(yōu)化方案
1.優(yōu)化I/O,DMA,減少內(nèi)存管理操作1).減少PCI-E的bus爭用,采用crossbar的全交叉超立方開關(guān)的方式[ Tips:16 lines 8 bits PCI-E總線拓?fù)?非crossbar!)的網(wǎng)絡(luò)線速不到滿載60% pps]2).減少爭搶式DMA,減少鎖總線[Tips:優(yōu)化指令LOCK,最好采用RISC,方可調(diào)高內(nèi)核HZ][ Tips:交換DMA映射,而不是在輸入/輸出buffer ring之間拷貝數(shù)據(jù)!現(xiàn)在,只有傻逼才會在DMA情況拷貝內(nèi)存,正確的做法是DMA重映射,交換指針!]3).采用skb內(nèi)存池,避免頻繁內(nèi)存分配/釋放造成的內(nèi)存管理框架內(nèi)的抖動[ Tips:每線程負(fù)責(zé)一塊網(wǎng)卡(甚至輸入和輸出由不同的線程負(fù)責(zé)會更好),保持一個預(yù)分配可循環(huán)利用的ring buffer,映射DMA]
宗旨:減少cache刷新和tlb刷新,減少內(nèi)核管理設(shè)施的工作(比如頻繁的內(nèi)存管理)
2.優(yōu)化中斷分發(fā)1).增加長路徑支持,減少進(jìn)程切換導(dǎo)致的TLB以及Cache刷新2).利用多隊列網(wǎng)卡支持中斷CPU親和力利用或者模擬軟多隊列提高并行性3).犧牲用戶態(tài)進(jìn)程的調(diào)度機(jī)會,全部精力集中于內(nèi)核協(xié)議棧的處理,多CPU多路并行的[ Tips:如果有超多的CPU,建議劃分cgroup ]4).中斷處理線程化,內(nèi)核線程化,多核心并行執(zhí)行長路經(jīng),避免切換抖動5).線程內(nèi)部,按照IXA NP微模塊思想采用模塊化(方案未實現(xiàn),待商榷)
宗旨:減少cache刷新和tlb刷新減少協(xié)議棧處理被中斷過于頻繁打斷[ 要么使用IntRate,要么引入中斷優(yōu)先級]
3.優(yōu)化路由查找算法1).分離路由表和轉(zhuǎn)發(fā)表,路由表和轉(zhuǎn)發(fā)表同步采用RCU機(jī)制2).盡量采用線程局部數(shù)據(jù)每個線程一張轉(zhuǎn)發(fā)表(由路由表生成,OpenVPN多線程采用,但失?。捎枚ㄎ欢亲铋L前綴查找(DxR或者我設(shè)計的那個)。若不采用為每個線程復(fù)制一份轉(zhuǎn)發(fā)表,則需要重新設(shè)計RW鎖或者使用RCU機(jī)制。3).采用hash/trie方式以及DxR或者我設(shè)計的DxRPro定位結(jié)構(gòu)
宗旨:采用定位而非查找結(jié)構(gòu)采用局部表,避免鎖操作
4.優(yōu)化lock1).查詢定位局部表,無鎖(甚至RW鎖都沒有)不禁止中斷2).臨界區(qū)和內(nèi)核線程關(guān)聯(lián),不禁中斷,不禁搶占(其實內(nèi)核編譯時搶占已經(jīng)關(guān)閉了)3).優(yōu)先級鎖隊列替換爭搶模型,維持cache熱度4).采用Windows的自旋鎖機(jī)制[ Tips:Linux的ticket spin lock由于采用探測全局lock的方式,會造成總線開銷和CPU同步開銷,Windows的spin lock采用了探測CPU局部變量的方式實現(xiàn)了真正的隊列l(wèi)ock,我設(shè)計的輸入輸出隊列管理結(jié)構(gòu)(下面詳述)思路部分來源于Windows的自旋鎖設(shè)計]
宗旨:鎖的粒度與且僅與臨界區(qū)資源關(guān)聯(lián),粒度最小化
優(yōu)化細(xì)節(jié)概覽1.DMA與輸入輸出隊列優(yōu)化
1.1.問題出在哪兒如果你對Linux內(nèi)核協(xié)議棧足夠熟悉,那么就肯定知道,Linux內(nèi)核協(xié)議棧正是由于軟件工程里面的天天普及的“一件好事”造成了轉(zhuǎn)發(fā)性能低效。這就是“解除緊密耦合”。Linux協(xié)議棧轉(zhuǎn)發(fā)和Linux服務(wù)器之間的根本區(qū)別在于,后者的應(yīng)用服務(wù)并不在乎數(shù)據(jù)包輸入網(wǎng)卡是哪個,它也不必關(guān)心輸出網(wǎng)卡是哪一個,然而對于Linux協(xié)議棧轉(zhuǎn)發(fā)而言,輸入網(wǎng)卡和輸出網(wǎng)卡之間確實是有必要相互感知的。Linux轉(zhuǎn)發(fā)效率低的根本原因不是路由表不夠高效,而是它的隊列管理以及I/O管理機(jī)制的低效,造成這種低效的原因不是技術(shù)實現(xiàn)上難以做到,而是Linux內(nèi)核追求的是一種靈活可擴(kuò)展的性能,這就必須解除出入網(wǎng)卡,驅(qū)動和協(xié)議棧之間關(guān)于數(shù)據(jù)包管理的緊密耦合。我們以Intel千兆網(wǎng)卡驅(qū)動e1000e來說明上述的問題。順便說一句,Intel千兆驅(qū)動亦如此,其它的就更別說了,其根源在于通用的網(wǎng)卡驅(qū)動和協(xié)議棧設(shè)計并不是針對轉(zhuǎn)發(fā)優(yōu)化的。
初始化:創(chuàng)建RX ring:RXbuffinfo[MAX]創(chuàng)建TX ring:TXbuffinfo[MAX]
RX過程:i = 當(dāng)前RX ring游歷到的位置;while(RXbuffinfo中有可用skb) {? ? ? ? skb = RXbufferinfo[i].skb;? ? ? ? RXbuffinfo[i].skb = NULL;? ? ? ? i ;? ? ? ? DMA_unmap(RXbufferinfo[i].DMA);? ? ? ? [Tips:至此,skb已經(jīng)和驅(qū)動脫離,完全交給了Linux協(xié)議棧]? ? ? ? [Tips:至此,skb內(nèi)存已經(jīng)不再由RX ring維護(hù),Linux協(xié)議棧拽走了skb這塊內(nèi)存]? ? ? ? OS_receive_skb(skb);? ? ? ? [Tips:由Linux協(xié)議棧負(fù)責(zé)釋放skb,調(diào)用kfree_skb之類的接口]? ? ? ? if (RX ring中被Linux協(xié)議棧摘走的skb過多) {? ? ? ? ? ? ? ? alloc_new_skb_from_kmem_cache_to_RXring_RXbufferinfo_0_to_MAX_if_possible;? ? ? ? ? ? ? ? [Tips:從Linux核心內(nèi)存中再次分配skb]? ? ? ? }}
TX過程:skb = 來自Linux協(xié)議棧dev_hard_xmit接口的數(shù)據(jù)包;i = TX ring中可用的位置TXbufferinfo[i].skb = skb;DMA_map(TXbufferinfo[i].DMA);while(TXbufferinfo中有可用的skb) {? ? ? ? DMA_transmit_skb(TXbufferinfo[i]);}[異步等待傳輸完成中斷或者在NAPI poll中主動調(diào)用]i = 傳輸完成的TXbufferinfo索引while(TXbufferinfo中有已經(jīng)傳輸完成的skb) {? ? ? ? skb = TXbufferinfo[i];? ? ? ? DMA_unmap(TXbufferinfo[i].DMA);? ? ? ? kfree(skb);? ? ? ? i ;}以上的流程可以看出,在持續(xù)轉(zhuǎn)發(fā)數(shù)據(jù)包的時候,會涉及大量的針對skb的alloc和free操作。如果你覺得上面的代碼不是那么直觀,那么下面給出一個圖示:
頻繁的會發(fā)生從Linux核心內(nèi)存中alloc skb和free skb的操作,這不僅僅是不必要的,而且還會損害CPU cache的利用。不要寄希望于keme_cache,我們可以看到,所有的網(wǎng)卡和socket幾乎是共享一塊核心內(nèi)存的,雖然可以通過dev和kmem cache來優(yōu)化,但很遺憾,這個優(yōu)化沒有質(zhì)的飛躍。
1.2.構(gòu)建新的DMA ring buffer管理設(shè)施-VOQ,建立輸入/輸出網(wǎng)卡之間隊列的關(guān)聯(lián)。類比Linux O(1)調(diào)度器算法,每一個cpu全局維護(hù)一個唯一的隊列,散到各個網(wǎng)卡,靠交換隊列的DMA映射指針而不是拷貝數(shù)據(jù)的方式優(yōu)化性能,達(dá)到零拷貝,這只是其一。關(guān)于交換DMA映射指針而不是拷貝數(shù)據(jù)這一點不多談,因為幾乎所有的支持DMA的網(wǎng)卡驅(qū)動都是這么做的,如果它們不是這么做的,那么肯定有人會將代碼改成這么做的。如果類比高端路由器的crossbar交換陣列結(jié)構(gòu)以及真實的VOQ實現(xiàn),你會發(fā)現(xiàn),在邏輯上,每一對可能的輸入/輸出網(wǎng)卡之間維護(hù)一條數(shù)據(jù)轉(zhuǎn)發(fā)通路是避免隊頭阻塞以及競爭的好方法。這樣排隊操作只會影響單獨的網(wǎng)卡,不需要再全局加鎖。在軟件實現(xiàn)上,我們同樣可以做到這個。你要明白,Linux的網(wǎng)卡驅(qū)動維護(hù)的隊列信息被內(nèi)核協(xié)議棧給割裂,從此,輸入/輸出網(wǎng)卡之間彼此失聯(lián),導(dǎo)致最優(yōu)的二分圖算法無法實施。事實上,你可能覺得把網(wǎng)卡作為一個集合,把需要輸出的數(shù)據(jù)包最為另一個集合,轉(zhuǎn)發(fā)操作需要做的就是建立數(shù)據(jù)包和網(wǎng)卡之間的一條路徑,這是一個典型的二分圖匹配問題,然而如果把建立路徑的操作與二分圖問題分離,這就是不再是網(wǎng)卡和數(shù)據(jù)包之間的二分圖匹配問題了。因為分離出來的路由模塊導(dǎo)致了針對每一個要轉(zhuǎn)發(fā)的數(shù)據(jù)包,其輸出網(wǎng)卡是唯一確定的。這個問題變成了處理輸出網(wǎng)卡輸出操作的CPU集合和輸出網(wǎng)卡之間的二分圖匹配問題。這里有一個優(yōu)化點,那就是如果你有多核CPU,那么就可以為每一塊網(wǎng)卡的輸出操作綁定一個唯一的CPU,二分圖匹配問題迎刃而解,剩下的就是硬件總線的爭用問題(對于高性能crossbar路由器而言,這也是一個二分圖匹配問題,但對于總線結(jié)構(gòu)的通用系統(tǒng)而言有點區(qū)別,后面我會談到)了,作為我們而言,這一點除了使用性價比更高的總線,比如我們使用PCI-E 16Lines 8 bits,沒有別的辦法。作為一個完全的方案,我不能寄希望于底層存在一個多核CPU系統(tǒng),如果只有一個CPU,那么我們能寄希望于Linux進(jìn)程調(diào)度系統(tǒng)嗎?還是那個觀點,作為一個通用操作系統(tǒng)內(nèi)核,Linux不會針對網(wǎng)絡(luò)轉(zhuǎn)發(fā)做優(yōu)化,于是乎,進(jìn)程調(diào)度系統(tǒng)是此方案的另一個優(yōu)化點,這個我后面再談。最后,給出我的數(shù)據(jù)包隊列管理VOQ的設(shè)計方案草圖。
在我的這個針對Linux協(xié)議棧的VOQ設(shè)計中,VOQ總要要配合良好的輸出調(diào)度算法,才能發(fā)揮出最佳的性能。
2.分離路由表和轉(zhuǎn)發(fā)表以及建立查找操作之間的關(guān)聯(lián)Linux協(xié)議棧是不區(qū)分對待路由表和轉(zhuǎn)發(fā)表的,而這在高端路由器上顯然是必須的。誠然,我沒有想將Linux協(xié)議棧打造成比肩專業(yè)路由器的協(xié)議棧,然而通過這個排名第二的核心優(yōu)化,它的轉(zhuǎn)發(fā)效率定會更上一層樓。在大約三個月前,我參照DxR結(jié)構(gòu)以及借鑒MMU思想設(shè)計了一個用于轉(zhuǎn)發(fā)的索引結(jié)構(gòu),可以實現(xiàn)3步定位,無需做最長前綴匹配過程,具體可以參見我的這篇文章 《以DxR算法思想為基準(zhǔn)設(shè)計出的路由項定位結(jié)構(gòu)圖解》,我在此就不再深度引用了。需要注意的是,這個結(jié)構(gòu)可以根據(jù)現(xiàn)行的Linux協(xié)議棧路由FIB生成,而且在路由項不規(guī)則的情況下可以在最差情況下動態(tài)回退到標(biāo)準(zhǔn)DxR,比如路由項不可匯聚,路由項在IPv4地址空間劃分區(qū)間過多且分布不均。我將我設(shè)計的這個結(jié)構(gòu)稱作DxR Pro 。至于說查找操作之間的關(guān)聯(lián),這也是一個深度優(yōu)化,底層構(gòu)建高速查詢流表實現(xiàn)協(xié)議棧短路(流表可參照conntrack設(shè)計),這個優(yōu)化思想直接參照了Netfilter的conntrack以及SDN流表的設(shè)計。雖然IP網(wǎng)絡(luò)是一個無狀態(tài)網(wǎng)絡(luò),中間路由器的轉(zhuǎn)發(fā)策略也應(yīng)該是一個無狀態(tài)的轉(zhuǎn)發(fā)。然而這是形而上意義上的理念。如果談到深度優(yōu)化,就不得不犧牲一點純潔性。設(shè)計一個流表,流的定義可以不必嚴(yán)格按照五元組,而是可以根據(jù)協(xié)議頭的任意字段,每一個表項中保存的信息包括但不限于以下的元素:*流表緩存路由項*流表緩存neighbour*流表緩存NAT*流表緩存ACL規(guī)則? ? ? ?*流表緩存二層頭信息這樣可以在協(xié)議棧的底層保存一張可以高速查詢的流表,協(xié)議棧收到skb后匹配這張表的某項,一旦成功,可以直接取出相關(guān)的數(shù)據(jù)(比如路由項)直接轉(zhuǎn)發(fā),理論上只有一個流的第一個數(shù)據(jù)包會走標(biāo)準(zhǔn)協(xié)議棧的慢速路徑(事實上,經(jīng)過DxR Pro 的優(yōu)化,一經(jīng)不慢了...)。在直接快速轉(zhuǎn)發(fā)中,需要執(zhí)行一個HOOK,執(zhí)行標(biāo)準(zhǔn)的例行操作,比如校驗和,TTL遞減等。??關(guān)于以上的元素,特別要指出的是和neighbour與二層信息相關(guān)的。數(shù)據(jù)轉(zhuǎn)發(fā)操作一向被認(rèn)為瓶頸在發(fā)不在收,在數(shù)據(jù)發(fā)送過程,會涉及到以下耗時的操作:>添加輸出網(wǎng)卡的MAC地址作為源-內(nèi)存拷貝>添加next hop的MAC地址作為目標(biāo)-內(nèi)存拷貝又一次,我們遇到了內(nèi)存操作,惱人的內(nèi)存操作!如果我們把這些MAC地址保存在流表中,可以避免嗎?貌似只是可以快速定位,而無法避免內(nèi)存拷貝...再一次的,我們需要硬件的特性來幫忙,這就是分散聚集I/O(Scatter-gather IO),原則上,Scatter-gather IO可以將不連續(xù)的內(nèi)存當(dāng)成連續(xù)的內(nèi)存使用,進(jìn)而直接映射DMA,因此我們只需要告訴控制器,一個將要發(fā)送的幀的MAC頭的位置在哪里,DMA就可以直接傳輸,沒有必要將MAC地址拷貝到幀頭的內(nèi)存區(qū)域。如下圖所示:
特別要注意,上述的流表緩存項中的數(shù)據(jù)存在大量冗余,因為next hop的MAC地址,輸出網(wǎng)卡的MAC地址,這些是可以由路由項唯一確定的。之所以保存冗余數(shù)據(jù),其原則還是為了優(yōu)化,而標(biāo)準(zhǔn)的通用Linux內(nèi)核協(xié)議棧,它卻是要避免冗余的...既然保存了冗余數(shù)據(jù),那么慢速路徑的數(shù)據(jù)項和快速路經(jīng)的數(shù)據(jù)項之間的同步就是一個必須要解決的問題。我基于讀寫的不對稱性,著手采用event的方式通知更新,比如慢速路徑中的數(shù)據(jù)項(路由,MAC信息,NAT,ACL信息等),一旦這些信息更改,內(nèi)核會專門觸發(fā)一個查詢操作,將快速流表中與之相關(guān)的表項disable掉即可。值得注意的是,這個查詢操作沒必要太快,因為相比較快速轉(zhuǎn)發(fā)而言,數(shù)據(jù)同步的頻率要慢天文數(shù)字個數(shù)量級...類似Cisco的設(shè)備,可以創(chuàng)建幾個內(nèi)核線程定期刷新慢速路徑表項,用來發(fā)現(xiàn)數(shù)據(jù)項的更改,從而觸發(fā)event。
[Tips:可以高速查找的流表結(jié)構(gòu)可用多級hash(采用TCAM的類似方案),也可以借鑒我的DxR Pro 結(jié)構(gòu)以及nf-HiPac算法的多維區(qū)間匹配結(jié)構(gòu),我個人比較推崇nf-HiPac]
3.路由Cache優(yōu)化雖說Linux的路由cache早已下課,但是它下課的原因并不是cache機(jī)制本身不好,而是Linux的路由cache設(shè)計得不好。因此以下幾點可以作為優(yōu)化點來嘗試。*)限制路由軟cache的大小,保證查找速度[實施精心設(shè)計的老化算法和替換算法][ 利用互聯(lián)網(wǎng)訪問的時間局部性以及空間局部性(需要利用計數(shù)統(tǒng)計)][ 自我PK:如果有了我的那個3步定位結(jié)構(gòu),難道還用的到路由cache嗎]*)預(yù)制常用IP地址到路由cache,實現(xiàn)一步定位[ 所謂常用IP需要根據(jù)計數(shù)統(tǒng)計更新,也可以靜態(tài)設(shè)置]
4.Softirq在不支持RSS多隊列網(wǎng)卡時的NAPI調(diào)度優(yōu)化*)將設(shè)備按照協(xié)議頭hash值均勻放在不同CPU,遠(yuǎn)程喚醒softirq,模擬RSS軟實現(xiàn)目前的網(wǎng)絡(luò)接收軟中斷的處理機(jī)制是,哪個CPU被網(wǎng)卡中斷了,哪個CPU就處理網(wǎng)卡接收軟中斷,在網(wǎng)卡只能中斷固定CPU的情況下,這會影響并行性,比如只有兩塊網(wǎng)卡,卻有16核CPU。如何將盡可能多的CPU核心調(diào)動起來呢?這需要修改網(wǎng)絡(luò)接收軟中斷處理邏輯。我希望多個CPU輪流處理數(shù)據(jù)包,而不是固定被中斷的數(shù)據(jù)包來處理。修改邏輯如下:
1.所有的rx softirq內(nèi)核線程組成一個數(shù)組struct task_struct rx_irq_handler[NR_CPUS];
2.所有的poll list組成一個數(shù)組struct list_head polll[NR_CPUS];
3.引入一把保護(hù)上述數(shù)據(jù)的自旋鎖spinlock_t rx_handler_lock;
4.修改NAPI的調(diào)度邏輯void __napi_schedule(struct napi_struct *n){? ? unsigned long flags;
? ? static int curr = 0;? ? unsigned int hash = curr %NR_CPUS;? ? local_irq_save(flags);? ? spin_lock(