dma_mmap_coherent和remap_pfn_range有什么区别?

9

目前,我正在使用一个示例驱动程序进行学习,并以此为基础创建了自己的自定义驱动程序。 mmap代码几乎完全相同,除了我允许用户管理其自己请求的大小并围绕此进行内存分配,以及我自动在 /dev 中创建字符设备。

为了解释上下文,对于我的用例,我想解决一个问题。当使用 kmalloc 分配的内存时,dma_mmap_coherent 可以正常工作,但是当我有一个保留的物理地址区域要使用 remap_pfn_range 时,它似乎静默地工作,并且 dmesg 不报告任何错误,但是当我尝试读取时,无论我写了什么,它总是返回 0xff 字节。这适用于在 ioremap 内存后在内核空间中使用 iowrite 和 ioread 或尝试使用小型 mmap 用户空间测试在用户空间中编写。

我认为我已经尽可能多地研究了这个主题。关于 remap_pfn_range 的所有文档都可以在kernel.org 页面和一些内核 gmain 邮件列表存档中找到,这些存档讨论了 remap_pfn_range 替换 remap_page_range 的问题。至于 dma_mmap_coherent,我能够找到更多一些的信息,包括来自 linux 存档的演示文稿

最终必须有一个区别;似乎有很多不同的方式将内核内存映射到用户空间中。我特别关心的问题是:dma_mmap_coherent 和 remap_pfn_range 之间有什么区别?

编辑 可以提供一个映射内核内存到用户空间的一般概述,涵盖如何在内核驱动程序 mmap 回调中使用不同的 API。


你最终解决了这个问题吗?也就是说,你是否成功地同时使用了mmap和dma_mmap_coherent?我无法做到这一点,所以我自己编写了读写函数。 - user1876942
是的,我确实使用了那些特定的函数。你需要一个例子吗?我特别在寻找remap_pfn_range和dma_mmap_coherent之间的区别,以便我知道应该走哪条路。 - Adam Miller
如果您能够发布一个工作示例,那就太好了。我猜用户端的代码是标准的,而kmod是我出错的地方。 - user1876942
当我有时间的时候,我会发一些东西。我相当确定我也是通过一个例子来学习的,但我很快就会制作和测试一个。你使用的是什么内核版本? - Adam Miller
哦,糟糕,我没有使用那个内核版本。API 发生了巨大的变化;我自己也没有看到过那个内核版本的示例。 - Adam Miller
1
看看这个:https://github.com/martinezjavier/ldd3 - Adam Miller
1个回答

7

dma_mmap_coherent()在dma-mapping.h中定义,作为dma_mmap_attrs()的包装器。dma_mmap_attrs()尝试查看是否与您正在操作的设备(struct device *dev)相关联的一组dma_mmap_ops,如果没有,则调用dma_common_mmap(),最终导致调用remap_pfn_range(),并将页面保护设置为非缓存(请参见dma-mapping.c中的dma_common_mmap())。

至于从用户空间映射内核内存的概述,以下是我从用户空间映射DMA缓冲区的快速简单方法:

  1. Allocate a Buffer via an IOCTL and designate a buffer ID for each buffer with some flags:

    /* A copy-from-user call needs to be done before in the IOCTL */
    static int my_ioctl_alloc(struct my_struct *info, struct alloc_info *alloc)
    {
    
            ...
            info->buf->kvaddr = dma_alloc_coherent(dev, alloc->size, info->buf->phyaddr, GFP_KERNEL);
            info->buf->buf_id = alloc->buf_id;
            ...
    }
    
  2. Define an mmap file ops :

    static const struct file_operations my_fops = {
            .open = my_open,
            .close = my_close,
            .mmap = my_mmap,
            .unlocked_ioctl = my_ioctl,
    };
    

    Do not forget to register the my_fops struct somewhere in your driver's probe function.

  3. Implement mmap file ops :

     static int my_mmap(struct file *fptr, struct vm_area_struct *vma)
     {
             ...
             desc_id = vma->vm_pgoff;
             buf = find_buf_by_id(alloc, desc_id);
             vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
             ret = remap_pfn_range(vma, vma->vm_start, buf->phyaddr >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot);
             if (ret) {
                  /* Error Handle */
             }
             return 0;
     }
    
使用这个,你的内核驱动程序应该具有分配和映射缓冲区所需的最小限度。释放缓冲区是额外加分的练习!
在应用中,您将打开()文件并获取有效的文件描述符fd,在执行复制到内核之前调用分配IOCTL并设置缓冲区ID。在mmap中,您将通过偏移参数提供缓冲区ID:
      mmap(NULL, buf_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buffer_id << PAGE_SHIFT);

PAGE_SHIFT是内核中一个与体系结构相关的编译时宏定义。希望这可以帮到您。

这不是符合checkpatch.pl标准的代码,也不是最佳实践,但这是我知道的一种方法。欢迎评论/改进/建议!

有兴趣的读者可以参考《Linux设备驱动程序》第15章:内存映射和DMA,了解教科书示例和背景信息。


1
这个答案真的帮了我很多,谢谢! - Marco Merlini
我不是驱动程序高手,但基本上是通过浏览 Linux 内核来找到哪个 DMA 调用映射到了哪里。当需要连续的缓冲区或物理地址时,编写内核驱动程序通常没有捷径可走! - hit.at.ro
你为什么说这不是好的实践方法?性能?稳定性?那最佳实践是什么? - Alexis
理想情况下,如果您的目标是执行 DMA 操作,并且您具有使用分散-聚集列表的能力,则最好跳过分配内核内存,而只是执行从用户空间内存到 DMA 的操作。或者,如果可能的话,请通过使用 HugePages 和 HugeTLB 来完全跳过 Linux VM。在某种意义上,这将更好地避免了导航 Linux VM 子系统的麻烦。 - hit.at.ro
@hit.at.ro 谢谢,这听起来很有趣。你能提供一些链接,介绍如何直接将DMA(实际上是PCIe总线主控)执行到用户空间内存吗?我们仍然需要分配非缓存DMA可用内存,这在用户空间中是否可能?对于相对较小的缓冲区大小,我认为不需要散布-聚集列表,使用4KB页面大小的连续内存拆分就可以了。 - Alexis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接