文章目录
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
读此文前可以查看先行了解下:虚拟内存(Virtual Memory),特别是其中的3.3 内存映射。
mmap共享存储映射又称为 存储I/O映射,是Unix 共享内存 概念中的一种。
在Unix进程间通信中,大致有:
管道 pipe(),用于父子进程间通信(不考虑传递描述符)
FIFO(有名管道) 非父子进程也能使用,以文件打通
文件 文件操作,效率可想而知
本地套接字 最稳定,也最复杂.套接字采用Unix域
共享内存 传递最快,消耗最小,传递数据过程不涉及系统调用
信号 数据固定且短小
IPC相关解析见:linux基础——linux进程间通信(IPC)机制总结。
其中,共享内存是IPC(进程间通信)中最快的,一旦共享内存映射到共享它的进程的地址空间中,这些进程的数据传递就不再涉及内核,因为它会以指针的方式读写内存,不涉及系统级调用。
(一)管道与共享存储映射对比
管道
请看上图,左图描述了fork()前通过pipe()开启管道的示意图,假设父进程从文件A中读取数据并通过管道传递给子进程,由子进程执行某些操作后写入文件B。
首先,进程(32bit操作系统)的数据区位于0-3G的虚拟地址空间中,3G-4G为内核区,注意,文件A和文件B并不是存储在内核区,这里只是示意。并且,本次父子进程完全按照最早期Unix的实现讲解,也就是说父子进程完全独立的空间,不涉及到后来的写时复制(copy on write,即改动前父子进程共享一份全局/非运行时数据)等技术。
- (1)父进程通过系统调用
read()从文件A读取数据的过程中,父进程的状态切换到内核态,读取数据并保存到父进程空间中的buf中,再切换回用户态。这里发生了第一次数据的拷贝。 - (2)父进程通过系统调用write()将读取的数据从buf中拷贝到管道的过程中,父进程状态切换到内核态,向管道写入数据,再切换回用户态。这里发生第二次数据拷贝。
- (3)子进程通过系统调用
read()从管道读取数据的过程中,子进程状态切换到内核态,读取数据并保存到子进程空间中的buf中,再切换回用户态。这里发生第三次数据拷贝。 - (4)子进程通过系统调用
write()将读取的数据从buf中拷贝到文件B的过程中,子进程状态切换到内核态,向文件B写入数据,再切换回用户态。这里发生第四次数据拷贝。
可以看到,这里发生了四次数据拷贝都是再内核与某个进程间进行的,这种开销往往更大(比存粹在内核中或单个进程内复制数据的开销更大)
因此,通过管道进行数据传递在编程上简单,而实际开销是作为一个追求极致效率的程序员所不允许的。接着我们来看看共享存储映射的开销是怎样的呢?
共享存储映射(存储I/O映射)
请看上图,该图描述了父进程使用mmap()使用共享存储映射,fork()后,fork会对内存映射文件进行特殊处理,也就是父进程在调用fork()之前创建的内存映射关系由子进程共享。该方式只有两次系统系统调用。而之前有四次调用。
因此,父子进程可以通过指针对该内存区域进行读写操作,以完成数据通信。
该方法的奇特之处在于,进程间通信的I/O操作在内核的掩盖下完成,对内存的直接存取操作不涉及系统调用,避免了进程状态的频繁切换与系统调用。
- (1)使用
mmap()建立共享存储映射区 - (2)父进程
fork(),子进程共享该区域 - (3)父进程读取文件A中的数据的过程中,切换至内核态,根据mmap返回的指针ptr,将数据拷贝到共享区域,再切换回来。这里发生第一次数据拷贝。
- (4)子进程根据ptr指针从内存读取数据到文件B,切换到内核态,write数据到文件B,再切换回来。这里发生第二次数据拷贝。
注意:这里是父进程直接copy文件A到共享区,子进程从共享区copy数据到文件B,即共享存储映射是将磁盘上的文件映射到进程的虚拟地址空间,其物理支撑是物理内存,而进程通信时就是通过物理内存来传递数据,而不是写入磁盘再读出来。
(二)mmap函数
mmap函数把一个文件或一个Posix共享内存区对象映射到调用进程的地址空间。
(1)使用普通文件以提供内存映射I/O
(2)使用特殊文件以提供匿名内存映射
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
//若成功则返回被映射区的起始地址,若出错则返回MAP_FAILED
addr:指定被映射到进程空间内的起始地址,通常设为 NULL,


3377

被折叠的 条评论
为什么被折叠?



