mmap(Memory Map,内存映射)是一种将文件或其它对象映射到进程虚拟地址空间的方法。通过 mmap,程序可以把文件的某一部分当作连续的内存来直接读写,而不需要再调用传统的 read 或 write 等 I/O 系统调用。
使用
mmap的函数签名如下:
cpp
void* mmap(
void* addr,
size_t length,
int prot,
int flags,
int fd,
off_t offset
);
六个参数分别是
txt
addr 希望映射到哪个虚拟地址,一般传 nullptr
length 映射多少字节
prot 这块内存允许你做什么:读 / 写 / 执行
flags 这块映射怎么和文件交互:私有 / 共享 等
fd 文件描述符
offset 从文件哪个位置开始映射
重点讲讲prot和flag的区别
prot控制内对这块内存做什么,常见值包括PROT_READ、PROT_WRITE、PROT_EXEC、PROT_NONE
分别控制可以对返回的void*进行什么类型的操作,读/写/可执行/什么都不允许
flags控制内存与文件的映射关系MAP_PRIVATE、MAP_SHARED、MAP_ANONYMOUS
比如private,表示对内存的修改不会映射到文件,相当于是私有的,shared则是对内存的修改会映射到文件,别的进程也可以看见。当参数是MAP_ANONYMOUS时,表示不需要文件进行映射,这个一般用于父子进程之间本来就有一份进程地址空间的拷贝。
offset一般时page_size的倍数
原理
mmap 不是"把文件读进内存",而是"在进程虚拟地址空间里建立一段映射关系,让这段虚拟地址背后对应文件页或匿名内存页"
mmap 干的事情,本质就是操作虚拟地址与物理页的映射关系。
与read的一个直观的对比如下
cpp
char buffer[4096];
read(fd, buffer, 4096); //这里经历了一次拷贝从文件->page cache到用户态buffer的拷贝
void* p = mmap(...);//这里没经历拷贝就建立了映射关系
p[offset]; //读写时发生缺页中断,按需加载,少一次到用户态的拷贝
所以mmap一开始只是建立一个映射,并不会立马加载数据
prot = prot_shared时,便是两个进程的虚拟地址映射到同一个物理页,写时标记该页为脏页,之后会修改文件,同样也可以主动调用::msync(...)落盘,
prot = prot_priate时,进程正常读没有问题,写时会触发写时复制,讲物理页复制到一个新的匿名页,并在其上做对应的修改,于是其他映射到物理页的进程看不到修改
prot = MAP_ANONYMOUS | MAP_SHARED时,可以和fork配合,先mmap,简历映射,再fork,此时子进程把父进程的虚拟地址空间布局 继承过去且由于是share模式,此时虽然是匿名页,且父子进程虽然是两个不同的进程,且有自己的页表,但这两个页表项可以指向同一个物理页。
以上是我个人一些浅显的理解,以后再补充吧