一直以来,网络协议栈都和内核密切相关,内核作为操作系统的控制者,也是负责网络资源分配的最佳人选。随着Linux系统的不断壮大,内核协议栈的功能性能和稳定性都得到了高度认可。
现在互联网业务蒸蒸日上,本该是性能瓶颈的网络传输,被聪明的开发者们通过集群、分布式等方式不断优化,将网络业务的力量不断提高。但随之而来,Linux内核作为一个调度者,不适宜对外提供业务服务,也不适合占用系统资源,无论多么巧妙的开发技巧,都存在面对单点性能瓶颈的问题。
而用户态协议栈,可以让Linux内核更专注系统的控制调度;将复杂的协议处理放到用户态,使用更多的系统资源,提供给开发者更自由的环境,做更多更酷的的事。
自己之前做过mTCP在x86和ARM(需要对mTCP的IO部分做一些修改才能在ARM下编译运行)下的测试工作,主要是测试吞吐和尾延迟,去年还做过一段时间将Web应用向mTCP移植的工作。总之,对mTCP的基本工作有一定的了解,但是没有深度调研mTCP的工作流程。近期由于工作需要,准备阅读mTCP的源码,梳理其代码结构和mTCP应用的工作流程。
这里以mTCP示例程序epserver为例简要分析,如图所示
mTCP 应用程序代码结构
如图,是mTCP epserver中的函数调用流程
部分mTCP API
int mtcp_init(const char *config_file)
只在application的main函数起来时初始化一次
1 2 3
| CreateAddressPool() SetRoutingTable() LoadARPTable()
|
mctx_t mtcp_create_context(int cpu)
在每个核上都会被调用
1 2 3
| 初始化本核协议栈的上下文,包括log; MTCPRunThread(void *arg)对[struct mtcp_manager *mtcp]进行初始化; RunMainLoop(struct mtcp_thread_context *ctx)
|
socketAPI
int mtcp_socket(mctx_t mctx, int domain, int type, int protocol)
创建 socket
1 2 3
| 从每个核上的 mtcp_manager_t mtcp 的 free_smap 链表中取下一个 socket 并完成初始化; socket类型有6种:MTCP_SOCK_UNUSED, MTCP_SOCK_STREAM, MTCP_SOCK_PROXY, MTCP_SOCK_LISTENER, MTCP_SOCK_EPOLL,MTCP_SOCK_PIPE AllocateSocket(mctx, type, FALSE)
|
int mtcp_listen(mctx_t mctx, int sockid, int backlog)
1 2 3 4 5
| 把socket类型设置为MTCP_SOCK_LISTENER; ListenerHTInsert() 创建struct tcp_listener对象,并初始化; 创建accept queue 把listener放入mtcp->listeners队列(说是hashtable结果是链表....)
|
int mtcp_accept(mctx_t mctx, int sockid, struct sockaddr addr, socklen_t addrlen)
从socket的listener对象的accept queue里取连接请求
1 2 3
| 如果没有请求且socket->opt中没有nonblock选项则pthread_cond_wait()等待; 创建一个类型为MTCP_SOCK_STREAM的socket并初始化; 如果还有连接请求未处理且epoll为电平触发,则生成一个MTCP_EPOLLIN事件放入USR_SHADOW_EVENT_QUEUE
|
ssize_t mtcp_recv(mctx_t mctx, int sockid, char *buf, size_t len, int flags)
1 2 3
| 检查stream状态机状态, 如果socket类型是MTCP_SOCK_PIPE,直接调用PipeRead()并返回 CopyToUser(mtcp, cur_stream, buf, len) 如果stream缓冲区还有未读数据且epoll为电平触发,产生一个MTCP_EPOLLIN事件放入USR_SHADOW_EVENT_QUEUE队列
|
ssize_t mtcp_write(mctx_t mctx, int sockid, const char *buf, size_t len)
1 2 3 4 5
| 检查stream状态机状态; 如果socket类型是MTCP_SOCK_PIPE,直接调用PipeWrite()并返回; CopyFromUser(mtcp, cur_stream, buf, len) 将stream放入发送队列,此过程需要上锁; 如果发送窗口有剩余,且epoll为电平触发,生成MTCP_EPOLLOUT事件放入USR_SHADOW_EVENT_QUEUE
|
协议栈触发API
static void RunMainLoop(struct mtcp_thread_context *ctx)
1 2 3 4 5 6 7 8
| 每个device从DPDK收包,并ProcessPacket()进行处理;
CheckRtmTimeout(); CheckTimewaitExpire(); CheckConnectionTimeout(); FlushEpollEvents(mtcp, ts) HandleApplicationCalls(mtcp, ts) 如果设置了ctx->interrupt,调用InterruptApplication(mtcp);
|
int ProcessTCPPacket(mtcp_manager_t mtcp, uint32_t cur_ts, const int ifidx, const struct iphdr *iph, int ip_len)
1 2 3 4
| ValidateSequence(mtcp, cur_stream, cur_ts, tcph, seq, ack_seq, payloadlen) 一些异常情况需要加入处理队列,回复ACK; 处理rst标志位; 根据cur_stream->state和TCP状态机进行处理;
|
static inline void FlushEpollEvents(mtcp_manager_t mtcp, uint32_t cur_ts)
1 2 3
| 此函数全程对该核上的ep->epoll_lock上锁 如果ep->mtcp_queue有内容且ep->usr_queue有剩余空间,把mtcpq的事件拷贝到usrq中 如果epoll处于waiting状态且usrq或shadowq中有事件,发出epoll_cond信号唤醒应用线程
|
static inline void HandleApplicationCalls(mtcp_manager_t mtcp, uint32_t cur_ts)
1 2 3 4 5 6
| 把mtcp->connectq上所有stream取下来并AddtoControlList(mtcp, stream, cur_ts),这些流在WriteTCPControlList()中被处理,发送control packet 把mtcp->sendq上所有stream取下来并AddtoSendList(mtcp, stream),这些流在WriteTCPDataList()中被处理 把mtcp->ackq上所有stream取下来并EnqueueACK(mtcp, stream, cur_ts, ACK_OPT_AGGREGATE),这些流在WriteTCPACKList()中被处理 处理mtcp->closeq上的stream 处理mtcp->resetq上的stream 处理mtcp->destroyq上的stream
|
static inline void WritePacketsToChunks(mtcp_manager_t mtcp, uint32_t cur_ts)
1 2 3 4
| WriteTCPControlList(); WriteTCPACKList(); WriteTCPDataList();
|
static void InterruptApplication(mtcp_manager_t mtcp)
1 2
| 如果epoll处于waiting状态,发送mtcp->ep->epoll_cond信号 检查每个端口的listener,发送pthread_cond_signal(&listener->accept_cond)信号
|
epoll API
int mtcp_epoll_create(mctx_t mctx, int size)
mtcp_epoll_ctl()