remap_pfn_range()详解

本文深入解析Linux内核中remap_pfn_range函数的原型、功能及其在驱动程序中的具体应用,包括如何用于映射内存中的保留页和设备I/O内存。详细介绍了struct vm_area_struct结构体的定义与作用,以及vm_area_struct结构如何描述虚存空间和映射文件数据。同时,阐述了不同共享模式下文件数据的映射差异,以及vm_area_struct结构在内存管理中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

remap_pfn_range()函数的原型:
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);
该函数的功能是创建页表。其中参数vma是内核根据用户的请求自己填写的,而参数addr表示内存映射开始处的虚拟地址,因此,该函数为addr~addr+size之间的虚拟地址构造页表。
另外,pfn(Page Fram Number)是虚拟地址应该映射到的物理地址的页面号,实际上就是物理地址右移PAGE_SHIFT位。如果PAGE_SHIFT为4kb,则PAGE_SHIFT为12,因为PAGE_SHIFT等于1<<PAGE_SHIFT。最后一个参数prot是新页所要求的保护属性。
在驱动程序中,一般能使用remap_pfn_range()映射内存中的保留页(如X86系统中的640KB~1MB区域)和设备I/O内存。因此,如果想把kmalloc()申请的内存映射到用户空间,则可以通过mem_map_reserve()把相应的内存设置为保留后就可以。

下面是struct vm_area_struct结构体的定义:

QUOTE:
/*
* This struct defines a memory VMM memory area. There is color: black; background-color: #a0ffff;">vm_area_struct {
struct mm_struct * vm_mm; /* VM area parameters */
unsigned long vm_start;
unsigned long vm_end;
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next;
pgprot_t vm_page_prot;
unsigned long vm_flags;
/* AVL tree of VM areas per task, sorted by address */
short vm_avl_height;
struct vm_area_struct * vm_avl_left;
struct vm_area_struct * vm_avl_right;
/* For areas with an address space and backing store,
* font-size: 10px;">vm_area_struct *vm_next_share;
struct vm_area_struct **vm_pprev_share;
struct vm_operations_struct * vm_ops;
unsigned long vm_pgoff; /* offset in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */
struct file * vm_file;
unsigned long vm_raend;
void * vm_private_data; /* was vm_pte (shared mem) */
};

vm_area_struct结构所描述的虚存空间以vm_start、vm_end成员表示,它们分别保存了该虚存空间的首地址和末地址后第一个字节的地址,以字节为单位,所以虚存空间范围可以用[vm_start, vm_end)表示。

  通常,进程所使用到的虚存空间不连续,且各部分虚存空间的访问属性也可能不同。所以一个进程的虚存空间需要多个vm_area_struct结构来描述。在vm_area_struct结构的数目较少的时候,各个vm_area_struct按照升序排序,以单链表的形式组织数据(通过vm_next指针指向下一个vm_area_struct结构)。但是当vm_area_struct结构的数据较多的时候,仍然采用链表组织的化,势必会影响到它的搜索速度。针对这个问题,vm_area_struct还添加了vm_avl_hight(树高)、vm_avl_left(左子节点)、vm_avl_right(右子节点)三个成员来实现AVL树,以提高vm_area_struct的搜索速度。

  假如该vm_area_struct描述的是一个文件映射的虚存空间,成员vm_file便指向被映射的文件的file结构,vm_pgoff是该虚存空间起始地址在vm_file文件里面的文件偏移,单位为物理页面。

  一个程序可以选择MAP_SHARED或MAP_PRIVATE共享模式将一个文件的某部分数据映射到自己的虚存空间里面。这两种映射方式的区别在于:MAP_SHARED映射后在内存中对该虚存空间的数据进行修改会影响到其他以同样方式映射该部分数据的进程,并且该修改还会被写回文件里面去,也就是这些进程实际上是在共用这些数据。而MAP_PRIVATE映射后对该虚存空间的数据进行修改不会影响到其他进程,也不会被写入文件中。

  来自不同进程,所有映射同一个文件的vm_area_struct结构都会根据其共享模式分别组织成两个链表。链表的链头分别是:vm_file->f_dentry->d_inode->i_mapping->i_mmap_shared, vm_file->f_dentry->d_inode->i_mapping->i_mmap。而vm_area_struct结构中的vm_next_share指向链表中的下一个节点;vm_pprev_share是一个指针的指针,它的值是链表中上一个节点(头节点)结构的vm_next_share(i_mmap_shared或i_mmap)的地址。

  进程建立vm_area_struct结构后,只是说明进程可以访问这个虚存空间,但有可能还没有分配相应的物理页面并建立好页面映射。在这种情况下,若是进程执行中有指令需要访问该虚存空间中的内存,便会产生一次缺页异常。这时候,就需要通过vm_area_struct结构里面的vm_ops->nopage所指向的函数来将产生缺页异常的地址对应的文件数据读取出来。

  vm_flags主要保存了进程对该虚存空间的访问权限,然后还有一些其他的属性。vm_page_prot是新映射的物理页面的页表项pgprot的默认值。

