内存DMA及设备内存控制详解

序言

对于PCIe 设备(PCIe Endpoint)来说,其和CPU CORE、DRAM 的交互,主要涉及两种类型的内存访问:

  1. 设备内存访问:PCIe 设备的 Device Memory(设备内存)的访问,例如CPU 需要读写配置 PCIe 网卡或显卡的 寄存器(设备内存)。发起者是CPU,响应者是设备。
  2. DMA内存访问:PCIe 设备需要 DMA 读写 主机的 DRAM 内存,例如 网卡收到数据报文后,需要将其上报主机,则通过 PCIe Endpoint 中的 DMA 控制器 进行 DMA 写 主机的 DRAM 内存。发起者是设备DMA控制器,响应者主机内存DRAM,不需要CPU的参与。

以CPU CORE 发起设备内存读访问为例。从上图的左侧开始,CPU CORE 执行程序指令时,发出内存读写访问的地址是虚拟地址,接着MMU(内存管理单元或称为地址翻译单元更好) 将该虚拟地址转换为物理地址。如果物理地址访问的数据不在Cache中,则通过 Root Complex 中的 host bridge,将物理地址转换为总线地址,最后基于总线地址访问设备内存空间(Device Memory)。本文将基于上图的计算机系统架构,追本溯源,详细的解释设备内存 和 DMA内存 的访问细节。

重要概念

对于本文架构图中涉及的主要概念解释如下,如果不想被概念搞晕,建议暂时滤过,等不清楚时再回查即可。

System Bus:系统总线,用于CPU Package 内部子系统之间的连接,例如连接L3 Cache 和 Root Complex。

Cache:X86 的 Cache 通常分为三级,各个CPU CORE 有自己的 L1和L2 Cache,所有CPU CORE 共享 L3 Cache。Intel I7 L3 为 8MB,L2 256KB,L1 共64KB,所以Cache Size从 L1 到L3是递增。对于程序指令或数据都可以加载到Cache,加载的单位是 Cache Line,通常为 64B。

Root Complex:其中包括了两个重要组件,一个是 PCIe Root Bridge,通常称为 Host Bridge,其 Bus:Device. Function 为 00:00.0;另一个是 iommu,其用于将设备(如PCIe Endpoint 网卡)发起DMA读写DRAM使用的总线地址,转换为DRAM内存的 物理地址。

Memory Controller:DRMA 内存控制器,通常包含在CPU package 中,用于控制对内存的访问。

PCIe bus:PCIe 总线,用于连接 Host Bridge 到 PCIe Switch,PCIe Bridge 以及 PCIe Endpoint。

DMA Controller:DMA 控制器,其通常位于 PCIe Endpoint 内,用于设备对 DRAM 中数据进行 ”DMA 读”或“DMA 写”。

Device Memory:设备内存,用于对设备进行控制,如配置设备、获取设备状态等;在系统对PCIe 总线进行深度优先扫描时,会根据设备的 Bar 空间大小(Device Memory Size,如下面的网卡例如,Bar 0,2,4 Size 分别为64K,1M,8K),分配 iomem 中映射的物理地址,下文会提到 iomem 的细节。

内存访问

如前面所所述,计算机内部主要涉及两种内存访问。一种是 物理内存DRAM,另一种是 设备内存(如网卡,显卡等PCIe设备)。

内存访问方向

以上两种类型内存的访问,主要包括上图中三个方向:

  1. CPU -> DRAM: 从CPU 读写物理内存中的数据或程序指令;
  2. Device DMA -> DRAM: 设备的DMA 控制器发出针对DRAM的 DMA 读写,例如网卡从物理内存中DMA读取数据包进行发包。
  3. CPU -> Device Memory: 主机CPU 发出针对设备的读写,例如主机需要初始化设置显卡寄存器,让其开始工作。

内存访问过程

