(2.6kernel)io_remap_page_range()与io_remap_pfn_range()

文章详细介绍了io_remap_page_range()函数的问题及从其过渡到io_remap_pfn_range()的过程。该转换旨在解决不同架构下I/O地址无法放入unsigned long变量的问题。

http://bbs.chinaunix.net/thread-2021443-1-1.html

io_remap_page_range() 现在不被推荐使用;使用io_remap_pfn_range() 来代替(http://lwn.net/Articles/129480/)。
详情可参考:http://lwn.net/Articles/129480/

io_remap_page_range() has always been a strange function. Its stated purpose is to portably map I/O memory into a process's address space. Its prototype has always differed from one system to the next, however, making portable use difficult. On most architectures it looks like this:

    int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr,
                            unsigned long phys_addr, unsigned long size, 
                            pgprot_t prot);

The sparc64 architecture, however, defines it this way:

    int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr,
                            unsigned long phys_addr, unsigned long size, 
                            pgprot_t prot, int space);

The extra argument (space) was necessary to deal with the inconvenient fact that I/O addresses on the sparc64 architecture would not fit into anunsigned long variable.

The change from remap_page_range() toremap_pfn_range() was done, in part, to address (so to speak) this issue. Since remapping must be done on a page-aligned basis anyway, there is no real point in using a regular physical address, which contains the offset within the page. Said offset, after all, must be zero. By using a page frame number instead, the range of the phys_addr argument is extended far enough to reach into I/O memory on all architectures. Theremap_pfn_range() work stopped short of actually fixing the io_remap_page_range() problem, however.

Randy Dunlap has now finished the task with a set of patches adding io_remap_pfn_range():

    int io_remap_pfn_range(struct vm_area_struct *vma, unsigned long from,
                           unsigned long pfn, unsigned long size, 
                           pgprot_t prot);

This function has the same prototype on all architectures. In-tree callers have been modified, and the feature removal schedule has been updated:io_remap_page_range() will go away in September, 2005.

