您的位置:首页 > 运维架构 > Linux

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

2013-12-16 17:07 483 查看
这一篇是说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");


应用程序和调试结果参考上一篇文章。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