例子:

QUOTE:

int memdev_mmap(struct file *filp, struct vm_area_struct *vma)

{

    vma->vm_flags |= VM_IO;

    vma->vm_flags |= VM_RESERVED;

    if(remap_pfn_range(vma, vma->vm->start, virt_to_phys(dev->data)>>PAGE_SHIFT, size, vma->vm_page_prot))

        return -EAGAIN;

return 0;  

}

<think>我们正在讨论Linux内核中的remap_pfn_range函数,特别是它用于重新映射RAM(物理内存)的使用方法和技术细节。根据引用,该函数用于为一段虚拟地址空间创建页表映射,将物理内存(包括保留页或设备I/O内存)映射到用户空间或内核空间。引用[1]详细说明了函数的参数:-`structvm_area_struct*vma`:由内核根据用户请求填充的虚拟内存区域描述符。-`unsignedlongvirt_addr`:映射开始的虚拟地址(用户空间或内核空间)。-`unsignedlongpfn`:物理地址的页面帧号(物理地址右移PAGE_SHIFT位)。-`unsignedlongsize`:映射区域的大小(字节)。-`pgprot_tprot`:新页的保护属性。引用[1]和[3]指出,该函数通常用于映射保留页(如X86系统的640KB~1MB区域)或设备I/O内存。如果要将`kmalloc()`申请的内存映射到用户空间,需要先用`mem_map_reserve()`(实际应为`SetPageReserved`)将页面标记为保留,以防止被系统回收。引用[2]补充了关于物理内存分配和映射的细节:从低端内存分配的页面可以用`page_address()`直接获取内核虚拟地址,而高端内存则需要用`kmap()`映射到内核的kmap区域。引用[4]提供了一个示例场景:在启动时保留一段内存,同时使用`ioremap`(用于I/O内存)映射到内核空间,再用`remap_pfn_range`映射到用户空间,实现内核和用户空间共享同一段物理内存。现在,针对用户的问题“remap_pfn_range函数重新映射RAMLinux内核内存管理”,我们需要解释如何用该函数映射RAM(常规物理内存)。###步骤和技术细节:1.**分配物理内存**:-使用`kmalloc`、`get_free_pages`等函数分配连续物理内存(返回的是内核虚拟地址)。-或者使用`alloc_pages`分配物理页面(返回`structpage*`)。2.**获取物理页帧号(PFN)**:-如果通过`kmalloc`等获得内核虚拟地址`addr`,则用`virt_to_phys(addr)`得到物理地址,再通过`phys_to_pfn`或直接右移`PAGE_SHIFT`得到PFN。-如果通过`alloc_pages`获得`structpage*`,则用`page_to_pfn(page)`得到PFN。3.**标记页面为保留(可选但推荐)**:-对于通过`kmalloc`等分配的页面,默认不属于保留页,但为了安全(防止被换出或回收),通常需要标记为保留:```cSetPageReserved(virt_to_page(addr));//对每个分配的页面```4.**在驱动中实现mmap操作**:-在驱动程序的`file_operations`结构中定义`.mmap`操作,指向自定义的`my_mmap`函数。-在`my_mmap`函数中调用`remap_pfn_range`建立映射。###示例代码(简化):```c#include<linux/mm.h>#include<linux/slab.h>staticchar*kernel_buffer;//分配的内核缓冲区指针staticintmy_mmap(structfile*filp,structvm_area_struct*vma){unsignedlongoffset=vma->vm_pgoff<<PAGE_SHIFT;unsignedlongpfn;unsignedlongsize=vma->vm_end-vma->vm_start;intret;//计算缓冲区对应的PFN(假设已经分配了kernel_buffer)pfn=virt_to_phys(kernel_buffer+offset)>>PAGE_SHIFT;//设置保护标志(根据vma->vm_flags调整)vma->vm_page_prot=pgprot_writecombine(vma->vm_page_prot);//建立映射ret=remap_pfn_range(vma,vma->vm_start,pfn,size,vma->vm_page_prot);if(ret){return-EAGAIN;}return0;}staticstructfile_operationsfops={.mmap=my_mmap,//其他操作...};staticint__initmy_init(void){//分配4KB物理连续内存kernel_buffer=kmalloc(4096,GFP_KERNEL);if(!kernel_buffer)return-ENOMEM;//标记分配的页面为保留(每页都需要标记)SetPageReserved(virt_to_page(kernel_buffer));//如果分配多页,需要循环标记//注册驱动...return0;}```###注意事项:1.**保留页的作用**:防止内核将这部分内存用于其他用途(如页面缓存或交换)。如果不标记为保留,当系统内存紧张时,内核可能回收这些页面,导致用户空间访问错误。2.**映射大小**:`remap_pfn_range`映射的大小必须是PAGE_SIZE的整数倍。3.**保护属性**:可以通过`pgprot_writecombine`等宏修改保护属性(例如,对于显存映射,可能需要合并写操作)。4.**高端内存**:如果物理内存位于高端内存区域,需要确保在映射期间页面被正确固定(使用`kmap`或类似函数),但`remap_pfn_range`本身不要求页面在内核空间有永久映射。###相关问题:1.`remap_pfn_range`和`io_remap_pfn_range`有什么区别?2.如何将非连续物理内存映射到用户空间?3.在映射物理内存到用户空间时,如何确保内存安全(防止用户空间程序破坏内核数据)?</think>### `remap_pfn_range` 函数在 Linux 内核内存管理中的应用 `remap_pfn_range()` 是 Linux 内核中用于建立**物理内存到虚拟地址空间映射**的关键函数,常用于设备驱动和内核模块开发。以下是针对 RAM 重新映射的核心技术细节: #### 一、函数原型与参数解析 ```c int remap_pfn_range( struct vm_area_struct *vma, // 目标虚拟内存区域 (VMA) unsigned long virt_addr, // 映射起始的虚拟地址 unsigned long pfn, // 物理页帧号 (Page Frame Number) unsigned long size, // 映射区域大小 (字节) pgprot_t prot // 页面保护属性 ); ``` - **关键参数**: 1. `pfn`:物理页帧号 = `物理地址 >> PAGE_SHIFT`(如 4KB 页时 `PAGE_SHIFT=12`) 2. `virt_addr`:用户空间/内核空间的虚拟地址起始点 3. `vma`:由内核自动填充,描述用户请求的映射区域[^1] #### 二、映射 RAM 的步骤 1. **分配物理内存**: ```c char *kernel_buf = kmalloc(size, GFP_KERNEL); // 分配连续物理内存 ``` 2. **获取物理页帧号 (PFN)**: ```c unsigned long pfn = virt_to_phys(kernel_buf) >> PAGE_SHIFT; ``` 3. **标记保留页**(防止被系统回收): ```c SetPageReserved(virt_to_page(kernel_buf)); // 标记为保留页[^1] ``` 4. **在驱动中实现 mmap 操作**: ```c static int my_mmap(struct file *filp, struct vm_area_struct *vma) { return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot); } ``` #### 三、关键注意事项 1. **内存类型**: - **RAM 映射**:需先通过 `kmalloc`/`alloc_pages` 分配物理内存[^2] - **I/O 内存**:需用 `ioremap()` 处理后再映射[^3] 2. **高端内存处理**: ```c struct page *page = alloc_pages(GFP_KERNEL, order); // 分配高端内存 void *kaddr = kmap(page); // 临时映射到内核空间[^2] unsigned long pfn = page_to_pfn(page); ``` 3. **保护属性配置**: - 典型设置:`pgprot_writecombine()` 用于显存等设备[^4] - 禁止缓存:`vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);` #### 四、典型应用场景 1. **用户空间共享内核内存**: ```c // 驱动中 remap_pfn_range(vma, vma->vm_start, pfn, size, prot); // 用户空间可直接访问 mmap 返回的地址 ``` 2. **设备寄存器映射**(需结合 `ioremap`): ```c void __iomem *regs = ioremap(phy_addr, size); pfn = PHYS_PFN(phy_addr); // 物理地址转 PFN ``` 3. **保留内存区域访问**(如 X86 的 640KB-1MB 区域)[^1] #### 五、错误处理与验证 1. **返回值检查**: ```c if (remap_pfn_range(...)) { printk(KERN_ERR "映射失败"); return -EAGAIN; } ``` 2. **地址对齐要求**: - `virt_addr` 和 `size` 必须页面对齐(`PAGE_SIZE` 倍数) - 物理地址需连续(非连续内存需多次调用或使用 `vm_insert_page`) > **重要提示**:映射后需在模块卸载时释放内存并清除保留标记[^1]: > ```c > kfree(kernel_buf); > ClearPageReserved(virt_to_page(kernel_buf)); > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值