我正在阅读有关内存映射文件的信息,来源说它比打开文件或读取文件的传统方法更快,例如分别打开系统调用和读取系统调用,而没有说明打开或读取系统调用的工作原理。
所以这是我的问题 ,开放系统调用是如何工作的?
据我所知,它将文件加载到内存中,而通过使用映射文件,只有它们的地址将保存在内存中,并且在需要时可以将请求的页面带入内存。
我希望澄清我迄今为止的理解。
编辑
我之前写的上面的理解几乎是错误的,正确的解释请参考 Pawel 接受的答案。
我正在阅读有关内存映射文件的信息,来源说它比打开文件或读取文件的传统方法更快,例如分别打开系统调用和读取系统调用,而没有说明打开或读取系统调用的工作原理。
所以这是我的问题 ,开放系统调用是如何工作的?
据我所知,它将文件加载到内存中,而通过使用映射文件,只有它们的地址将保存在内存中,并且在需要时可以将请求的页面带入内存。
我希望澄清我迄今为止的理解。
编辑
我之前写的上面的理解几乎是错误的,正确的解释请参考 Pawel 接受的答案。
由于您没有提供任何细节,我假设您对类 Unix 系统的行为感兴趣。
实际上open()
,系统调用只创建一个文件描述符,然后可由mmap()
或使用read()
。
内存映射 I/O 和标准 I/O 都通过页面缓存内部访问磁盘上的文件,页面缓存是缓存文件的缓冲区,以减少 I/O 操作的数量。
标准 I/O 方法(使用write()
and read()
)涉及执行系统调用,然后将数据从(或如果您正在写入)页面缓存复制到应用程序选择的缓冲区。除此之外,非顺序访问还需要另一个系统调用lseek()
。系统调用很昂贵,复制数据也是如此。
当一个文件被内存映射时,通常进程地址空间中的一个内存区域被直接映射到页面缓存,这样所有已经加载的数据的读取和写入都可以在没有任何额外延迟的情况下执行(没有系统调用,没有数据复制)。只有当应用程序尝试访问尚未加载的文件区域时,才会发生页面错误并且内核从磁盘加载所需的数据(整个页面)。
编辑: 我看到我还必须解释 memory paging。在大多数现代架构中,物理内存是一块真正的硬件,而虚拟内存则是为进程创建地址空间。内核决定如何将虚拟内存中的地址映射到物理内存中的地址。最小的单位是内存页(通常但不总是 4K)。它不必是 1:1 映射,例如所有虚拟内存页面都可以映射到相同的物理地址。
在内存映射的 I/O 部分应用程序地址空间和内核的页面缓存映射到同一个物理内存区域,因此程序可以直接访问页面缓存。
Pawel 很好地解释了读/写是如何执行的。让我解释一下最初的问题:fopen(3) 是如何工作的:当用户空间进程遇到 fopen(在 libc 或任何用户空间库中定义)时,它会将其转换为 open(2) 系统调用。首先,它从 fopen 获取参数,将它们与 open() 系统调用号一起写入特定于体系结构的寄存器。这个数字告诉内核系统调用用户空间程序想要运行。加载这些寄存器后,用户空间进程中断内核(通过softirq
,传统上 x86 上的 INT 80H)并阻塞。
内核验证提供的参数和访问权限等,然后返回错误或调用实际的系统调用,vfs_open()
在这种情况下。vfs_open()
检查 fd 数组中的可用文件描述符并分配结构文件。访问文件的引用计数增加,fd 返回给用户程序。这样就完成了 open 以及大多数系统调用的工作。
open()
与read()
/一起write()
,接下来的close()
过程无疑比在缓冲区缓存中拥有内存映射文件要漫长得多。
有关 open 和 read 如何在 Linux 上工作的清晰说明,您可以阅读此. 代码片段来自旧版本的内核,但理论仍然成立。
您仍然需要使用 open() 系统调用来获取有效的文件描述符,然后将其传递给 mmap()。至于为什么 mmaped IO 更快,这是因为没有从(到)用户空间到(从)内核空间缓冲区的数据副本,这就是读写系统调用发生的情况。