【问题标题】:Understanding memory mapping conceptually从概念上理解内存映射
【发布时间】:2020-01-08 20:36:43
【问题描述】:

我已经在cs.stackexchange.com 上问过这个问题,但我决定也在这里发布。

我已经阅读了一些关于堆栈交换的博客和问题,但我无法理解内存映射文件的真正缺点是什么。我看到经常列出以下内容:

  1. 您无法使用 32 位地址空间对大文件 (>4GB) 进行内存映射。现在这对我来说很有意义。

  2. 我想到的一个缺点是,如果内存映射的文件过多,这可能会导致可用系统资源(内存)减少 => 可能会导致页面被逐出 => 可能会出现更多页面错误。因此,在决定要映射哪些文件及其访问模式时需要谨慎。

  3. 内核映射和数据结构的开销 - according to Linus Torvalds。我什至不会尝试质疑这个前提,因为我不太了解 Linux 内核的内部结构。 :)

  4. 如果应用程序尝试从页面缓存中未加载的文件部分读取,它(应用程序)将招致页面错误形式的惩罚,这反过来意味着操作的 I/O 延迟增加。

问题 #1:标准文件 I/O 操作不也是这种情况吗?如果应用程序尝试读取尚未缓存的文件的一部分,它将导致系统调用导致内核从设备加载相关的页面/块。最重要的是,需要将页面复制回用户空间缓冲区。

这里是否担心页面错误通常比系统调用更昂贵 - 我对Linus Torvalds says here 的解释?是不是因为页面错误阻塞=>线程没有从CPU调度=>我们在浪费宝贵的时间?还是我在这里遗漏了什么?

  1. 不支持内存映射文件的异步 I/O。

问题 #2:支持内存映射文件的异步 I/O 是否存在架构限制,或者只是没有人能够做到这一点?

问题 #3:相关性模糊,但我对this article 的解释是内核可以预读标准 I/O(即使没有 fadvise())但不能预读内存映射文件(除非已发出madvice()) 的建议。这是准确的吗?如果这句话是真的,这就是为什么标准 I/O 的系统调用可能更快,而不是内存映射文件几乎总是会导致页面错误?

