从概念上理解内存映射

Understanding memory mapping conceptually

我已经在 cs.stackexchange.com 上问过这个问题,但还是决定在这里 post 提出这个问题。

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

  1. 您不能使用 32 位地址 space 内存映射大文件 (>4GB)。现在这对我来说很有意义。

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

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

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

问题 #1:标准文件 I/O 操作不也是如此吗?如果应用程序试图从尚未缓存的文件部分读取,将导致系统调用导致内核从设备加载相关 page/block。最重要的是,页面需要被复制回 user-space 缓冲区。

这里的问题是页面错误在某种程度上比一般的系统调用更昂贵——我对 Linus Torvalds says here 的解释?是因为页面错误正在阻塞 => 线程没有被安排关闭 CPU => 我们在浪费宝贵的时间吗?还是我遗漏了什么?

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

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

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

QUESTION #1: Isn't this the case for a standard file I/O operation as well? If an application tries to read from a part of a file that is not yet cached, it will result in a syscall that will cause the kernel to load the relevant page/block from the device. And on top of that, the page needs to be copied back to the user-space buffer.

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

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

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

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

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

And on top of that, the page needs to be copied back to the user-space buffer.

此副本可能没有您认为的那么糟糕。 CPU 将缓存数据,以便下一次访问不必从主内存重新加载,速度可能比 L1 CPU 缓存慢 100 倍。

在上面的例子中,mmap可以接管read的两倍。

Is the concern here that page-faults are somehow more expensive than syscalls in general - my interpretation of what Linus Torvalds says here? Is it because page-faults are blocking => the thread is not scheduled off the CPU => we are wasting precious time? Or is there something I'm missing here?

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

QUESTION #2: Is there an architectural limitation with supporting async I/O for memory mapped files, or is it just that it no one got around to doing it?

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

QUESTION #3: Vaguely related, but my interpretation of this article is that the kernel can read-ahead for standard I/O (even without fadvise()) but does not read-ahead for memory mapped files (unless issued an advisory with madvice()). Is this accurate? If this statement is in-fact true, is that why syscalls for standard I/O maybe faster, as opposed to a memory mapped file which will almost always cause a page-fault?

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

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


额外 read 好处

对于某些人来说,read 提供 套接字 管道 的使用。此外,字符设备,例如 ttyS0,只能与 read 一起使用。如果您编写一个从命令行获取文件名的命令行程序,这将很有用。如果您使用 mmap 构建,可能很难支持这些文件。