【问题标题】:Why does fopen/fgets use both mmap and read system calls to access the data?为什么 fopen/fgets 使用 mmap 和 read 系统调用来访问数据?
【发布时间】:2026-02-10 19:30:01
【问题描述】:

我有一个小示例程序,它只是fopens 一个文件并使用fgets 来读取它。使用strace,我注意到对fgets 的第一次调用运行mmap 系统调用,然后read 系统调用用于实际读取文件的内容。在fclose,文件是munmaped。如果我改为直接使用 open/read 打开读取文件,这显然不会发生。我很好奇这个mmap 的目的是什么,以及它正在完成什么。

在我的基于 Linux 2.6.31 的系统上,当需要大量虚拟内存时,这些 mmaps 有时会挂起几秒钟,在我看来是不必要的。

示例代码:

#include <stdlib.h>
#include <stdio.h>
int main ()
{
   FILE *f;
   if ( NULL == ( f=fopen( "foo.txt","r" )))
   {
     printf ("Fail to open\n");
   }
   char buf[256];
   fgets(buf,256,f);
   fclose(f);
}

这是运行上述代码时相关的 strace 输出:

open("foo.txt", O_RDONLY)               = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=9, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb8039000
read(3, "foo\nbar\n\n"..., 4096)        = 9
close(3)                                = 0
munmap(0xb8039000, 4096)                = 0

【问题讨论】:

    标签: c linux file mmap


    【解决方案1】:

    mmap'ed 不是文件 - 在这种情况下,mmap 是匿名使用的(不是在文件上),可能是为后续读取将使用的缓冲区分配内存。

    malloc 实际上会导致对mmap 的调用。同样,munmap 对应于对free 的调用。

    【讨论】:

    • 有趣。因此,我从中收集到 FILE* 上的所有读取操作实际上并没有读入提供的缓冲区,而是读入堆上额外分配的缓冲区,然后复制到我的缓冲区中。另外,malloc 是否总是导致调用 mmap?我一直认为堆是在用户空间中本地管理的,只有在需要向进程地址空间添加更多内存时才进行系统调用。我也一直认为 sbrk/brk 用于此目的,而不是 mmap。
    • @bdk:是的,标准库中的文件函数(不是系统调用)确实保留了自己的缓冲区,因此当您在循环中连续调用 fgets(buf, 1, f) 时,不会导致数百个read 系统调用。当malloc 在用户空间中没有更多可用空间时,它会导致mmap - 例如,第一个malloc(8) 可能会导致mmap(4096),随后的malloc(8) 将返回指向已经分配的区域,直到用完为止。
    • 谢谢!这就解释了。每次我使用 strace 尝试追踪某些东西时,我最终都会学到一些新东西。
    【解决方案2】:

    mmap 没有映射文件;相反,它为 stdio FILE 缓冲分配内存。通常malloc 不会使用mmap 来服务这么小的分配,但似乎glibc 的stdio 实现是直接使用mmap 来获取缓冲区。这可能是为了确保它是页面对齐的(尽管posix_memalign 可以实现相同的效果)和/或确保关闭文件将缓冲内存返回给内核。我质疑页面对齐缓冲区的有用性。大概是为了提高性能,但我看不出有任何帮助,除非您正在读取的文件偏移量也是页面对齐的,即使这样,它似乎也是一个可疑的微优化。

    【讨论】:

      【解决方案3】:

      从我读过的内容来看,内存映射函数在处理大文件时很有用。现在大的定义是我不知道的。但是对于大文件,它们比“缓冲”i/o 调用要快得多。

      在您发布的示例中,我认为文件是由open() 函数打开的,mmap 用于分配内存或其他东西。

      从mmap函数的语法可以看得很清楚:

      void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);

      倒数第二个参数采用非负的文件描述符。 而在堆栈跟踪中它是-1

      【讨论】:

      • 这是错误的。在 POSIX 上 stdio 不能用 mmap 实现,因为当文件被截断时语义错误(它将与 SIGBUS 一起崩溃而不是给出错误)。 mmap OP 询问的不是文件映射;这只是一个匿名的内存分配。
      • 这就是我所说的“mmap 用于分配内存或其他东西”...我没有说 mmap 用于映射“该”文件。
      【解决方案4】: