DPDK — Userspace PMD 源码分析

article/2025/9/30 22:02:13

目录

文章目录

  • 目录
  • PMD driver 通过 IGB_UIO 与 UIO 进行交互
  • 注册一个 UIO 设备
  • PMD 的应用层实现
  • Interrupt DPDK(中断模式)

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 的上限。然后进行报文处理,处理完毕,再次循环。

Interrupt DPDK(中断模式)

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

在这里插入图片描述

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

Interrupt DPDK 的原理和 NAPI 很像,就是 PMD 在没数据包需要处理时自动进入睡眠,改为中断通知,接收到收包中断信号后,再激活主动轮询。其中的通知就是上述的 NIC 数据链路状态改变通知。

并且 Interrupt DPDK 还可以在其他 User Process 共享一个 CPU Core 的同时,仍高尺着更高的调度优先级。

在这里插入图片描述


http://chatgpt.dhexx.cn/article/qEdtbbH4.shtml

相关文章

java pmd 安装_PMD-Java代码静态分析工具使用

如今,使用代码分析工具来代替人工进行代码审查,已经是大势所趋了。用于Java代码检测的工具中,不乏许许多多的佼佼者,其中PMD就是其中一款。PMD既可以独立运行,也可以以命令行的形式运行,还可以作为插件在ID…

java pmd 安装_PMD的安装及使用

PMD是一种开源分析Java代码错误的工具。 与其他分析工具不同的是,PMD通过静态分析获知代码错误。也就是说,在不运行Java程序的情况下报告错误。PMD附带了许多可以直接使用的规则,利用这些规则可以找出Java源程序的许多问题。此外,…

java pmd 安装_4. PMD 使用,编译和自定义规则

一 PMD简介 PMD是一款代码静态检查工具,可以检查出很多代码中潜在的bug以及让人感到疑惑的代码,具体大家可以百度下。 二 PMD源代码下载 下载地址: 需要注意的是注意选择branch,一般选择最新的branch;然后可以用git clone下来,或者直接下载zip压缩包。 如下: 从上图也可…

java pmd 插件下载_pmd eclipse插件

PMD是Eclipse上的一款源代码分析插件,与其他分析工具不同的是,PMD通过静态分析获知代码错误。也就是说,它可以在不运行Java程序的情况下报告错误。pmd 支持Java、JavaScript、XML、XSL等,欢迎下载! Eclipse PMD插件更新…

java pmd checkstyle_提高代码质量 CheckStyle FindBugs PMD

注:这是一篇翻译文章,原文:How to improve quality and syntax of your Android code,为了理解连贯,翻译过程中我修改了一些陈述逻辑和顺序,同时也加了一些自己的补充。 在这片文章中,我将从工具…

【C语言】 C语言图形编程 俄罗斯方块 课程设计

程序简介 由纯C语言实现的俄罗斯方块小程序,代码长度700行,使用Turbo C图形库美化了界面,并具俄罗斯方块游戏的所有基本功能。玩家可以通过键盘的“上下左右”键移动“方块”,“空格”去翻转方块,每当方块可以将一行“…

简单c语言图形程序设计,c语言实现一些简单图形的打印

1 #define _CRT_SECURE_NO_WARNINGS 1 因为笔者采用的是VS的编译环境所以有了上面的这一句话 我们都知道平面图形是由一条条线段构成,所以我们就先实现线段的打印 1 //打印自定义长度的线段 2 #include 3 intmain() 4 {5 int i = 0; 6 intn; 7 while (~scanf("%d",&…

C语言图形化编程 【二】

C语言图形编程 二 3 基本贴图3.1 声明一个存储图片的变量3.2 图片的路径3.3 显示图片3.4 透明贴图 4 鼠标操作4.1 声明一个存储鼠标信息的变量4.2 获取鼠标4.3 分类讨论鼠标消息的来源 3 基本贴图 3.1 声明一个存储图片的变量 格式: IMAGE img; //类型 变量名(你要贴的图片的…

基于C语言的图形化编程软件,图形化编程工具

原标题:图形化编程工具 1.产品介绍 唯众图形化编程工具是一款基于拖拽式图形化设计的可视化编程工具库,通过拖拽式图形化编程完成程序设计。作为一种易于掌握的图像化编程环境,是编程初学者学习和掌握程序设计方法的有力工具。用图形化编程方式去理解程序语言,可以让学生更…

C语言:编程打印图形

题目 编程打印以下图形: 代码 #include "stdio.h"void main() {char ch = A;int i,j;for(i=

c语言图形时钟编程,c语言程序+图形编程——打造简易的时钟

全程代码小编就在这里发出来了哈,最终的效果图, // c语言+图形编程 电脑时钟 //包含头文件 #include #include #include void Draw_Dial();//绘制静态的表盘 void Draw_Hand(int hour, int minute, int secend); //绘制表针 //主函数 int main() {initgraph

C语言图形编程--俄罗斯方块制作(一)详解

效果图 用C语言实现俄罗斯方块&#xff0c;需要先解决下面几个问题&#xff1a; 1、如何用C语言绘制图形界面 EasyX图形库(http://www.easyx.cn)即TC的图形库在VC下的移植。 包含库#include <graphics.h> 先初始化图形窗口 initgraph(WINDOW_WIDTH, WINDOW_HIGH) ;WINDOW…

C语言图形代码:三角、金字塔、圣诞树、爱心

在C语言的学习过程中&#xff0c;我们熟练掌握循环以后就可以使用循环编写&#xff0c;我们喜欢的图形代码啦&#xff01; 下面我向大家分别展示四种使用C语言循环编写的代码。 后两种都可以用来给自己的女朋友展示喔 1.三角形 2.金字塔 3.圣诞树 4.爱心 &#xff08;1&#…

linux下c语言图形界面实现,「分享」C语言如何编写图形界面

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 贴吧内经常有人问C语言是不是只能用于字符终端界面开发,不能用于图形界面。大家也都有回答,需要其他的库。MFC,GTK,QT。 本人近期刚用GTK库加上纯C写成了第一个LINUX实用程序。现在与大家分享: 主界面程序gmaxtrix.c #includ…

c语言图形时钟编程,c语言+图形编程——打造浅易的时钟

全程代码小编就在这里发出来了哈 // c语言图形编程 电脑时钟 //包罗头文件 #include #include #include #define PI 3.141592654 void Draw_Dial();//绘制静态的表盘 void Draw_Hand(int hour, int minute, int secend); //绘制表针 //主函数 int main() { initgraph(640, 4…

简单c语言图形程序设计,c语言简单图形编程

c语言编程如何实现图形化? 如果你是说编写用户界面的C语言编程,那就应该学习平台编程.主流平台有windows,linux等. 楼主是领会到C得要点了.C不像JAVA和C#,它们才叫编写应用程序,因为它们的库直接可以编写出程序的应用部分(比如用户界面,调用系统资源等). 但是C语言不能叫做 c语…

C语言图形编程|设置位置

一、对光标位置进行修改 1、通过自定义函数来实现将光标移动到指定位置 void gotoxy(int x,int y) {HANDLE hCon;hCon GetStdHandle(STD_OUTPUT_HANDLE);COORD Pos;Pos.X x;Pos.Y y;SetConsoleCursorPosition(hCon,Pos); } 代码优化: void gotoxy(int x,int y) {COORD P…

C语言图形化编程

画折线图 #include<stdio.h> #include<conio.h> #include<graphics.h> #include<stdlib.h>void hDrawk(char c[]); void vDrawk(char c[]); void Drawfline(int b[]); int xs48,ys50;void hDrawk(char c[]) {int i,j65;line(65,360,565,360);for(i0;i&…

C语言图形化编程 【一】

C语言图形化编程 一 一、绘图窗口1.1 加载头文件1.2 创建一个窗口1.3 关闭窗口1.4 窗口坐标1.5 颜色1.6 设置颜色1.6.1 背景颜色1.6.2 线颜色1.6.3 文字颜色1.6.4 填充颜色 二、基本的绘图2.1 画点函数2.2 画线函数2.3 画矩形函数非填充(空心&#xff09;填充(实心)有边界线无边…

数据结构——图的五种种类【无向图-有向图-简单图-完全无向图-有向完全图】

目录&#xff1a; 一&#xff1a;无向图 1.定义 2.图形化解释 3.结合​表达式介绍 二&#xff1a;有向图 1.定义 2.图形化解释 3.结合​表达式介绍 有向图和无向图区别&#xff1a; 三&#xff1a;简单图 1.定义 2.图形化解释 四&#xff1a;完全无向图 1.定义 …