POSIX环境提供至少两种访问文件的方法.有标准的系统调用open()
,read()
,write()
,和朋友,但也有使用的选项mmap()
,将文件映射到虚拟内存.
何时优先使用一个而不是另一个?它们各自的优势是什么,包括两个接口?
如果你有多个进程从同一个文件以只读方式访问数据,那么mmap很棒,这在我编写的服务器系统类型中很常见.mmap允许所有这些进程共享相同的物理内存页面,从而节省大量内存.
mmap还允许操作系统优化分页操作.例如,考虑两个程序; 程序A将1MB文件读入使用malloc创建的缓冲区,程序B将1MB文件格式化为内存.如果操作系统必须将A的一部分内存交换掉,它必须将缓冲区的内容写入交换,然后才能重用内存.在B的情况下,任何未修改的mmap页面都可以立即重用,因为操作系统知道如何从他们mmap的现有文件中恢复它们.(操作系统可以通过最初将可写mmap的页面标记为只读并捕获seg错误来检测哪些页面未修改,类似于写入时复制策略).
mmap对于进程间通信也很有用.您可以在需要通信的进程中将文件映射为读/写,然后在mmap'd区域中使用同步原语(这是MAP_HASSEMAPHORE标志的用途).
如果您需要在32位计算机上使用非常大的文件,那么mmap的一个地方可能很尴尬.这是因为mmap必须在进程的地址空间中找到一个连续的地址块,该地址空间足够大,以适应所映射文件的整个范围.如果您的地址空间变得碎片化,这可能会成为一个问题,您可能有2 GB的地址空间可用,但没有单独的范围可以适合1 GB的文件映射.在这种情况下,您可能必须以比您想要的更小的块来映射文件.
mmap作为读/写替代品的另一个潜在尴尬是你必须在页面大小的偏移量上开始映射.如果您只想在偏移X处获取一些数据,则需要修复该偏移量,以便与mmap兼容.
最后,读/写只是你的方式可以与某些类型的文件的工作.mmap不能用于管道和ttys之类的东西.
我发现mmap()不具有优势的一个领域是读取小文件(16K以下).与仅执行单个read()系统调用相比,读取整个文件的页面错误开销非常高.这是因为内核有时可以在您的时间片中完全满足读取,这意味着您的代码不会被切换掉.由于页面错误,似乎更有可能安排另一个程序,使文件操作具有更高的延迟.
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 - 大多数文件系统缓存都针对这些大小进行优化.