介绍完VM的基本概念后, 我们可以讲述mmap和munmap系统调用了. mmap调用实际上就是一个内存对象vma的创建过程, mmap的调用格式是:
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
其中start是映射地址, length是映射长度, 如果flags的MAP_FIXED不被置位, 则该参数通常被忽略, 而查找进程地址空间中第一个长度符合的空闲区域;Fd是映射文件的文件句柄, offset是映射文件中的偏移地址;prot是映射保护权限, 可以是PROT_EXEC, PROT_READ, PROT_WRITE, PROT_NONE, flags则是指映射类型, 可以是MAP_FIXED, MAP_PRIVATE, MAP_SHARED, 该参数必须被指定为MAP_PRIVATE和MAP_SHARED其中之一, MAP_PRIVATE是创建一个写时拷贝映射(copy-on-write), 也就是说如果有多个进程同时映射到一个文件上, 映射建立时只是共享同样的存储页面, 但是某进程企图修改页面内容, 则复制一个副本给该进程私用, 它的任何修改对其它进程都不可见. 而MAP_SHARED则无论修改与否都使用同一副本, 任何进程对页面的修改对其它进程都是可见的.
mmap系统调用的实现过程是:
1.先通过文件系统定位要映射的文件;
2.权限检查, 映射的权限不会超过文件打开的方式, 也就是说如果文件是以只读方式打开, 那么则不允许建立一个可写映射;
3.创建一个vma对象, 并对之进行初始化;
4.调用映射文件的mmap函数, 其主要工作是给vm_ops向量表赋值;
5.把该vma链入该进程的vma链表中, 如果可以和前后的vma合并则合并;
6.如果是要求VM_LOCKED(映射区不被换出)方式映射, 则发出缺页请求, 把映射页面读入内存中.
munmap(void * start, size_t length):
该调用可以看作是mmap的一个逆过程. 它将进程中从start开始length长度的一段区域的映射关闭, 如果该区域不是恰好对应一个vma, 则有可能会分割几个或几个vma.
msync(void * start, size_t length, int flags):
把映射区域的修改回写到后备存储中. 因为munmap时并不保证页面回写, 如果不调用msync, 那么有可能在munmap后丢失对映射区的修改. 其中flags可以是MS_SYNC, MS_ASYNC, MS_INVALIDATE, MS_SYNC要求回写完成后才返回, MS_ASYNC发出回写请求后立即返回, MS_INVALIDATE使用回写的内容更新该文件的其它映射. 该系统调用是通过调用映射文件的sync函数来完成工作的.
brk(void * end_data_segement):
将进程的数据段扩展到end_data_segement指定的地址, 该系统调用和mmap的实现方式十分相似, 同样是产生一个vma, 然后指定其属性. 不过在此之前需要做一些合法性检查, 比如该地址是否大于mm->end_code, end_data_segement和mm->brk之间是否还存在其它vma等等. 通过brk产生的vma映射的文件为空, 这和匿名映射产生的vma相似, 关于匿名映射不做进一步介绍. 库函数malloc就是通过brk实现的.