背景:

mmap()是linux用户空间中特别强大且常用的一个接口,集合了多种关于内存或文件读写的功能,这里我们剖析一下它在内核中的实现。

PS:本文假设在读者了解mmap基本使用的情况下,对mmap()在内核中的实现做个梳理

函数定义:

返回值:
若成功则返回映射区域的起始地址。

形参:
addr: 用于指定映射区域的起始地址;若为0,则由系统自动选择合适的地址。可配合形参flags中的MAP_FIXED使用。

fd: 要被映射的文件的句柄。

length: 文件要被映射长度,单位byte。

offset: 从文件起始地址的offset偏移处开始映射。

prot: 指明映射区的保护要求。 PROT_READ|PROT_WRITE|PROT_EXEC|PROT_NONE == 可读|可写|可执行|不可访问 (权限要以被open文件的权限为基础,不可冲突)

flags:
MAP_FIXED:配合addr使用
MAP_HUGETLB:用于从hugepage pool中申请大页
MAP_ANONYMOUS:匿名映射,映射的区域将被初始化为0
MAP_SHARED:共享映射。对被映射文件的写操作会写回(sync)源文件
MAP_PRIVATE:私有映射。对被映射文件的写不会写到源文件,而是触发cow机制重新分配内存写入。(这样的话,修改就不会被其他进程看到了)
MAP_POPULATE:对于文件映射,在mmap时就会把文件内容预读到映射区(此特性只支持private)
MAP_LOCKED: 效果等同与mlock(),防止被映射的内存被交换到swap分区。同时,此flags还会使mmap阶段就产生缺页异常为mmap映射的地址分配物理内存。(MAP_LOCKED对应vma flags中的VM_LOCKED)

注意:这里只介绍了常用的参数,更详细的可参考mmap的manual手册。

mmap使用场景:

具体场景对应的flags
私有文件映射映射动态库MAP_PRIVATE
私有匿名映射malloc分配大内存在glibc中对应的mmap()实现MAP_PRIVATE|MAP_ANONYMOUS
共享文件映射进程间读写同一文件MAP_SHARED
共享匿名映射用于进程间共享内存MAP_SHARED|MAP_ANONYMOUS
大页内存映射用于申请大页内存MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB
待补充.....

mmap内核实现:

在分析代码实现前,读者需要事先对strcut mm_struct和struct vm_area_struct这两个关键的数据结构有一定的了解,因为mmap主要的工作就是为进程分配一段合适的vm_area_struct,下面列出他们的关键定义,并做了部分注释:


mmap对应系统调用sys_mmap2(),调用关系:

进一步查看调用关系与实现:

get_unmaped_area()具体工作:

判断当前进程的虚拟内存是否有足够的空间,并返回一段没有映射的地址空间,具体实现涉及体系架构,默认的实现是arch_get_unmapped_area()。但若此次mmap是文件映射且其文件系统对file->f_op->get_unmapped_area有自己的实现,则调用自己的实现。

mmap_region()具体工作:

  1. 尝试已经获取的地址是否可以通过在现有的vma上进行扩展复用(复用的前提是新vma需要和老vma地址相邻,且flags属性相同)

2. 若复用不成功,则新分配一个vma_area_struct数据结构 vma = vm_area_alloc(mm);

3. 初始化vma结构:

若是文件映射(私有或共享):
vma->vm_file = get_file(file); //关联文件操作
callmmap(file, vma); //调用文件中file->f_op->mmap的实现
再次尝试是否可以将新vma和临近的vma进行合并

若是私有匿名映射:
vma_set_anonymous(vma); //将vma的vm_ops赋NULL

若是共享匿名映射:
shmem_zero_setup(vma); //关联/dev/zero文件,并设置vm_ops等

3. 将初始化好的vma插入vma的链表和红黑树:vma_link(mm, vma, prev, rb_link, rb_parent);

4. 判断VM_LOCKED标志(即flags是否包含MAP_LOCKED或MAP_POPULATE):
若未设置,则mmap到此为止,直接返回addr,并不分配实际物理页,由用户访问时触发缺页再按需分配物理页。
若已设置,则调用mm_populate(),通过人工触发minor pagefault为其分配物理页。


关于缺页异常与mmap:

不管是用户访问mmap地址时触发缺页或是mmap中就人工触发缺页,它们在缺页处理流程中走的都是相同的处理分支(可参看Linux内存管理:缺页异常(一)文中的大图,具体是阶段四)。

各种类型的映射如何处理,如下:

(待补充)

提示:在mmap中对共享匿名映射的处理shmem_zero_setup(vma),其为vma设置了 vma->vm_ops = &shmem_vm_ops,以及vma->vm_file = file,所以共享匿名映射可以看成一种"特殊的文件映射"。


原创文章,转载和引用请注明出处。