DPDK — Userspace PMD 源码分析

news2024/9/28 1:24:47

目录

文章目录

  • 目录
  • PMD driver 通过 IGB_UIO 与 UIO 进行交互
  • 注册一个 UIO 设备
  • PMD 的应用层实现
  • 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 进程仍具有更高的调度优先级。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/354193.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

松下PLC通过fpwin上传写入MRTC模块方法

目录 PLC程序上传方法 加密模块使用 PLC程序上传方法 手动将PLC模式设置为prog模式查看PLC是否设置为禁止上传查询指示灯是否变蓝,变蓝则需要将PLC禁止上传功能取消。 3.当上述动作操作完成后,将PLC程序导入到PLC中。为了配合加密程序使用,…

进程通信方式

无名管道( pipe ): 管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。高级管道(popen): 将另一个程序当做一个新的进程在当前程序进…

主成分分析(PCA)原理详解

1. 相关背景 在许多领域的研究与应用中,通常需要对含有多个变量的数据进行观测,收集大量数据后进行分析寻找规律。多变量大数据集无疑会为研究和应用提供丰富的信息,但是也在一定程度上增加了数据采集的工作量。更重要的是在很多情形下&…

Windows server——部署web服务

作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​​ 目录 前言 本章重点 一.web讲解 1.WWW概述 (1)WWW服务概述 &…

Linux应用编程下连接本地数据库进行增删改查系列操作

文章目录前言一、常用SQL操作语句二、相关函数解析三、连接本地数据库四、编译运行五、程序源码前言 本篇为C语言应用编程下连接Linux本地数据库进行增删改查系列操作。 在此之前,首先当然是你需要具备一定的数据库基础,所以下面我先列出部分常用的SQL…

如何利用文件上传漏洞-以及如何修复它们

目录什么是文件上传漏洞?文件上传请求如何工作为什么文件上传漏洞是个问题?漏洞 #1:通过文件内容远程代码执行(Web Shell 上传)绕过黑名单保护案例 1:案例 2:案例 3:案例 4&#xff…

【Spark分布式内存计算框架——Spark SQL】10. External DataSource(上)数据源与格式

第六章 External DataSource 在SparkSQL模块,提供一套完成API接口,用于方便读写外部数据源的的数据(从Spark 1.4版本提供),框架本身内置外部数据源: 在Spark 2.4版本中添加支持Image Source(图…

音频信号处理笔记(一)

相关课程:【音频信号处理及深度学习教程】 文章目录01 信号的时域分析1.1 分帧1.1.1 幅值包络1.1.2 均方根能量0 信号的叠加:https://teropa.info/harmonics-explorer/ 一个复杂信号分解成若干简单信号分量之和。不同个频率信号的叠加: 由于和差化积&a…

【Mysql基础 —— SQL语句(一)】

文章目录概述使用启动/停止mysql服务连接mysql客户端数据模型SQLSQL语句分类DDL数据库操作表操作查询创建数据类型修改删除DML添加数据修改数据删除数据DQL基础查询条件查询聚合函数分组查询排序查询分页查询执行顺序DCL管理用户权限控制概述 数据库(Database&#…

如何评价一个新技术——以 ChatGPT 为例

开源社KAIYUANSHE近期微信公众号订阅功能做调整啦!没有被星标的账号在信息流里可能不显示大图了!快星标⭐我们,就可以及时看到发布的文章啦!STEP01 点击右上角标志STEP02 点击【设为星标】缘起2015 年的时候,我写过一篇…

DaVinci 偏好设置:用户 - UI 设置

偏好设置 - 用户/ UI 设置Preferences - User/ UI Settings工作区选项Workspace Options语言Language指定 DaVinci Resolve 软件界面所使用的语言。目前支持英语、简体中文、日语、西班牙语、葡萄牙语、法语、俄语、泰语和越南语等等。启动时重新加载上一个工作项目Reload last…

Python 连接数据源与邮件功能(九)

文章目录一、概述二、Python 连接数据源1)Python MySQL 基础操作1、部署MySQL2、MySQL Connector 库【1】安装 mysql-connector-python 库【2】连接 MySQL【3】增加数据【4】查询数据【5】更新数据【6】删除数据2、PyMySQL 库【1】安装 PyMySQL 库【2】连接 MySQL【…

2023年数学建模美赛D题(Prioritizing the UN Sustainability Goals):SDGs 优先事项的选择

正在写,不断更新,别着急。。。 4. SDGs 优先事项的选择 4.1 基于SDG密度分布图选择优先事项 虽然每个可持续发展目标的接近度矩阵和中心性度量的结果是通用的,并创建了基本的可持续发展目标网络,但由于各国在网络的不同部分取得…

前端网格布局grid

网格布局 <style> .container {border:none;display: grid;height: 600px;grid-template-columns: 200px 1fr; /*两列&#xff0c;第一列200px&#xff0c;第二列自适应*/grid-template-rows: 50px 1fr 30px; /*三行&#xff1a;第一行&#xff1a;50px,第二行&#…

【C语言每日一题】——猜名次

【C语言每日一题】——猜名次&#x1f60e;前言&#x1f64c;猜名次&#x1f64c;解题思路分享&#xff1a;&#x1f60d;解题源码分享&#xff1a;&#x1f60d;总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭&#xff1a;全神…

大数据之-Nifi-应用场景2-2_设置putfile处理器自动创建目标文件夹_以及存在重复文件时自动覆盖---大数据之Nifi工作笔记0006

上一节我们留了两个问题,一个是,如果我们没有创建putfile要写入的目标文件夹,会报错吗? 可以看到我们putfile目标文件夹是上面这个目录 我们来试一试,如果目标文件夹不存在,putfile处理器会自动创建吗 首先我们删除这个target目标文件夹 然后我们进入cd source目录,源文件夹目…

echart在微信小程序的使用

echart在微信小程序的使用 echarts不显示在微信小程序 <!-- 微信小程序的echart的使用 --> <view class"container"><ec-canvas id"mychart-dom-bar" canvas-id"mychart-bar" ec"{{ ec }}"></ec-canvas> &l…

SVNH数据(.mat格式)转为图像(.png)matlab代码

一、获取SVNH数据数据集集地址-http://ufldl.stanford.edu/housenumbers/提供两种格式的数据&#xff1a;1.Format 1&#xff0c;图像形式&#xff0c;压缩包2.Format 2&#xff0c; .mat格式的数据10 classes, 1 for each digit. Digit 1 has label 1, 9 has label 9 and 0 ha…

Git在日常使用过程中的常用命令

文章目录Git在日常使用过程中的常用命令写在前面1. 创建本地仓库2. 将本地文件添加到暂存区3. 将本地文件从暂存区撤销4. 将修改提交到本地仓库5. clone远程版本库到本地。6. 为本地版本库添加一个远程仓库7. 推送到远程仓库8. 将远程分支内容拉取到本地Git在日常使用过程中的常…

Docker下快速搭建RabbitMQ单例及集群

引子生命在于折腾&#xff0c;为上数据实时化用到了消息传送的内容&#xff0c;当时也和总公司人员商量选型&#xff0c;kafka不能区分分公司就暂定用了RbtMQ刚好个人也在研究容器及分布式部署相关内容就在docker上实践单机 docker&#xff08;要想快 先看问题 避免踩坑&#x…