Linux内核接口remap_pfn_range的用法及示例
Linux内核中的remap_pfn_range
函数用于将物理内存映射到用户空间,常用于驱动程序的mmap
实现。以下是其用法及示例的详细说明:
函数原型
1 2 3 4 5 6 7
| int remap_pfn_range( struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot );
|
核心步骤
实现驱动的mmap
方法
在字符设备驱动的file_operations
结构中定义mmap
回调函数。
计算物理页帧号(PFN)
将物理地址转换为PFN:pfn = (phys_addr >> PAGE_SHIFT)
。
调整内存保护标志
根据硬件需求设置prot
,如禁用缓存(pgprot_noncached
)。
调用remap_pfn_range
建立用户空间虚拟地址到物理内存的映射。
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| #include <linux/fs.h> #include <linux/mm.h> #include <linux/module.h>
#define DEVICE_SIZE (4 * 1024)
static phys_addr_t my_dev_phys_addr; static void *my_dev_virt_addr;
static int my_mmap(struct file *filp, struct vm_area_struct *vma) { unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; unsigned long size = vma->vm_end - vma->vm_start;
if (offset + size > DEVICE_SIZE) { return -EINVAL; }
unsigned long pfn = (my_dev_phys_addr + offset) >> PAGE_SHIFT;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
if (remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) { return -EAGAIN; }
return 0; }
static struct file_operations my_fops = { .owner = THIS_MODULE, .mmap = my_mmap, };
static int __init my_init(void) { my_dev_virt_addr = dma_alloc_coherent(NULL, DEVICE_SIZE, &my_dev_phys_addr, GFP_KERNEL); if (!my_dev_virt_addr) { return -ENOMEM; }
return 0; }
static void __exit my_exit(void) { dma_free_coherent(NULL, DEVICE_SIZE, my_dev_virt_addr, my_dev_phys_addr); }
module_init(my_init); module_exit(my_exit); MODULE_LICENSE("GPL");
|
关键说明
物理地址获取
- 示例中使用
dma_alloc_coherent
分配物理连续内存,适用于DMA操作。
- 实际硬件驱动可能通过设备树或PCI BAR获取物理地址。
偏移量处理
用户空间的offset
参数对应vma->vm_pgoff
(页帧偏移),需转换为字节偏移量后再计算PFN。
保护标志调整
- 使用
pgprot_noncached
禁用缓存,确保直接访问硬件内存。
- 写合并(
pgprot_writecombine
)可优化某些设备的写入性能。
错误处理
- 检查映射范围是否越界(
DEVICE_SIZE
)。
- 若
remap_pfn_range
失败,返回-EAGAIN
或-EFAULT
。
用户空间调用示例
1 2 3 4 5 6 7 8 9 10
| int fd = open("/dev/mydev", O_RDWR); void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
*(uint32_t*)addr = 0x1234; uint32_t val = *(uint32_t*)addr;
munmap(addr, 4096); close(fd);
|
注意事项
- 对齐要求:
addr
和size
必须是页对齐的(PAGE_SIZE
的整数倍)。
- 安全性:仅映射允许用户空间访问的物理内存,避免泄露内核数据。
- 缓存一致性:若设备内存被CPU和外部硬件共享,需正确配置缓存策略。
通过合理使用remap_pfn_range
,驱动程序可高效实现用户空间与内核/硬件内存的交互。
上述示例不适用dma的接口
上述示例不使用dma的接口
不依赖DMA接口的remap_pfn_range
示例
以下是使用内核通用接口分配物理连续内存的示例,替代dma_alloc_coherent
,适用于不需要DMA的场景:
修改后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| #include <linux/fs.h> #include <linux/mm.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/gfp.h>
#define DEVICE_ORDER 0 #define DEVICE_SIZE (PAGE_SIZE * (1 << DEVICE_ORDER))
static struct page *my_dev_page = NULL; static phys_addr_t my_dev_phys_addr; static void *my_dev_virt_addr;
static int my_mmap(struct file *filp, struct vm_area_struct *vma) { unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; unsigned long size = vma->vm_end - vma->vm_start;
if (offset + size > DEVICE_SIZE) { return -EINVAL; }
unsigned long pfn = (my_dev_phys_addr + offset) >> PAGE_SHIFT;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
if (remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) { return -EAGAIN; }
return 0; }
static struct file_operations my_fops = { .owner = THIS_MODULE, .mmap = my_mmap, };
static int __init my_init(void) { my_dev_page = alloc_pages(GFP_KERNEL, DEVICE_ORDER); if (!my_dev_page) { return -ENOMEM; }
my_dev_phys_addr = page_to_phys(my_dev_page); my_dev_virt_addr = page_address(my_dev_page);
memset(my_dev_virt_addr, 0, DEVICE_SIZE);
return 0; }
static void __exit my_exit(void) { if (my_dev_page) { __free_pages(my_dev_page, DEVICE_ORDER); } }
module_init(my_init); module_exit(my_exit); MODULE_LICENSE("GPL");
|
关键修改说明
物理内存分配方式
- 使用
alloc_pages
代替dma_alloc_coherent
,直接分配物理连续的页。
DEVICE_ORDER
指定分配页数为2^ORDER
,此处为ORDER=0
(即1页,4KB)。
物理地址获取
- 通过
page_to_phys(my_dev_page)
将struct page
转换为物理地址。
- 内核虚拟地址通过
page_address(my_dev_page)
获取。
内存释放
- 使用
__free_pages
释放分配的物理页,需匹配alloc_pages
的order
参数。
缓存策略
- 通过
pgprot_noncached
禁用缓存,确保直接访问物理内存(适用于硬件寄存器或非缓存内存)。
用户空间调用(不变)
1 2 3 4 5 6 7 8 9 10
| int fd = open("/dev/mydev", O_RDWR); void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
*(uint32_t*)addr = 0x1234; uint32_t val = *(uint32_t*)addr;
munmap(addr, 4096); close(fd);
|
注意事项
物理连续性保证
alloc_pages
分配的内存是物理连续的,但若系统内存碎片化,可能分配失败(需处理-ENOMEM
)。
页对齐要求
remap_pfn_range
要求addr
和size
按页对齐(PAGE_SIZE
的整数倍)。
安全性与权限
- 确保用户空间程序只能访问允许的内存范围(通过
offset + size > DEVICE_SIZE
检查)。
- 根据硬件需求设置
vm_page_prot
(如PROT_READ
/PROT_WRITE
权限)。
替代接口选择
- 若需要分配多个非连续物理页,可改用
vmalloc
(虚拟连续但物理不连续),但需配合vmalloc_to_pfn
转换物理地址。
适用场景
- 硬件寄存器映射:直接访问设备寄存器。
- 自定义共享内存:用户空间与内核共享物理连续缓冲区。
- 性能敏感操作:通过禁用缓存提升访问速度。
通过此方案,开发者无需依赖DMA接口即可实现物理内存到用户空间的映射,适用于大多数非DMA场景。