1. 程式人生 > >Linux核心軟RPS實現網路接收軟中斷的負載均衡分發

Linux核心軟RPS實現網路接收軟中斷的負載均衡分發

例行的Linux軟中斷分發機制與問題

Linux的中斷分為上下兩半部,一般而言(事實確實也是如此),被中斷的CPU執行中斷處理函式,並在在本CPU上觸發軟中斷(下半部),等硬中斷處理返回後,軟中斷隨即開中斷在本CPU執行,或者wake up本CPU上的軟中斷核心執行緒來處理在硬中斷中pending的軟中斷。
       換句話說,Linux和同一箇中斷向量相關的中斷上半部和軟中斷都是在同一個CPU上執行的,這個可以通過raise_softirq這個介面看出來。這種設計的邏輯是正確的,但是在某些不甚智慧的硬體前提下,它工作得並不好。核心沒有辦法去控制軟中斷的分發,因此也就只能對硬中斷的發射聽之任之。這個分為兩類情況:

1.硬體只能中斷一個CPU

按照上述邏輯,如果系統存在多個CPU核心,那麼只能由一個CPU處理軟中斷了,這顯然會造成系統負載在各個CPU間不均衡。

2.硬體盲目隨機中斷多個CPU

注意”盲目“一詞。這個是和主機板以及匯流排相關的,和中斷源關係並不大。因此具體中斷哪個CPU和中斷源的業務邏輯也無關聯,比如主機板和中斷控制器並不是理會網絡卡的資料包內容,更不會根據資料包的元資訊中斷不同的CPU...即,中斷源對中斷哪個CPU這件事可以控制的東西幾乎沒有。為什麼必須是中斷源呢?因此只有它知道自己的業務邏輯,這又是一個端到端的設計方案問題。
       因此,Linux關於軟中斷的排程,缺乏了一點可以控制的邏輯,少了一點靈活性,完全就是靠著硬體中斷源中斷的CPU來,而這方面,硬體中斷源由於被中斷控制器和匯流排與CPU隔離了,它們之間的配合並不好。因此,需要加一個軟中斷排程層來解決這個問題。
       本文描述的並不是針對以上問題的一個通用的方案,因為它只是針對為網路資料包處理的,並且RPS在被google的人設計之初,其設計是高度定製化的,目的很單一,就是提高Linux伺服器的效能。而我,將這個思路移植到了提高Linux路由器的效能上。

基於RPS的軟中斷分發優化

在Linux轉發優化那篇文章《Linux轉發效能評估與優化(轉發瓶頸分析與解決方案)》中,我嘗試了網絡卡接收軟中斷的負載均衡分發,當時嘗試了將該軟中斷再次分為上下半部:
上半部:用於skb在不同的CPU間分發。
下半部:使用者skb的實際協議棧接收處理。
事實上,利用Linux 2.6.35以後加入的RPS的思想可能會有更好的做法,根本不用重新分割網路接收軟中斷。它基於以下的事實:

事實1:網絡卡很高階的情況

如果網絡卡很高階,那麼它一定支援硬體多佇列特性以及多中斷vector,這樣的話,就可以直接繫結一個佇列的中斷到一個CPU核心,無需軟中斷重分發skb。

事實2:網絡卡很低檔的情況

如果網絡卡很低檔,比如它不支援多佇列,也不支援多箇中斷vector,且無法對中斷進行負載均衡,那麼也無需讓軟中斷來分發,直接要驅動裡面分發豈不更好(其實這樣做真的不好)?事實上,即便支援單一中斷vector的CPU間負載均衡,最好也要禁掉它,因為它會破壞CPU cache的親和力。

為什麼以上的兩點事實不能利用

中斷中不能進行復雜耗時操作,不能由複雜計算。中斷處理函式是裝置相關的,一般不由框架來負責,而是由驅動程式自己負責。協議棧主框架只維護一個介面集,而驅動程式可以呼叫介面集內的API。你能保證驅動的編寫人員可以正確利用RPS而不是誤用它嗎?
       正確的做法就是將這一切機制隱藏起來,外部僅僅提供一套配置,你(驅動編寫人員)可以開啟它,關閉它,至於它怎麼工作的,你不用關心。
       因此,最終的方案還是跟我最初的一樣,看來RPS也是這麼個思路。修改軟中斷路徑中NAPI poll回撥!然而poll回撥也是驅動維護的,因此就在資料包資料的公共路徑上掛接一個HOOK,來負責RPS的處理。

為什麼要禁掉低端網絡卡的CPU中斷負載均衡

答案似乎很簡單,答案是:因為我們自己用軟體可以做得更好!而基於簡單硬體的單純且愚蠢的盲目中斷負載均衡可能會(幾乎一定會)弄巧成拙!
       這是為什麼?因為簡單低端網絡卡硬體不識別網路流,即它只能識別到這是一個數據包,而不能識別到資料包的元組資訊。如果一個數據流的第一個資料包被分發到了CPU1,而第二個資料包分發到了CPU2,那麼對於流的公共資料,比如nf_conntrack中記錄的東西,CPU cache的利用率就會比較低,cache抖動會比較厲害。對於TCP流而言,可能還會因為TCP序列包並行處理的延遲不確定性導致資料包亂序。因此最直接的想法就是將屬於一個流的所有資料包分發了一個CPU上。

我對原生RPS程式碼的修改

要知道,Linux的RPS特性是google人員引入的,他們的目標在於提升伺服器的處理效率。因此他們著重考慮了以下的資訊:
哪個CPU在為這個資料流提供服務;
哪個CPU被接收了該流資料包的網絡卡所中斷;
哪個CPU執行處理該流資料包的軟中斷。
理想情況,為了達到CPU cache的高效利用,上面的三個CPU應該是同一個CPU。而原生RPS實現就是這個目的。當然,為了這個目的,核心中不得不維護一個”流表“,裡面記錄了上面三類CPU資訊。這個流表並不是真正的基於元組的流表,而是僅僅記錄上述CPU資訊的表。
       而我的需求則不同,我側重資料轉發而不是本地處理。因此我的著重看的是:
哪個CPU被接收了該流資料包的網絡卡所中斷;
哪個CPU執行處理該流資料包的軟中斷。
其實我並不看中哪個CPU排程傳送資料包,傳送執行緒只是從VOQ中排程一個skb,然後傳送,它並不處理資料包,甚至都不會去訪問資料包的內容(包括協議頭),因此cache的利用率方面並不是傳送執行緒首要考慮的。
       因此相對於Linux作為伺服器時關注哪個CPU為資料包所在的流提供服務,Linux作為路由器時哪個CPU資料傳送邏輯可以忽略(雖然它也可以通過設定二級快取接力[最後講]來優化一點)。Linux作為路由器,所有的資料一定要快,一定儘可能簡單,因為它沒有Linux作為伺服器執行時伺服器處理的固有延遲-查詢資料庫,業務邏輯處理等,而這個服務處理的固有延遲相對網路處理處理延遲而言,要大得多,因此作為伺服器而言,網路協議棧處理並不是瓶頸。伺服器是什麼?伺服器是資料包的終點,在此,協議棧只是一個入口,一個基礎設施。
       在作為路由器執行時,網路協議棧處理延遲是唯一的延遲,因此要優化它!路由器是什麼?路由器不是資料包的終點,路由器是資料包不得不經過,但是要儘可能快速離開的地方!
       所以我並沒有直接採用RPS的原生做法,而是將hash計算簡化了,並且不再維護任何狀態資訊,只是計算一個hash:
target_cpu = my_hash(source_ip, destination_ip, l4proto, sport, dport) % NR_CPU;
[my_hash只要將資訊足夠平均地進行雜湊即可!]
僅此而已。於是get_rps_cpu中就可以僅有上面的一句即可。
       這裡有一個複雜性要考慮,如果收到一個IP分片,且不是第一個,那麼就取不到四層資訊,因為可能會將它們和片頭分發到不同的CPU處理,在IP層需要重組的時候,就會涉及到CPU之間的資料互訪和同步問題,這個問題目前暫不考慮。

NET RX軟中斷負載均衡總體框架

