盱眙县住房和城乡建设局网站,南通高端网站设计,重庆网润集团有限公司,机械类 网站源码目录 文章目录目录PMD driver 通过 IGB_UIO 与 UIO 进行交互注册一个 UIO 设备PMD 的应用层实现PMD 同样支持中断处理方式PMD driver 通过 IGB_UIO 与 UIO 进行交互
IGB_UIO 内核模块的另一个主要功能就是让用于态的 PMD 网卡驱动程序得以与 UIO 进行交互。对于 PMD 的实现来说…目录 文章目录目录PMD driver 通过 IGB_UIO 与 UIO 进行交互注册一个 UIO 设备PMD 的应用层实现PMD 同样支持中断处理方式PMD driver 通过 IGB_UIO 与 UIO 进行交互
IGB_UIO 内核模块的另一个主要功能就是让用于态的 PMD 网卡驱动程序得以与 UIO 进行交互。对于 PMD 的实现来说重点是处于用户态的 PMD 驱动程序如何通过 igb_uio 内核驱动模块与 UIO 进行交互从而实现数据包处理的内核旁路。 调用 igbuio_setup_bars()设置 uio_info 的 uio_mem 和 uio_port。igb_uio 内核模块在发现了 PCI 设备的 Memory BAR 和 IO BAR 之后会将这些 resources 的信息保存到 uioX 设备的 maps 中这样处于用户态的 PMD 就可以访问这些原本只能被内核访问的 BAR 空间了。 设置 uio_info 的其他成员。 调用 uio_register_device()注册 UIO 设备。PMD 通过 uioX 设备与 igb_uio 内核驱动模块进行交互。 打开 uioX 设备应用层已经可以使用 uioX 设备了。DPDK 的应用层代码会打开 uioX 设备。在函数 pci_uio_alloc_resource() 中。打开对应的 uioX 设备时对应的内核操作为 uio_open()其又会调用 igb_uio 的 open()。 设置中断信息igb_uio 默认的中断模式为 RTE_INTR_MODE_MSIX在 igbuio_pci_enable_interrupts() 中。 注册中断。当打开 uio 设备时igb_uio 就会注册了一个中断。为什么作为轮询模式的 PMD 驱动需要注册中断呢因为即使应用层可以通过 UIO 来实现设备驱动但是设备的某些事件还是需要内核进行响应然后通知应用层的。 PMD 的中断处理已经非常简单了。其中的关键步骤是调用 uio_event_notify()将注册的 UIO 设备的 “内存空间” 映射到用户态的应用空间让 PMD 得以真正的从用户态中去访问内存。UIO 的 mmap 函数为 uio_mmap。至此UIO 就可以让 PMD 驱动程序在用户态应用层访问设备的大部分资源了。 应用层 UIO 初始化。同时DPDK 还需要把 PCI 设备的 BAR 映射到应用层。在 pci_uio_map_resource() 函数中会调用 pci_uio_map_resource_by_index() 做资源映射。 在 PMD 驱动程序中DPDK 应用程序会调用 rte_eth_rx_burst() 读取数据报文。如果网卡接收 Buffer 的描述符表示已经完成一个报文的接收e.g. 有 E1000_RXD_STAT_DD 标志则 rte_mbuf_raw_alloc() 一个 mbuf 进行处理。 对应 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 5月 8 00:18 /dev/uio0switch (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);
} 初始化 uio_device 结构体指针 idev主要包括等待队列 wait、中断事件计数 event、次设备号 minor 等。在 /dev 目录下创建了一个 uio 设备设备名为 uio%d%d 为次设备号 minor。
$ ls -l /dev/uio*
crw------- 1 root root 243, 0 5月 8 00:18 /dev/uio0接着就是调用 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 5月 8 00:19 addr
-r--r--r-- 1 root root 4096 5月 8 00:19 name
-r--r--r-- 1 root root 4096 5月 8 00:19 offset
-r--r--r-- 1 root root 4096 5月 8 00:19 size
$ ls -l /sys/class/uio/uio0/maps/map1/
总用量 0
-r--r--r-- 1 root root 4096 5月 8 00:19 addr
-r--r--r-- 1 root root 4096 5月 8 00:19 name
-r--r--r-- 1 root root 4096 5月 8 00:19 offset
-r--r--r-- 1 root root 4096 5月 8 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最后就是注册中断了中断的中断号、中断标志等在前面有讲到这里看下注册的中断处理函数 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 (retIRQ_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 进程仍具有更高的调度优先级。