如何用mmap()映射设备IO内存到用户空间?兼析LDD3代码差异
关于《Linux设备驱动程序第三版》(LDD3)中mmap()两段代码的疑问解答
第一段代码的映射原理与映射对象
先看这段代码:
static int simple_remap_mmap(struct file *file, struct vm_area_struct *vma) { if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, vma->vm_end - vma->vm_start, vma->vm_page_prot)) return -EAGAIN; vma->vm_ops = &simple_remap_vm_ops; simple_vma_open(vma); return 0; }
- 物理地址的传递方式:这段代码并非没用到物理地址,而是通过用户态
mmap调用的offset参数间接传入。内核会把用户传入的offset右移PAGE_SHIFT位(转换为页框号PFN),存到vma->vm_pgoff里。remap_pfn_range的第三个参数就是这个PFN,对应设备IO内存的物理地址(物理地址 = PFN << PAGE_SHIFT)。 - 映射对象:这段代码专门用来映射设备IO物理内存,比如外设寄存器、显存这类独立于系统RAM的硬件地址区域。这类地址不在内核的常规页管理体系中,所以可以直接通过PFN映射到用户空间。而普通RAM的物理页受内核页管理跟踪,直接用
remap_pfn_range映射会破坏页表一致性,因此不能用这段代码映射RAM。
两段代码的差异原因
第二段代码如下:
static struct vm_operations_struct simple_remap_vm_ops = { .open = simple_vma_open, .close = simple_vma_close, }; static int simple_remap_mmap(struct file *filp, struct vm_area_struct *vma) { int rv; size_t len = vma->vm_end - vma->vm_start; struct page *pages; /* * To prevent from memory over run. Limit the maximux mmap size here * to 10 * PAGE_SIZE */ if (len >> PAGE_SHIFT > 10) { pr_err("Map size overflow! len=%ld pages\n", len >> PAGE_SHIFT); return -EFAULT; } /* * Allocate necessary pages and initialize it properly. * We initialize all memory to a special value: 0xdeadbeef */ pages = alloc_pages(GFP_KERNEL, get_order(len)); memset32(page_address(pages), 0xdeadbeef, (1 << get_order(len)) * PAGE_SIZE / 4); rv = remap_pfn_range(vma, vma->vm_start, page_to_pfn(pages), vma->vm_end - vma->vm_start, vma->vm_page_prot); if (rv) { return -EAGAIN; } pr_debug("vm_start: %#lx, vm_end: %#lx\n, len: %#lx, vm_pgoff: %#lx(%#lx), ops=%p\n", vma->vm_start, vma->vm_end, len, vma->vm_pgoff, vma->vm_pgoff << PAGE_SHIFT, vma->vm_ops); vma->vm_ops = &simple_remap_vm_ops; simple_vma_open(vma); return 0; }
两段代码的核心差异在于映射对象完全不同:
- 第一段是映射预先存在的硬件IO物理内存:这类内存是硬件自带的固定物理地址区域,不需要内核分配,只需要建立虚拟地址到物理地址的页表映射即可。
- 第二段是映射内核动态分配的系统RAM:这里的目标是给用户空间提供一块共享内存,所以必须先通过
alloc_pages从内核页分配器申请物理页,初始化后再把这些页的PFN(通过page_to_pfn获取)映射到用户空间。
简单来说,第一段是“映射已有硬件地址”,第二段是“先分配内存再映射”,这就是为什么第二段需要显式分配页面,而第一段不需要的根本原因。
内容的提问来源于stack exchange,提问作者bielu000
相关产品推荐
相关产品推荐

