据说mmap()
将文件映射到内存,并且花费在调用进程的虚拟地址空间内存上。 它是否真的将数据复制到内存中,或者数据仍然存在于磁盘中? mmap()
比read()
更快吗?
mmap
函数唯一能做的就是改变一些内核数据结构,可能还有页表。 它实际上并没有将任何东西放入物理内存中。 在调用mmap
,分配的区域可能甚至不指向物理内存:访问它将导致页面错误。 这种页面错误是由内核透明处理的,实际上这是内核的主要职责之一。
使用mmap
会发生什么情况是数据保留在磁盘上,并且在进程读取数据时将其从磁盘复制到内存。 它也可以被推测性地复制到物理内存。 当你的进程被换出时, mmap
区域中的页面不必被写入交换,因为它们已经被长期存储支持 – 除非你修改了它们。
但是, mmap
会像malloc
和其他类似的函数(主要是在幕后使用mmap
,或者sbrk
,它基本上是mmap
一个特殊版本)一样占用虚拟地址空间。 使用mmap
读取文件和read
文件的主要区别在于, mmap
区域中未修改的页面不会影响整体内存压力,只要不被使用,它们几乎是“自由的”,内存明智的。 相比之下,使用read
函数读取的文件无论使用与否,总是会造成内存压力,以及是否被修改。
最后, mmap
的速度比read
的用例要快 – 随机访问和页面重用。 为了线性遍历一个文件,特别是一个小文件, read
通常会更快,因为它不需要修改页表,并且占用很少的系统调用。
作为一个建议,我可以说任何你要扫描的大文件都应该在64位系统上用mmap
完整地读取,你可以在32位系统上将它们mmap
为虚拟内存不多的块。
另请参阅: mmap()与读取块
另请参阅(感谢James): 我应该何时使用mmap进行文件访问?
复制并不意味着原件被销毁。
它将磁盘的内容映射到内存中,所以当然在某些时候必须复制这些位,是的。
因为这意味着它需要地址空间,占用了进程虚拟地址空间的一部分。
数据仍然存在于磁盘上。 操作系统分配一些物理内存并将文件数据复制到其中,以便访问那里的文件内容(当您尝试访问文件数据时会发生这种情况)。 该物理内存映射到进程的虚拟地址空间。 操作系统可以取消映射文件中未使用的长部分,并在需要时将其映射回来。 如果没有空闲的物理内存,unmaps可能会更积极,导致性能不佳。
内存映射文件:
使用较少的物理内存和虚拟地址空间比简单的“文件读/写”,因为没有文件缓冲区在这里和那里(在操作系统,在C标准库和在你的程序),没有不必要的复制之间。
可以是( 也许是,如果在某些条件下有足够的空闲物理内存 ,取决于我们正在讨论的数据量以及操作系统允许我们用于mmap的物理内存量)比简单的“文件读取/写“因为上述,因为你避免了”文件读取“系统调用涉及的用户和内核模式之间的转换。 剩下的唯一转换是映射当前未映射的特定页面的转换,它们是由页面错误(= CPU异常)启动的,这些页面错误在内核中处理。 只要你需要的东西被映射,在访问文件数据的时候就没有用户内核的转换。
进程的“虚拟内存”是可用的地址范围。 为了在内存中提供一些东西,你需要保留一个地址范围,所以mmap()
占用一些虚拟内存。
在Linux下(很多其他系统可能使用类似的机制),读取文件时,内容首先被读入内核分配的内存(在Linux中是“页面缓存”)。 如果你使用mmap()
,那么这个内存只需在进程的地址空间中分配一些地址就可以使用。 如果使用read()
,进程将分配一个需要地址(虚拟内存)和地点(物理内存)的缓冲区,并将数据从页面缓存复制到该缓冲区(需要更多的物理内存)。
数据只在实际访问时从磁盘读取。 在mmap()
情况下,它意味着当你实际地址的内存,在read()
它是复制到你的缓冲区,所以在read()
调用。
因此mmap()
对于大文件更为有效,特别是随机访问。 缺点是它只能用于文件,而不能用于类似文件的对象(管道,套接字,设备,/ proc文件等),并且在页面错误期间检测到IO错误,并且难以处理它发送SIGBUS信号),而读可以返回错误,应用程序可以尝试恢复(大多数不会)。 后者主要是由于连接丢失导致IO失败的网络文件系统。