当文件被映射为私有时,所做的更改不会提交到底层文件。它是文件的私有内存副本。当文件被映射为共享时,内核会自动将所做的更改提交到底层文件。映射为共享的文件可用于所谓的内存映射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)组合可能非常有用。
calloc
会跳过对内核新分配的页面进行清零操作,因此它们仍然是“干净的”,所以读取时仍然可以获得由相同共享的零填充页面支持的多个虚拟页面。(除了大映射的第一页,glibc malloc在实际返回的指针之前的16个字节中放置其元数据。) - undefined我理解匿名页面之所以被称为匿名页面,是因为它们没有具名的文件系统来源,而映射页面则是具体文件的映射。例如,您可以在任何用户空间进程中使用简单的 malloc 操作来获取匿名页面...
关于内核结构: 显然是 struct page,但对于匿名页面,您将在 page->mapping 中看到 struct anon_vma,而对于映射页面,则是连接到具体 inode 的 struct address_space。
我不确定内存映射页面是什么意思,所以我不会谈论它。
关于匿名页面,通常在内核进行页面帧回收时提到。匿名页面的实例包括进程的堆栈、堆、共享内存和任何已修改的共享库。在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支持交换区的原因。
与匿名页面相关的内核数据结构有两种。
为了回收匿名页面,内核必须知道正在使用匿名页面来更改它们的PTE(页面表项)的所有进程。我们称之为反向映射或rmap。
struct address_space用于由共享内存维护反向映射。
struct anon_vma用于其余匿名页面维护反向映射。
内核使用LRU算法来回收页面帧。 对于kernel 5.0+,请查看struct pglist_data中的struct lruvec。