LDD:内存映射和DMA

地址类型

用户虚拟地址 物理地址 总线地址 内核逻辑地址 内核虚拟地址

用户虚拟地址

用户空间程序所看到的常规地址。用户地址是32位或者64位的,这取决于硬件的体系结构。每个进程都有自己的虚拟地址空间。

物理地址

该地址在处理器和系统内存之间使用

内核逻辑地址

内核逻辑地址组成了内核的常规地址空间。该地址映射了部分或者全部内存,并经常被视为物理地址。逻辑地址使用硬件内建的指针大小,因此在安装了大量内存的32位系统中,它无法寻址全部的物理地址。逻辑地址通常保存在unsigned long或者void *这样类型的变量中。Kmalloc返回的内存就是内核逻辑地址。

内核虚拟地址
内核虚拟地址和逻辑地址的相同之处在于,它们都将内核空间的地址映射到物理地址上。内核虚拟地址与物理地址的映射不必是线性的和一对一的,而这是逻辑地址的特点。所有逻辑地址都是内核虚拟地址,但许多内核虚拟地址不是逻辑地址。举例,vmalloc分配的内存具有一个虚拟地址,虚拟地址通常保存在指针变量中。

页:物理地址被分成离散的单元,系统对内存的操作都是基于页。大部分系统使用每页4k。常量PAGE_SIZE给出了页的大小。

页帧数:如果忽略地址偏移量,并将除去偏移量的剩余位移到右端,称该结果为页帧数。内存地址,无论是虚拟的还是物理的,都被分为页号和一个页内的偏移量。举例,如果使用每页4096字节,那么最后的12位是偏移量,剩余的高位则是页号。移动位以在页帧数和地址间进行转换是一个常用操作;宏PAGE_SHIFT将告诉程序员,必须移动多少位才能完成这个转换。

低端内存:存在于内核空间上的逻辑地址内存。几乎所有现在读者遇到的系统,它全部的内存都是低端内存。

高端内存:是指那些不存在逻辑地址的内存,这是因为他们处于内核虚拟地址之上。

内存映射和页结构

page结构和虚拟地址之间的转换

页表

在任何现代的系统中,处理器必须使用某种机制,将虚拟地址转换为相应的物理地址,这种机制被称为页表

虚拟内存区(VMA)

进程内存映射至少包含下面这些区域:

程序的可执行代码(text)区域

多个数据区,包括初始化数据、非初始化数据(BSS)以及程序堆栈。

与每个活动的内存映射对应的区域。

cat /proc/<pid/maps>可以了解到进程的内存区域。

/proc/self/maps是一个特殊的文件,它始终指向当前进程。

/proc/*/maps中的每个成员都与vm_area_struct结构中的一个成员对应

vm_area_struct结构(重要)

mmap设备操作

内存映射可以提供给用户程序直接访问设备内存的能力。

不是所有的设备都能进行mmap抽象的。比如像串口和其他面向流的设备就不能mmap。对mmap的另外一个限制是:必须以PAGE_SIZE为单位进行映射。

mmap()系统调用

mmap(caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset)

两种建立页表的方法:1.使用remap_pfn_range函数一次全部建立;2.或者通过nopage VMA方法每次建立一个页表。

使用remap_pfn_range和io_remap_page_range负责为一段物理地址建立新的页表。 

详细参考博客

Linux地址映射--地址转换(mmap,vma)_yiyeguzhou100的专栏-CSDN博客_mmap 地址对齐

DMA

分配DMA缓冲区

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

低层分配DMA缓冲区的方法,使用GFP_DMA标志调用kmalloc或者get_free_pages从DMA区间分配内存。

Get_free_pages函数可以分配多达几M字节的内存,但通常会失败,此时系统内存中充满了内存碎片。如果需要为DMA缓冲区分配一大块内存,需考虑是否有替代的方法。比如分散/聚集I/O

通用DMA层

DMA映射:解决缓存一致性问题

Dma_addr_t表示总线地址

根据DMA缓冲区期望保留时间的长短,PCI代码区分两种类型的DMA映射:

一致性DMA映射

这种类型的映射存在于驱动程序生命周期中。一致性映射的缓冲区必须可同时被CPU和外围设备访问。因此一致性映射必须保存在一致性缓存中

流式DMA映射

 流失映射接口比一致性映射复杂,因为需要接受指定的内核缓冲区来建立DMA映射,处理它没得选择的内存地址。

上层已经准备好了buffer,交由device driver通过DMA发送出去,那么显然,以流映射方式更为简便,因为只需要刷cacheDMA就能看到实际的buffer数据。那么采用一致性映射可不可以呢? 也是可以的,只是麻烦而已,那就得申请一块一致性DMA内存,然后把上层的buffer拷贝到这里,然后启动DMA去传输。

总而言之:

一致性DMA

dma_alloc_coherent(dev,size, &dma_handle, gfp);

流式DMA

dma_map_single(dev,addr, size, direction);

dma_unmap_single(dev,dma_handle, size, direction);

一致性DMA可以认为是“同步的”,就是DMA和CPU之间看到的物理内存是一致的。流式DMA则不然。

DMA操作和CPU之间的主要隔阂就是cache,因为一般来说DMA只操作物理内存,不会动cache,但CPU却首先看到的是L1L2cache,所以设备驱动就需要调用正确的DMA函数来操作cache。拿网卡收发包为例,假如CPU发包给网卡,那CPU填好skb的数据之后,得先把cache里有关这个skb数据的行给刷到物理内存,否则网卡从物理内存拿到的数据不是真正所要的数据。反之,CPU把skb数据装配好DMA rx descriptor的时候,得先invalid掉这个skb数据在cache里的行。这样DMA把收到的包填到物理内存后再中断告知CPU时,CPU就可以避免从cache拿到关于这个skb的老(脏)数据,而会从物理内存取包而重新建立数据cache。dma_map_singledma_unmap_single做的就是这个事情,它会根据数据的方向来判断该是clean cache还是incalidcache。

那么DMA描述符呢,DMA控制器和CPU都要对DMA描述符做频繁操作,当CPU和DMA需要频繁的操作一块内存区域的时候,一致性DMA映射就比较合适。所以DMA描述符特别适用于一致性DMA。当然,你也可以对DMA描述符用流式操作,但那样开销就比较大了。

顺便说一句,刷cache是比较耗时的,特别是刷的区域比较大的时候。现代的很多处理器,CPU和DMA控制器之间从硬件上就能保证cache一致性,如ARM的ACP功能,这样像dma_map_single只是返回物理地址,而dma_unmap_single什么都不做。极大的提高了系统性能。

详细参考

https://blog.csdn.net/zqixiao_09/article/details/51089088

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值