【问题讨论】:

    标签: io virtual-memory memory-mapped-files mmu


    【解决方案1】:

    问题 #1:标准文件 I/O 操作不也是这种情况吗?如果应用程序尝试读取尚未缓存的文件的一部分,它将导致系统调用导致内核从设备加载相关的页面/块。最重要的是,需要将页面复制回用户空间缓冲区。

    您对缓冲区执行read,I/O 设备会将其复制到那里。还有 async 读取或 AIO,其中数据将在设备提供时由内核在后台传输。你可以用线程和read 做同样的事情。对于mmap 情况,您无法控制或不知道页面是否已映射。 read 的情况更明确。这是从,

    ssize_t read(int fd, void *buf, size_t count);
    

    您指定bufcount。您可以明确地将数据放置在程序中所需的位置。作为程序员,你可能知道数据不会被再次使用。随后对read 的调用可以重用上次调用中的相同buf。这有很多好处;最容易看到的是更少的内存使用(或至少是地址空间和 MMU 表)。 mmap 将不知道将来是否仍要访问页面。 mmap 不知道只有页面中的一些数据是感兴趣的。因此,read 更加明确。

    假设您在磁盘上有 4096 条大小为 4095 字节的记录。您需要阅读/查看两条随机记录并对它们执行操作。对于read,可以用malloc()分配两个4095缓冲区或者使用static char buffer[2][4095]数据。 mmap() 必须为每条记录平均映射 8192 字节以填充两页或总共 16k。访问每个mmap 记录时,该记录跨越两页。这会导致每个记录访问出现两个页面错误。此外,内核必须分配四个 TLB/MMU 页来保存数据。

    或者,如果read 到顺序缓冲区,则只需要两个页面,只需要两个系统调用 (read)。此外,如果对记录的计算量很大,则缓冲区的局部性将使其比mmap 数据快得多(CPU 缓存命中)。

    最重要的是,页面需要被复制回用户空间缓冲区。

    这个副本可能没有你想象的那么糟糕。 CPU 将缓存数据,这样下次访问就不必从主内存重新加载,速度可能比 L1 CPU 缓存慢 100 倍。

    在上述情况下,mmap 的占用时间是 read 的两倍。

    这里是否担心页面错误通常比系统调用更昂贵 - 我对 Linus Torvalds 在这里所说的内容的解释?是不是因为页面错误阻塞=>线程没有从CPU调度=>我们在浪费宝贵的时间?还是我在这里遗漏了什么?

    我认为重点是您无法控制 mmap。您mmap 该文件不知道是否有任何部分在内存中。如果您只是随机访问该文件,那么它将继续从磁盘读回它,并且您可能会在不知不觉中根据访问模式而颠簸。如果访问是纯顺序的,那么乍一看可能并不好。但是,通过重新读取一个新的chunk到同一个用户缓冲区,可以更好地利用CPU的L1/L2 CPU缓存和TLB;既适用于您的进程,也适用于系统中的其他进程。如果您将所有块读取到唯一的缓冲区并按顺序处理,那么它们将大致相同(请参阅下面的注释)。

    问题 #2:支持内存映射文件的异步 I/O 是否存在架构限制,或者只是没有人能够做到这一点?

    mmap 已经类似于 AIO,但它的大小固定为 4k。即,完整的mmap 文件不需要在内存中即可开始对其进行操作。从功能上讲,它们是获得相似效果的不同机制。它们在架构上是不同的。

    问题 #3:相关性模糊,但我对本文的解释是内核可以预读标准 I/O(即使没有 fadvise())但不能预读内存映射文件(除非发出madvice()) 的咨询。这是准确的吗?如果这句话是真的,这就是为什么标准 I/O 的系统调用可能更快,而不是内存映射文件几乎总是会导致页面错误?

    read 的不良编程可能与mmap 一样糟糕。 mmap 可以使用madvise。它与使mmap 工作所必须发生的所有Linux MM 的东西更相关。这完全取决于您的用例;根据访问模式,两者都可以更好地工作。我认为 Linus 只是说两者都不是灵丹妙药。

    例如,如果您将read 分配到一个比系统内存更多的缓冲区,并且您使用与mmap 执行相同处理的交换,那么您的情况会更糟。您可能有一个没有交换的系统,mmap 用于随机读取访问会很好,并且允许您管理比实际内存更大的文件。设置使用read 执行此操作将需要更多代码,这通常意味着更多错误,或者如果您很天真,您只会收到 OOM 终止消息。注意但是,如果访问是顺序的, read 没有那么多代码,它可能会比 mmap 更快。


    额外的read 好处

    对于某些人来说,read 提供 socketspipes 的使用。此外,char 设备(例如 ttyS0)仅适用于 read。如果您编写从命令行获取文件名的命令行程序,这可能会很有帮助。如果你用mmap 构建,可能很难支持这些文件。

    【讨论】:

    • 我对异步 I/O 的定义是“应用程序要求内核从偏移量 f 加载/存储 x 个字节,并让我(应用程序)知道加载何时完成(或)提供监控机制来跟踪加载/存储进度”。我很难想象为什么 mmap'd 文件不同于读/写。在这两种机制中,内核只需要更新页面缓存(内存中的页面)就可以了。后备存储自然页面大小与它有什么关系?不是由设备驱动程序来抽象吗?
    • 如果我异步读取并想加载
    • 这是另一个good page to examine;查看内存映射文件的优势read + write和mmap的区别
    • 我的意图并不是要挑出其中一个作为灵丹妙药。重新阅读您链接的答案+页面后,我认为 mmap 与 read 的最坏情况下的性能可能并没有那么不同。也许这是 mmap 的 POTENTIAL 比读取更差的平均情况,这就是我所缺少的?这让我恍然大悟,因为页面提到 mmap 可能会产生轻微的页面错误(即使页面存在于物理内存中也加载 TLB 条目)。这些也可能发生在 read 中,但是没有办法通过 read 访问超过提供的缓冲区大小的随机偏移量。
    • 我试图理解并提出某种心智模型,以便在使用 mmap 时使用比使用 read 更有利。
    猜你喜欢
    • 2017-11-26
    • 2013-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-25
    • 2013-01-23
    • 1970-01-01
    相关资源
    最近更新 更多