mTCP
是一款面向多核系統的用戶態網絡協議棧
互聯網的發展,使得用戶對網絡應用的性能需求愈來愈高。人們不斷挖掘CPU處理能力增強,添加核的數量,但這並無使得網絡設備的吞吐率線性增長,其中一個緣由是內核協議棧成爲了限制網絡性能提高的瓶頸。node
互斥上鎖是多核平臺性能的第一殺手。如今的服務器端應用爲了儘量的實現高併發,一般都是採用多線程的方式監聽客戶端對服務端口發起的鏈接請求。首先,這會形成多個線程之間對accept
隊列的互斥訪問。其次,線程間對文件描述符空間的互斥訪問也會形成性能降低。git
內核中協議棧處理數據報文都是逐個處理的, 缺乏批量處理的能力。程序員
頻繁的短鏈接會引發大量的用戶態/內核態模式切換,頻繁的上下文切換會形成更多的Cache Miss
github
用戶態協議棧-便是將本來由內核完成了協議棧功能上移至用戶態實現。編程
經過利用已有的高性能Packet IO
庫 (以DPDK
爲例)旁路內核,用戶態協議棧能夠直接收發網絡報文,而沒有報文處理時用戶態/內核態的模式切換。除此以外,因爲徹底在用戶態實現,因此具備更好的可擴展性仍是可移植性。緩存
mTCP
做爲一種用戶態協議棧庫的實現,其在架構以下圖所示:服務器
mTCP
以函數庫的形式連接到應用進程,底層使用其餘用戶態的Packet IO
庫。網絡
總結起來,mTCP
具備如下特性:數據結構
epoll
事件驅動系統BSD
風格的socket API
Packet IO
庫TCP
爲了不多線程訪問共享的資源帶來的開銷。mTCP
將全部資源(如flow pool
socket buffer
)都按核分配,即每一個核都有本身獨有的一份。而且,這些數據結構都是cache
對齊的。多線程
從上面的架構圖能夠看到,mTCP
須要爲每個用戶應用線程(如Thread0
)建立一個額外的一個線程(mTCP thread0
)。這兩個線程都被綁定到同一個核(設置CPU
親和力)以最大程度利用CPU
的Cache
。
因爲內部新增了線程,所以mTCP
在將報文送給用戶線程時,不可避免地須要進行線程間的通訊,而一次線程間的通訊可比一次系統調用的代價高多了。所以mTCP
採用的方法是批量進行報文處理,這樣平均下來每一個報文的處理代價就小多了。
epoll
事件驅動系統對於習慣了使用epoll
編程的程序員來講,mTCP
太友好了,你須要作就是把epoll_xxx()
換成mtcp_epoll_xxx()
一樣的,應用程序只須要把BSD
風格的Socket API
前面加上mtcp_
就足夠了,好比mtcp_accept()
在mTCP
中, Packet IO
庫也被稱爲IO engine
, 當前版本(v2.1)mTCP
支持DPDK
(默認)、 netmap
、onvm
、 psio
四種IO engine
。
如前所述mTCP
須要會爲每一個用戶應用線程建立一個單獨的線程,而這實際上須要每一個用戶應用線程顯示調用下面的接口完成。
mctx_t mtcp_create_context(int cpu);
這以後,每一個mTCP
線程會進入各自的Main Loop
,每一對線程經過mTCP
建立的緩衝區進行數據平面的通訊,經過一系列Queue
進行控制平面的通訊
每個mTCP
線程都有一個負責管理資源的結構struct mtcp_manager
, 在線程初始化時,它完成資源的建立,這些資源都是屬於這個核上的這個線程的,包括保存鏈接四元組信息的flow table
,套接字資源池socket pool
監聽套接字listener hashtable
,發送方向的控制結構sender
等等
既然是純用戶態協議棧,那麼全部套接字的操做都不是用glibc
那一套了,mTCP
使用socket_map
表示一個套接字,看上去是否是比內核的那一套簡單多了!
struct socket_map { int id; int socktype; uint32_t opts; struct sockaddr_in saddr; union { struct tcp_stream *stream; struct tcp_listener *listener; struct mtcp_epoll *ep; struct pipe *pp; }; uint32_t epoll; /* registered events */ uint32_t events; /* available events */ mtcp_epoll_data_t ep_data; TAILQ_ENTRY (socket_map) free_smap_link; };
其中的socketype
表示這個套接字結構的類型,根據它的值,後面的聯合體中的指針也就能夠解釋成不一樣的結構。注意在mTCP
中,咱們一般認爲的文件描述符底層也對應這樣一個socket_map
enum socket_type { MTCP_SOCK_UNUSED, MTCP_SOCK_STREAM, MTCP_SOCK_PROXY, MTCP_SOCK_LISTENER, MTCP_SOCK_EPOLL, MTCP_SOCK_PIPE, };
mTCP
實現的epoll
相對於內核版本也簡化地多,控制結構struct mtcp_epoll
以下:
struct mtcp_epoll { struct event_queue *usr_queue; struct event_queue *usr_shadow_queue; struct event_queue *mtcp_queue; uint8_t waiting; struct mtcp_epoll_stat stat; pthread_cond_t epoll_cond; pthread_mutex_t epoll_lock; };
它內部保存了三個隊列,分別存儲發生了三種類型的事件的套接字。
MTCP_EVENT_QUEUE
表示協議棧產生的事件,好比LISTEN
狀態的套接字accept
了,ESTABLISH
的套接字有數據能夠讀取了USR_EVENT_QUEUE
表示用戶應用的事件,如今就只有PIPE
;USR_SHADOW_EVENT_QUEUE
表示用戶態因爲沒有處理完,而須要模擬產生的協議棧事件,好比ESTABLISH
上的套接字數據沒有讀取完.mTCP
使用tcp_stream
表示一條端到端的TCP
流,其中保存了這條流的四元組信息、TCP
鏈接的狀態、協議參數和緩衝區位置。tcp_stream
存儲在每線程的flow table
中
typedef struct tcp_stream { socket_map_t socket; // code omitted... uint32_t saddr; /* in network order */ uint32_t daddr; /* in network order */ uint16_t sport; /* in network order */ uint16_t dport; /* in network order */ uint8_t state; /* tcp state */ struct tcp_recv_vars *rcvvar; struct tcp_send_vars *sndvar; // code omitted... } tcp_stream;
mTCP
使用struct mtcp_sender
完成發送方向的管理,這個結構是每線程每接口的,若是有2個mTCP
線程,且有3個網絡接口,那麼一共就有6個發送控制器
struct mtcp_sender { int ifidx; /* TCP layer send queues */ TAILQ_HEAD (control_head, tcp_stream) control_list; TAILQ_HEAD (send_head, tcp_stream) send_list; TAILQ_HEAD (ack_head, tcp_stream) ack_list; int control_list_cnt; int send_list_cnt; int ack_list_cnt; };
每一個控制器內部包含了3個隊列,隊列中元素是 tcp_stream
Control
隊列:負責緩存待發送的控制報文,好比SYN-ACK
報文Send
隊列:負責緩存帶發送的數據報文ACK
隊列:負責緩存純ACK
報文假設咱們的服務端應用在某個應用線程建立了一個epoll
套接字和一個監聽套接字,而且將這個監聽套接字加入epoll
,應用進程阻塞在mtcp_epoll_wait()
,而mTCP線程在本身的main Loop
中循環
SYN
報文。mTCP
線程在main Loop
中讀取底層IO
收到該報文, 在嘗試在本線程的flow table
搜索後,發現沒有此四元組標識的流信息,因而新建一條tcp stream
, 此時,這條流的狀態爲TCP_ST_LISTEN Control
隊列,狀態切換爲TCP_ST_SYNRCVD,表示已收到TCP
的第一次握手mTCP
線程在main Loop
中讀取Control
隊列,發現其中有剛剛的這條流,因而將其取出,組裝SYN-ACK
報文,送到底層IO
mTCP
線程在main Loop
中讀取底層收到的對端發來這條流的ACK
握手信息,將狀態改成TCP_ST_ESTABLISHED(TCP的三次握手完成),而後將這條流塞入監聽套接字的accept
隊列epoll
的,所以mTCP
線程還會將一個MTCP_EVENT_QUEUE
事件塞入struct mtcp_epoll
的mtcp_queu
e隊列。mtcp_epoll_wait()
就能讀取到該事件,而後調用mtcp_epoll_accept()
從Control
隊列讀取到鏈接信息,就能完成鏈接的創建。mTCP: a Highly Scalable User-level TCP Stack for Multicore Systems
mTCP Github Repo
內核協議棧的優化方案 FastSocket
另外一種用戶態協議棧 F-stack