[转]dpdk kni 原理(比较详细的源码分析)

本文详细剖析了DPDK中的KNI接口,从内核模块初始化、设备操作到用户态API的使用,展示了零拷贝技术在提升效率中的关键作用,适合开发者理解高性能网络通信的内核层面实现。

DPDK之KNI原理 - 程序员大本营

看了好多kni的文章, 这个文章写的比较细. 不过以下这个图没有, 我这里加一下:

 

DPDK是一个优秀的收发包kit,但它本身并不提供用户态协议栈,因此由将数据报文注入内核协议栈的需求,也就是KNI(Kernel NIC Interface)。作为用户态和内核的接口,其因为没有系统调用和内存拷贝,因此比传统的tun/tap设备要更高效。

借用DPDK文档的一个KNI的结构图。

 

图1. kni结构图

毫无疑问,KNI必然要也需要内核模块的支持,即rte_kni.ko。其共有三个参数,分别是lo_mode,kthread_mode和carrier。

lo_mode可配置为lo_mode_none,lo_mode_fifo,和lo_mode_fifo_skb,默认为lo_mode_none。另外两个在实际产品中基本不会用到。

kthread_mode可配置为single和multiple,默认为single。

carrier可配置为off和on,默认为off。

模块初始化函数kni_init也非常简单。除了解析上面的参数配置外,比较重要的就是注册misc设备和配置lo_mode。

 

图2. kni_init

 

图3. kni_net_config_lo_mode

配置lo_mode,函数指针kni_net_rx_func指向不同的函数,默认是kni_net_rx_func。

通过register_pernet_subsys或者register_pernet_gen_subsys,注册了kni_net_ops,保证每个namespace都会调用kni_init_net进行初始化(初始化动作在此不介绍了)。

注册为misc设备后,其工作机制由注册的miscdevice决定,即

图4. kni_misc

 

先看open函数kni_open,

 

图5. kni_open

代码非常简单,检查保证一个namespace只能打开kni一次,打开后将kni基于namespace的私有数据赋值给打开的文件file->private_data,以便后面使用。

DPDK在初始化阶段会调用rte_kni_init,打开kni设备。

 

图6. rte_kni_init

如何使用kni设备呢?内核的kni模块,提供了ioctl的支持。

 

图7. kni_ioctl

一共两个有效的option,RTE_KNI_IOCTL_CREATE和RTE_KNI_IOCTL_RELEASE,分别对应DPDK用户态的rte_kni_alloc和rte_kni_release,即申请kni interface和释放kni interface。

在rte_kni_alloc中,关键的代码是kni_reserve_mz申请连续的物理内存,并用其作为各个ring。

 

图8. rte_kni_alloc

而在kni的内核实现中,

 

图9. kni_ioctl_create

通过phys_to_virt将ring的物理地址转成虚拟地址使用,这样就保证了KNI的用户态和内核态使用同一片物理地址,从而做到零拷贝。

然后就是注册是netdev,启动内核接收线程。

 

图10. kni_ioctl_create

进入kni_run_thread,

 

图11. kni_run_thread

如果KNI模块的参数指定了多线程模式,每创建一个kni设备,就创建一个内核线程。如果为单线程模式,则检查是否已经启动了kni_thread。没有的话,创建唯一的kni内核thread kni_single,有的话,则什么都不做。

不失一般性,可以看kni_thread_single的实现。

 

图12. kni_thread_single

在持有读锁的情况下,遍历所有的kni设备,执行接收动作。这时,根据rte_kni.ko加载时的模块参数lo_mode

的值不同,执行不同的动作。只关心实际使用的lo_mode_none模式,其处理函数为:

 

图13. kni_net_rx_normal(1)

检查释放队列是否还有空位,没有的话,意味着读取后的数据无法增加到释放队列,故直接返回。

从kni->rx_q读取数据到kni->pa中。没有任何报文,则直接返回。

 

图14. kni_net_rx_normal(2)

循环处理收到的kni数据,将数据复制到申请的skb中。

 

图15. kni_net_rx_normal(3)

设置skb相关参数,调用netif_rx_ni将skb传给内核协议栈处理。最后把读取的数据追加到释放队列中。

这是DPDK app向KNI设备写入数据,也就是发给内核的情况。当内核从KNI设备发送数据时,按照内核的流程处理,最终会调用到net_device_ops->ndo_start_xmit。对于KNI驱动来说,即kni_net_tx。

 

图16. kni_net_tx(1)

对skb报文长度做检查,不能超过mbuf的大小。然后检查发送队列tx_q是否还有空位,“内存队列”是否有剩余的mbuf。

 

图17. kni_net_tx(2)

从alloc_q取出一个内存块,将其转换为虚拟地址,然后将skb的数据复制过去,最后将其追加到发送队列tx_q中。

 

图18. kni_net_tx(3)

发送完成后,就直接释放skb并更新统计计数。

以上,是KNI在内核部分的实现,下面看看DPDK应用层如何使用KNI接口。DPDK提供了两个API rte_kni_rx_burst和rte_kni_tx_burst,用于从KNI接收报文和向KNI发送报文。

 

图19. rte_kni_rx_burst

接收报文时,从kni->tx_q直接取走所有报文。前面内核用KNI发送报文时,填充的就是这个fifo。当取走了报文后,DPDK应用层的调用kni_allocate_mbufs,负责给tx_q填充空闲mbuf,供内核使用。

rte_kni_tx_burst流程也很简单。

 

图20. rte_kni_tx_burst

先将要发送给KNI的报文地址转换为物理地址,然后enqueue到kni->rx_q中(内核的KNI实现也是从这个fifo中读取报文),最后调用kni_free_mbufs释放掉内核处理完的mbuf报文。

至此,DPDK的KNI原理分析完毕。

### 使用DPDK KNI进行网络接口接管 #### 配置DPDK环境 为了使用DPDK KNI进行网络接口接管,首先需要确保已经正确安装并配置好DPDK环境。这包括下载适合版本的DPDK源码包、编译以及设置大页内存等必要操作[^2]。 #### 编写KNI示例程序 编写简单的测试应用程序来验证KNI功能是否正常工作非常重要。通常情况下,可以从官方文档中的例子入手,在此基础上修改以满足特定需求。例如创建一个新的虚拟网口并与之关联相应的物理端口: ```c #include <rte_kni.h> // 创建KNI实例函数... struct rte_kni *kni = rte_kni_alloc(...); ``` #### 理解KNI工作机制 当涉及到具体实现细节时,理解KNI的工作机制至关重要。它实际上是在用户空间和内核之间建立了一座桥梁——即所谓的“Kernel NIC Interface”。此接口能够有效地减少数据传输过程中的复制次数,并且使得基于DPDK的应用可以直接利用Linux系统的标准命令行工具来进行管理和监控[^1]。 #### 接入内核协议栈 为了让来自外部的数据帧进入操作系统内部处理流程(比如TCP/IP堆栈),必须将捕获到的信息传递给上层软件组件。对于每一个接收到的新分组而言,如果发现其目的地正是本地主机,则应该调用`rte_kni_tx_burst()`方法将其送至对应的队列等待进一步解析;相反地,如果是准备发送出去的内容则需经过类似的路径反向流动直至最终抵达目标位置[^4]。 #### 调试与优化 在整个过程中可能会遇到各种各样的挑战,因此掌握有效的调试技巧显得尤为关键。除了依赖于传统的日志记录外,还可以借助于诸如perf之类的性能分析器找出潜在瓶颈所在之处加以改进。另外值得注意的是,由于不同硬件平台间存在差异性,所以在移植项目之前务必仔细阅读相关手册以便做出适当调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值