1 简介
mmap/munmap是用户空间最常用的一个系统调用接口,常用于在用户程序中分配内存、读写文件、链接动态库文件、多进程间共享内存。
2 函数原型
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
int munmap(void *start, size_t length)
2.1 参数说明
- addr :用于指定映射到进程地址空间的起始地址,为了程序的可移植性,一般设置为NULL,让内核来选择一个合适的地址。
- len:表示映射到进程地址空间的大小。
- prot:用于设置映射区域的读写属性。
- flags:用于设置内存映射区域的属性,例如共享映射、私有映射等。
- fd:表示这是一个文件映射,fd是打开文件的句柄。
- offset:在文件映射中,表示文件的偏移量。
2.2 prot
- PROT_EXEC:表示映射的页面可以执行
- PROT_READ:表示映射的页面可以读取
- PROT_WRITE:表示映射的页面可以写入
- PROT_NONE:表示映射的页面不可以访问
2.3 flags
- MAP_SHARED:创建一个共享映射的区域,多个进程可以通过共享映射方式来映射一个文件,这样几个进程之间都可以看到映射内容的改变。修改后的内容会同步到磁盘文件中。
- MAP_PRIVATE:创建一个私有的写时复制的映射,多个进程可以通过私有映射的方式来映射一个文件,这几个进程之间不可以看到映射内容的改变,并且修改后的内容也不会同步到磁盘文件中。
- MAP_FIXED:使用参数addr映射创建映射,如果在内核中无法映射指定的地址addr,那么mmap会返回失败,参数addr要求按页对齐。如果addr和length指定的进程地址空间和已有的VMA区域重叠,那么内核会调用do_munmap()函数把这段重叠区域销毁,然后重新映射新的内容。
- MAP_POPULATE:对于文件映射来说,会提前预读文件内容到映射区域,该特性只支持私有映射。
3 匿名映射
没有映射文件的映射称为匿名映射,这种映射的内存区域会被初始化为0。
3.1 私有匿名映射
当使用参数 fd = -1 且 flags = MAP_ANONYMOUS | MAP_PRIVATE时,创建的mmap映射是私有匿名映射。
私有匿名映射最常见的用途是在 glibc 分配大块的内存中,当需要分配的内存大于MMAP_THREASHOLD(128KB)时,glibc 会默认使用mmap代替brk来分配内存。
3.1 共享匿名映射
当使用参数 fd = -1 且 flags = MAP_ANONYMOUS | MAP_SHARED时,创建的映射是共享匿名映射。共享匿名内存让相关进程共享一块内存区域,通常用于父子进程。
4 文件映射
与匿名映射相反,文件映射与实际文件相关联,通常是把文件的内容映射到进程地址空间,这样应用程序就可以像操作进程地址空间一样读写文件。
4.1 私有文件映射
创建文件时 flags 标志位被设置为MAP_PRIVATE,这种映射方式是私有文件映射,其最常见的应用场景是加载动态库。
4.2 共享文件映射
创建文件时 flags 标志位被设置为MAP_SHARED,这种映射方式是私有文件映射,如果prot参数指定了PROT_WRITE,那么打开文件时需要指定O_RDWR表标志位。共享文件映射通常有以下两个应用场景:
- 读写文件。把文件内容映射到进程地址空间,同时对映射的内容做了修改,内核的回写机制最终会把修改的内容同步到磁盘中。
- 进程间通信。进程之间的进程地址空间相互隔离,一个进程不能访问到另一个进程的地址空间。如果多个进程映射了同一个文件并且共享,就可以实现了多进程间的通信。如果一个进程对文件内容进行修改,另一个进程就可以看到。