mmap原理
找了好些文章,都没能讲明白mmap的原理。本文是转载自知乎上的一篇文件,整个文章的篇幅较少,可以帮助大家快速的了解mmap的原来,大家自行阅读即可,原文的链接我贴在最后了。
简短的总结一下:mmap是直接将进程的虚拟地址和内核空间的page cache进行映射从而省去了从内核空间向用户空间的copy。
在《一文看懂零拷贝技术》中我们介绍了 零拷贝技术 的原理,而且我们知道 mmap 也是零拷贝技术的一种实现。在本文中,我们主要介绍 mmap 的原理。
一、传统的读写文件
一般来说,修改一个文件的内容需要如下3个步骤:
- 把文件内容读入到内存中。
- 修改内存中的内容。
- 把内存的数据写入到文件中。
过程如图 1 所示:

如果使用代码来实现上面的过程,代码如下:
read(fd, buf, 1024); // 读取文件的内容到buf
... // 修改buf的内容
write(fd, buf, 1024); // 把buf的内容写入到文件
从图 1 中可以看出,页缓存(page cache) 是读写文件时的中间层,内核使用 页缓存 与文件的数据块关联起来。所以应用程序读写文件时,实际操作的是 页缓存。
二、使用 mmap 读写文件
从传统读写文件的过程中,我们可以发现有个地方可以优化:如果可以直接在用户空间读写 页缓存,那么就可以免去将 页缓存 的数据复制到用户空间缓冲区的过程。
那么,有没有这样的技术能实现上面所说的方式呢?答案是肯定的,就是 mmap。
使用 mmap 系统调用可以将用户空间的虚拟内存地址与文件进行映射(绑定),对映射后的虚拟内存地址进行读写操作就如同对文件进行读写操作一样。原理如图 2 所示:

前面我们介绍过,读写文件都需要经过 页缓存,所以 mmap 映射的正是文件的 页缓存,而非磁盘中的文件本身。由于 mmap 映射的是文件的 页缓存,所以就涉及到同步的问题,即 页缓存 会在什么时候把数据同步到磁盘。
Linux 内核并不会主动把 mmap 映射的 页缓存 同步到磁盘,而是需要用户主动触发。同步 mmap 映射的内存到磁盘有 4 个时机:
- 调用
msync函数主动进行数据同步(主动)。 - 调用
munmap函数对文件进行解除映射关系时(主动)。 - 进程退出时(被动)。
- 系统关机时(被动)。
三、mmap的使用方式
下面我们介绍一下怎么使用 mmap,mmap 函数的原型如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
下面介绍一下 mmap 函数的各个参数作用:
addr:指定映射的虚拟内存地址,可以设置为 NULL,让 Linux 内核自动选择合适的虚拟内存地址。length:映射的长度。prot:映射内存的保护模式,可选值如下:PROT_EXEC:可以被执行。PROT_READ:可以被读取。PROT_WRITE:可以被写入。PROT_NONE:不可访问。
flags:指定映射的类型,常用的可选值如下:MAP_FIXED:使用指定的起始虚拟内存地址进行映射。MAP_SHARED:与其它所有映射到这个文件的进程共享映射空间(可实现共享内存)。MAP_PRIVATE:建立一个写时复制(Copy on Write)的私有映射空间。MAP_LOCKED:锁定映射区的页面,从而防止页面被交换出内存。- ...
fd:进行映射的文件句柄。offset:文件偏移量(从文件的何处开始映射)。
介绍完 mmap 函数的原型后,我们现在通过一个简单的例子介绍怎么使用 mmap:
int fd = open(filepath, O_RDWR, 0644); // 打开文件
void *addr = mmap(NULL, 8192, PROT_WRITE, MAP_SHARED, fd, 4096); // 对文件进行映射
在上面例子中,我们先通过 open 函数以可读写的方式打开文件,然后通过 mmap 函数对文件进行映射,映射的方式如下:
addr参数设置为 NULL,表示让操作系统自动选择合适的虚拟内存地址进行映射。length参数设置为 8192 表示映射的区域为 2 个内存页的大小(一个内存页的大小为 4 KB)。prot参数设置为PROT_WRITE表示映射的内存区为可读写。flags参数设置为MAP_SHARED表示共享映射区。fd参数设置打开的文件句柄。offset参数设置为 4096 表示从文件的 4096 处开始映射。
mmap 函数会返回映射后的内存地址,我们可以通过此内存地址对文件进行读写操作。我们通过图 3 展示上面例子在内核中的结构:

