基于dpdk的⽤户态协议栈f-stack实现分析——lvyilong316 项⽬背景
F-Stack 这个项⽬起始于DNSPod的授权DNS项⽬,当时是12年,DPDK还未开源的时候,腾讯就基于DPDK做了授权DNS,做完的时候正好DPDK也开源了,正式上线后10GE单⽹卡性能达到1100万qps,后⾯⼜实现了⼀个简易的TCP协议栈⽤于⽀持TCP DNS。后来DNSPod合并进⼊腾讯云,腾讯云有⼤量业务需要⾼性能的接⼊服务,所以就在原来授权DNS的TCP协议栈基础上实现了较为完整的
TCP/IP协议栈(参考了mTCP,SeaStar,lwIP等),加上了协程框架,并且上层兼容了腾讯SNG的服务端框架SPP,使得腾讯云原有的业务可以⽆缝迁⼊,但是经过近⼀年的线上使⽤运⾏,腾讯发现,线上的⽹络环境各式各样,这个TCP/IP协议栈并不能满⾜全部需求(各种tunnel 等),⽽⾃⼰去实现各种协议既费⼒⼜不讨好,所以经过⼀段时间的调研,决定移植FreeBSD的协议栈。 整体介绍
这⾥不再对这个协议栈的特点和介绍展开,⽹上都有,其实就是dpdk的⼀些特点,另⼀点就是对现有posix编程接⼝⽐较友好吧,提供了线程和epoll等接⼝,⽅便应⽤的移植。这⾥主要是对f-stack的实现框架转发路径做⼀个简单分析。 工程管理系统
f-stack没有使⽤⾃⼰开发的协议栈,据说早期使⽤的是基于mTCP的⾃研协议栈,但后来因为兼容性,在公有云环境使⽤会有诸多问题。如果⽐较笼统的说,f-stack可以等价于dpdk+freebsd协议栈。F-stack⾃⼰的关键⼯作就是将两者结合起来,⼊⽅向将流量从dpdk导⼊freebsd协议栈,出⽅向由freebsd协议栈导⼊dpdk。当然freebsd协议栈也是做了少量改动的,⽐如:
1) 调度:对 FreeBSD Network Stack 的内核线程、中断线程、定时器线程、sched、sleep 等进⾏了去除。官学清
qc工程图
2) 锁:对 FreeBSD Network Stack 的锁操作进⾏了去除,包括 mtx、rw、rm、sx、cond 等。
3) 内存相关:phymem、uma_page_slab_hash、uma、kmem_malloc、malloc 等实现。
4) 全局变量:pcpu、curthread、proc0、thread0 等初始化。
5) 环境变量:setenv、getenv 实现。
6) SYS_INIT:mi_startup。
7) 时钟:timecounter、ticks、hz、定时器等实现。
8) 其他:Linux 和 FreeBSD 的 errno 转换、胶⽔代码、移除了不需要的功能模块等。 现金支票用途填写但这些改动涉及的代码⾮常少,据说只有⼏百⾏,所以⾮常⽅便将最新的freebsd代码更新过来,便于维护。这样的设计的优势就是⼀⽅⾯可以⾼效的利⽤dpdk框架的⾼性能转发,另⼀⽅⾯不⾄于陷⼊协议栈的复杂开发当中。
下⾯简单的看⼀下f-stack的代码结构:
## Structure of F-Stack code
├── app -- Nginx(1.11.10)/Redis(3.2.8)/Microthread framework (适配f-stack的Nginx和Redis)
├── config.ini (f-stack的配置⽂件)
├── doc (f-stack的⼀些说明⽂档)
├── dpdk -- Intel DPDK(16.07) directory (放置dpdk代码)
├── example – DEMO (使⽤f-stack的例⼦)
├── freebsd -- FreeBSD(11.0) Network Stack directory (freebsd协议栈代码)
├── lib -- F-Stack lib directory (f-stack⾃⾝的代码)
├── mk
└── start.sh
初始化分析
我们直接先看⼀下f-stack提供的demo的main函数,有⼀个宏观认识。
代码⾮常简单,和正常的socket编程⼀样调⽤socket,bind,listen等接⼝,可以看到f-stack其提供的ff_socket,ff_bind,ff_listen和posix接⼝是完全兼容的。其他关键的区别就是最开始的ff_init(argc, argv)调⽤,这个主意是为了f-stack框架的启动初始化,下⾯会分析,另⼀个就是ff_run(loop, NULL),熟悉dpdk的⼈应该能猜到,这就是启动主循环开始⽹卡的收发逻辑。
下⾯⼀张图看⼀下ff_init的主要初始化逻辑。
其中最关键的就是ff_dpdk_if_up,这个函数为每个dpdk接管的port创建⼀个struct ifnet结构,并将这个结构注册到freebsd中,其中调⽤的ether_ifattach就是freebsd的接⼝,这个结构可以理解为⽹卡对应的虚拟设备,这个设备也是dpdk和freebsd关联的核⼼。Ifnet有很多handle函数,会被做如下初始化:
ifp->if_start = ff_veth_start;
ifp->if_transmit = ff_veth_transmit;
ifp->if_qflush = ff_veth_qflush;烯烃复分解
ifp->if_output = ether_output;
ifp->if_input = ether_input;
……
其中核⼼是if_input,if_output和if_transmit三个函数,if_input就是ifnet的收包函数,可以理解为协议栈的⼊⼝函数,有些类似于kernel协议栈的netif_receive_skb函数;但是把if_output⽐喻成协议栈的出⼝函数就有点不恰当了,因为启动还有bridge的转发逻辑,其中(ether_output)调⽤的ifp->if_transmit可以理解为真正的协议栈出⼝函数,类似于kernel的dev_queue_xmit。
转发流程分析
转发流程以ff_run开始的main_loop开始,我们以tcp收到syn+ack及回复流程为例,分析这个转发过程(syn+ack处理不会上送app,由协议栈完成),直接上图,流程如下:
>谷氨酸受体