这一篇是说mmap()的另一种实现方法,利用remap_pfn_page一次性映射。
先简单看看remap_pfn_page的源代码吧。这个代码有些函数基于平台基于版本。我的平台:arm920tlinux-3.2.36。
先对参数解读:
vma:用户层使用的vma
addr:用户的起始地址
pfn:内核空间的物理地址(内核这么写的)。我觉得只能说是内核空间地址。
size:映射大小
prot:页保护标志。
int remap_pfn_range(struct vm_area_struct*vma, unsigned long addr, unsigned long pfn, unsignedlong size, pgprot_t prot) { pgd_t *pgd; unsigned long next; unsigned long end = addr + PAGE_ALIGN(size); struct mm_struct *mm = vma->vm_mm; int err; /* VM_IO: 标志一个 VMA 作为内存映射的 I/O 区. 在其他方面, VM_IO 标志阻止这个区被包含在进程核转储中 VM_RESERVED: 告知内存管理系统不要试图交换出这个 VMA; 它应当在大部分设备映射中设置. VM_PFNMAP:纯粹的PFN,内存管理不要用struct page管理。 */ if (addr == vma->vm_start && end == vma->vm_end) {//整个vma vma->vm_pgoff = pfn; vma->vm_flags |=VM_PFN_AT_MMAP;//全映射 //VM_PFN_AT_MMAP: PFNMAP vma that is fullymapped at mmap time } else if (is_cow_mapping(vma->vm_flags))//是否为写时拷贝机制。 /* COW判判断的标志为:VM_MAYWRITE; */ return -EINVAL; vma->vm_flags |= VM_IO | VM_RESERVED | VM_PFNMAP; err = track_pfn_vma_new(vma, &prot, pfn, PAGE_ALIGN(size));//arm上它是一个空函数。用于跟踪PFN映射的内存类型。可以看看x86上干了什么,arm是空,就不看了。 if (err) { vma->vm_flags &= ~(VM_IO| VM_RESERVED | VM_PFNMAP); vma->vm_flags &=~VM_PFN_AT_MMAP; return -EINVAL; } BUG_ON(addr >= end); pfn -= addr >> PAGE_SHIFT;//这里减去了,下面又加上传入remap_pud_range里面处理。在下面的操作中,唯一有影响的是untrack_pfn_vma(),这个函数对应上面的track_pfn_vma_new(),在arm平台也是空。 pgd = pgd_offset(mm, addr);//全局目录页地址:(mm->pgd+addr>>PGDIR_SHIFT)(PGDIR_SHIFT :21) flush_cache_range(vma, addr, end); do { next = pgd_addr_end(addr, end); //公式:(addr + PGDIR_SIZE)& PGDIR_MASK //下面是pud,在内存初始化时我就说个这个一层一层的操作: //pdg->pud->pmd->pte,这个过程就不详细分析了。我之前的文章有简单的分析。 err = remap_pud_range(mm, pgd,addr, next, pfn + (addr>> PAGE_SHIFT), prot); if (err) break; } while (pgd++, addr = next, addr != end); if (err) untrack_pfn_vma(vma, pfn,PAGE_ALIGN(size)); return err; }
现在有个vma,内核有个起始地址是sh_mem。
那么我们看传入的对应参数:
vma: vma
addr: vma->vm_start
pfn: page_to_pfn(virt_to_page((unsignedlong)sh_mem + (vma->vm_pgoff << PAGE_SHIFT)));我的是kmalloc分配的,如果用vmalloc的话,可以考虑vmalloc_to_pfn((unsignedlong)sh_mem + (vma->vm_pgoff << PAGE_SHIFT)),我没试过,如用需要,好自为之。
size: vma->vm_end – vma->vm_start
prot: vma->vm_page_prot
下面是简单的例子:
rpr就是remap_pfn_range简写。
#include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/slab.h> #include <linux/mm.h> #include <linux/miscdevice.h> #define DEV_NAME "rpr" #define SHARE_MEM_SIZE (PAGE_SIZE * 2) static char *sh_mem = NULL; static struct vm_operations_struct rpr_vm_ops; static int rpr_mmap(struct file *filp, struct vm_area_struct *vma) { int ret = 0; struct page *page = NULL; unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start); if (size > SHARE_MEM_SIZE) { ret = -EINVAL; goto fail; } page = virt_to_page((unsigned long)sh_mem + (vma->vm_pgoff << PAGE_SHIFT)); ret = remap_pfn_range(vma, vma->vm_start, page_to_pfn(page), size, vma->vm_page_prot); if (ret) { goto fail; } vma->vm_ops = &rpr_vm_ops; return 0; fail: return ret; } static struct file_operations rpr_fops = { .owner = THIS_MODULE, .mmap = rpr_mmap, }; static struct miscdevice rpr_dev = { MISC_DYNAMIC_MINOR, DEV_NAME, &rpr_fops, }; static int rpr_init(void) { int result; result = misc_register(&rpr_dev); if (result) { return result; } sh_mem = kmalloc(SHARE_MEM_SIZE, GFP_KERNEL); if (sh_mem == NULL) { result = -ENOMEM; goto fmem; } sprintf(sh_mem, "XiaoLuLu"); sprintf(sh_mem + PAGE_SIZE, "Love Linux forever!\n"); return 0; fmem: misc_deregister(&rpr_dev); return result; } static void rpr_exit(void) { kfree(sh_mem); misc_deregister(&rpr_dev); } module_init(rpr_init); module_exit(rpr_exit); MODULE_LICENSE("Dual BSD/GPL");
应用程序和调试结果参考上一篇文章。