四、总结
本文主要介绍了 mmap 的原理和使用方式,通过本文我们可以知道,使用 mmap 对文件进行读写操作时可以减少内存拷贝的次数,并且可以减少系统调用的次数,从而提高对读写文件操作的效率。
由于内核不会主动同步 mmap 所映射的内存区中的数据,所以在某些特殊的场景下可能会出现数据丢失的情况(如断电)。为了避免数据丢失,在使用 mmap 的时候可以在适当时主动调用 msync 函数来同步映射内存区的数据。
主要体现在读上,写的时候其实没减少copy次数。
文章原链接:一文读懂 mmap 原理 - 知乎
函数定义
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。
函数原型
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
start:映射区的开始地址,设置0时表示由系统决定映射区的起始地址。
length:映射区的长度。//长度单位是以字节为单位,不足一内存页按一内存页处理
prot:期望的内存保护标志,不能与文件的打开模式冲突PROT_EXEC //页内容可以被执行PROT_READ //页内容可以被读取PROT_WRITE //页可以被写入PROT_NONE //页不可访问
flags:指定映射对象的类型,映射选项和映射页是否可以共享。MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者[munmap](https://baike.baidu.com/item/munmap)()被调用,文件实际上不会被更新。MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时[内存不足],对映射区的修改会引起段违例信号。MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。MAP_GROWSDOWN //用于堆栈,告诉[内核]VM系统,映射区可以向下扩展。MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。MAP_FILE //兼容标志,被忽略。MAP_32BIT //将映射区放在进程[地址空间]的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立[页表]入口。
fd:有效的文件描述符。一般是由[open]()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。
offset:被映射对象内容的起点。
传统的read和write的文件操作:
- open一个文件,使用read系统调用读取文件的一部分或者全部;
- 内核将文件中的数据从磁盘区域拷贝到内核空间缓冲区;
- 然后再从内核空间缓冲区读取到进程用户空间缓冲区;
-
read()和write()系统调用在操作磁盘文件时不会直接发起磁盘访问,而是仅仅在用户空间缓冲区与内核缓冲区高速缓存(kernel buffer cache)之间复制数据。

mmap原理
- open一个文件,然后调用mmap系统调用,将文件的内容的全部或一部分直接映射到进程虚拟空间中文件存储映射部分;
- 完成映射关系后,mmap返回值是一个指针,进程可以通过采用指针方式读写操作这一段内存;
- 但mmap并不分配物理地址空间,只是占有了进程的虚拟地址空间,当多个进程需要同时访问这个文件的时候,每个进程都将文件所存储的内核缓冲区映射到自己的进程空间虚拟地址中。
- 当第一个进程访问内核缓冲区的时候,由于没有实际拷贝数据,这时候MMU(内存管理单元)在地址映射表中找不到与该虚拟内存空间相对应的物理地址,此时会触发缺页中断;
-
缺页中断后,内核会将文件的这一页数据读入到内核缓冲区中,并更新进程的页表,使得页表指向内核缓冲区这一页,之后有其他的进程再次访问这一页的时候,该页已经在内存中了

补充知识
进程的虚拟地址空间由多个虚拟内存区域构成,包括常见的栈,堆,bss数据段,初始数据段,text数据段和内存映射等,每一个都是独立的虚拟内存区域。

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
