什么时候应该使用mmap进行文件访问?
POSIX环境至less提供两种访问文件的方式。 有标准的系统调用open()
, read()
, write()
和friends,但也有使用mmap()
将文件映射到虚拟内存的选项。
什么时候最好使用一个呢? 他们个人的优点是什么,包括两个接口的优点?
如果您有多个进程以同一文件的只读方式访问数据,mmap是非常棒的,这在我所写的服务器系统中是很常见的。 mmap允许所有这些进程共享相同的物理内存页面,从而节省大量内存。
mmap还允许操作系统优化分页操作。 例如,考虑两个程序; 程序A将1MB文件读入用malloc创build的缓冲区,将程序B将1MB文件映射到内存中。 如果操作系统必须将A的部分内存交换出来,则必须先将缓冲区的内容写入交换区,才能重新使用内存。 在B的情况下,任何未修改的mmap'd页面都可以立即重用,因为操作系统知道如何从现有的文件中恢复它们。 (操作系统可以通过最初将可写入的mmap页面标记为只读并捕获seg错误,类似于Write on Write策略,检测哪些页面未被修改。
mmap对于进程间通信也很有用。 您可以将文件映射为需要进行通信的进程中的读/写,然后在mmap'd区域中使用同步基元(这是MAP_HASSEMAPHORE标志的用途)。
一个地方mmap可能会很笨拙,如果你需要在32位机器上处理非常大的文件。 这是因为mmap必须在进程的地址空间中find一个连续的地址块,这个地址空间足够大以适应被映射文件的整个范围。 如果你的地址空间变成碎片,这可能会成为一个问题,你可能有2 GB的地址空间,但是没有一个单独的范围可以适应1 GB的文件映射。 在这种情况下,您可能必须将文件映射到比您想要的更小的区块。
mmap替代读/写的另一个潜在的尴尬是,你必须开始在页面大小的偏移量上的映射。 如果你只是想在偏移量X获得一些数据,你将需要修正偏移量,所以它与mmap兼容。
最后,读/写是处理某些types文件的唯一方法。 mmap不能用于pipe道和ttys之类的东西。
一个地方,我发现mmap()不是一个优势是读小文件(16K以下)。 与仅执行一个read()系统调用相比,页面错误的读取整个文件的开销是非常高的。 这是因为内核有时可以在您的时间片内完全满足读取,这意味着您的代码不会切换。 出现页面错误时,看起来更有可能安排另一个程序,使得文件操作具有更高的延迟。
对大文件进行随机访问时, mmap
具有优势。 另一个优点是你可以用内存操作(memcpy,指针算术)来访问它,而不用担心缓冲。 当结构大于缓冲区时,使用缓冲区时,正常的I / O有时会非常困难。 处理这个问题的代码通常很难做到,mmap通常更容易。 这就是说,使用mmap
时有一些陷阱。 正如人们已经提到的那样, mmap
设置成本很高,所以值得使用的只是一个给定的大小(因机器而异)。
对于纯粹的顺序访问文件,它也并不总是更好的解决scheme,但适当的调用madvise
可以缓解这个问题。
对于体系结构(SPARC,itanium)的alignment限制,必须小心,使用读取/写入IO时,缓冲区通常会正确alignment,并且在取消引用已转换指针时不会陷入陷阱。
您还必须小心,不要在地图之外访问。 如果在地图上使用string函数,并且文件末尾不包含\ 0,则很容易发生。 当您的文件大小不是页面大小的倍数时,大多数情况下都是有效的,因为最后一页用0填充(映射区域总是大小为页面大小的倍数)。
与传统IO相比,内存映射具有巨大的速度优势。 当内存映射文件中的页面被触摸时,它允许操作系统从源文件读取数据。 这可以通过创buildOS检测到的错误页面来工作,然后操作系统自动从文件中加载相应的数据。
这与分页机制的工作方式相同,通常通过读取系统页面边界和大小(通常为4K)的数据(通常是大多数文件系统高速caching所针对的大小)来针对高速I / O进行优化。
除了其他不错的答案之外,Google专家罗伯特·洛夫(Robert Love)撰写的Linux系统程序devise报价:
mmap()的优点
通过mmap()操作文件与标准的read()和write()系统调用相比有一些优点。 其中有:
从内存映射文件中读取和写入内容可以避免使用read()或write()系统调用时出现的无关副本,其中必须将数据复制到用户空间缓冲区或从用户空间缓冲区中复制数据。
除了任何潜在的页面错误之外,从内存映射文件读取和写入不会导致任何系统调用或上下文切换开销。 这和访问内存一样简单。
当多个进程将同一个对象映射到内存中时,数据在所有进程之间共享。 只读和共享可写映射全部共享; 私有可写映射还没有共享COW(copy-on-write)页面。
寻找映射涉及简单的指针操作。 不需要lseek()系统调用。
由于这些原因,对于许多应用程序来说,mmap()是一个明智的select。
mmap()的缺点
使用mmap()时需要注意以下几点:
内存映射的大小始终是整数页。 因此,支持文件的大小和整数页面之间的差异被“浪费”为松散空间。 对于小文件,映射的很大一部分可能会浪费。 例如,对于4 KB的页面,一个7字节的映射会浪费4,089个字节。
内存映射必须符合进程的地址空间。 对于32位地址空间,大量不同大小的映射会导致地址空间碎片化,从而很难find大的空闲连续区域。 当然,这个问题在64位地址空间中不太明显。
在内核中创build和维护内存映射和相关的数据结构是有开销的。 这个开销通常可以通过消除上一节中提到的双重副本来消除,特别是对于较大和经常访问的文件。
由于这些原因,当映射文件很大时(因此浪费的空间占整个映射的一小部分),或者当映射文件的总大小能够被页面大小(因此没有浪费的空间)。