Answers:
mmap
如果您有多个进程从同一文件中以只读方式访问数据,那将非常好,这在我编写的服务器系统中很常见。 mmap
允许所有这些进程共享相同的物理内存页面,从而节省大量内存。
mmap
还允许操作系统优化分页操作。例如,考虑两个程序;程序A
将1MB
文件读入用创建的缓冲区malloc
,程序B mmaps
将1MB文件读入内存。如果操作系统必须换出部分A
内存,则它必须写入缓冲区的内容以进行换用,然后才能重新使用内存。在B
这种情况下,任何未修改mmap
的d页面都可以立即重用,因为OS知道如何从其原先的文件中还原它们mmap
。(操作系统可以通过将可写mmap
d页最初标记为只读并捕获段错误来检测未修改的页,类似于“写时复制”策略)。
mmap
对于进程间的通信也是有用的。您可以mmap
在需要进行通信的进程中将其作为读/写文件,然后在该mmap'd
区域中使用同步原语(这是该MAP_HASSEMAPHORE
标志的作用)。
一个麻烦的地方mmap
是如果您需要在32位计算机上处理非常大的文件。这是因为mmap
必须在进程的地址空间中找到一个连续的地址块,该地址块必须足以容纳要映射的文件的整个范围。如果您的地址空间变得零散,这可能会成为问题,您可能有2 GB的可用地址空间,但是没有一个单独的范围可以容纳1 GB的文件映射。在这种情况下,您可能需要将文件映射成比您希望适合的文件小的块。
mmap
替代读取/写入的另一个潜在尴尬之处是,您必须在页面大小的偏移量上开始映射。如果您只想获取一些偏移量数据,X
则需要修正该偏移量,使其与兼容mmap
。
MAP_HASSEMAPHORE
特定于BSD。
我发现mmap()没有优势的一个方面是读取小文件(16K以下)时。与仅执行单个read()系统调用相比,页面错误读取整个文件的开销非常高。这是因为内核有时可以完全满足您的时间片读取要求,这意味着您的代码不会消失。由于页面错误,似乎更有可能安排另一个程序,从而使文件操作具有更高的延迟。
malloc
占用一块内存,read
并将其放入1个内存。这允许使用与处理内存映射相同的代码来处理malloc'ed。
read
访问的开销高于虚拟内存操作的开销。
mmap
必须更新页面表中的4个条目。但是使用read
复制到16K的缓冲区还涉及更新4个页表项,更不用说它需要将16K复制到用户地址空间中。那么,您能否详细说明一下页表上的操作差异,以及它的价格如何昂贵mmap
?
mmap
当您可以随机访问大文件时,它具有优势。另一个优点是您可以使用内存操作(memcpy,指针算术)访问它,而不必担心缓冲。当结构大于缓冲区时,使用缓冲区时,正常的I / O有时会非常困难。通常很难正确处理的代码,而mmap通常更容易。这就是说,使用时会有一些陷阱mmap
。正如人们已经提到的,mmap
它的建立成本很高,因此仅在给定的大小(因机器而异)上才值得使用。
对于纯顺序访问文件,尽管适当地调用,但这也不总是更好的解决方案。 madvise
可以缓解问题。
您必须注意架构的对齐限制(SPARC,itanium),使用读/写IO时,缓冲区通常会正确对齐,并且在取消引用强制转换指针时不会陷阱。
您还必须注意不要在地图之外访问。如果在地图上使用字符串函数,并且文件末尾不包含\ 0,则很容易发生这种情况。当文件大小不是页面大小的倍数时,大多数情况下它将起作用,因为最后一页用0填充(映射区域的大小始终是页面大小的倍数)。
除了其他不错的答案外,还有Google专家罗伯特·洛夫(Robert Love)撰写的Linux系统编程引文:
优点
mmap( )
mmap( )
与标准read( )
和write( )
系统调用相比,通过操作文件具有许多优势。其中包括:
读取和写入内存映射文件避免了使用
read( )
或write( )
系统调用时发生的不必要的复制,在复制或复制中,必须在用户空间缓冲区之间来回复制数据。除了潜在的页面错误之外,读取和写入内存映射文件不会招致任何系统调用或上下文切换开销。它就像访问内存一样简单。
当多个进程将同一对象映射到内存中时,数据将在所有进程之间共享。只读和共享的可写映射完全共享。私有可写映射具有尚未共享的COW(写时复制)页面。
寻找映射涉及琐碎的指针操作。不需要
lseek( )
系统调用。由于这些原因,
mmap( )
对于许多应用程序是明智的选择。缺点
mmap( )
使用时有几点要记住
mmap( )
:
内存映射的页面大小始终是整数。因此,备份文件的大小与整数页之间的差被“浪费”为松弛空间。对于小文件,可能会浪费很大一部分映射。例如,对于4 KB页面,一个7字节的映射浪费了4,089字节。
内存映射必须适合进程的地址空间。使用32位地址空间时,大量的各种大小的映射会导致地址空间碎片化,从而很难找到较大的自由连续区域。当然,对于64位地址空间,此问题不那么明显。
在内核内部创建和维护内存映射以及相关的数据结构会产生开销。通常,通过消除上一部分中提到的重复副本,可以消除这种开销,尤其是对于较大且经常访问的文件。
出于这些原因,
mmap( )
当映射文件很大(因此,任何浪费的空间仅占总映射的一小部分)时,或者当映射文件的总大小可被页面大小均分时,最大程度地实现的好处(因此没有浪费的空间)。
与传统的IO相比,内存映射具有巨大的速度优势。当触摸内存映射文件中的页面时,它使操作系统能够从源文件读取数据。这是通过创建故障页面来工作的,操作系统会检测到这些页面,然后操作系统会自动从文件中加载相应的数据。
这与分页机制的工作方式相同,并且通常通过读取系统页面边界和大小(通常为4K)上的数据进行高速I / O的优化(大多数文件系统缓存已针对该大小进行了优化)。
pread
。在Solaris 9 Sparc(V890)上,pread访问的速度比memcpy
mmap的速度慢2到3倍。但是您是对的,顺序访问不一定会更快。
尚未列出的一个优点是能够mmap()
将只读映射保留为干净页面。如果在进程的地址空间中分配了一个缓冲区,然后用于read()
从文件填充缓冲区,则与该缓冲区相对应的内存页现在已变脏由于已被写入而了。
内核无法从RAM中删除脏页。如果有交换空间,则可以将它们调出页面进行交换。但这是昂贵的,并且在某些系统(例如仅具有闪存的小型嵌入式设备)上根本没有交换。在这种情况下,缓冲区将被卡在RAM中,直到进程退出,或者可能将其返回madvise()
。
不写给 mmap()
页面是干净的。如果内核需要RAM,则可以简单地将其删除并使用页面所在的RAM。如果具有映射的进程再次访问它,则将导致页面错误,内核会从它们最初来自的文件中重新加载页面。 。首先,以相同的方式填充它们。
使用映射文件不需要一个以上的过程即可。
read()
,最终将放入数据的页面与它们可能来自的文件没有关系。因此,除非交换空间,否则无法将其写出。如果文件是mmap()ed
,并且映射是可写的(相对于只读),并且已写入,则取决于映射是MAP_SHARED
还是MAP_PRIVATE
。可以/必须将共享映射写入文件,但不能将私有映射写入文件。