前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >DPDK之KNI原理

DPDK之KNI原理

原创
作者头像
glinuxer
发布2019-04-26 08:37:28
12.2K1
发布2019-04-26 08:37:28
举报
文章被收录于专栏:专注网络研发

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

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

图1. 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
图2. kni_init
图3. kni_net_config_lo_mode
图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
图4. kni_misc

先看open函数kni_open,

图5. kni_open
图5. kni_open

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

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

图6. rte_kni_init
图6. rte_kni_init

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

图7. 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
图8. rte_kni_alloc

而在kni的内核实现中,

图9. kni_ioctl_create
图9. kni_ioctl_create

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

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

图10. kni_ioctl_create
图10. kni_ioctl_create

进入kni_run_thread,

图11. kni_run_thread
图11. kni_run_thread

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

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

图12. kni_thread_single
图12. kni_thread_single

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

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

图13. kni_net_rx_normal(1)
图13. kni_net_rx_normal(1)

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

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

图14. kni_net_rx_normal(2)
图14. kni_net_rx_normal(2)

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

图15. kni_net_rx_normal(3)
图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)
图16. kni_net_tx(1)

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

图17. kni_net_tx(2)
图17. kni_net_tx(2)

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

图18. kni_net_tx(3)
图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
图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
图20. rte_kni_tx_burst

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

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档