mmap即memory map,也就是内存映射

mmap是一种内存映射文件的方法,即将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read、write等系统调用函数。相反,内核空间对这段区域的修改也直接反应到用户空间,从而可以实现不同进程间的文件共享。如下图所示
image.png

mmap具有如下特点
1、mmap向应用程序提供的内存访问接口是内存地址连续的,但是对应的磁盘文件的block可以不是地址连续的。
2、mmap提供的内存空间是虚拟空间(虚拟内存),而不是无论空间(物理内存),因此完全可以分配远远大于物理内存大小的虚拟空间。
3、mmap负责映射文件逻辑上一段连续的数据(物理上可以不连续存储)映射为连续内存,而这里的文件可以是磁盘文件、驱动假造出来的文件(例如DMA技术)已经设备。
4、mmap由操作系统负责管理,对同一个文件地址的映射将被所有线程共享,操作系统确保线程安全以及线程可见性。

mmap的设计很有启发性。基于磁盘的读写单位是block(一般大小为4kb),而基于内存的读写单位是地址(虽然内存的管理与分配单位是4kb)。换言之,CPU进行一次磁盘读写操作设计的数据至少是4kb,但是进行一次内存操作设计的数据流是基于地址的,也就是通常的64bit(64位操作系统)。mmap下的进程可以采用指针的方式进行读写操作,这是值得注意的。

2、mmap的I/O模型

mmap也是一种零拷贝技术,其I/O模型如下图
image.png
mmap技术有如下特点
1、利用DMA技术来取代CPU来在内存与其他组件直接的数据拷贝,例如从磁盘到内存,从内存到网卡。
2、用户空间的mmap file使用虚拟内存,时间上并不占用无论内存,只有在内核空间的kerne buffer cache才占据实际的物理内存
3、mmap函数需配合wrie()系统调用进行配合操作,这与sendfile()函数有所不同,后者一次性代替了read()以及write(),因此mmap也至少需要4次上下文切换。
4、mmap紧急能够避免内核空间到永固空间的全程CPU负责的数据拷贝,但是内核空间内部还是需要全程CPU负责的数据拷贝

里面mmap()替换read(),配合write()调用的整个流程如下
1、用户进程调用mmap(),从用户态陷入内核态,将内核缓冲区映射到用户缓存区。
2、DMA控制权将数据从磁盘拷贝到内核缓冲区(可见其使用了Page Cache机制)
3、mmap()返回,上下文从内核态切换回用户态
4、用户进程调用write()尝试把文件数据写到内核里的套接字缓冲区,再次陷入内核态
5、CPU将内核缓冲区中的数据拷贝到套接字缓冲区
6、DMA控制器将数据从套接字缓冲区拷贝到网卡完成数据传输
7、write()返回,上下文从内核态切换回用户态

3、mmap的优势

1、简化用户进程编程

在用户空间来看,通过mmap机制以后,磁盘上的文件仿佛就在内存中,把访问磁盘文件简化为按地址访问内存。这样一来,应用程序自然不需要使用文件系统的write(写入)、read(读取)、fsync(同步)等系统调用,因为现在只要面向内存的虚拟空间进行开发。

但是,这并不意味着我们不再需要进行这些系统调用,而是说这些系统调用由操作系统在mmap机制内部封装好了。

(1) 基于缺页异常的懒加载

出于节约物理内存以及mmap方法快速返回的目的,mmap映射采用懒加载机制。具体来说,通过mmap申请1000g内存可能紧急占用了100M的虚拟内存空间,甚至没有分配实际的物理内存空间。当你访问相关内存地址时,才会进行正在的write、read等系统调用。CPU会通过陷入缺页异常的方式来讲磁盘上的数据加载到物理内存中,此时才会发生真正的物理内存分配。

(2)数据一致性由OS保证

当发生数据修改时,内存出现脏页,与磁盘文件出现不一致。mmap机制下由操作系统自动完成内存数据落盘(脏页回刷),用户进程通常并不需要手动管理数据落盘。

2、读写效率提高:避免内核空间到用户空间的数据拷贝

简而言之,mmap被认为快的原因是因为建立了页到用户进程的虚拟地址空间映射,以读取文件为例,避免了页从内核空间拷贝到用户空间。

3、避免只读操作时的swap操作

虚拟内存带来了种种好处,但是一个最大的问题在于所有进程的虚拟内存大小总和可能大于物理内存总大小,因此当操作系统物理内存不够用时就会把部分内存swap到磁盘上。
在mmap下没如果虚拟空间没有发生写操作,那么由于通过mmap操作得到的内存数据完全可以通过再次调用mmap操作映射文件得到。但是,通过其他方式分配的内存,在没有发生写操作的情况下,操作系统并不知道如何简单的从现有文件中(除非其重新执行一遍应用程序,但是代价很大)恢复内存数据美因茨必须将内存swap到磁盘上。

4、节约内存

由于用户空间与内核空间实际上共用同一份数据,因此在大文件场景下实际物理内存占用上有优势。

4、mmap不是银弹

mmap也有缺陷,在相关场景下的性能存在缺陷
1、由于mmap使用时必须实现指定好内存映射的大小,因此mmap并不适合变长文件
2、如果更新文件的操作很多,mmap避免两态拷贝的优势就被摊还,最终函数落在了大量的脏页回写及由此引发的随机I/O上,所以在随机写很多的情况下,mmap方式在效率上不一定会比带缓冲区的一般写快
3、读/写小文件,mmap与通过read系统调用相比有着更高的开销与延迟,同时mmap的刷盘由系统全权控制,但是在小数据量的情况下由应用本身手动控制更好
4、mmap受限于操作系统内存大小:例如在32位操作系统上,虚拟内存总大小也就2GB,但由于mmap必须要做内存中找到一块连续的地址块,此时你就无法对4GB大小的文件完全进行mmap,在这种情况下你必须分多块分表进行mmap,但是此时地址内存已经不再连续,使用mmap的意义大打折扣,而且引入了额外的复杂性

5、mmap使用场景

mmap的使用场景实际上非常受限,在如下场合可以选择使用mmap机制
1、多个线程以只读的方式同时访问一个文件,这是因为mmap机制下多线程共享了同一物理内存空间,因此节约了内存
2、mmap非常适合用于进程间通信,这是因为对同一文件对于的mmap分配的物理内存天然多线程共享,并可以依赖于操作系统的同步原语
3、mmap虽然比sendfile等机制多了一次CPU全程参与的内存拷贝,但是用户空间与内核空间并不需要数据拷贝,因此在正确情况下并不比sendfile效率差。