【问题标题】:Why mmap a file result in using more memory than file's size?为什么 mmap 文件会导致使用比文件大小更多的内存?
【发布时间】:2012-04-18 19:21:46
【问题描述】:

我正在尝试mmap,并附带以下示例代码:

    int main() {

    int fd;
    char *filename = "/home/manu/file";
    struct stat statbuf;
    int i = 0;
    char c = *(filename);

    // Get file descriptor and file length
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("fopen error");
    }
    if (fstat(fd, &statbuf) < 0) {
        perror("fstat error");
    }
    printf("File size is %ld\n", statbuf.st_size);

    // Map the file
    char* mmapA = (char*) mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE,
            fd, 0);
    if (mmapA == MAP_FAILED) {
        perror("mmap error");
        return 1;
    }

    // Touch all the mapped pages
    while (i < statbuf.st_size) {
        c = mmapA[i];
        i++;
    }
    c++;

    // Close file descriptor
    if (close(fd) == -1) {
        perror("close");
        return 1;
    }

    //Unmap file
    munmap(mmapA, statbuf.st_size);

    return EXIT_SUCCESS;
}

文件大小为 137948 字节 = 134.7 KB。 为了检查程序的内存,我使用顶部,主要是 RES 和 VIRT 列。我在三个不同的地方寻找这些值:

  1. 就在mmap 调用之前
  2. 就在mmap 通话之后
  3. 在读取所有映射内存以将文件有效加载到主内存后(页面错误后)

top 报告的值是

  1. VIRT = 1828 RES = 244
  2. VIRT = 1964 RES = 248
  3. VIRT = 1964 RES = 508

1964 - 1828 = 136,我猜以千字节为单位,因此完美匹配文件的大小。

但我无法理解 508 - 248 = 260 的 RES 差异 .. 为什么它与虚拟内存大小和文件大小不同?

【问题讨论】:

  • 该操作系统被编码为最适合最常见的用途,或者最适合大声喊叫的客户的典型用途——它做了很多可能对您的使用没有意义的事情。你真的在乎吗?
  • @MartinBeckett 不,我根本不在乎;-) 这只是为了学习目的,我很好奇。
  • 好吧,您总是可以阅读源代码;-) 我的猜测是,它总是在 mmap 上方保留一定的内存以防它扩展 - 即使在这里您以只读方式打开。但我不知道所以我没有添加这个作为答案
  • @MartinBeckett 这里看源码的问题,是我不知道在哪里看的吗?在我看来(但我完全不确定)它可能不在 mmap 实现中(因为它有效地映射了请求的虚拟内存量),但它是在读取时处理页面错误的代码。不是吗?
  • 旁注。不要触摸每个字节,而是使用sysconf(_SC_PAGE_SIZE);通常为 4096:i += pagesize;

标签: c linux linux-kernel mmap


【解决方案1】:

有一点是肯定的:结果取决于系统的状态,而不仅取决于正在运行的应用程序。在我的机器上,前两次运行程序时 RES 增加了 136 kB,但随后的运行根本没有增加 - 可能操作系统已经将整个文件放在缓存中。有趣的是,值本身在运行之间存在显着差异。在第一次运行中,RES 的跳跃是从 344 到 480 kB,但后面运行的 RES 值一直为 348 kB。 SHR 也有类似的变化:第一次跳跃 136 kB,后来没有变化。

在运行应用程序之前,我可以通过覆盖稍后使用dd 用零映射的文件来随意强制原始大小写(跳转 136 kB)。

我查看了pmaps 的输出,但在两种情况下都完全相同,并且在调用mmap() 后没有改变。

我无法在此处重现超大 RES 跳跃,但这是您可以执行的操作。假设您的二进制文件编译为a.out。在 mmap() 之后插入 10 秒睡眠,在 munmap() 之前插入另外 10 秒睡眠。这提供了一个时间窗口来转储有趣的信息。我们将从/proc 中读取哪些文件驻留在内存中。为此,请在一次运行中打开终端中的两个选项卡

./a.out

然后立即在另一个选项卡中:

for ((i=0;i<4;i++)); do cat /proc/$(ps -fe | egrep '[a]\.out' | awk '{print $2}')/smaps > smaps.$i; sleep 5; done

这将在四个单独的文件中创建程序地图状态的 4 个快照。连续编号的快照之一之间的差异应显示 RES 大小激增期间的变化。在我的机器上,在示例运行期间,快照 1 和 2 之间存在差异,变化是 [注意我更改了映射文件的名称,但在这里并不重要]:

user@machine:~$ diff -u smaps.{1,2}
--- smaps.1     2012-04-19 00:01:46.000000000 +0200
+++ smaps.2     2012-04-19 00:01:51.000000000 +0200
@@ -84,13 +84,13 @@
 MMUPageSize:           4 kB
 b782f000-b7851000 r--p 00000000 08:05 429102     /tmp/tempfile
 Size:                136 kB
-Rss:                   0 kB
-Pss:                   0 kB
+Rss:                 136 kB
+Pss:                 136 kB
 Shared_Clean:          0 kB
 Shared_Dirty:          0 kB
-Private_Clean:         0 kB
+Private_Clean:       136 kB
 Private_Dirty:         0 kB
-Referenced:            0 kB
+Referenced:          136 kB
 Swap:                  0 kB
 KernelPageSize:        4 kB
 MMUPageSize:           4 kB

所发生的正是应该发生的事情:映射文件最初根本不驻留,后来有 136 kB 驻留。

在您的系统上,差异应该会引导您找到 RES 中额外更改的来源 - 您应该能够找出 Rss 值发生更改的其他文件的名称。有些条目不是文件,而是其他内存区域,例如,您可能会找到诸如[heap][stack] 之类的标记。这也应该证明或反驳 nos 关于正在加载系统库和堆栈使用量增长的建议。

【讨论】:

  • 非常感谢您提供完整的答案。使用smaps,至于你,我在内存映射的文件的RSS中只有136 kb的差异。因此,我想知道 top 或 ps(我以前使用过)在 RSS 标签下准确地报告了什么。
  • topprocps 包的一部分,它也从/proc 获取数据,但显然来自与smaps 不同的地方。如果您深入了解code,您可能能够在top 中找到RES 列的确切来源。
猜你喜欢
  • 2016-02-07
  • 2015-05-20
  • 1970-01-01
  • 2021-04-05
  • 2020-04-12
  • 1970-01-01
  • 1970-01-01
  • 2018-10-25
  • 1970-01-01
相关资源
最近更新 更多