本節給出一個總體的框架,網絡卡很低端,假設如下:
不支援多佇列;
不支援中斷負載均衡;
只會中斷CPU0。

它的框架如下圖所示:



CPU親和接力優化

本節稍微提一點關於輸出處理執行緒的事,由於輸出處理執行緒邏輯比較簡單,就是執行排程策略然後有網絡卡傳送skb,它並不會頻繁touch資料包(請注意,由於採用了VOQ,資料包在放入VOQ的時候,它的二層資訊就已經封裝好了,部分可以採用分散/聚集IO的方式,如果不支援,只能memcpy了...),因此CPU cache對它的意義沒有對接收已經協議棧處理執行緒的大。然而不管怎樣,它還是要touch這個skb一次的,為了傳送它,並且它還要touch輸入網絡卡或者自己的VOQ,因此CPU cache如果與之親和,勢必會更好。
       為了不讓流水線單獨的處理過長,造成延遲增加,我傾向於將輸出邏輯放在一個單獨的執行緒中,如果CPU核心夠用,我還是傾向於將其綁在一個核心上,最好不要綁在和輸入處理的核心同一個上。那麼綁在哪個或者哪些上好呢?
       我傾向於共享二級cache或者三級cache的CPU兩個核心分別負責網路接收處理和網路傳送排程處理。這就形成了一種輸入輸出的本地接力。按照主機板構造和一般的CPU核心封裝,可以用下圖所示的建議:



為什麼我不分析程式碼實現

第一,基於這樣的事實,我並沒有完全使用RPS的原生實現,而是對它進行了一些修正,我並沒有進行復雜的hash運算,我放寬了一些約束,目的是使得計算更加迅速,無狀態的東西根本不需要維護!
第二,我發現我逐漸看不懂我以前寫的程式碼分析了,同時也很難看明白大批批的程式碼分析的書,我覺得很難找到對應的版本和補丁,但是基本思想卻是完全一樣的。因此我比較傾向於整理出事件被處理的流程,而不是單純的去分析程式碼。
宣告:本文是針對底端通用裝置的最後補償,如果有硬體結合的方案,自然要忽略本文的做法。

例行牢騷與感嘆

小小生病發燒中,公司裡一大堆積壓的事情,老婆公司最近老是開會!我本將心向明月,奈何明月照溝渠!我晚上會幹什麼?很多人會覺得我很累,晚上一定會美美睡上一覺!不!沒有,我晚上在通訊,在DIY,在debug!在看書,在UNIX,在Cisco!在古羅馬,古希臘!因為這是我唯一屬於自己的時間!只要外面下著雨,越大越好,我就可以持續4天不睡覺不吃飯,中間補一頓簡單的,有水即可,保持嘴巴里進入的有機物最小化!我想說,任何哪家公司的哥們兒跟我比加班,敢死磕麼?包括華為的!
       這不是憤怒的宣洩,這是一種能力。我記得我在小學五年級發現我原來可以這樣。初中的時候經常這樣,有時是為了求解幾道超級難度的數學題,到了大學,這就成了常事,學習也好,有時就是打遊戲看片,純粹的玩,跟女朋友聊天幾個小時,她困了睡覺,我等著,等著她醒來繼續。上班以後,換了N家公司,因為加班死磕過好幾個,一通宵+一天半可否。記得在一家公司每週四例行通宵,這可把我興奮的,特別是大雨天!我討厭正常的作息,我比較喜歡非週末集體通宵,然後在晚上工作之後,第二天大家要麼調休,要麼昏昏欲睡,而我望著他們有種虐人的感覺,時間都去哪了,時間就在那,拿到需要門檻!我有能力通過非週末持續通宵把大家所有人帶入一種惡性迴圈,但我不會那麼做,因為我是一個善良的人。所以我經常會說,加班是我的專利,而不是你們的,加班對你們而言是一種折磨,當然,沒事在公司呆著避高峰,躲債躲老婆躲家務,賺報銷費的除外!
       昨晚通宵,外面下著雨!收穫:羅馬/埃特魯里亞的關係;SONET/SDH成幀標準;本文。其間照顧發燒的小小。