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