背景:
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()具体工作:
- 尝试已经获取的地址是否可以通过在现有的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,所以共享匿名映射可以看成一种"特殊的文件映射"。
原创文章,转载和引用请注明出处。