DPDK — Userspace PMD 源码分析

Posted 范桂飓

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DPDK — Userspace PMD 源码分析相关的知识,希望对你有一定的参考价值。

目录

文章目录

PMD driver 通过 IGB_UIO 与 UIO 进行交互

IGB_UIO 内核模块的另一个主要功能就是让用于态的 PMD 网卡驱动程序得以与 UIO 进行交互。对于 PMD 的实现来说,重点是处于用户态的 PMD 驱动程序如何通过 igb_uio 内核驱动模块与 UIO 进行交互,从而实现数据包处理的内核旁路。

  1. 调用 igbuio_setup_bars(),设置 uio_info 的 uio_mem 和 uio_port。igb_uio 内核模块在发现了 PCI 设备的 Memory BAR 和 IO BAR 之后会将这些 resources 的信息保存到 uioX 设备的 maps 中,这样处于用户态的 PMD 就可以访问这些原本只能被内核访问的 BAR 空间了。

  2. 设置 uio_info 的其他成员。

  3. 调用 uio_register_device(),注册 UIO 设备。PMD 通过 uioX 设备与 igb_uio 内核驱动模块进行交互。

  4. 打开 uioX 设备,应用层已经可以使用 uioX 设备了。DPDK 的应用层代码,会打开 uioX 设备。在函数 pci_uio_alloc_resource() 中。打开对应的 uioX 设备时,对应的内核操作为 uio_open(),其又会调用 igb_uio 的 open()。

  5. 设置中断信息,igb_uio 默认的中断模式为 RTE_INTR_MODE_MSIX,在 igbuio_pci_enable_interrupts() 中。

  6. 注册中断。当打开 uio 设备时,igb_uio 就会注册了一个中断。为什么作为轮询模式的 PMD 驱动需要注册中断呢?因为,即使应用层可以通过 UIO 来实现设备驱动,但是设备的某些事件还是需要内核进行响应,然后通知应用层的。

  7. PMD 的中断处理已经非常简单了。其中的关键步骤是调用 uio_event_notify(),将注册的 UIO 设备的 “内存空间” 映射到用户态的应用空间,让 PMD 得以真正的从用户态中去访问内存。UIO 的 mmap 函数为 uio_mmap。至此,UIO 就可以让 PMD 驱动程序在用户态应用层访问设备的大部分资源了。

  8. 应用层 UIO 初始化。同时,DPDK 还需要把 PCI 设备的 BAR 映射到应用层。在 pci_uio_map_resource() 函数中会调用 pci_uio_map_resource_by_index() 做资源映射。

  9. 在 PMD 驱动程序中,DPDK 应用程序,会调用 rte_eth_rx_burst() 读取数据报文。如果网卡接收 Buffer 的描述符表示已经完成一个报文的接收(e.g. 有 E1000_RXD_STAT_DD 标志),则 rte_mbuf_raw_alloc() 一个 mbuf 进行处理。

  10. 对应 RTC 模型的 DPDK 应用程序来说,就是不断的调用 rte_eth_rx_burst() 去询问网卡是否有新的报文。如果有,就取走所有的报文或达到参数 nb_pkts 的上限。然后进行报文处理,处理完毕,再次循环。

注册一个 UIO 设备

Linux 上的驱动设备一般都是运行在内核态的,提供接口函数给用户态函数调用即可。而 UIO 技术则是将驱动的大部分事情移到了用户态。之所以能够实现,正如前面所说,是因为 igb_uio 将 PCI BAR 空间的物理地址、大小等信息都记录下来并传给了用户态。

除了记录 BAR 空间资源信息,UIO 框架还会在内核态实现中断处理相关的初始化工作。如下 igbuio_pci_probe 的代码片段:

* fill uio infos */  
udev->info.name = "igb_uio"; 
udev->info.version = "0.1"; 
udev->info.handler = igbuio_pci_irqhandler; 
udev->info.irqcontrol = igbuio_pci_irqcontrol;          

