内存映射页和匿名页是什么?

43
我无法理解Linux中的内存映射页面和匿名页面。有人能否给出一个例子来解释一下?与此相关的内核数据结构有哪些?

2
mmaped文件是由页面缓存支持的虚拟内存页;而匿名页(使用标志MAP_ANONYMOUS的mmap)是由零页支持的虚拟内存页(在读取时仅返回零;写入由标志禁止)。当您首次写入匿名页时,将分配新的物理页并填充为零。此页将映射到您的匿名页,并重新启动写入。 - osgx
2
试试这个:http://www.tldp.org/LDP/tlk/mm/memory.html(完整书籍在此:http://www.tldp.org/LDP/tlk/tlk.html)作为在线书籍;或者尝试Daniel P. Bovet的《深入理解Linux内核》作为离线书籍(我认为它是一个有注释的内核源代码清单)。 - osgx
你在哪里听说过内存映射页面这个术语?有任何实例或参考资料吗? - firo
3个回答

98
正确的术语是内存映射文件和匿名映射。当提到内存映射时,通常指的是mmap(2)。使用mmap有两个类别。一个是共享vs私有映射,另一个是文件vs匿名映射。组合在一起,你得到以下4种组合:
  1. 私有文件映射
  2. 共享文件映射
  3. 私有匿名映射
  4. 共享匿名映射
文件映射指定磁盘上的文件将有n个字节映射到内存中。函数mmap(2)的第4个参数作为文件描述符传递给要映射到内存中的文件。第5个参数是要读取的字节数,作为偏移量。使用mmap创建内存映射文件的典型流程如下:
  1. 打开(2)文件以获取文件描述符。
  2. 对文件执行fstat(2)以从文件描述符数据结构获取大小。
  3. 使用从open(2)返回的文件描述符对文件进行mmap(2)处理。
  4. 关闭(2)文件描述符。
  5. 对内存映射文件进行任何操作。

当文件被映射为私有时,所做的更改不会提交到底层文件。它是文件的私有内存副本。当文件被映射为共享时,内核会自动将所做的更改提交到底层文件。映射为共享的文件可用于所谓的内存映射I/O和IPC。如果需要文件的持久性,则应使用内存映射文件而不是共享内存段进行IPC。

如果您使用strace(1)来观察进程初始化,您会注意到文件的不同部分使用mmap(2)作为私有文件映射进行映射。系统库也是如此。

以下是从strace(1)输出的示例,其中mmap(2)被用于将库映射到进程中。

open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=42238, ...}) = 0
mmap(NULL, 42238, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ff7ca71e000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY)      = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\356\341n8\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1926760, ...}) = 0
mmap(0x386ee00000, 3750152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x386ee00000

匿名映射不由文件支持。具体来说,当使用MAP_ANONYMOUS标志作为mmap(2)的第3个参数时,甚至不使用mmap(2)的第4个(文件描述符)和第5个(偏移量)参数。使用/dev/zero作为文件是使用MAP_ANONYMOUS标志的替代方法。

对我来说,“Anonymous”这个词是一个糟糕的选择,因为它听起来好像文件被匿名映射了。相反,文件是匿名的,即没有指定文件。

匿名私有映射最常见的用途是进程的堆和栈段。您可以使用共享匿名映射,以便应用程序可以共享内存区域,但我不知道为什么您不会改用SYSV或POSIX共享内存。

由于使用匿名映射映射的内存保证填充为零,因此对于一些期望/需要零填充的内存区域的应用程序,使用mmap(2)而不是malloc(2)+ memset(2)组合可能非常有用。


回答不错,但我认为问题应该是关于匿名页面和页面缓存中的页面。它们不属于用户空间的东西。 - firo
对于某些期望/需要零填充区域的应用程序来说,这可能是有用的 - 实际上,glibc的calloc会跳过对内核新分配的页面进行清零操作,因此它们仍然是“干净的”,所以读取时仍然可以获得由相同共享的零填充页面支持的多个虚拟页面。(除了大映射的第一页,glibc malloc在实际返回的指针之前的16个字节中放置其元数据。) - undefined

11

我理解匿名页面之所以被称为匿名页面,是因为它们没有具名的文件系统来源,而映射页面则是具体文件的映射。例如,您可以在任何用户空间进程中使用简单的 malloc 操作来获取匿名页面...

关于内核结构: 显然是 struct page,但对于匿名页面,您将在 page->mapping 中看到 struct anon_vma,而对于映射页面,则是连接到具体 inode 的 struct address_space


4

我不确定内存映射页面是什么意思,所以我不会谈论它。

关于匿名页面,通常在内核进行页面帧回收时提到。匿名页面的实例包括进程的堆栈、堆、共享内存和任何已修改的共享库。在Linux中,所有动态共享库都通过以下系统调用映射到进程的虚拟内存地址空间:

firo@linux-6qg8:~> strace -e mmap,openat ls 2>&1 |grep -A1 libc.so
openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
mmap(NULL, 3906144, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0)

如果页面中的写操作属于MAP_PRIVATE文件或库,将会触发从文件后端页面到匿名页面的更改。

按照定义,匿名页面(也称为匿名内存)只是一种没有后端设备可供内核在执行页面帧回收时交换的页面。这就是Linux支持交换区的原因。

与匿名页面相关的内核数据结构有两种。

  1. 为了回收匿名页面,内核必须知道正在使用匿名页面来更改它们的PTE(页面表项)的所有进程。我们称之为反向映射或rmap。

    struct address_space用于由共享内存维护反向映射。

    struct anon_vma用于其余匿名页面维护反向映射。

  2. 内核使用LRU算法来回收页面帧。 对于kernel 5.0+,请查看struct pglist_data中的struct lruvec。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接