A. CPU访问物理内存(CPU -> DRAM)

  1. CPU使用虚拟地址发出内存读写请求
  2. MMU将虚拟地址转为DRAM的物理地址
  3. 如果访问数据的物理地址在Cache 有缓存数据,则直接从Cache 读取即可
  4. 如果Cache 中没有缓存,则通过内存控制器,访问DRMA(当然这涉及到页表的管理,不在本文的讨论范围),如果有兴趣,强烈建议深入阅读《深入理解计算机系统》

B. 设备访问物理内存(Device DMA -> DRAM)

  1. 外设使用总线地址,发起针对DRAM的DMA读写访问
  2. IOMMU负责将总线地址转为物理地址
  3. 通过内存控制器,访问DRMA

C. 设备内存访问(CPU -> Device Memory)

  1. CPU使用虚拟地址发出内存读写请求
  2. MMU将虚拟地址转为设备内存的物理地址(通过 cat /proc/iomem 可以查看到)
  3. Host Bridge 将物理地址转为总线地址
  4. 通过PCIe总线寻址到设备,并进行设备内存读写

总结:访问物理内存时,可以认为 虚拟地址 和 总线地址 都是Virtual的地址,都需要通过一个地址转换硬件(CPU侧是MMU,device侧是IOMMU),将虚拟地址转换为物理内存DRAM的实际物理地址。而访问设备内存时,通过Host Bridge将物理地址转换为设备内存的总线地址。

地址空间

虚拟地址:CPU Virtual Address Space(VA)

虚拟地址 被主机CPU用于访问物理内存,或者设备(PCIe EP Device)的 Bar 空间的内存(寄存器)。每个进程都有相同的虚拟地址空间(例如32位Linux系统,最大支持4G的地址空间,但只有高地址3GB用于进程的虚拟地址空间,低地址或顶端的1G给内核使用)。CPU执行指令访问内存使用的地址是虚拟地址,然后通过MMU、TLB 及 Page Tables 将虚拟地址转换为内存的物理地址。

物理地址:CPU Physical Address Space(PA)

我们可以认为物理地址空间,就是所有硬件的内存映射空间(MMIO- memory map I/O)。可将物理内存RAM看成一种特殊的MMIO空间。OS在给硬件设备编址时,其会看到物理内存 以及设备内存(通过PCIe Bar 空间映射的设备寄存器),所以进行了统一的物理地址的编址。如下,低地址给RAM使用,而PCI 设备内存通常位于高地址。

物理地址空间的编址 可以通过 cat /proc/iomem 查看:

[root@node2 ~]# cat /proc/iomem
00000000-00000fff : Reserved
00001000-0005cfff : System RAM   // System RAM:物理内存
0005d000-0005dfff : Reserved
0005e000-0009ffff : System RAM
......
00100000-3fffffff : System RAM         // Kernel Space01000000-01c00ea0 : Kernel code01e00000-02214fff : Kernel rodata02400000-026294bf : Kernel data02893000-02bfffff : Kernel bss
......
8f800000-dfffffff : PCI Bus 0000:00   // PCI Root Bridge Bus 0 下挂PCIe 设备内存的总大小8f800000-8f9fffff : PCI Bus 0000:028fa00000-8fbfffff : PCI Bus 0000:028fc00000-8fdfffff : PCI Bus 0000:048fe00000-8fffffff : PCI Bus 0000:0490000000-9fffffff : 0000:00:02.090000000-902fffff : BOOTFBa0000000-a02fffff : PCI Bus 0000:01  // PCIe bus 01 下挂PCIe 设备内存的总大小a0000000-a00fffff : 0000:01:00.1   // PCIe 网卡 Bar 2 空间(设备内存)映射的物理内存a0000000-a00fffff : bnxt_ena0100000-a01fffff : 0000:01:00.0a0100000-a01fffff : bnxt_ena0200000-a020ffff : 0000:01:00.1  // PCIe 网卡 Bar 0 空间(设备内存)映射的物理内存a0200000-a020ffff : bnxt_ena0210000-a021ffff : 0000:01:00.0a0210000-a021ffff : bnxt_ena0220000-a0221fff : 0000:01:00.1  // PCIe 网卡 Bar 1 空间(设备内存)映射的物理内存a0220000-a0221fff : bnxt_ena0222000-a0223fff : 0000:01:00.0a0222000-a0223fff : bnxt_ena0224000-a0243fff : 0000:01:00.1a0244000-a0263fff : 0000:01:00.1

