结构体struct vm_area_struct :
当用户空间进程调用mmap,将设备内存映射到它的地址空间时,系统通过创建一个表示该映射的VMA(虚拟内存区)作为响应。支持mmap的驱动程序需要帮助进程完成VMA的初始化。
该结构中的重要成员:
unsigned long vm_start;
unsigned long vm_end;
该VMA所覆盖的虚拟地址范围。
struct file * vm_file;
指向与该区域相关联的file结构体指针。
unsigned long vm_pgoff;
以页为单位,文件中该区域的偏移量。当映射一个文件或设备时,它是该区域中被映射的第一页在文件中的位置。
unsigned long vm_flags;
描述该区域的一套标志。驱动程序最感兴趣的标志是VM_IO和VM_RESERVED。
VM_IO将VMA设置成一个内存映射IO区域。VM_IO会阻止系统将该区域包含在进程的核心转存中。VM_RESERVED告诉内存管理系统不要将该VMA交换出去;大多数设备映射中都设置该标志。
struct vm_operations_struct * vm_ops;
内核能调用的一套函数,用来对该内存区进行操作。它的存在表示内存区域是一个内核“对象”。
一个驱动程序只映射与其外围设备相关的一小段地址,而不是映射全部地址的例子。为了向用户空间只映射部分内存的需要,驱动程序只需要使用偏移量即可。下面的代码揭示了驱动程序如何对起始于物理地址simple_region_start(页对齐),大小为simple_region_size字节的区域进行映射的工作过程:
(注:“外围设备” 即是设备文件。 vm_pgoff; 为要映射区域在“外围设备”文件中的偏移量(以页为单位),“外围设备”文件即是指外围设备的一段内存。这个偏移量即使相对于这段内存开始的偏移量)
unsigned long off = vma->vm_pgoff << PAGE_SHIFT; //以页为单位转为以字节为单位
unsigned long physical = simple_region_start + off; //计算相对于文件开始的偏移
unsigned long vsize = vma->vm_end - vma->vm_start; //要映射的区域大小
unsigned long psize = simple_region_size - off; //偏移之后文件的剩余空间
if (vsize > psize) //是否偏移之后文件的剩余空间不足以满足要映射的空间大小
return -EINVAL; /* 跨度过大*/
remap_pfn_range(vma, vma_>vm_start, physical, vsize, vma->vm_page_prot);
(注:我认为physical应向右移PAGE_SHIFT,因为函数此处为页帧号)
关于函数 remap_pfn_range 和 io_remap_page_range:
建立新页来映射物理地址的工作由 remap_pfn_range 和 io_remap_page_range 来处理, 它们有下面的原型:
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);
int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long phys_addr, unsigned long size, pgprot_t prot);
由这个函数返回的值常常是 0 或者一个负的错误值. 让我们看看这些函数参数的确切含义:
vma
页范围被映射到的虚拟内存区
virt_addr
重新映射应当开始的用户虚拟地址. 这个函数建立页表为这个虚拟地址范围从 virt_addr 到 virt_addr_size.
pfn
页帧号, 对应虚拟地址应当被映射的物理地址. 这个页帧号简单地是物理地址右移 PAGE_SHIFT 位. 对大部分使用, VMA 结构的 vm_paoff 成员正好包含你需要的值. 这个函数影响物理地址从 (pfn<到 (pfn<
size
正在被重新映射的区的大小, 以字节.
prot
给新 VMA 要求的"protection". 驱动可(并且应当)使用在 vma->vm_page_prot 中找到的值.
====
http://chxxxyg.blog.163.com/blog/static/15028119320103982342980/