remap_pfn_range实现 mmap

 函数声明:

int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot);
/**
* remap_pfn_range - remap kernel memory to userspace
* @vma: user vma to map to:内核找到的虚拟地址区间
* @addr: target user address to start at:要关联的虚拟地址
* @pfn: physical address of kernel memory:要关联的设备的物理地址,也即要映射的物理地址所在的物理帧号,可将物理地址>>PAGE_SHIFT
* @size: size of map area
* @prot: page protection flags for this mapping
*
* Note: this is only safe if the mm semaphore is held when called.
*/

实现mmap 

static int xxx_mmap(struct file *file, struct vm_area_struct *vma)
{
    unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
    unsigned long pfn_start = (virt_to_phys(kbuff) >> PAGE_SHIFT) + vma->vm_pgoff;
    unsigned long virt_start = (unsigned long)kbuff + offset;
    unsigned long size = vma->vm_end - vma->vm_start;
    int ret = 0;

    printk("phy: 0x%lx, offset: 0x%lx, size: 0x%lx\n", pfn_start << PAGE_SHIFT, offset, size);

    ret = remap_pfn_range(vma, vma->vm_start, pfn_start, size, vma->vm_page_prot);
    if (ret)
        printk("%s: remap_pfn_range failed at [0x%lx  0x%lx]\n",
            __func__, vma->vm_start, vma->vm_end);
    else
        printk("%s: map 0x%lx to 0x%lx, size: 0x%lx\n", __func__, virt_start,
            vma->vm_start, size);

    return ret;
}

第3行的vma_pgoff表示的是该vma表示的区间在缓冲区中的偏移地址,单位是页。这个值是用户调用mmap时传入的最后一个参数,不过用户空间的offset的单位是字节(当然必须是页对齐),进入内核后,内核会将该值右移PAGE_SHIFT(12),也就是转换为以页为单位。因为要在第9行打印这个编译地址,所以这里将其再左移PAGE_SHIFT,然后赋值给offset。

第4行计算内核缓冲区中将被映射到用户空间的地址对应的物理页帧号。virt_to_phys接受的虚拟地址必须在低端内存范围内,用于将虚拟地址转换为物理地址,而vmaloc返回的虚拟地址不在低端内存范围内,所以需要用专门的函数。

第5行计算内核缓冲区中将被映射到用户空间的地址对应的虚拟地址

第6行计算该vma表示的内存区间的大小

第11行调用remap_pfn_range将物理页帧号pfn_start对应的物理内存映射到用户空间的vm->vm_start处,映射长度为该虚拟内存区的长度。由于这里的内核缓冲区是用kzalloc分配的,保证了物理地址的连续性,所以会将物理页帧号从pfn_start开始的(size >> PAGE_SHIFT)个连续的物理页帧依次按序映射到用户空间。

你可能感兴趣的:(linux)