在作者使用这台主机上,物理地址范围 a0000000-a02fffff 对应: PCI Bus 0000:01。该Bus位于PCI Bridge 和 PCI Endpoint之间(可以回到文章开始的图查看),所以可以通过命令 ( lspci -s 00:01.0 -vvv )查看到 Bus 上一级 Bridge 的信息,得到Bridge下挂设备的总地址空间,即下图中可以被CPU访问预取的 Memory 范围:00000000a0000000-00000000a02fffff

而在该PCI Bridge下,下挂PCIe Endpoint的其中一个Function(01:00.1),其 Bar2 对应物理地址范围:a0000000-a00fffff : 0000:01:00.1(Bus:Device.Function),即为 PCIe 网卡 Bar 2(下图Region 2)空间(设备内存)映射的物理地址空间。

总线地址:Bus Address Space(BA)

总线地址是为 终端设备读写DMA 内存 或者 主机读写设备配置空间(PCIe Bar)使用的总线地址空间的地址(PCI 总线)。

我们在调用【dma = dma_map_single(device, buf, size, DMA_TO_DEVICE)】,将数据 buf(虚拟地址) 建立 dma地址映射给device 访问时,返回的类型为 dma_addr_t 的 dma 地址,就是 bus address。该地址可以传给设备用于DMA的物理内存数据读取。

在一些系统中,总线地址和物理地址是一样的(我们可以调用 phy = virt_to_phys(buf) 得到虚拟地址buf 对应的物理地址 phy, 确认 phy 和 dma 地址是否相同)。Host Bridge和 IOMMU可以实现 物理地址和总线地址的任意映射。

注意:从设备的角度,不管是访问设备内存,或者设备发起DMA读写,都是使用的总线地址。

访问过程详解

CPU 读取设备内存

首先,CPU需要通过ioremap,将虚拟地址A映射到物理地址空间 MMIO 中物理机地址B;

然后,CPU发起 ioread、iowrite 请求,发起时用的虚拟地址C,接着MMU转换为物理地址B;

最后,PCIe Host Bridge 将物理地址B转换为总线地址A,通过总线地址A对Bar空间进行读写。

注意:对Bar Register 的读写基于 PCIe的 Configuration TLP 操作。

代码示例:

/* CPU ioremap */
struct pci_dev *pdev;
u8 __iomem *hw_addr = ioremap(pci_resource_start(pdev, 0),pci_resource_len(pdev, 0));u32 val = 10;
u32 reg_offset = 0;
/* 将 10 写入hw_addr[reg_offset] */
writel(val, &hw_addr[reg_offset]);
value = readl(&hw_addr[reg]);

Device DAM 访问物理内存

首先,CPU基于虚拟机地址X,通过MMU,将数据写入物理地址Y对应的 物理内存中;

然后,CPU 调用如dma_map_single 这样的API,将总线地址Z 映射到 虚拟地址X和物理地址Y。

最后,Device 通过总线地址Z,发出DMA读请求(PCIe Memeory Read),接着IOMMU将总线地址翻译为物理地址Y 去 读写物理内存 DMA Buffer.

编程示例:

struct device *dev;		/* device for DMA mapping */
struct sk_buff *skb;
dma = dma_map_single(dev, skb->data, size, DMA_TO_DEVICE);
/* tx_desc 是网卡发包时用到的描述符,* 硬件设备可以通过DMA 访问到描述符绑定的dma地址,然后硬件可以基于该DMA地址发起内存访问*/
tx_desc->read.buffer_addr = cpu_to_le64(dma);

DMA映射编程

