什么时候应该使用mmap进行文件访问?


276

POSIX环境至少提供两种访问文件的方式。有标准的系统调用open()read()write(),和朋友,但也有使用的选项mmap(),将文件映射到虚拟内存。

什么时候比另一种更好?包括两个接口,它们各自的优点是什么?


16
另请参见mmap()与阅读块,以及Linus Torvalds的这篇帖子在其中的答案之一中提到。
MvG

Answers:


299

mmap如果您有多个进程从同一文件中以只读方式访问数据,那将非常好,这在我编写的服务器系统中很常见。 mmap允许所有这些进程共享相同的物理内存页面,从而节省大量内存。

mmap还允许操作系统优化分页操作。例如,考虑两个程序;程序A1MB文件读入用创建的缓冲区malloc,程序B mmaps将1MB文件读入内存。如果操作系统必须换出部分A内存,则它必须写入缓冲区的内容以进行换用,然后才能重新使用内存。在B这种情况下,任何未修改mmap的d页面都可以立即重用,因为OS知道如何从其原先的文件中还原它们mmap。(操作系统可以通过将可写mmapd页最初标记为只读并捕获段错误来检测未修改的页,类似于“写时复制”策略)。

mmap对于进程间的通信也是有用的。您可以mmap在需要进行通信的进程中将其作为读/写文件,然后在该mmap'd区域中使用同步原语(这是该MAP_HASSEMAPHORE标志的作用)。

一个麻烦的地方mmap是如果您需要在32位计算机上处​​理非常大的文件。这是因为mmap必须在进程的地址空间中找到一个连续的地址块,该地址块必须足以容纳要映射的文件的整个范围。如果您的地址空间变得零散,这可能会成为问题,您可能有2 GB的可用地址空间,但是没有一个单独的范围可以容纳1 GB的文件映射。在这种情况下,您可能需要将文件映射成比您希望适合的文件小的块。

mmap替代读取/写入的另一个潜在尴尬之处是,您必须在页面大小的偏移量上开始映射。如果您只想获取一些偏移量数据,X则需要修正该偏移量,使其与兼容mmap

最后,读/写只是你的方式可以与某些类型的文件的工作。 mmap不能用于管道ttys之类的东西。


10
可以在正在增长的文件上使用mmap()吗?还是在分配mmap()内存/文件时固定大小?
乔纳森·莱夫勒

29
进行mmap调用时,必须指定大小。因此,如果您想执行类似尾部操作的操作,则不太适合。
唐·诺伊菲尔德

5
Afaik MAP_HASSEMAPHORE特定于BSD。
PatrickSchlüter,2010年

6
@JonathanLeffler当然,您可以在正在增长的文件上使用mmap(),但是当文件达到最初分配的空间限制时,必须以新的大小再次调用mmap()。LevelDB的PosixMmapFile就是一个很好的例子。但是它从1.15开始不再使用mmap。您可以从Github中
包跳2015年

4
如果需要多次处理文件,mmap也可能很有用:分配虚拟内存页面的成本只需支付一次。
臂架

69

我发现mmap()没有优势的一个方面是读取小文件(16K以下)时。与仅执行单个read()系统调用相比,页面错误读取整个文件的开销非常高。这是因为内核有时可以完全满足您的时间片读取要求,这意味着您的代码不会消失。由于页面错误,似乎更有可能安排另一个程序,从而使文件操作具有更高的延迟。


4
+1我可以确认。对于小文件,它更快地malloc占用一块内存,read并将其放入1个内存。这允许使用与处理内存映射相同的代码来处理malloc'ed。
PatrickSchlüter,2010年

35
这就是说,你的辩解是不对的。调度程序与差异完全无关。不同之处在于对页表的写访问,这是内核的全局结构,其中包含哪些进程持有哪个内存页及其访问权限。此操作可能会非常昂贵(它可能会使高速缓存行无效,它可能会通过TLB,表是全局的,因此必须防止并发访问等)。您需要一定大小的映射,以便read访问的开销高于虚拟内存操作的开销。
PatrickSchlüter,2010年

1
@PatrickSchlüter好的,我知道mmap()开始时会涉及开销,涉及修改页表。假设我们将文件的16K映射到内存。对于4K的页面大小,mmap必须更新页面表中的4个条目。但是使用read复制到16K的缓冲区还涉及更新4个页表项,更不用说它需要将16K复制到用户地址空间中。那么,您能否详细说明一下页表上的操作差异,以及它的价格如何昂贵mmap
flow2k

45

mmap当您可以随机访问大文件时,它具有优势。另一个优点是您可以使用内存操作(memcpy,指针算术)访问它,而不必担心缓冲。当结构大于缓冲区时,使用缓冲区时,正常的I / O有时会非常困难。通常很难正确处理的代码,而mmap通常更容易。这就是说,使用时会有一些陷阱mmap。正如人们已经提到的,mmap它的建立成本很高,因此仅在给定的大小(因机器而异)上才值得使用。

对于纯顺序访问文件,尽管适当地调用,但这也不总是更好的解决方案。 madvise可以缓解问题。

您必须注意架构的对齐限制(SPARC,itanium),使用读/写IO时,缓冲区通常会正确对齐,并且在取消引用强制转换指针时不会陷阱。

您还必须注意不要在地图之外访问。如果在地图上使用字符串函数,并且文件末尾不包含\ 0,则很容易发生这种情况。当文件大小不是页面大小的倍数时,大多数情况下它将起作用,因为最后一页用0填充(映射区域的大小始终是页面大小的倍数)。


30

除了其他不错的答案外,还有Google专家罗伯特·洛夫(Robert Love)撰写的Linux系统编程引文:

优点 mmap( )

mmap( )与标准read( )write( )系统调用相比,通过操作文件具有许多优势。其中包括:

  • 读取和写入内存映射文件避免了使用read( )write( )系统调用时发生的不必要的复制,在复制或复制中,必须在用户空间缓冲区之间来回复制数据。

  • 除了潜在的页面错误之外,读取和写入内存映射文件不会招致任何系统调用或上下文切换开销。它就像访问内存一样简单。

  • 当多个进程将同一对象映射到内存中时,数据将在所有进程之间共享。只读和共享的可写映射完全共享。私有可写映射具有尚未共享的COW(写时复制)页面。

  • 寻找映射涉及琐碎的指针操作。不需要lseek( )系统调用。

由于这些原因,mmap( )对于许多应用程序是明智的选择。

缺点 mmap( )

使用时有几点要记住mmap( )

  • 内存映射的页面大小始终是整数。因此,备份文件的大小与整数页之间的差被“浪费”为松弛空间。对于小文件,可能会浪费很大一部分映射。例如,对于4 KB页面,一个7字节的映射浪费了4,089字节。

  • 内存映射必须适合进程的地址空间。使用32位地址空间时,大量的各种大小的映射会导致地址空间碎片化,从而很难找到较大的自由连续区域。当然,对于64位地址空间,此问题不那么明显。

  • 在内核内部创建和维护内存映射以及相关的数据结构会产生开销。通常,通过消除上一部分中提到的重复副本,可以消除这种开销,尤其是对于较大且经常访问的文件。

出于这些原因,mmap( )当映射文件很大(因此,任何浪费的空间仅占总映射的一小部分)时,或者当映射文件的总大小可被页面大小均分时,最大程度地实现的好处(因此没有浪费的空间)。


13

与传统的IO相比,内存映射具有巨大的速度优势。当触摸内存映射文件中的页面时,它使操作系统能够从源文件读取数据。这是通过创建故障页面来工作的,操作系统会检测到这些页面,然后操作系统会自动从文件中加载相应的数据。

这与分页机制的工作方式相同,并且通常通过读取系统页面边界和大小(通常为4K)上的数据进行高速I / O的优化(大多数文件系统缓存已针对该大小进行了优化)。


15
请注意,mmap()并不总是比read()快。对于顺序读取,mmap()不会给您带来可衡量的优势-这是基于经验和理论证据的。如果您不相信我,请编写您自己的测试。
蒂姆·库珀

1
我可以给出来自我们项目的数字,这是短语数据库的一种文本索引。索引数个千兆字节,密钥保存在三叉树中。索引仍在并行增长以进行读取访问,映射部分外部的访问通过进行pread。在Solaris 9 Sparc(V890)上,pread访问的速度比memcpymmap的速度慢2到3倍。但是您是对的,顺序访问不一定会更快。
PatrickSchlüter,2010年

19
只是一点点挑剔。它不像分页机制那样工作,它是分页机制。映射文件是将文件的存储区域分配给文件,而不是匿名交换文件。
PatrickSchlüter,2010年

2

尚未列出的一个优点是能够mmap()将只读映射保留为干净页面。如果在进程的地址空间中分配了一个缓冲区,然后用于read()从文件填充缓冲区,则与该缓冲区相对应的内存页现在已变脏由于已被写入而了。

内核无法从RAM中删除脏页。如果有交换空间,则可以将它们调出页面进行交换。但这是昂贵的,并且在某些系统(例如仅具有闪存的小型嵌入式设备)上根本没有交换。在这种情况下,缓冲区将被卡在RAM中,直到进程退出,或者可能将其返回madvise()

不写给 mmap()页面是干净的。如果内核需要RAM,则可以简单地将其删除并使用页面所在的RAM。如果具有映射的进程再次访问它,则将导致页面错误,内核会从它们最初来自的文件中重新加载页面。 。首先,以相同的方式填充它们。

使用映射文件不需要一个以上的过程即可。


内核不能通过先将其内容写到基础文件中来丢弃“脏” mmap页面吗?
杰里米·弗里斯纳

2
使用时read(),最终将放入数据的页面与它们可能来自的文件没有关系。因此,除非交换空间,否则无法将其写出。如果文件是mmap()ed,并且映射是可写的(相对于只读),并且已写入,则取决于映射是MAP_SHARED还是MAP_PRIVATE。可以/必须将共享映射写入文件,但不能将私有映射写入文件。
TrentP
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.