图文剖析神秘的「Linux内存映射」
简单回顾
应用程序在运行起来之后(进程),是相互独立的,都有自己的进程地址空间。但是往往在一些业务上需要进程间的通信,来完成系统的某个完整的功能。我们来看下进程间通信能干那些事情?如下:
- 数据传输。一个进程需要发送数据到另一个进程,这种需求肯定是存在的。
- 共享数据。如果有多个进程想要访问数据,一个进程修改了内容,另一个进程能够立即看到内容变化。
- 资源保护:上面的的操作中存在竞争情况,内核需要提供锁和同步机制。
- 通知:一个进程需要向另一个进程发送消息,通知发生了某个事件。
- 控制:有些进程需要控制另一个进程的运行。典型的例子就是gdb,可参考之前文章【gdb到底是怎么实现的?】
进程间的通信方式一般可以分为八种,如下:

我们今天就来深入剖析下内存映射的实现原理。
内存映射
我记得在我之前的文章中讲过一次内存映射的实现原理,不知道各位还记不记的。今天为什么还要讲呢?主要是上一次讲的还不够深入。先给个图各位回忆下:

这幅图只是讲解了大致的原理,接下来我们就仔细的研究下,到底是怎么实现的?
内存映射实现原理深入剖析
首先是创建虚拟映射区域,分为下面几个步骤:
- 在当前的虚拟地址空间中,寻找一段满足要求大小的虚拟地址
- 为此虚拟地址分配一个虚拟内存区域(vm_area_struct结构,如图)
- 初始化该虚拟内存区域
- 插入该虚拟内存区域到进程的虚拟地址区域链表(也是树)中

然后是实现地址的映射关系(即:进程虚拟地址空间---->>>文件磁盘地址),分为下面几个步骤:

- 依次通过待映射的文件指针,文件描述符&文件结构体,最终调用内核中的mmap()
- 内核空间中的mmap通过虚拟文件系统inode模块 定位到文件磁盘物理地址
- 通过remap_pfn_range()建立页表,实现了文件地址和虚拟地址区域的映射关系。
到这里仅仅是创建了虚拟空间和映射地址,没有任何文件数据的拷贝。真正的拷贝时刻是到了进程发生了读写操作。
最后是进程访问映射空间,实现文件内容到物理内存的数据拷贝,如下步骤:
- 进程的读写操作,访问虚拟地址空间这一段映射地址
- 若进程通过写操作改变了其内容,一定时间后系统会自动回写脏页到对应的磁盘地址,即完成了写入文件的操作。(注意:修改时,脏页面不会立即更新,而是在之后的msync()来强制同步。)

总结
- 用户空间和内核空间的高效交互;通过映射的区域直接交互的方式
- 数据拷贝次数减少;对文件的读取操作跨过了页缓存,减少了数据的拷贝次数
- 文件读取的效率高,用内存的读写方式操作IO读写方式
- 可实现高效的大规模数据传输,借助硬盘空间协助大数据操作时,采用mmap可提高效率
应用场景
在Linux系统中,根据内存映射的原理,它的应用场景如下:
- 实现内存共享:如跨进程通信
- 提高数据读写效率;如文件读写操作。