常用的DMA映射有两种类型,一种是一致性DMA映射(consistent DMA mapping);另一种是流式DMA映射(streaming DMA mapping)。理解这两种常用的 DMA映射类型,是编程的基础。

一致性DMA映射

一致性DMA 映射关闭了 L1/L2/L3 Cache。首先,当 CPU 写入数据时,则会直接放入内存,而不会在Cache进行缓存,所以设备可以立即DMA读取到CPU写入的数据;其次,当设备DMA写入数据到内存后,则CPU可以立即读取到该变化的数据,而不会读取Cache中的脏数据,因为Cache关闭了。

Consistent DMA mapping的常用场景:

  1. 网卡驱动程序 和 网卡DMA控制器往往是通过内存中的一些描述符(形成环)进行交互(描述符中包括收发数据包用到的大块DMA内存地址),这些保存描述符的memory,需要被主机CPU和网卡DMA控制器频繁的读写,并且在任何一端写,都需要另一端立即可以访问到,所以通常采用Consistent DMA mapping比较方便。

2. SCSI硬件适配器上的DMA 与 主存中的一些数据结构(mailbox command)进行交互,这些保存mailbox command的memory一般采用Consistent DMA mapping。

代码示例

如下调用 dma_alloc_coherent 分配一块一致性DMA内存(取自intel ixgbe驱动 linux-4.18.0-348\drivers\net\ethernet\intel\ixgbe\ixgbe.h & linux-4.18.0-348\drivers\net\ethernet\intel\ixgbe\ixgbe_main.c):

struct ixgbe_ring {struct device *dev;		/* device for DMA mapping */void *desc;			/* descriptor ring memory */dma_addr_t dma;			/* phys. address of descriptor ring */unsigned int size;		/* length in bytes */u16 count;			/* amount of descriptors */
}
/*** ixgbe_setup_tx_resources - allocate Tx resources (Descriptors)* @tx_ring:    tx descriptor ring (for a specific queue) to setup** Return 0 on success, negative on failure**/
int ixgbe_setup_tx_resources(struct ixgbe_ring *tx_ring)
{struct device *dev = tx_ring->dev;// 返回是 DMA地址 对应的虚拟地址 (tx_ring->desc)tx_ring->desc = dma_alloc_coherent(dev,  // 进行DMA映射的PCI设备对应的devicetx_ring->size,   // 描述符环对应的总大小(bytes)&tx_ring->dma,  // 输出:DMA 地址GFP_KERNEL);
...
}

参考内核 Document/core-api/dma-api.rst

void *
dma_alloc_coherent(struct device *dev, size_t size,dma_addr_t *dma_handle, gfp_t flag)Consistent memory is memory for which a write by either the device or 
the processor can immediately be read by the processor or device
without having to worry about caching effects.

流式DMA映射

因为一致性DMA关闭了Cache,虽然使用带来了方便,但是会牺牲数据读写的性能。例如Intel当前的CPU package,支持在 PCIe Endpoint DMA 访问内存时,将数据放入到L3 Cache,然后DMA 控制器从L3 Cache 读取数据(这是对我们DMA控制器直接访问DRAM内存常识的挑战)。而这些硬件的优化,在使用流式DMA影射时,可以充分发挥性能的优势。

streaming DMA mapping的常用场景:

  1. 网卡进行数据传输使用的DMA buffer,发送数据是,主机准备好DMA Buffer,由硬件DMA读取;接受数据时,也是主机准备好DMA Buffer,由硬件DMA 写入接受到的数据。
  2. 文件系统中的各种数据buffer,这些buffer中的数据最终要被读写到SCSI设备上去

代码示例1

如下dma_map_single 将 虚拟地址 skb->data 指向的内存,映射到dma 总线地址上,然后将dma 地址写入描述符给硬件DMA读取 skb->data 指向的内存数据。

需要注意的是,skb->data 指向的数据区域需要是连续物理内存。内核采用带k字的分配函数(如kmalloc)即得到连续的物理内存,而使用v开头的分配函数,如vmalloc,则不能保证。