<think>我们正在讨论Linux内核开发中的一个具体问题:使用dma_alloc_coherent分配DMA缓冲区,然后使用remap_pfn_range将其映射到用户空间。下面我将逐步解释这个过程。 ### 1. 分配DMA缓冲区 在内核中,我们使用`dma_alloc_coherent`函数来分配一块可以被DMA访问的连续内存。该函数的原型如下: ```c void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag); ``` - `dev`:设备指针,通常为`&pdev->dev`(平台设备)或`&pci_dev->dev`(PCI设备)。 - `size`:要分配的内存大小。 - `dma_handle`:返回的DMA总线地址(物理地址)。 - `flag`:分配标志,如`GFP_KERNEL`或`GFP_ATOMIC`。 分配成功返回内核虚拟地址,失败返回`NULL`。 ### 2. 建立用户空间映射 要将这块内存映射到用户空间,我们需要在驱动中实现mmap操作。通常,我们在文件操作结构`file_operations`中设置`.mmap`函数指针,指向我们自定义的mmap函数。 在自定义的mmap函数中,我们使用`remap_pfn_range`函数来建立映射。但是,由于`dma_alloc_coherent`分配的内存可能不在常规的内核直接映射区域(即不是通过`__get_free_pages`等分配),所以我们需要先获取到对应的页帧号(PFN)。 注意:`dma_alloc_coherent`返回的内核虚拟地址不能直接用于`remap_pfn_range`,因为该地址可能位于vmalloc区域(特别是当分配较大内存时)。因此,我们需要通过`dma_handle`(即物理地址)来计算PFN。 #### 步骤: 1. 在驱动中,我们保存分配的内核虚拟地址和对应的DMA物理地址(`dma_handle`)。 2. 在mmap函数中,使用`remap_pfn_range`将物理地址范围映射到用户空间。 #### 示例代码: 假设我们有一个设备结构体保存分配的信息: ```c struct my_device { void *dma_vaddr; // 内核虚拟地址 dma_addr_t dma_paddr; // 物理地址(总线地址) size_t dma_size; // 分配的大小 }; ``` 在mmap函数中: ```c static int my_mmap(struct file *filp, struct vm_area_struct *vma) { struct my_device *dev = filp->private_data; unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; unsigned long size = vma->vm_end - vma->vm_start; unsigned long pfn; // 检查映射范围是否超出分配的内存 if (offset + size > dev->dma_size) return -EINVAL; // 计算物理地址对应的页帧号(PFNpfn = dev->dma_paddr >> PAGE_SHIFT; // 使用remap_pfn_range建立映射 if (remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) return -EAGAIN; return 0; } ``` ### 3. 注意事项 - **缓存一致性**:`dma_alloc_coherent`分配的内存是非缓存的,因此不需要考虑缓存一致性问题。但是,如果使用其他方式分配(如`dma_alloc_wc`用于写合并),则需要注意。 - **内存释放**:在驱动卸载时,需要使用`dma_free_coherent`释放分配的内存。 - **权限**:在mmap中,用户空间的映射权限由`vma->vm_page_prot`决定,通常我们直接使用它。但有时可能需要调整,例如使用`pgprot_noncached`来确保非缓存访问。 ### 4. 引用说明 在站内引用中,提到通过调整`CONSISTENT_DMA_SIZE`来解决分配失败的问题。这是因为`dma_alloc_coherent`在ARM平台上使用的一致性内存区域大小有限制(默认为2MB),当分配超过这个限制时,会失败。因此,可以通过修改内核源码中的`CONSISTENT_DMA_SIZE`为更大的值(如4MB)来解决[^1]。但请注意,这需要重新编译内核。 ### 总结步骤 1. 使用`dma_alloc_coherent`分配DMA缓冲区,保存内核虚拟地址和物理地址。 2. 在驱动中实现mmap方法,使用`remap_pfn_range`将物理地址映射到用户空间。 3. 确保映射范围在分配的内存大小之内。 4. 在驱动卸载时释放内存。 §§相关问题§§ 1. 如何解决`dma_alloc_coherent`分配大内存失败的问题? 2. 除了`remap_pfn_range`,还有哪些方法可以将内核内存映射到用户空间? 3. 在ARM平台上使用`dma_alloc_coherent`有哪些注意事项?</think>在Linux内核开发中,使用`dma_alloc_coherent`分配DMA缓冲区并通过`remap_pfn_range`映射到用户空间的步骤如下: ### 1. **分配DMA缓冲区** 使用`dma_alloc_coherent`分配物理连续且缓存一致的内存: ```c void *dma_vaddr = dma_alloc_coherent(dev, size, &dma_paddr, GFP_KERNEL); if (!dma_vaddr) { // 处理分配失败 } ``` - `dev`:设备结构体指针(如`&pdev->dev`) - `size`:缓冲区大小(字节) - `dma_paddr`:返回的DMA物理地址 - `GFP_KERNEL`:内存分配标志 > 若分配失败,可调整`CONSISTENT_DMA_SIZE`扩大DMA内存池[^1]。 ### 2. **实现mmap操作** 在驱动文件的`file_operations`中注册mmap回调: ```c static struct file_operations fops = { .mmap = my_mmap, // 其他操作... }; ``` ### 3. **映射到用户空间** 在`my_mmap`函数中使用`remap_pfn_range`: ```c static int my_mmap(struct file *filp, struct vm_area_struct *vma) { unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; unsigned long pfn = dma_paddr >> PAGE_SHIFT; // 物理页帧号 unsigned long vsize = vma->vm_end - vma->vm_start; // 检查映射范围是否合法 if (offset + vsize > size) return -EINVAL; // 建立映射 if (remap_pfn_range(vma, vma->vm_start, pfn, vsize, vma->vm_page_prot)) return -EAGAIN; return 0; } ``` - `dma_paddr`:步骤1获得的物理地址 - `vma->vm_page_prot`:建议使用`pgprot_noncached()`禁用缓存 ### 4. **关键注意事项** 1. **缓存一致性** `dma_alloc_coherent`保证设备CPU的缓存一致性,无需额外操作。 2. **地址对齐** 物理地址按页对齐(`PAGE_SIZE`),映射大小需是页大小的整数倍。 3. **用户空间访问** 映射后用户程序可直接访问: ```c // 用户空间代码 void *user_addr = mmap(..., fd, 0); ``` 4. **释放资源** 驱动卸载时释放内存: ```c dma_free_coherent(dev, size, dma_vaddr, dma_paddr); ``` ### 5. **常见问题解决** - **分配失败**:检查`dmesg`错误日志,调整`CONSISTENT_DMA_SIZE`[^1] - **映射错误**:确保物理地址有效且未越界 - **性能问题**:大内存分配建议使用CMA(Contiguous Memory Allocator) > 此方法适用于需要零拷贝访问DMA缓冲区的场景(如视频采集、高速网络传输)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值