进程与内存5-mmap实现2(remap_pfn_range方法原理及实例)

这一篇是说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");


应用程序和调试结果参考上一篇文章。


你可能感兴趣的:(linux,内存管理,ARM,移动设备,linux内核)