注册的 uio 设备名为 igb_uio,内核态中断处理函数为 igbuio_pci_irqhandler,中断控制函数 igbuio_pci_irqcontrol。

$ ls -l /dev/uio*
crw------- 1 root root 243, 0 58 00:18 /dev/uio0
switch (igbuio_intr_mode_preferred)  
	case RTE_INTR_MODE_MSIX:  
		msix_entry.entry =0; 
		if (pci_enable_msix(dev,&msix_entry,1)==0)                                                                         
			udev->info.irq =msix_entry.vector; 
			udev->mode =RTE_INTR_MODE_MSIX; 
			break; 
		  

	case RTE_INTR_MODE_LEGACY:  
		if (pci_intx_mask_supported(dev))  
			udev->info.irq_flags =IRQF_SHARED; 
			udev->info.irq =dev->irq; 
			udev->mode =RTE_INTR_MODE_LEGACY; 
			break; 
		

变量 igbuio_intr_mode_preferred 表示中断的模式,它由 igb_uio 驱动的参数 intr_mode 决定,有 MSI-X 中断和 Legacy 中断两种模式,默认为 MSI-X 中断模式。

  • MSI-X 中断模式:调用 pci_enable_msix 函数向 PCI 子系统申请分配一个 MSI-X 中断。若分配成功就会初始化 uio_info 的 irq 为申请到的中断号。
  • 传统的 Intx 中断模式:调用 pci_intx_mask_supported 函数读取 PCI 配置空间,检查是否支持 Intx 中断。

在对 uio_info 内存和中断相关的成员初始化之后,就开始调用 uio_register_device 函数来注册 uio 设备了。

idev->owner = owner; 
idev->info = info; 

init_waitqueue_head(&idev->wait); 
atomic_set(&idev->event, 0);

idev->dev =device_create(&uio_class,parent,
						 MKDEV(uio_major, idev->minor),
						 idev, 
						 "uio%d",
						 idev->minor); 

ret =uio_dev_add_attributes(idev); 
info->uio_dev =idev; 

if (info->irq &&(info->irq !=UIO_IRQ_CUSTOM))  
	ret =devm_request_irq(idev->dev,info->irq,uio_interrupt,                                                      
	info->irq_flags,info->name,idev); 
  
  1. 初始化 uio_device 结构体指针 idev,主要包括等待队列 wait、中断事件计数 event、次设备号 minor 等。
  2. 在 /dev 目录下创建了一个 uio 设备,设备名为 uio%d,%d 为次设备号 minor。
$ ls -l /dev/uio*
crw------- 1 root root 243, 0 58 00:18 /dev/uio0
  1. 接着就是调用 uio_dev_add_attributes 函数在 /sys/class/uio/uioX/ 目录下创建 maps 和 portio 接口。前面讲到会遍历此 PCI 设备的 BAR 空间,将存储器空间类型的 BAR 的物理地址等信息存储在 uio_info 的 mem 数组中,这里就会根据此 mem 数组在 maps 目录下为每个寄存器类型的 BAR 创建一个目录。
$ ls -l /sys/class/uio/uio0/maps/map0/
总用量 0
-r--r--r-- 1 root root 4096 58 00:19 addr
-r--r--r-- 1 root root 4096 58 00:19 name
-r--r--r-- 1 root root 4096 58 00:19 offset
-r--r--r-- 1 root root 4096 58 00:19 size
$ ls -l /sys/class/uio/uio0/maps/map1/
总用量 0
-r--r--r-- 1 root root 4096 58 00:19 addr
-r--r--r-- 1 root root 4096 58 00:19 name
-r--r--r-- 1 root root 4096 58 00:19 offset
-r--r--r-- 1 root root 4096 58 00:19 size

可以看出,igb_uio 网卡有两个类型为 IORESOURCE_MEM 的 BAR,分别为 BAR1 和 BAR4,这里就创建了 map0 和 map1 两个子目录分别对应 BAR1 和 BAR1。

