POSIX环境提供了至少两种访问文件的方法。有标准的系统调用open()、read()、write()和朋友,但也有使用mmap()将文件Map到虚拟内存的选项。什么时候使用一个比另一个更好?它们各自的优点是什么,值得包括两个接口?
open()
read()
write()
mmap()
eqoofvh91#
如果您有多个进程以只读方式访问同一文件中的数据,mmap非常好,这在我编写的服务器系统中很常见。mmap允许所有这些进程共享相同的物理内存页,从而节省大量内存。mmap还允许操作系统优化分页操作。例如,考虑两个方案;程序A将1MB文件读入用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不能用于pipes和ttys。
mmap
A
1MB
malloc
B
mmap'd
MAP_HASSEMAPHORE
X
goqiplq22#
我发现mmap()在阅读小文件(16K以下)时不是一个优势。与只执行单个read()系统调用相比,读取整个文件的页面错误开销非常高。这是因为内核有时可以完全在你的时间片内完成读操作,这意味着你的代码不会切换。对于页面错误,似乎更有可能调度另一个程序,从而使文件操作具有更高的延迟。
ttvkxqim3#
mmap在随机访问大文件时具有优势。另一个优点是,您可以通过内存操作(memcpy,指针运算)访问它,而无需担心缓冲。当你的结构比缓冲区大时,使用缓冲区时,正常的I/O有时会非常困难。处理这些问题的代码通常很难正确,mmap通常更容易。也就是说,在使用mmap时存在某些陷阱。正如人们已经提到的,mmap的设置成本相当高,因此只值得在给定的大小(因机器而异)下使用。对于对文件的纯顺序访问,它也不总是更好的解决方案,尽管适当地调用madvise可以缓解这个问题。你必须小心你的体系结构的对齐限制(req,itq),对于读/写IO,缓冲区通常是正确对齐的,并且在解引用一个强制转换的指针时不会陷入困境。你也必须小心,你不访问Map之外。如果在map上使用字符串函数,并且文件末尾不包含\0,则很容易发生这种情况。当您的文件大小不是页面大小的倍数时,它将在大多数情况下工作,因为最后一页填充为0(Map区域的大小始终是页面大小的倍数)。
madvise
ldioqlga4#
除了其他不错的答案,引用谷歌MavenRobert Love写的Linux system programming:
mmap( )
通过mmap( )操作文件比标准的read( )和write( )系统调用有很多优点。其中包括:
read( )
write( )
lseek( )
因此,mmap( )是许多应用程序的明智选择。
在使用mmap( )时,有几点需要记住:
由于这些原因,mmap( )的好处在Map文件很大(因此任何浪费的空间只占总Map的一小部分),或者Map文件的总大小可被页面大小整除(因此没有浪费空间)时最能实现。
vecaoik15#
与传统IO相比,内存Map具有巨大的速度优势。它允许操作系统在访问内存Map文件中的页面时从源文件读取数据。这是通过创建错误页面来实现的,操作系统检测到错误页面,然后操作系统自动从文件中加载相应的数据。这与分页机制的工作方式相同,通常通过阅读系统页面边界和大小(通常为4K)的数据来优化高速I/O-大多数文件系统缓存都针对此大小进行了优化。
ymdaylpp6#
mmap()的一个优势还没有列出,那就是它能够将只读Map保持为 clean 页面。如果在进程的地址空间中分配一个缓冲区,然后使用read()从文件填充缓冲区,那么与该缓冲区相对应的内存页现在是“脏”的,因为它们已经被写入。内核不能从RAM中删除脏页。如果有交换空间,则可以将它们调出以进行交换。但是这是昂贵的,并且在某些系统上,例如只有闪存的小型嵌入式设备,根本没有交换。在这种情况下,缓冲区将被卡在RAM中,直到进程退出,或者可能用madvise()返回。未写入mmap()的页面是干净的。如果内核需要RAM,它可以简单地删除它们并使用页面所在的RAM。如果拥有Map的进程再次访问它,它会导致页面错误,内核会从它们最初来自的文件重新加载页面。就像他们最初的生活方式一样。这不需要多个进程使用Map文件,这是一个优势。
madvise()
6条答案
按热度按时间eqoofvh91#
如果您有多个进程以只读方式访问同一文件中的数据,
mmap
非常好,这在我编写的服务器系统中很常见。mmap
允许所有这些进程共享相同的物理内存页,从而节省大量内存。mmap
还允许操作系统优化分页操作。例如,考虑两个方案;程序A
将1MB
文件读入用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
不能用于pipes和ttys。goqiplq22#
我发现mmap()在阅读小文件(16K以下)时不是一个优势。与只执行单个read()系统调用相比,读取整个文件的页面错误开销非常高。这是因为内核有时可以完全在你的时间片内完成读操作,这意味着你的代码不会切换。对于页面错误,似乎更有可能调度另一个程序,从而使文件操作具有更高的延迟。
ttvkxqim3#
mmap
在随机访问大文件时具有优势。另一个优点是,您可以通过内存操作(memcpy,指针运算)访问它,而无需担心缓冲。当你的结构比缓冲区大时,使用缓冲区时,正常的I/O有时会非常困难。处理这些问题的代码通常很难正确,mmap通常更容易。也就是说,在使用mmap
时存在某些陷阱。正如人们已经提到的,mmap
的设置成本相当高,因此只值得在给定的大小(因机器而异)下使用。对于对文件的纯顺序访问,它也不总是更好的解决方案,尽管适当地调用
madvise
可以缓解这个问题。你必须小心你的体系结构的对齐限制(req,itq),对于读/写IO,缓冲区通常是正确对齐的,并且在解引用一个强制转换的指针时不会陷入困境。
你也必须小心,你不访问Map之外。如果在map上使用字符串函数,并且文件末尾不包含\0,则很容易发生这种情况。当您的文件大小不是页面大小的倍数时,它将在大多数情况下工作,因为最后一页填充为0(Map区域的大小始终是页面大小的倍数)。
ldioqlga4#
除了其他不错的答案,引用谷歌MavenRobert Love写的Linux system programming:
mmap( )
的优势通过
mmap( )
操作文件比标准的read( )
和write( )
系统调用有很多优点。其中包括:read( )
或write( )
系统调用时发生的无关复制,在这些系统调用中,必须将数据复制到用户空间缓冲区,或者从用户空间缓冲区复制数据。lseek( )
系统调用。因此,
mmap( )
是许多应用程序的明智选择。mmap( )
的缺点在使用
mmap( )
时,有几点需要记住:由于这些原因,
mmap( )
的好处在Map文件很大(因此任何浪费的空间只占总Map的一小部分),或者Map文件的总大小可被页面大小整除(因此没有浪费空间)时最能实现。vecaoik15#
与传统IO相比,内存Map具有巨大的速度优势。它允许操作系统在访问内存Map文件中的页面时从源文件读取数据。这是通过创建错误页面来实现的,操作系统检测到错误页面,然后操作系统自动从文件中加载相应的数据。
这与分页机制的工作方式相同,通常通过阅读系统页面边界和大小(通常为4K)的数据来优化高速I/O-大多数文件系统缓存都针对此大小进行了优化。
ymdaylpp6#
mmap()
的一个优势还没有列出,那就是它能够将只读Map保持为 clean 页面。如果在进程的地址空间中分配一个缓冲区,然后使用read()
从文件填充缓冲区,那么与该缓冲区相对应的内存页现在是“脏”的,因为它们已经被写入。内核不能从RAM中删除脏页。如果有交换空间,则可以将它们调出以进行交换。但是这是昂贵的,并且在某些系统上,例如只有闪存的小型嵌入式设备,根本没有交换。在这种情况下,缓冲区将被卡在RAM中,直到进程退出,或者可能用
madvise()
返回。未写入
mmap()
的页面是干净的。如果内核需要RAM,它可以简单地删除它们并使用页面所在的RAM。如果拥有Map的进程再次访问它,它会导致页面错误,内核会从它们最初来自的文件重新加载页面。就像他们最初的生活方式一样。这不需要多个进程使用Map文件,这是一个优势。