在《深⼊理解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
因篇幅问题不能全部显示,请点此查看更多更全内容