内存映射(mmap)是 Linux 内核的一个重要机制,它为程序提供了一种将文件内容直接映射到进程虚拟地址空间的方式。同时内存映射也是虚拟内存管理和文件 IO 的重要组成部分。
在 Linux 中,虚拟内存管理是基于内存映射来实现的。在调用 mmap 函数时,会创建一个 vm_area_struct
结构体,该结构体代表了一段连续的虚拟地址空间,它们会相应地映射到一个后备文件或者一个匿名文件的虚拟页。
一个 vm_area_struct
结构体映射到一组连续的页表项,这些页表项指向物理内存中的一页。这样就把一个文件和物理内存页相映射起来。当进程试图访问映射到 vm_area_struct
的虚拟地址空间时,如果该空间没有在内存中,则会发生缺页异常,内核会通过文件系统将文件中对应的数据读入内存。
内存映射的优点是可以有效地减少文件 IO 的次数,提高文件读写性能。同时内存映射还支持多进程共享同一个映射,可以节省内存空间,并且方便不同的进程之间进行通信。
虚拟地址映射的过程涉及到对 vm_area_struct
的匹配以及对页表项的查找和操作。具体来说,当程序访问一个虚拟地址时,系统会根据已有的 vm_area_struct
结构来确定这个虚拟地址是否属于某个区域。
如果没有匹配到相应的 vm_area_struct
,就会触发段错误,因为访问了一个未分配的虚拟地址,这表示程序在访问一个未经内核分配的内存区域,是非法的操作。
如果匹配到了相应的 vm_area_struct
,系统会根据虚拟地址和页表的映射关系找到对应的页表项PTE。如果 PTE 没有分配,就会触发缺页异常,此时系统会将相应的文件数据加载到物理内存中;如果 PTE 已经分配,就可以直接从对应的物理页的偏移位置读取数据。
在这个过程中,虚拟页有三种状态:
- 未分配虚拟页:指的是没有使用 mmap 建立相应的
vm_area_struct
,因此也就没有对应到具体的页表项。 - 已分配虚拟页,未映射到物理页:表示已经使用了 mmap 建立的
vm_area_struct
,虚拟页可以映射到对应的页表项,但页表项尚未指向具体的物理页。 - 已分配虚拟页,已映射到物理页:表示已经使用了 mmap 建立的
vm_area_struct
,虚拟页可以映射到对应的页表项,并且页表项已经指向具体的物理页。
mmap 函数可以将虚拟地址映射到一个后备文件或者一个匿名文件。当操作系统分配物理内存时,实际上会利用匿名文件的 mmap 来完成内存分配。
mmap和虚拟内存管理
用户进程的虚拟内存管理是通过Linux内核中的mm_struct
结构来表示一个用户进程的虚拟内存地址空间。该结构包含了几个重要的字段来描述不同区域的地址范围和属性。
-
start_code
和end_code
:指定了进程的代码段的起始地址和结束地址,用于表示可执行代码的边界。一旦ELF二进制文件映射到虚拟内存后,这些地址就不会再改变。 -
start_data
和end_data
:指定了进程数据段的起始地址和结束地址,用于表示已初始化数据的边界。类似于代码段,这些地址在映射后也不再改变。 -
start_brk
和brk
:指定了堆的起始地址和结束地址,用于表示动态分配内存的边界。start_brk
表示堆的初始位置,在进程的整个生命周期中保持不变,而brk表示堆的结束位置,会随着堆的长度动态改变。 -
stack_top
:指定了栈的起始位置,一般位于用户进程地址空间的顶部,用于存放函数调用的栈帧。 -
task_size
:指定了用户进程地址空间的长度,即用户空间的顶部边界。 -
mmap_base
:指定了用户进程虚拟地址空