LDD3-内存映射与DMA

地址类型

用户虚拟地址:

  这时在用户空间程序能看到的常规地址,32或64位,每个进程都有自己的虚拟地址空间。

物理地址“

该地址在处理器和系统内存之间使用,物理地址是32位或64位。

总线地址:

该地址在外围总线和内存之间使用,通常和处理器使用的物理地址相同。

内核逻辑地址:

内核逻辑地址组成了内核的常规地址空间。该地址映射了部分或全部内存,并经常被视为物理地址。

在大多数体系结构中,逻辑地址和与其相关联的物理地址的不同,仅仅在于它们之间存在一个固定偏移。kmalloc返回的就是内核逻辑地址。

内核虚拟地址:

和内核逻辑地址的相同之处是,它们都将内核空间的地址映射到物理地址。但内核虚拟地址与物理地址的映射不必是线性的和一对一的。比如vmalloc分配的内存具有一个虚拟地址(但并不存在直接的物理映射)。kmap函数也返回一个虚拟地址,虚拟地址通常保存在指针变量中。需要强调的是,内核逻辑地址页是内核虚拟地址,但反之不然。

如果有一个逻辑地址,__pa()返回其对应的物理地址,__va()也能将物理地址逆向映射到逻辑地址。

物理地址和页

物理地址被分成离散的单元,页,大多数系统都使用4096字节一页。实际上,无论虚拟还是物理地址,它们都被分为页号和一个页内偏移量。如果页大小是4096字节,那么最后12位是偏移量,而剩余的高位则指定了页号,如果将这些高位右移偏移量的位数,那么结果称为页帧数。宏PAGE_SHIFT将告诉程序员,必须移动多少位才能完成这个转换。

高端和低端内存

内核将4GB的虚拟地址空间分割为用户空间和内核空间,典型的是3GB分配给用户空间,1GB分配给内核空间。内核代码和数据结构必须与这样的空间匹配,内核无法直接操作没有映射到内核地址空间的内存。换句话说,内核对任何内存的访问都需使用自己的虚拟地址空间。

只有内存的低端部分拥有逻辑地址,在访问特定的高端内存前,必须建立明确的虚拟映射。

内存映射和页结构

page结构

执行直接I/O访问

字符设备中执行直接I/O是不可行的。

直接内存访问

DMA需要设备驱动程序分配一个或多个适合执行DMA的特殊缓冲区。许多驱动程序在初始化阶段分配了它们的缓冲区,并且一直使用它们直到关闭。

缓冲区的主要问题是:当大于一页时,它们必须占据连续的物理页,这是因为设备使用ISA或PCI系统总线传输数据,而这两种使用的都是物理地址。

受到设备的地址总线限制,应使用GFP_DMA标志调用kmalloc或则get_free_pages从DMA区间分配内存。

使用DMA的设备驱动程序将与连接到总线接口上的硬件通信,但基于DMA的硬件使用总线地址,而非物理地址。

DMA操作最终会分配缓冲区,并将总线地址传递给设备。

处理复杂的硬件

执行DMA之前,要确定:是否给定的设备在当前主机上具备执行这些操作的能力,默认情况下,内核假设这些设备都能在32位的地址上执行DMA,如果不是,应该调用下面函数通知内核:

    int dma_set_mask(struct device *dev, u64 mask)

再次强调,如果设备支持常见的32位DMA操作,则没有必要调用dma_set_mask.

DMA映射

一个dma映射是要分配的DMA缓冲区与为改缓冲区生成的,设备可访问地址的组合。我们可以通过对virt_to_bus函数的调用获得该地址,但是不建议。

IOMMU在设备可访问的地址范围规划了物理内存。

DMA映射建立一个新的结构类型--dma_addr_t来表示总线地址。dma_addr_t类型的变量对驱动程序是不透明的,如果CPU直接使用了dma_addr_t,将会发生不可预期的问题。

两种类型的DMA映射:

一致性DMA映射

  这种类型的映射存在于驱动程序生命周期,一致性映射的缓冲区必须可同时被CPU和外围设备访问。因此一致性映射必须保存在一致性缓冲中,建立和使用它的开销是很大的。

 流式DMA映射

  通常为单独的操作建立流式映射。当使用流式映射时,一些体系架构可以优化性能。

建立一致性DMA映射

void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, int flag)

函数的返回值是缓冲区的内核虚拟地址,可以被CPU访问。而与其相关的总线地址,返回时保存在dma_handle中。flag通常是GFP_KERNEL或GFP_ATOMIC

不需要时:dma_free_coherent(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle)

dma_alloc_coherent函数获得的映射,可能最小大小为单个页。

 一致性DMA映射是一段关闭cache memory,所以不存在cache问题,比如DMA 描述符,DMA和CPU都需要频繁操作一块内存区域的时候,一致性DMA映射就比较合适。所以DMA描述符特别适用于一致性DMA。当然,你也可以对DMA描述符用流式操作,但那样开销就比较大了。

流式DMA没有关闭cache,使用时候需要注意。CPU写数据,需要去clean cache,将数据从cache写到DRAM。CPU读数据时,

需要将cache中数据无效,让CPU去memory中读数据,填充到cache。

Flush cache:先clean再invalid

 

DMA池

。。。

建立流式DMA映射

建立流式映射时,必须告诉内核数据流动的方向,dma_dat_direction枚举类型:

DMA_TO_DEVICE----如果数据被发送到设备

DMA_FROM_DEVICE-----如果数据被发送到CPU

DMA_BIDIRECTIONAL----如果数据可双向移动

DMA_NOEE---只是调试

当只有一个缓冲区要被传输时,使用:dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction)

----这里的buffer是虚拟地址

  • 缓冲区的传送方向只能匹配映射时指定的方向值
  • 一旦缓冲区被映射,它将属于设备,而不是处理器。直到缓冲区被撤销映射前,驱动程序不能以任何方式访问其中的内容。,也就是说在所有数据写入到缓冲区之前,不要映射缓冲区。
  • DMA活动期间不能撤销对缓冲区的映射。

为什么驱动程序不能访问映射后的缓冲区呢?

  1. 当一个缓冲区建立DMA映射时,内核必须保证在该缓冲区内的全部数据都被写入内存。当调用dma_unmap_single时,很可能有些数据还在处理器的缓冲中,因此必须被显示刷新。刷新之后,处理器写入缓冲区的数据对设备是不可见的。
  2. 如果要映射的缓冲区位于设备不能访问的内存区。

分散/聚集映射

 

posted @ 2022-05-15 17:13  老胡同学  阅读(267)  评论(0编辑  收藏  举报