DPDK之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原理分析完毕。

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

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android 技术栈

好用的Chrome插件

用来清除网页上乱七八糟的广告,比如网站的竞价广告的显示,使自己想要的结果显示在前面。

9420
来自专栏KEN DO EVERTHING

「 从0到1学习微服务SpringCloud 」06 统一配置中心Spring Cloud Config

「 从0到1学习微服务SpringCloud 」01 一起来学呀! 「 从0到1学习微服务SpringCloud 」02 Eureka服务注册与发现 「 从0到...

20920
来自专栏那些年我们学过的前端

miniRedux实现与源码解读

10620
来自专栏维基链技术专区

晓说区块链 | 区块链未提供遍历查询功能,是出于怎样的设计理念?

区块链是一种分布式账本,它的本质也是一种数据库,但为什么绝大部分的区块链核心代码都没有提供遍历和过滤的查询功能呢?这里面涉及到一种什么样的设计理念?

16800
来自专栏微信公众号【程序员黄小斜】

改了 3 年的技术简历,终于能让面试官看顺眼了

  怎样制作一份优秀的技术简历。相信很多同学都对此有疑问,实际上,制作简历这件事,说难也不难,说简单也不简单。

12850
来自专栏LieBrother

依赖倒置原则

1. High level modules should not depend upon low level modules.Both should depen...

9810
来自专栏那些年我们学过的前端

hexo搭建个人博客

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,可以在非浏览器环境下,解释运行 JS 代码。

36810
来自专栏finleyMa

Jenkins Free Style 结合 Github 实现持续集成

Github 提交代码 -> 触发WebHook -> 触发Jenkins 执行 build

9720
来自专栏编程微刊

2019最受欢迎的前端7个UI框架

官网:https://mint-ui.github.io/#!/zh-cn Github: https://github.com/ElemeFE/mint-u...

1.5K50
来自专栏木子昭的博客

[开源工具]SwitchHosts!更方便的切换hosts

SwitchHosts开源地址: https://github.com/oldj/SwitchHosts

30120

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励