前言
前面分析Fastsocket慢慢湊成了幾篇爛文字,要把一件事情堅持做下來,有時味同爵蠟,但既然選擇了,也得硬著頭皮做下去。閑話少說,文歸正文。本文接自上篇內核模塊篇,繼續記錄學習Fastsocket內核的筆記內容。
Fastsocket建立在SO_REUSEPORT支持基礎上
Linux kernel 3.9包含TCP/UDP支持多進程、多線程綁定同一個IP和端口的特性,即SO_REUSEPORT
;在內核層面同時也讓線程/進程之間各自獨享SOCKET,避免CPU核之間以鎖資源爭奪accept queue
的調用。在fastsocket/kernel/net/sock.h定義sock_common
結構時,可以看到其身影:
unsigned char skc_reuse:4;
unsigned char skc_reuseport:4;
在多個socket.h文件中(比如fastsocket/kernel/include/asm/socket.h),定義了SO_REUSESORT的變量值:
#define SO_REUSEPORT 15
在fastsocket/kernel/net/core/sock.c的sock_setsockopt和sock_getsockopt函數中,都有SO_REUSEPORT
的身影:
sock_setsockopt函數中:
case SO_REUSEADDR:
sk->sk_reuse = valbool;
break;
case SO_REUSEPORT:
sk->sk_reuseport = valbool;
break;
sock_getsockopt函數體中:
case SO_REUSEADDR:
v.val = sk->sk_reuse;
break;
case SO_REUSEPORT:
v.val = sk->sk_reuseport;
break;
在SO_REUSEPORT
特性支持之前的事件驅動驅動服務器資源競爭:

之后呢,可以看做是并行的了:

Fastsocket沒有重復發明輪子,在SO_REUSEPORT
基礎上進行進一步的優化等。
嗯,后面準備寫一個動態鏈接庫小程序,打算讓以前的沒有硬編碼SO_REUSEPORT
的程序也能夠在Linux kernel >= 3.9系統上享受真正的端口重用的新特性的支持。
Fastsocket架構圖

下面按照其架構圖所示內核層面從上到下一一列出。
虛擬文件系統VFS的改進
因為Linux Kernel VFS的同步損耗嚴重
- VFS對文件節點Inode和目錄Dentry有同步需求
- 但SOCKET只需要在內存中存在即可,非嚴格意義上文件系統,其不需要路徑,不需要為Inode和Dentry加鎖
- 代碼層面略過不必須的常規鎖,但又保持了足夠的兼容性
提交記錄:
a209dfc vfs: dont chain pipe/anon/socket on superblock s_inodes list
4b93688 fs: improve scalability of pseudo filesystems
對VFS的改進,在所提升的性能中占有超過60%的比例,效果非常明顯:

Local Listen Table
對于多核多接收隊列來說,linux原生的協議棧只能listen在一個socket上面,并且所有完成三次握手還沒來得及被應用accept的套接字都會放入其附帶的accept隊列中,accept系統調用必須串行的從隊列取出,當并發量較大時多核競爭,這將成為性能瓶頸,影響建立連接處理速度。

Local Listen Table,fastsocket為每一個CPU核克隆監聽套接字,并保存到其本地表中,CPU核之間不會存在accept的競爭關系。下面為引用描述內容:
- 每個core有一個listen socket table。應用程序建立連接的時候,執行過程會調用local_listen()函數,有兩個參數,一個是socket FD,一個是core number. new socket從原始的listen socket(global)拷貝到per-core local socket table. 這些對于應用程序來說都是透明的,提供給應用程序的socketFD是抽象過的,隱藏了底層的實現。
- 當一個TCP SYN到達本機,kernel首先去local listen table中找匹配的listen socket,如果找到,就通過網卡RSS傳遞這個socket到一個core,否則就去global listen table中找。
- 容錯方面,當進程崩潰的話,local listen socket會被關閉,進入的連接將會被引導到global Listen socket, 這樣的話,別的process可以處理這些連接。由于local listen socket和global listen socket共享FD,所以kernel將會把新的connet通知到相應的process。
- 如果應用程序進程使用accept()系統調用,那么處理過程是首先去global listen table中查找和操作(因為是讀操作,沒有使用鎖),如果沒有找到,那么去core的local table中查找。如果找到,就返回給應用程序。由于listen的時候把socket綁定到了一個core,所以查找的時候也去這個core的local table中查找。
- epoll兼容性,如果應用程序使用epoll_ctl()系統調用,來把一個listen socket添加到Epoll set中,那么local的listen socket和global的listen socket都被epoll監控。事件發生的時候,epoll_wait()系統調用會返回listen socket,accept()系統調用就會處理這個socket。這樣就保證了epoll實現的兼容性。
使用流程圖概括上面所述:

Local Established Table
Linux內核使用一個全局的hash表以及鎖操作來維護establised sockets(被用來跟蹤連接的sockets)。Fastsocket 想法是把全局table分散到per-Core table,當一個core需要訪問socket的時候,只在隸屬于自己的table中搜索,因此不需要鎖操縱,也不存在資源競爭。由fastsocket建立的socket本地local established table中,其他的regular sockets保存在global的table中。core首先去自己的local table中查找(不需要鎖),然后去global中查找。

Receive Flow Deliver
默認情況下,應用程序主動發包的時候,發出去的包是通過正在執行本進程的那個CPU 核(系統分配的)來完成的;而接收數據包的時CPU 核是由前面提到的RSS或RPS來傳遞。這樣一來,連接可能由不同的兩個CPU核來完成。連接應該在本地化處理。RFS和Intel網卡的FlowDirector可以從軟件和硬件上緩解這種情況,但是不完備。
RFD(Receive Flow Deliver)主要的思想是CPU核數主動發起連接的時候可以把CPU core的標識和連接的source port編碼到一起。CPU cores和ports的關系由一個關系集合來決定【cores,ports】, 對于一個port,有唯一的一個core與之對應。當一個core來建立connection的時候,RFD隨機選擇一個跟當前core匹配的port。接收包的時候,RFD負責決定這個包應該讓哪一個core來處理,如果當前core不是被選中的cpu core,那么就deliver到選中的cpu core。

一般來說,RFD對代理程序收益比較大,單純的WEB服務器可以選擇禁用。
小結
以上參考了大量的外部資料進行整理而成,進而可以獲得一個較為整體的Fastsocket內核架構印象。
Fastsocket的努力,在單個TCP連接的管理從網卡觸發的硬中斷、軟中斷、三次握手、數據傳輸、四次揮手等完整的過程在完整在一個CPU核上進行處理,從而實現了每一個CPU核心TCP資源本地化,這樣為多核水平擴展打好了基礎,減少全局資源競爭,平行化處理連接,同時降低文件鎖的副作用,做到了極為高效的短連接處理方案,不得不贊啊。
引用資料: