dma_mmap_coherent和remap_pfn_range有什么区别?

Ada*_*ler 7 c linux linux-kernel

目前,我正在使用一个示例驱动程序来学习,并从中我自己的自定义驱动程序.mmap代码几乎完全相同,除了我允许用户管理他们自己的请求大小并围绕它进行内存分配以及我在/ dev中自动创建char设备这一事实.

为了解释上下文,对于我的用例,我想缩小我遇到的问题.dma_mmap_coherent在使用kmalloc内存时可以正常工作,但是当我有一个保留的物理地址区域时,我想使用remap_pfn_range,它可以安静地工作,而dmesg不会报告任何错误,但是当我去阅读时,无论如何我写在那里它总是返回0xff字节.无论我在ioremap内存之后使用iowrite和ioread在内核中使用还是尝试使用小型mmap'ing用户态测试在userland中编写,都是如此.

我已经做了尽可能多的关于这个主题的研究.我能找到的有关remap_pfn_range的文档的是kernel.org页面,而一些内核gmain邮件列表存档在remap_pfn_range上,取代了remap_page_range.至于dma_mmap_coherent,我能够找到更多,包括来自linux档案的演示文稿.

最终必须有所不同; 似乎有很多不同的方法将内核内存映射到用户区.我的具体问题是:dma_mmap_coherent和之间有什么区别remap_pfn_range

编辑它可能很好地提供一般概述将内核内存映射到用户区的方法,包括如何在内核驱动程序mmap回调中使用不同的apis.

hit*_*.ro 6

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

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

  1. 通过 IOCTL 分配缓冲区并为每个缓冲区指定一个缓冲区 ID,并带有一些标志:

    /* 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;
            ...
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 定义一个 mmap 文件 ops :

    static const struct file_operations my_fops = {
            .open = my_open,
            .close = my_close,
            .mmap = my_mmap,
            .unlocked_ioctl = my_ioctl,
    };
    
    Run Code Online (Sandbox Code Playgroud)

    不要忘记在驱动程序的探测函数中的某处注册 my_fops 结构。

  3. 实现 mmap 文件操作:

     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;
     }
    
    Run Code Online (Sandbox Code Playgroud)

有了这个,你的内核驱动程序应该有最少的分配和 mmap 缓冲区。释放缓冲区是获得奖励积分的练习!

在应用程序中,您将 open() 文件并获取有效的文件描述符 fd,在执行复制到内核之前调用分配 IOCTL 并设置缓冲区 ID。在 mmap 中,您将通过 offset 参数给出缓冲区 ID:

      mmap(NULL, buf_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buffer_id << PAGE_SHIFT);
Run Code Online (Sandbox Code Playgroud)

PAGE_SHIFT 是内核中固定的架构相关编译时宏。希望这可以帮助。

这不是符合 checkpatch.pl 的代码,也不是最佳实践,但这是我知道如何做到这一点的一种方式。欢迎评论/改进/建议!

请参阅 Linux 设备驱动程序 - 第 15 章:内存映射和 DMA 以获取教科书示例和为感兴趣的读者提供的良好背景信息。