操作系统修炼秘籍(19):进程间通信(4):文件映射和共享内存
文件映射
文件映射(Memory-mapped file)是将磁盘上文件的某段数据映射到内核的一段物理内存上,然后将此物理内存映射到一个或多个进程的虚拟内存中。映射了文件的进程可以直接读、写这段内存来达到读、写磁盘文件的功能。如果多个进程请求映射的文件区段相同,则只映射一次。
所以,文件映射进行了两次映射,一次映射是将属于磁盘文件的部分或全部数据块映射到物理内存页中,另一次映射是将物理内存页映射到进程的虚拟页上。
如图,图中假设磁盘数据块大小和内存页大小相同,将某文件中的6个数据块(可能只是该文件的一部分数据)映射到物理内存的页1、2、3、4、5、6中,并且两个进程都映射该磁盘文件相同的数据区域,所以两个进程都指向相同的物理内存页。
使用文件映射时,最开始该内存区是空的。当访问这段内存时,如果访问的数据不在内存中(例如第一次访问),将出现page fault,于是会从磁盘中读取相应的数据块拷贝到这段内存(基于操作系统提供的预读优化,可能一次性会多读一些数据到映射的内存中)。当向此内存区写数据时,会自动写入到磁盘文件对应的数据块中(可以设置为其它写方式)。
此外,通过设置文件映射方式,还支持写时复制。某进程要向该映射区的某内存页写数据时,会拷贝该页到自己的虚拟内存中,然后写数据到该副本页,而不会影响映射区的数据,而且以后该进程对该页的操作都将使用该副本。
既然每个进程都能访问到文件映射的物理内存,那么这段物理内存分配在进程虚拟内存布局的哪一部分呢?这个简单思考下即可得知答案,进程的虚拟内存中,只有堆内存和堆栈中间的未分配地址空间是可以由用户进程自由使用的地址区域,如果将文件映射到堆中,假如映射的文件数据较大,已分配的堆内存中空闲页很可能不足以映射该文件,操作系统需要为其分配更多的堆内存,另一方面,就算当前堆内存能放下文件映射,但也很可能因此而导致空闲页所剩不多,进程之后的运行很可能还是要请求分配新的堆内存,如此看来,倒不如直接映射在堆栈中间那片未分配的地址空间,并且为堆和栈都预留一段未分配空间。图中给出文件映射在进程内存布局中的位置。
共享内存
共享内存(Shared Memory)是直接从内核维护的内存中划分一片内存,并将该内存映射到一个或多个进程中。
因为可能多个进程映射到同一共享内存,所以某进程对此内存数据的修改会直接影响其它进程,这样就能在进程之间传递消息。但也正因为如此,在使用共享内存时,应当保证没有两个或以上的进程同时修改共享内存数据。
共享内存是效率最高的进程间通信方式,它完全内存化操作,且没有任何内存拷贝行为,此外,内存映射到不同进程之后,操作系统就不再参与该片内存的操作,用户进程可以有权访问这段内存。
内存共享和文件映射非常像,不同之处就在于共享内存没有对具体的磁盘文件进行映射,而是直接映射物理内存到进程中。所以,它也映射在进程堆栈中间的那片未分配内存上。如图。