原创

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 条评论
登录 后参与评论

相关文章

  • 系统编程语言Rust特点介绍(1)

    最近在学习Rust语言,感觉这门语言有点意思,因此写一篇文章分享。我不会去介绍Rust的基本语法,什么变量声明,if..else..,循环等等。这些东西,文档介...

    glinuxer
  • 系统编程语言Rust特点介绍(2)—— 所有权系统

    很抱歉,第2篇距离第1篇长达3个月。。。工作繁忙加上家里事多。。。不找客观原因了,咱们开始聊聊Rust的所有权系统。

    glinuxer
  • 以讹传讹的“tcp_tw_reuse”

    因为Linux是一个通用的操作系统,所以其运行环境也是为了通用环境设计的,不会太好,也不会太坏,因为其要默认兼容大部分环境。因此在做服务端部署的...

    glinuxer
  • IntelliJ IDEA 2020.1 Crack 插件永久激活

    进入目录C:\Windows\System32\drivers\etc,修改hosts文件,增加下面一行

    架构探险之道
  • 机器学习之线性回归

    文中的所有数据集链接:https://pan.baidu.com/s/1TV4RQseo6bVd9xKJdmsNFw

    润森
  • Android-Universal-Image-Loader源码分析

    ImageLoader 是 android 使用中出现比较早(PS:即的刚接触安卓项目的时候就用的是这个图片加载图,算算已经快5年了),使用最多的一个开源图片加...

    静默加载
  • Python识别图片中的文字

    不知道大家有没有遇到过这样的问题,就是在某个软件或者某个网页里面有一篇文章,你非常喜欢,但是不能复制。或者像百度文档一样,只能复制一部分,这个时候我们就会选择截...

    ZackSock
  • Python 字符串大小写转换

    capitalize() 函数补充 需要注意的是: 1、首字符会转换成大写,其余字符会转换成小写。 2、首字符如果是非字母,首字母不会转换成大写,会...

    周小董
  • 06. OCR学习路径之CRNN文本识别

    在了解了如何检测到文本之后,我们需要识别出检测文本内的文字信息。在文本识别完成之后,整个OCR光学字符识别的过程才算基本完成。那么,本次课程主要讲述识别文本的算...

    Aalto
  • 思考力|《金字塔原理》

    正如封面所写,这本书是麦肯锡40年经典培训教材,不仅如此,它甚至是整个咨询业的标准。 来帮助我们提高清晰思考问题,表达观点的能力, 成为写作和表达方面的能手。 ...

    杨熹

扫码关注云+社区

领取腾讯云代金券