Linux系统:内存映射概念以及相关函数(mmap、munmap、msync)介绍
1.1 基本概念
- 内存映射根据种类分为:
- 文件映射:将一个文件的一部分直接映射到虚拟进程地址空间中,虚拟进程地址空间会对应一部分内存RAM分页
- 共享文件映射:在同一个文件上面的映射变更,对其它共享该文件映射的进程是可见的(应为这个时候多个进程共享同一个物理内存分页),并且会把变更反映到磁盘文件上
- fork创建的子进程会继承父进程的映射关系,并且共享相同的物理内存分页
- 对应的内存分页最初以映射部分的文件内容进行初始化
- exec会丢失映射关系
- 用途:
- 内存映射I/O,一种替代write和read的比较好的方案
- 共享内存
- 私有文件映射:在同一个文件上面的映射变更,对其它共享该文件映射的进程是不可见的,并且对一个分页的修改的时候,会通过写时复制技术重新分配一个分页,把原来需要修改的分页内容复制到新分页中,并且会修改进程相应的页表内容,所以这种修改不会对底层磁盘文件发生影响
- fork创建的子进程会继承父进程的映射关系,并且共享相同的物理内存分页
- 对应的内存分页最初以映射部分的文件内容进行初始化
- exec会丢失映射关系
- 用途:
- 用文件的内容来初始化内存分页,比如用共享库文件或者二进制文件
- 共享文件映射:在同一个文件上面的映射变更,对其它共享该文件映射的进程是可见的(应为这个时候多个进程共享同一个物理内存分页),并且会把变更反映到磁盘文件上
- 匿名映射:将一个虚拟文件(没有对应的文件)映射到虚拟进程地址空间中
- 共享文件映射
- 私有文件映射
- 文件映射:将一个文件的一部分直接映射到虚拟进程地址空间中,虚拟进程地址空间会对应一部分内存RAM分页
1.2 创建一个映射
-
函数功能:系统调用在调用进程的虚拟地址空间创建一个映射
-
函数返回值:成功返回映射之后的对应的虚拟地址,错误返回MAP_FAILED((void *)-1)
-
函数原型:
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
-
函数参数:
-
addr:指定了映射被放置的开始虚拟地址,一般设置为NULL,表示内核自己选择一个分页边界的整倍数的地址,如果是一个非NULL值,内核会把他作为一个提示地址,选择相邻的分页边界的整倍数的地址返回,如果设置了MAP_FIXED标志,addr就必须为一个分页边界的整倍数的地址
-
length:制定了映射的字节数,系统会自动选择一个分页整倍数的长度,所以实际大小可能比length大
-
prot:一个位掩码,指定了施加于映射之上的保护信息,可以选择多个,这里需要注意这个部分的权限是访问虚拟地址对应的那部分内存分页的权限
-
flag:控制映射操作的选项位掩码,只能选择一个,举例常用的如下:
- MAP_SHARED:创建共享映射
- MAP_PRIVATE:创建私有映射
-
fd:被映射的文件的描述符
-
offset:被映射文件的起点值
-
1.3 解除映射
-
函数功能:从调用进程的虚拟进程地址空间删除一个映射关系
-
函数返回值:返回0成功,失败返回-1
-
函数原型:
#include <sys/mman.h> int munmap(void *addr, size_t length);
-
函数参数:
- addr:映射被放置的开始虚拟地址
- length:映射的长度
-
需要解除的映射范围是可以调整的,并不一定是全部
-
进程终止和使用exec()都会使得映射关系被接触,需要使用msync()确保修改从内存分页写入底层文件
1.4 文件映射详细概念
-
文件映射的具体步骤:
- open打开一个文件获得文件描述符fd
- 将文件描述符作为参数传入mmap
- 映射完毕之后,可以关闭fd,不会造成任何影响
- 这里需要注意,fd上面的权限和mmap的prot权限一定要匹配,后果后面会讲到
-
共享文件映射,这部分主要讲解其映射原理,看以下博客链接:
http://blog.csdn.net/joejames/article/details/37958017
-
物理内存到底层文件之间的数据交换是靠内核完成的
-
内核空间高速缓存:存在于物理内存的内核地址空间部分
-
用户空间高速缓存:存在于物理内存的用户地址空间部分
-
内存映射用于内存I/O时候的特点:
- 内核空间和用户空间可以理解为共享同一个缓存
- 内存映射I/O相比write和read的提升主要体现在少了一次内核空间高速缓存到用户空间高速缓存的复制过程,如果文件很大,多次磁盘I/O就有多次复制,很浪费时间。但是内核空间高速缓存足够大到容纳任何一个数据(也就是只需要一次磁盘I/O),那么想比如write和read的提升几乎没有,因为内核空间高速缓存到用户空间高速缓存的复制过程也只有一次了,毕竟时间主要浪费在磁盘I/O上面
1.5 边界情况
- 情况1:
-
情况2:mmap(0,2199,prot,MAO_SHARED,fd,0)
- 因为2199不满足分页的整倍数,一个分页大小是4bytes(4096),所以扩充到了4095,扩充部分可以访问,但是这部分内存会被初始化为0,不会映射到底层文件,也不可以被其它进程共享,即使指定了足够大的length
-
情况3:
- 8192超出了向上舍入区域(2200到4095),访问4096到8191时候会发生SIGBUS错误
1.6 内存保护和文件访问模式交互
- 一般情况下:
- PORT_READ和PORT_EXEC和O_RDONLY或者O_RDWR配合使用
- PORT_WRITE和O_WRONLY或者O_RDWR配合使用
- 注意:特殊架构下
- O_RDWR与所有内存保护兼容
- 没有任何内存保护与O_WRONLY兼容
- O_RDONLY标记打开一个文件:
- MAP_PRIVATE:任意内存保护组合
- MAP_SHARE:PORT_REA(PORT_READ | PORT_EXEC)
1.7 同步映射区域msync()
-
对映射区域的修改,内核不一定会马上把数据从内核高速缓存区域刷入磁盘,所以这个时候可以用msync()认为控制这个过程
-
函数功能:控制映射部分内存与映射文件的同步
-
函数返回值:成功返回0,失败返回-1
-
函数原型:
#include <sys/mman.h> int msync(void *addr, size_t length, int flags);
-
函数参数:
- addr:映射被放置的开始虚拟地址
- length:映射的长度
- flags:
- MS_SYNC:执行同步操作,阻塞到所有被修改的内存分页写入底层文件为止。内核高速缓存区域马上同步底层文件
- MS_ASYNC:执行异步操作,后面某个时候写入磁盘。内核高速缓存区域后面某个时刻同步底层文件