C语言 什么时候应该使用mmap访问文件?

kx7yvsdv  于 2023-10-16  发布在  其他
关注(0)|答案(6)|浏览(120)

POSIX环境提供了至少两种访问文件的方法。有标准的系统调用open()read()write()和朋友,但也有使用mmap()将文件Map到虚拟内存的选项。
什么时候使用一个比另一个更好?它们各自的优点是什么,值得包括两个接口?

eqoofvh9

eqoofvh91#

如果您有多个进程以只读方式访问同一文件中的数据,mmap非常好,这在我编写的服务器系统中很常见。mmap允许所有这些进程共享相同的物理内存页,从而节省大量内存。
mmap还允许操作系统优化分页操作。例如,考虑两个方案;程序A1MB文件读入用malloc创建的缓冲区,程序B将1 MB文件读入内存。如果操作系统必须将A的部分内存换出,它必须先将缓冲区的内容写入交换区,然后才能重用内存。在B的情况下,任何未修改的mmap'd页面都可以立即重用,因为操作系统知道如何从它们被mmap'd的现有文件中恢复它们。(操作系统可以通过最初将可写的mmap'd页面标记为只读并捕获seg faults来检测哪些页面未修改,类似于Copy on Write策略)。
mmap也适用于inter process communication。您可以在需要通信的进程中将文件mmap作为读/写对象,然后在mmap'd区域中使用同步原语(这就是MAP_HASSEMAPHORE标志的用途)。
mmap有一个地方可能会很尴尬,那就是如果您需要在32位机器上处理非常大的文件。这是因为mmap必须在进程的地址空间中找到一个连续的地址块,该地址块要足够大,以适应被Map的文件的整个范围。如果您的地址空间变得碎片化,这可能会成为一个问题,您可能有2 GB的地址空间可用,但没有一个单独的范围可以容纳1 GB的文件Map。在这种情况下,您可能必须将文件Map到比您希望的更小的块中。
使用mmap替代读/写的另一个潜在的尴尬之处是,您必须在页面大小的偏移量上开始Map。如果你只是想在偏移量X处获取一些数据,你需要修复该偏移量,使其与mmap兼容。
最后,读/写是你处理某些类型文件的唯一方法。mmap不能用于pipesttys

goqiplq2

goqiplq22#

我发现mmap()在阅读小文件(16K以下)时不是一个优势。与只执行单个read()系统调用相比,读取整个文件的页面错误开销非常高。这是因为内核有时可以完全在你的时间片内完成读操作,这意味着你的代码不会切换。对于页面错误,似乎更有可能调度另一个程序,从而使文件操作具有更高的延迟。

ttvkxqim

ttvkxqim3#

mmap在随机访问大文件时具有优势。另一个优点是,您可以通过内存操作(memcpy,指针运算)访问它,而无需担心缓冲。当你的结构比缓冲区大时,使用缓冲区时,正常的I/O有时会非常困难。处理这些问题的代码通常很难正确,mmap通常更容易。也就是说,在使用mmap时存在某些陷阱。正如人们已经提到的,mmap的设置成本相当高,因此只值得在给定的大小(因机器而异)下使用。
对于对文件的纯顺序访问,它也不总是更好的解决方案,尽管适当地调用madvise可以缓解这个问题。
你必须小心你的体系结构的对齐限制(req,itq),对于读/写IO,缓冲区通常是正确对齐的,并且在解引用一个强制转换的指针时不会陷入困境。
你也必须小心,你不访问Map之外。如果在map上使用字符串函数,并且文件末尾不包含\0,则很容易发生这种情况。当您的文件大小不是页面大小的倍数时,它将在大多数情况下工作,因为最后一页填充为0(Map区域的大小始终是页面大小的倍数)。

ldioqlga

ldioqlga4#

除了其他不错的答案,引用谷歌MavenRobert Love写的Linux system programming

mmap( )的优势

通过mmap( )操作文件比标准的read( )write( )系统调用有很多优点。其中包括:

  • 阅读和写内存Map文件避免了使用read( )write( )系统调用时发生的无关复制,在这些系统调用中,必须将数据复制到用户空间缓冲区,或者从用户空间缓冲区复制数据。
  • 除了任何潜在的页面错误之外,对内存Map文件的阅读和写入不会引起任何系统调用或上下文切换开销。就像访问内存一样简单。
  • 当多个进程将同一对象Map到内存中时,数据在所有进程之间共享。只读和共享的可写Map是完全共享的;私有可写Map共享其尚未COW(写时复制)页面。
  • 在Map周围查找涉及到琐碎的指针操作。不需要lseek( )系统调用。

因此,mmap( )是许多应用程序的明智选择。

mmap( )的缺点

在使用mmap( )时,有几点需要记住:

  • 内存Map的大小始终为整数个页面。因此,备份文件的大小与整数个页面之间的差异被“浪费”为松弛空间。对于小文件,可能会浪费很大比例的Map。例如,对于4 KB页面,7字节Map浪费4,089字节。
  • 内存Map必须适合进程的地址空间。对于32位地址空间,大量不同大小的Map可能会导致地址空间碎片化,从而很难找到大的空闲连续区域。当然,对于64位地址空间,这个问题就不那么明显了。
  • 在内核中创建和维护内存Map和相关的数据结构会产生开销。通过消除上一节中提到的双重复制,通常可以避免这种开销,特别是对于较大和经常访问的文件。

由于这些原因,mmap( )的好处在Map文件很大(因此任何浪费的空间只占总Map的一小部分),或者Map文件的总大小可被页面大小整除(因此没有浪费空间)时最能实现。

vecaoik1

vecaoik15#

与传统IO相比,内存Map具有巨大的速度优势。它允许操作系统在访问内存Map文件中的页面时从源文件读取数据。这是通过创建错误页面来实现的,操作系统检测到错误页面,然后操作系统自动从文件中加载相应的数据。
这与分页机制的工作方式相同,通常通过阅读系统页面边界和大小(通常为4K)的数据来优化高速I/O-大多数文件系统缓存都针对此大小进行了优化。

ymdaylpp

ymdaylpp6#

mmap()的一个优势还没有列出,那就是它能够将只读Map保持为 clean 页面。如果在进程的地址空间中分配一个缓冲区,然后使用read()从文件填充缓冲区,那么与该缓冲区相对应的内存页现在是“脏”的,因为它们已经被写入。
内核不能从RAM中删除脏页。如果有交换空间,则可以将它们调出以进行交换。但是这是昂贵的,并且在某些系统上,例如只有闪存的小型嵌入式设备,根本没有交换。在这种情况下,缓冲区将被卡在RAM中,直到进程退出,或者可能用madvise()返回。
未写入mmap()的页面是干净的。如果内核需要RAM,它可以简单地删除它们并使用页面所在的RAM。如果拥有Map的进程再次访问它,它会导致页面错误,内核会从它们最初来自的文件重新加载页面。就像他们最初的生活方式一样。
这不需要多个进程使用Map文件,这是一个优势。

相关问题