linux dma映射讲解

在《深入理解Linux内核》中的第545页介绍了DMA的相关操作。说道DMA,那就不得不提到Cache(高速缓存)的问题。书中引用了如下一段例子来描述了Cache一致性问题:
“假设设备驱动程序把一些数据填充到内存缓冲区中,然后立刻命令硬件设备利用DMA传送方式读取该数据。如果DMA访问这些物理RAM内存单元,而相应的硬件高速缓存行的内容还没有写入RAM中,那么硬件设备所读取的至就是内存缓冲区中的旧值。”
现在有两种方法来处理DMA缓冲区:
一致性DMA映射:
书上讲的比较抽象,通俗地所就是任何对DMA缓冲区的改写都会直接更新到内存中,也称之为“同步的”或者“一致的”。
流式DMA映射:
根据个人的理解,这里的流即输入输出流,我们需要事先指定DMA缓冲区的方向,比如是”读缓冲区”还是“写缓冲区”。也称之为“异步的”或“非一致性的”,详细的内容请看下文。

由于x86体系结构中,硬件设备驱动程序本身会“窥探”所访问的硬件告诉缓存,因此x86体系结构中不存在DMA一致性问题。而对于其他一些架构如MIPS,SPARC以及POWERPC(包括ARM在内)需要在软件上保证其DMA一致性。

对于以上两者如何选择,书中有一个合适的建议, 如果CPU和DMA处理器以不可预知的方式去访问一个缓冲区,那么必须强制使用一致性DMA映射方式(这里我对不可预知的理解是,不能确定在何时它们访问缓冲区),其他情形下,流式DMA映射方式更可取,因为在一些体系结构中处理一致性DMA映射是很麻烦的,并且可能导致更低的系统性能。

这里详细介绍流式DMA:
需要访问的缓冲区需要在数据传送之前被映射(这里的映射也就是需要调用一些函数告知内核,该缓冲区进行流式映射),在传送之后被取消映射。

启动一次流式DMA数据传输分为如下步骤:
1. 分配DMA缓冲区。
在DMA设备不采用S/G(分散/聚集)模式的情况下,必须保证缓冲区是物理上连续的,linux内核有两个函数用来分配连续的内存:kmalloc()和__get_free_pages()。这两个函数都有分配连续内存的最大值,kmalloc以分配字节为单位,最大约为64KB,__get_free_pages()以分配页为单位,最大能分配2^order数目的页,order参数的最大值由include/linux/Mmzone.h文件中的MAX_ORDER宏决定(在默认的2.6.18内核版本中,该宏定义为10。也就是说在理论上__get_free_pages函数一次最多能申请1<<10 * 4KB也就是4MB的连续物理内存,在Xilinx Zynq Linux内核中,该宏定义为11)。
2. 建立流式映射。
在对DMA冲区进行读写访问之后,且在启动DMA设备传输之前,启用dma_map_single()函数建立流式DMA映射,这两个函数接受缓冲区的线性地址作为其参数并返回相应的总线地址。
3. 释放流式映射。
当DMA传输结束之后我们需要释放该映射,这时调用dma_unmap_single()函数。
注意:
(1). 为了避免高速缓存一致性问题,驱动程序在开始从RAM到设备的DMA数据传输之前,如果有必要,应该调用dma_sync_single_for_device()函数刷新与DMA缓冲区对应的高速缓存行。
(2). 从设备到RAM的一次DMA数据传送完成之前设备驱动程序是不可以访问内存缓冲区的,但如果有必要的话,驱动程序在读缓冲区之前,应该调用dma_sync_single_for_cpu()函数使相应的硬件高速缓存行无效。
(3). 虽然kmalloc底层也是用__get_free_pages实现的,不过kmalloc对应的释放缓冲区函数为kfree,而__get_free_pages对应的释放缓冲区函数为free_pages。具体与__get_free_pages有关系的几个申请与释放函数如下:
申请函数:
alloc_pages(gfp_mask,order)
返回第一个所分配页框描述符的地址,或者如果分配失败则返回NULL。
__get_free_pages(gfp_mask,order)
类似于alloc_pages(),但它返回第一个所分配页的线性地址。 如果需要获得线性地址对应的页框号,那么需要调用virt_to_page(addr)宏产生线性地址。
释放函数:
__free_pages(page,order)
这里主要强调page是要释放缓冲区的线性首地址所在的页框号
free_pages(page,order)
这个函数类似于__free_pages(page,order),但是它接收的参数为要释放的第一个页框的线性地址addr

你可能感兴趣的:(Linux/内核)