Linux的網絡協議棧非常獨立,上下通過兩個接口分別和用戶態以及設備相連,也可以看作是北向和南向接口...北向通過socket接口,南向通過qdisc接口(你可以認為是上層的netdev queue,對於接收接口,NAPI的poll隊列則是另一個例子),不管是socket還是qdisc,都是基於隊列來管理的,也就是說,三個部分是獨立的,socket只能看到讀寫隊列,而看不到協議棧本身,socket在讀一個數據的時候,它取的是隊列裡面的數據,至於說這個數據是誰放進去的,它並不知道,是不是協議棧放進去的,它也不必驗證。
socket隔離了用戶進程和協議棧,RX/TX queue隔離了協議棧和設備驅動。
這種隔離方式給編程和設計帶來了簡便,然而卻不利於性能。
Linux的RPS設計,旨在讓一個CPU既處理數據包的協議棧接收流程(軟中斷內核線程上下文,或者任意上下文的軟中斷處理),又運行用戶態處理該數據包的進程。我說這種設計有利也有弊,如果僅僅是旨在提高cache利用率,那麼這種設計是對的,但是有沒有想過別的情況,如果一個CPU在NET RX軟中斷處理的最後將一個skb推到了一個socket隊列,並試圖喚醒等待進程,那麼它下一步該干些什麼呢?實際上它下一步應該返回設備,繼續去poll下一個skb,然而RPS的設計不是這樣,RPS的設計旨在希望讓該CPU繼續處理用戶態進程....這就必然要進行一次進程切換以及用戶/內核態的切換,雖然服務器的CPU cache利用率提高了,但是協議棧處理相關的CPU cache利用率反而降低了。事實上,CPU cache是否在進程切換以及用戶/內核態切換後刷新,這個是體系結構相關的,並不是說所有的體系結構都能帶來好的結果。
必須做進一步的測試。
我覺得最好的辦法就是用戶進程和內核的NET RX軟中斷處在不同的CPU核心上,然而這兩個CPU核心共享二級cache或者三級cache。
...
Linux內核隨之發展出了更好的方案,那就是突破上述的獨立三大部分,讓socket直接深入到設備層直接poll skb!!注意,這是一個poll操作,並不是讓socket直接處理協議棧流程。socket直接poll的意思是說,socket在隊列中沒有讀到數據包的時候,並不是睡眠,然後等待NET RX內核線程將數據包放入隊列後將其喚醒,而是直接去問設備:現在有數據包嗎?如果有,我直接帶走它們去協議棧,而不需要你送它們去了。這是一種“拉”的方式,而不是以往的那種“推”的方式,拉和推的區別在於,對於接收者,拉是同一個實體,是主動的,而推則是被動的。
這就解決了RPS試圖解決卻又沒有完美解決的問題。這種機制叫做busy poll。
RPS試圖讓軟中斷處理完數據包後,切換到用戶進程,此時軟中斷將間歇,然後數據包中斷後又要切回來...busy poll就不是這樣,它直接繞過了軟中斷這個執行體,直接靠socket自身所在的執行體來主動拉取數據包進行處理。避免了大量的任務交接導致的切換問題。
我不曉得對於轉發的情況,是否也能采用busy poll的方式來提高性能,這需要測試。以上的闡述只是理想情況,真實情況是,socket可能替別的socket從設備拉取了一個數據包,甚至這個數據包只是轉發的,不與任何socket關聯...因為數據包只有經過標准的路由以及四層處理後,才能和一個具體socket關聯,在設備驅動層,指望找到這個關聯是徒勞且無望的!不管怎麼說,控制權在用戶自己手中,憑概率來講,如果你的設備中大量的數據包都是轉發包,就不要開啟這個功能,如果你的進程擁有少量的socket處理大量的數據包,那就開啟它,不管怎樣,這只是一個用法和配置的問題,何時開啟,以及份額設置多少,需要一個事前采樣的過程。