当前位置: 首页 > Linux

DMA呢

时间:2023-04-06 02:59:13 Linux

在VIP群里,大家议论纷纷。可见大家对Linux技术和嵌入式技术的热切和热情。欢迎大家加入奔叔的VIP私密群,一起研究技术,一起奔跑,一起成长。关于DMA有朋友问到DMA,所以大家在写驱动的时候要格外小心。因为DMA问题主要影响缓存的一致性。linux内核提供了很多API来帮助你实现DMA操作。我们知道DMA使用的物理内存,所以内核中有两种接口来分配物理内存__get_free_page()和alloc_page()在页中分配物理内存memory上面两个接口分配的内存可以作为DMAbuffer的原材料。但是我们系统分配的物理内存都是可缓存的,也就是开启了缓存功能。另外,DMA引擎不需要CPU参与DMA传输,所以从这个角度来看,同一个缓存行可能会被CPU和DMA引擎同时访问,从而导致缓存行被破坏,又怎样?要保证DMA运行时,麻烦制造者缓存不会来捣乱吗?一个简单的想法是,当我们进行DMA传输时,我关闭这个缓冲区的缓存。这就是内核实现的一致性DMA缓冲区映射(英文名称:ConsistentDMAmappings)。在这里,一致意味着同步或一致。CPU和DMA都可以同时看到数据的变化,不需要软件做一些额外的flush动作。核心函数是:dma_addr_tdma_handle;cpu_addr=dma_alloc_coherent(dev,size,&dma_handle,gfp);这个函数返回两个值,其中cpu_addr是一个虚拟地址,CPU可以通过这个地址访问这个buffer,另一个dma_handle物理地址,可以传给DMA引擎。这里分配的大小在PAGE_SIZE中。另外,这个函数会调用alloc_page()来分配物理页,所以不要在中断上下文中使用这个API。大家可以想想一致性DMA的缺点。一个明显的缺点是缓存总是关闭的,因此性能受到限制。会很低。例如,DMA传输完成后,CPU取DMA缓冲区的数据。这时候缓存关闭了,CPU读写变得很慢。有没有办法保证DMA传输的一致性,提高性能呢?尤其是CPU访问这个DMA缓冲区的性能?有一种方法,就是流式DMA。有些关于streamingDAM的书把它翻译成streamingDMA,从字面上看不太好理解。其实可以分解DMA传输,比如:设备的FIFO->DMAbuffer。这就是代码中提到的DMA_FROM_DEVICE。数据流是从设备到物理内存。注意DMAbuffer在主存中,也就是DDR。这时,从CPU的角度来看,CPU的任务就是从设备中读取数据,也就是我们常说的“DMA读”。.DMA缓冲区->设备的FIFO。这就是代码所说的DMA_TO_DEVICE。数据流从主访问设备DDR到设备。注意DMAbuffer在主存中,也就是DDR。这时,从CPU的角度来看,CPU的任务就是向设备写入数据,也就是我们常说的“DMAWrite”。(DMA读或者写这个词不是很严谨,但是大家的口头禅都是这么说的,代码中的DMA_FROM_DEVICE和DMA_TO_DEVICE这两个宏比较严谨。)然后很多人就疑惑了,DMA读DMA写和缓存有什么关系呢?连续操作呢?DMA读取是否需要无效缓存或刷新缓存?让我们来看看这张照片。从图中可以看出,CPU需要进行DMA写操作,即将内存中的bufferA写入设备的FIFOA,那么有可能是cache中的数据还没有完全写入bufferA在内存中,那么此时如果启动了DMA,最后传输到deviceFIFOA中的数据其实并不是CPU要写的,因为还有一些数据潜伏在cacheA中没有同步到内存中.这个场景有些相似。我们把内存拷贝到U盘里,马上拔出来,发现U盘里面什么都没有。我们来看看DMA读取情况。CPU要将设备的FIFOB的数据读入内存缓冲区B,如果DMA传输使能时内存缓冲区B对应的缓存不失效,则DMA将数据从FIFOB传输到内存后BufferB,CPU读取内存BufferB的数据,然后将之前剩余的cacheline的内容先读给CPU,但CPU实际上还没有读取到最新的FIFOB数据。总结:DMA写入需要刷新缓存行。DMA读取需要无效的高速缓存行。流式DMA常用的API有:dma_handle=dma_map_single(dev,addr,size,direction);dma_unmap_single(dev,dma_handle,大小,方向);dma_sync_sg_for_cpu()dma_sync_sg_for_device()streamingDMAmapping对于当CPU可以操作DMAbuffer有严格的要求,只有在dma_unmap_single之后CPU才能操作buffer。原因是流式DMA缓冲区被缓存了。map时刷新缓存,deviceDMA完成unmap时刷新缓存(根据数据流向回写或失效),保证缓存数据的一致性。unmap前的CPU操作缓冲区不能保证数据的一致性。因此,内核需要严格保证操作时序。如果你创建的DMAbuffer需要多次来回操作,比如CPU需要同步访问buffer,device需要访问buffer。然后就可以使用内核提供的函数dma_sync_single_for_cpu和dma_sync_single_for_device来进行同步操作,而不需要频繁的创建和释放DMA缓冲区。当然,内核除了支持单页DMA映射外,还支持scatterlists形式的DMA映射,使用时稍微复杂一些。如何在ARM中操作缓存?有同学问在ARM中如何操作缓存?我们支持两种缓存操作,一种是flush,一种是invalid。其中每一个都可以刷新整个缓存,或者根据范围进行操作。我们先看一下代码路径:dma_map_single()->(ops->map_page)->arm_dma_map_page()->__dma_page_cpu_to_dev()->outer_inv_range()->v7_dma_inv_range我们看一下这段代码,主要包含处理器上的几个mcr操作。有同学问,这些协处理操作是什么意思?好了,笨叔叔以ARM?ArchitectureReferenceManual为例,看看ARM芯片手册是怎么描述的。首先,我们翻到B章5.8.1。我们看到这里有协处理器CP15的描述。我们知道它做什么,它有什么。从图中红圈可以看出,CP15中的C7主要用于缓存管理和地址转换。继续看c7的描述,翻到1765页,从这张描述C7寄存器的图可以看出,CRm是c14,opc2是1,这里有个寄存器名,DCCIMVAC,主要是根据MVA(这里可以理解为虚拟地址或物理地址)。干净且无效的数据缓存行。另外opc2为2的寄存器是DCCISW,主要是根据set和way使cacheline失效。我们可以在手册中搜索“DCCIMVAC”,找到更详细的说明,比如B6.2中有更详细的介绍。越来越有趣的问题私密VIP群里有很多有趣的问题:匿名页面是否存在于用户地址空间,页面缓存是否存在于内核空间,这样的理解是否正确?如果是这样的话,那么如果要统计一个用户进程占用的物理内存,是不是还要加上pagecache消耗的内存呢?如果malloc申请的内存先从highmem分配,达到highmem的min水位,是直接阻塞直接回收,还是跳转到lowmem(假设lowmem内存足够)继续分配?如果先运行A,则运行B。此时A进程缓存未命中,B进程缓存命中。返回A的时候,如果A无效,可能是无效的B命中的缓存?欢迎大家加入本大叔的私人VIP群