答案 0 :(得分:9)
这是我用过的,简而言之......
get_user_pages
固定用户页面并为您提供一组struct page *
指针。
dma_map_page
上的 struct page *
以获取页面的DMA地址(也称为“I / O地址”)。这也会创建一个IOMMU映射(如果您的平台需要)。
现在告诉您的设备使用这些DMA地址将DMA执行到内存中。显然它们可能是不连续的;内存只能保证在页面大小的倍数内是连续的。
dma_sync_single_for_cpu
执行任何必要的缓存刷新或反弹缓冲区blitting等等。这个调用保证了CPU实际上可以看到DMA的结果,因为在许多系统上,修改CPU后面的物理RAM会导致过时的缓存。
dma_unmap_page
释放IOMMU映射(如果您的平台需要它)。
put_page
取消固定用户页面。
请注意,必须一直检查错误,因为整个地方的资源都很有限。 get_user_pages
返回一个完全错误的负数(-errno),但它可以返回一个正数,告诉你它实际管理多少个页面(物理内存不是无限的)。如果这比你要求的要少,你仍然必须遍历做引脚的所有页面,以便在它们上面调用put_page
。 (否则你正在泄漏内核内存;非常糟糕。)
dma_map_page
也可以返回错误(-errno),因为IOMMU映射是另一种有限的资源。
dma_unmap_page
和put_page
返回void
,像往常一样用于Linux“释放”功能。 (Linux内核资源管理例程只返回错误,因为实际上出了问题,不是因为你搞砸了并传递了一个坏指针或什么的。基本的假设是你从不搞砸了,因为这是内核代码虽然get_user_pages
会检查以确保用户地址的有效性,但如果用户向您发送了错误的指针,则会返回错误。)
如果您想要一个友好的界面来分散/收集,您还可以考虑使用_sg函数。然后,您可以拨打dma_map_sg
而不是dma_map_page
,dma_sync_sg_for_cpu
而不是dma_sync_single_for_cpu
等。
另请注意,这些功能中的许多功能可能会在您的平台上出现或多或少的无操作,因此您可以经常摆脱草率。 (特别是,dma_sync _...和dma_unmap _...在我的x86_64系统上什么都不做。)但是在这些平台上,调用本身都没有被编译成任何东西,所以没有任何借口可以马虎。
答案 1 :(得分:8)
好的,这就是我做的。
免责声明:我是纯粹意义上的黑客,我的代码并不是最漂亮的。
我阅读了LDD3和infiniband源代码以及其他前任的东西,并决定get_user_pages
并固定它们以及所有其他的钻孔工作在饥饿时考虑太痛苦。此外,我正在与PCIe总线上的另一个人合作,我还负责“设计”用户空间应用程序。
我编写了驱动程序,以便在加载时通过调用函数myAddr[i] = pci_alloc_consistent(blah,size,&pci_addr[i])
来预先分配尽可能多的缓冲区,直到它失败。 (失败 - > myAddr[i]
是NULL
我想,我忘了)。我能够分配大约2.5GB的缓冲区,每个4MiB的大小在我的微型机器中,只有4GiB的内存。缓冲区总数取决于加载内核模块的时间。在引导时加载驱动程序并分配大多数缓冲区。在我的系统中,每个缓冲区的大小最大为4MiB。不知道为什么。我cat
ted /proc/buddyinfo
以确保我没有做任何愚蠢的事情,这当然是我常用的开始模式。
然后,驱动程序继续将pci_addr
的数组连同它们的大小一起提供给PCIe设备。然后司机坐在那里等待中断风暴开始。同时在用户空间中,应用程序打开驱动程序,查询已分配缓冲区的数量(n)及其大小(使用ioctl
或read
等),然后继续调用系统调用{{1多次(n)次。当然mmap()
必须在驱动程序中正确实现,LDD3页面422-423很方便。
用户空间现在有n个指向驱动程序内存区域的指针。当驱动程序被PCIe设备中断时,它会被告知哪些缓冲区“已满”或“可用”被吸干。该应用程序依次在mmap()
或read()
上被告知哪些缓冲区充满了有用的数据。
棘手的部分是管理用户空间到内核空间同步,这样PCIe进入DMA的缓冲区也不会被用户空间修改,但这就是我们付出的代价。我希望这是有道理的,我很高兴被告知我是个白痴,但请告诉我原因。
我还推荐这本书:http://www.amazon.com/Linux-Programming-Interface-System-Handbook/dp/1593272200。七年前,当我写第一个Linux驱动程序时,我希望我有那本书。
通过添加更多内存并且不让内核使用它并且ioctl()
ping用户空间/内核空间划分的两边,还有另一种类型的技巧可能,但PCI设备也必须支持高于32位DMA寻址。我没有尝试,但如果我最终被迫,我不会感到惊讶。
答案 2 :(得分:5)
好吧,如果您有LDD,您可以查看第15章,更准确地说是第435页,其中描述了直接I / O操作。
可以帮助您实现此目的的内核调用是get_user_pages
。在您的情况下,因为您要将数据从内核发送到用户空间,所以应将write标志设置为1。
还要注意,异步I / O可能允许您实现相同的结果,但是您的用户空间应用程序不必等待读取完成,这可能会更好。
答案 3 :(得分:4)
好好看看Infiniband驱动程序。他们付出了很多努力,将零拷贝DMA和RDMA用于用户空间工作。
我忘了在保存之前添加这个:
直接对用户空间内存映射执行DMA很多问题,因此除非您有非常高的性能要求,如Infiniband或10 Gb以太网,否则不要这样做。而是将DMA'd数据复制到用户空间缓冲区。它会为你带来很多悲伤。
仅举一个例子,如果用户的程序在DMA完成之前退出怎么办?如果用户内存在退出后重新分配给另一个进程但硬件仍然设置为DMA到该页面怎么办?灾难!
答案 4 :(得分:2)
remap_pfn_range函数(用于驱动程序中的mmap调用)可用于将内核内存映射到用户空间。
可以在mem字符驱动程序drivers/char/mem.c中找到一个真实的例子。