$ cat /sys/class/uio/uio0/maps/map1/name
BAR4
$ cat /sys/class/uio/uio0/maps/map1/addr
0x0000000440000000
  1. 最后就是注册中断了,中断的中断号、中断标志等在前面有讲到,这里看下注册的中断处理函数 uio_interrupt。
static irqreturn_t uio_interrupt(intirq,void *dev_id) 
 
    struct uio_device *idev =(struct uio_device *)dev_id;                                                           
    irqreturn_t ret =idev->info->handler(irq,idev->info); 
    
    if (ret==IRQ_HANDLED) 
        uio_event_notify(idev->info); 
    return ret; 
 

此函数首先调用 igb_uio 驱动中设置的中断处理函数 igbuio_pci_irqhandler 来检查中断是不是此设备的中断,如果是就返回 IRQ_HANDLED 表示需要处理,接着调用函数 uio_event_notify 来唤醒等待队列 wait 上进程来处理中断事宜。

PMD 的应用层实现

当 DPDK Application 启动时,会首先进行 EAL 初始化,如下图:

在 pci_uio_alloc_resource 中,主要是打开 DPDK Application 要管理的 uioX 设备。

同时,DPDK App 还需要把 PCI 设备的 BAR 映射到应用层。在 pci_uio_map_resource() 中,除了调用上图中的 pci_uio_alloc_resource,还会调用 pci_uio_map_resource_by_index 做资源映射。

下面就是 PMD 在应用层的驱动实现了。以最简单的 e1000 驱动为例,其初始化函数 eth_igb_dev_init 如下。

上面我们提到了,当 uioX 设备有事件触发时,由 eth_igb_interrupt_handler() 负责处理,实现了用户态的中断处理。


eth_igb_interrupt_handler 的实现非常简单,只是处理设备的状态变化事件,如:Link Status。

接下来,就是最重要的了,PMD 如何读取网卡数据。DPDK App 会调用 rte_eth_rx_burst 读取数据报文。

在这个函数中,会调用驱动 dev->rx_pkt_burst 来做实际的操作。以 e1000 为例,即 eth_igb_recv_pkts。

这里的实现很简单。如果网卡接收 buffer descriptor 表示已经完成一个报文的接收,有 E1000_RXD_STAT_DD 标志,则 rte_mbuf_raw_alloc 一个 mbuf,进行处理。如果没有报文,直接跳出循环。

对应 RTC 模型的 DPDK App 来说,就是不断的调用 rte_eth_rx_burst 去 “询问” 网卡是否有新的报文。如果有,就取走所有的报文或达到参数 nb_pkts 的上限。然后进行报文处理,处理完毕,再次循环。

PMD 同样支持中断处理方式

值得注意的是,因为 PMD 理论上始终在轮训,所以运行在 PMD 的 Core 会处于用户态 CPU 100% 的状态,如下图:

但由于,网络空闲时 CPU 会长期处于空转状态,带来了电力能耗的问题。所以,DPDK 引入了 Interrupt DPDK(中断 DPDK)模式。

Interrupt DPDK 的原理和 NAPI 很像,就是 PMD 在没数据包需要处理时自动进入睡眠,改为中断通知,接收到收包中断信号后,激活主动轮询。这就是所谓的链路状态中断通知。并且 Interrupt DPDK 还可以和其他进程共享一个 CPU Core,但 DPDK 进程仍具有更高的调度优先级。

以上是关于DPDK — Userspace PMD 源码分析的主要内容,如果未能解决你的问题,请参考以下文章

2020-01-14 转载【dpdk】使用libpcap-PMD驱动收发包

How-to: Build VPP FD.IO with Mellanox DPDK PMD on top CentOS 7.7 with inbox drivers.

pmd 使用笔记

dpdk源码分析:cmdline中的命令添加

dpdk源码分析:交互式命令行的实现 命令添加

dpdk源码分析:交互式命令行的实现 初始化与退出