前言
DPDK KNI是什么
KNI全称为Kernel NIC Interface,是DPDK框架下实现的DPDK与内核的高性能通信方案。
KNI解决什么问题
主要解决物理网卡被DPDK接管后,仍然需要使用内核协议栈的问题;
此外,相对于TUN/TAP方式,减少一次拷贝,性能更高。
KNI如何使用
- 加载rte_kni.ko模块
- 整体初始化:rte_kni_init
- 创建/释放一个KNI接口:rte_kni_alloc/rte_kni_release
- 报文收发:rte_kni_rx_burst/rte_kni_tx_burst
几个问题
KNI是DPDK引入的技术,早期实现上为了高性能有不少问题,以下逐个分析(注:部分问题在最新版本中已经解决)
KNI FIFO内存一致性问题
KNI实现中,DPDK程序与内核间,一般都是两个线程在处理,通信方式是FIFO,收发方向都有单独的FIFO;
早期实现仅考虑了strongly-ordered systems,典型的x86,因为未触发乱序问题(buffer与read/write指针的乱序行为)。
后续版本中,ARM环境的引入暴露了此问题,导致mbuf被复用等问题发生。
解决方式是引入合适的memory barrier。
KNI 巨帧包(scattered packets)处理问题
之前提到KNI相对于TUN/TAP方式,减少了一次拷贝。
- TUN/TAP方式
- 第一次拷贝:DPDK程序将报文发往内核,此时write系统调用拷贝;
- 第二次拷贝:内核收到消息后,拷贝到skb中。
- KNI方式
- DPDK程序将报文发往内核,通过共享大页内存实现,避免了此次拷贝;然后将报文从大页拷出到skb中。
从上面的对比可以看出,KNI核心是共享大页内存,FIFO传递物理地址,内核收到后转换为内核地址进行处理。
但是在网络中,存在一种mbuf chain情况(典型的,mbuf本身2k,收包9k,会串成5个mbuf构成的chain),在用户态这些mbuf都是通过虚拟地址方式连结,往KNI传递时会遇到地址转换问题。
分两种情况:
- chain上所有mbuf属于同一块大页内存,此时通过首片的偏移能够推算出后续的偏移;KNI默认采用此方式。
- chain上的mbuf属于不同大页内存,此时就不能通过首片来计算。KNI内核会得到错误地址,严重时导致crash。 修复方式:提前将chain上mbuf地址转换为物理地址。 https://git.dpdk.org/dpdk/commit/?id=5eb1708ec1db1f2d644f44a42468139df0b0ad6c
KNI释放时mbuf泄露问题
KNI早期实现未考虑KNI释放时,FIFO中mbuf的释放问题,反复创建/释放KNI口的情况下可能会导致mbuf泄露。
修复方式:KNI释放时转换未处理的mbuf
https://git.dpdk.org/dpdk/commit/?id=e77fec694936ce067153c5a6596c6bc818baaa5c
后续发展
evendfd epoll workqueue方式处理报文
当前KNI内核部分,是通过启动一个内核线程轮询收包实现,为了避免占用CPU过多,又加入了sleep机制,导致KNI的CPU/时延/吞吐等都不是很理想。更合理的方式是通过eventfd机制,用户态发包后唤醒kernel处理,采用NAPI类似方式,使得CPU/时延/吞吐达到理想状态。
多队列支持
当前KNI不支持多队列,极限性能受限(single线程模式不超过1GBps,mutiple线程模式又占用过多资源)。
在实现eventfd基础上,再实现多队列机制,可以让性能更加,同时在闲时不占用额外资源。
参考连接
https://doc.dpdk.org/guides/prog_guide/kernel_nic_interface.html
http://doc.dpdk.org/api/rte__kni_8h.html