struct device *dev;		/* device for DMA mapping */
struct sk_buff *skb;
dma = dma_map_single(dev, skb->data, size, DMA_TO_DEVICE);
/* tx_desc 是网卡发包时用到的描述符,* 硬件设备可以通过DMA 访问到描述符绑定的dma地址,然后硬件可以基于该DMA地址发起内存访问*/
tx_desc->read.buffer_addr = cpu_to_le64(dma);

代码示例2

如下调用dev_alloc_pages 分配一个page,再调用 dma_map_page_attrs 将该页映射dma 地址,最后调用dma_sync_single_range_for_device 将页面的数据同步给设备访问:

	struct page *page;dma_addr_t dma;struct device *dev;		/* device for DMA mapping */int page_size = PAGE_SIZE;/* alloc new page for storage */page = dev_alloc_pages(0);/* map page for use */dma = dma_map_page_attrs(dev, page, 0,page_size,DMA_FROM_DEVICE,DMA_ATTR_SKIP_CPU_SYNC);/* sync the buffer for use by the device */dma_sync_single_range_for_device(dev, dma,0, page_size,DMA_FROM_DEVICE);

总结

本文结合计算机系统的架构,我们从内存访问的角度,介绍了各种地址空间(虚拟、物理、总线)的概念。以及物理内存和设备内存访问三个方向(CPU->DRAM, CPU -> Device Memory, Device DMA -> DRAM)。最后介绍了DMA 映射编程常用的方法,如果觉得不过瘾,建议继续研究作者参考的内核 Document。

参考

1,linux-4.18.0-348/Document/core-api/dma-api-howto.rst

2,linux-4.18.0-348/Document/core-api/dma-api.rst

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

③ 软件工程CMM、CMMI模型【软考中级-软件设计师 考点】

个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ ③ 软件工程CMM、CMMI模型【软考中级-软件设计…

flink 反压原理

背景 在flink中由于数据倾斜或者数据处理速率的不匹配,很容易引起反压,本文就看一下flink反压的原理 flink反压原理 flink全流程pineline的反压实现其实依赖于TaskManager之间的反压和TaskManager内部的反压来实现 1.TaskManager之间的反压 2.Task…

视频下载软件 Downie4 mac中文介绍

Downie mac是一款Mac平台上非常实用的视频下载工具。它支持下载各种视频网站上的视频,并且具有快速、稳定、易于使用的特点。 Downie支持下载各种视频网站上的视频,包括YouTube、Vimeo、Netflix、Hulu、Amazon等等。它具有快速、稳定的下载速度&#xff…

Python---判定表法(功能测试)

能对多条件依赖关系进行设计测试点---判定表法 等价类、边界值分析法主要关注单个输入类条件的测试 定义:是一种以表格形式表达多条件逻辑判断的工具。 条件桩: 列出问题中的所有条件,列出条件的次序无关紧要动作桩: 列出问题中可能采取的操作,操作的…

python基于VGG19实现图像风格迁移

目录 1、原理 2、代码实现 1、原理 图像风格迁移是一种将一张图片的内容与另一张图片的风格进行合成的技术。 风格(style)是指图像中不同空间尺度的纹理、颜色和视觉图案,内容(content)是指图像的高级宏观结构。 实…

mac 安装homebrew ,golang

mac 安装homebrew ,golang 安装homebrew安装golang选择 apple arm 版本安装配置环境变量 安装homebrew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"回车执行指令后,根据提示操作。具体包括以下提示操作&am…

每日一练 | 网络工程师软考真题Day46

阅读以下说明,答复以下【问题1】至【问题6】 【说明】 某公司总部效劳器1的操作系统为Windows Server 2003,需安装虚拟专用网〔VPN〕效劳,通过Internet与子公司实现平安通信,其网络拓扑结构和相关参数如图2-1所示。 【问题1】在Wi…

sql-50练习题16-20

