PMD是Poll Mode Driver的缩写,即基于用户态的轮询机制的驱动。本文将介绍PMD的基本原理。
在不考虑vfio的情况下,PMD的结构图如下:
图1. PMD结构图
虽然PMD是在用户态实现设备驱动,但还是依赖于内核提供的策略。其中uio模块,是内核提供的用户态驱动框架,而igb_uio是DPDK kit中拥有与uio交互,bind指定网卡的内核模块。
当使用DPDK脚本dpdk-devbind来bind网卡时,会通过sysfs与内核交互,让内核使用指定驱动来匹配网卡。具体的行为向/sys/bus/pci/devices/(pci id)/driver_override写入指定驱动名称,或者向/sys/bus/pci/drivers/igb_uio(驱动名称)/new_id写入要绑定网卡的PCI ID。前者是配置设备,让其选择驱动。后者是是配置驱动,让其支持新的PCI设备。按照内核的文档https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-pci,这两个动作都会促使驱动bind新设备。但是在dpdk-devbind脚本中,还是通过向/sys/bus/pci/drivers/igb_uio(驱动名称)/bind写入pci id来保证bind。—— 也许是处于兼容性考虑吧。
当使用igb_uio bind指定设备后,内核会调用igb_uio注册的struct pci_driver的probe函数,即igbuio_pci_probe。在这个函数中,设置PCI的一些操作(如设置PCI BAR、DMA等),不是重点,那是驱动工程师的职责:) 对于PMD来说,重点是与UIO的交互。
1. 调用igbuio_setup_bars,设置uio_info的uio_mem和uio_port。
图2. igbuio_setup_bars
2. 设置uio_info的其他成员
图3. igb_uio设置uio_info
3. 调用uio_register_device,注册uio设备。
查看/dev目录,可以发现对应的uioX设备,如下图:
图4. uio设备
打开uio设备
这时,应用层已经可以使用uio设备了。DPDK的应用层代码,会打开uioX设备。在函数pci_uio_alloc_resource中,
图5. 打开uio设备
当open对应的uio设备时,对应的内核操作为uio_open,其又会调用igb_uio的open函数,流程图如下:
图6. open uio设备
igb_uio的默认中断模式为RTE_INTR_MODE_MSIX,在igbuio_pci_enable_interrupts的关键代码如下:
图7. 设置中断信息
图8. 注册中断
当打开uio设备时,igb_uio注册了一个中断。这时大家应该有个疑问,PMD不是用户态轮询设备吗?为什么还要申请中断,注册中断处理函数呢?这是因为,即使应用层可以通过uio来实现设备驱动,但是设备的某些事件还是需要内核进行响应,然后通知应用层。当然,现在的中断处理已经非常简单了。
图9. igb_uio中断处理函数
其中的关键步骤是调用uio_event_notify。
图10. uio_event_notify
这个函数很简单:1. 增加uio设备的“事件”数; 2. 唤醒在idev->wait等待队列中的task;3. 使用信号异步通知async_queue队列中的进程;目前DPDK没有使用异步IO的方式,所有对于DPDK的PMD来说,只有前两个语句有用。
uio模块除了实现了上面的“事件”通知,还支持了mmap方法,用于将注册的uio设备的“内存空间”映射到应用空间。其mmap的函数为uio_mmap,关键代码如下:
图11.uio_mmap
至此,uio已经可以让PMD的应用层访问设备的大部分资源了。接下来,要转过去看看PMD的应用层。
当DPDK的app启动时,会进行EAL初始化,如下图:
图12. 应用层uio初始化
在pci_uio_alloc_resource中,主要是打开dpdk要管理的uio设备
图13. 打开ui设备
同时,DPDK还需要把PCI设备的BAR映射到应用层。在pci_uio_map_resource函数中,除了调用上图中的pci_uio_alloc_resource,还会调用pci_uio_map_resource_by_index做资源映射。
图14. pci_uio_map_resource
下面就是PMD的应用层的驱动实现了。以最简单的e1000驱动为例,在其初始化函数eth_igb_dev_init中,
图15. 注册e1000的中断处理函数
从图11和图12的代码中,可以看出当uio设备有事件时,由eth_igb_interrupt_handler负责处理,实现了用户态的中断处理。
图16. eth_igb_interrupt_handler
eth_igb_interrupt_handler非常简单,只是处理设备的状态变化事件,如link status。
接下来,就是最重要的了,PMD如何读取网卡数据。DPDK的应用代码,会调用rte_eth_rx_burst读取数据报文。
图17.rte_eth_rx_burst
在这个函数中,会调用驱动dev->rx_pkt_burst来做实际的操作。以e1000为例,即eth_igb_recv_pkts。
图18 eth_igb_recv_pkts
图19 eth_igb_recv_pkts
这里的实现很简单。如果网卡接收buffer的描述符表示已经完成一个报文的接收(有E1000_RXD_STAT_DD标志),则rte_mbuf_raw_alloc一个mbuf,进行处理。如果没有报文,直接跳出循环。
对应RTC模型的DPDK应用来说,就是不断的调用rte_eth_rx_burst去“问”网卡是否有新的报文。如果有,就取走所有的报文或达到参数nb_pkts的上限。然后进行报文处理,处理完毕,再次循环。
以上就是PMD的大体流程。
原文链接;https://cloud.tencent.com/developer/article/1411982
(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂
更多DPDK相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加qun免费
领取,关注我持续更新哦! !