sql-50练习题16-20 前言数据库表结构介绍学生表课程表成绩表教师表 1-6 检索"01"课程分数小于60,按分数降序排列的学生信息1-7 按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩1-8 查询各科成绩最高分、最低分和平均分:以如下形式…

c++-set和map

文章目录 前言一、set容器1、set容器介绍2、set的使用2.1 set的构造函数和迭代器2.2 set的容量2.3 set修改操作 3、multiset容器3.1 multiset容器介绍3.2 multiset容器使用 二、map容器1、map容器介绍2、map容器使用2.1 map的构造函数与迭代器2.2 map中元素的修改2.3 map的容量…

Java修仙传之Flink篇

大道三千:最近我修Flink 目前个人理解: 处理有界,无界流的工具 FLINK: FLINK定义: Flink特点 Flink分层API 流的定义 有界数据流(批处理): 有界流:数据结束了,程序也…

正则表达式包含数字和字符匹配

至少6位。 pattern : (?.[0-9])(?.[A-Za-z])[0-9A-Za-z]{6,} 正则表达式中的“?”是一个正向预查字符,它的意思是匹配前一个字符出现的最少一次。具体来说,当一个匹配出现时,它会检查前一个字符是否符合要求,如果符合&#xf…

【Java 进阶篇】深入理解 Java Response:从基础到高级

HTTP响应(Response)是Web开发中的一个关键概念,它是服务器向客户端(通常是浏览器)返回数据的方式。理解如何在Java中处理和构建HTTP响应是开发Web应用程序的重要一部分。本文将从基础知识到高级技巧,详细介…

ardupilot开发 --- 深度相机 篇

1. ZED 相机 1.1 规格 2. RealSense 需要机载计算机作为中介!!

分布式锁-Redis红锁解决方案

一 分布式锁的概念 1:概念 分布式锁(多服务共享锁) 在分布式的部署环境下,通过锁机制来让多客户端互斥的对共享资源进行访问控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共…

使用 Authing 快速实现一套类似 OpenAI 的认证、API Key 商业权益授权机制

如果你有经常使用 OpenAI 或者 HuggingFace 这一类面向开发者的 SaaS 服务,对于 API Key 肯定不会陌生。我们在使用这些服务时,通常都会在其平台上面创建一套 API Key,之后我们才能在代码中通过这一串 API key 访问其服务;同时&am…

处理SAP资产折旧AFAB 过账报错:“科目 8019010100 要求一个成本会计分配”

会计在进行资产折旧AFAB时 报错如下所示: 原因分析: 折旧时没有把资产设置得成本中心带到过账凭证的成本中心字段中去。而资产中已经维护了成本中心了。 所以要在资产过账的科目分配中设置一下路径如下: 或者TCODE:ACSET科目设置这…

Jmeter(二十一):jmeter导入和导出接口的处理(超详细)

JMeter测试导入接口 利用Jmeter测试上传文件,首先可根据接口文档或者fiddler抓包分析文件上传的接口;如下图: 以下是我通过fiddler所截取的文件上传的接口 1、填写导入接口的信息 查看文件上传栏下的填写信息: 文件名称&#x…

RT-Thread 7. RT-Thread Studio ENV修改MCU型号

1. 修改MCU型号 2.在ENV界面输入 scons -c scons --dist3. dist下为更新后完整源代码 4.导入RT-Thread Studio 发现GD32F330已经生效了。 5. 自己编写startup_gd32f3x0.S,准确性待验证 ;/* ; * Copyright (c) 2006-2021, RT-Thread Development Team ; * ; * SPD…

MySQL主从复制原理

1、MySQL主从复制的三个步骤及其原理图 slave会从master读取binlog来进行数据同步 MySQL复制过程分成三步: 1、master将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events。 2、slave将ma…

WebService与RESTful两种接口风格示例

下面我将分别用WebService(SOAP)和RESTful API的例子来说明它们是如何工作的。 1. WebService (SOAP) 示例: 假设有一个在线计算器服务,它提供了一个加法操作的SOAP WebService。 SOAP请求(客户端到服务器&#xff…