Content #
虚拟内存(Virtual Memroy)对应于物理内存(Physical Memory)。其中,前者是由操作系统抽象出来的一个概念,它在后者的基础之上进行了一层抽象,以帮助运行于其上的应用程序合理地分配内存,并管理内存使用。
因此,如下面的代码所示,我们在应用程序中打印出的各种指针值,它们实际上都对应于虚拟内存中的某个地址,而非实际的物理内存地址(Physical Address, PA)。这些地址被称为“虚拟地址(Virtual Address,VA)”。所有程序可以使用的虚拟地址则构成了虚拟地址空间(VAS)。
#include <stdio.h>
int main(void) {
int x = 10;
printf("%p", &x); // 0x7fff32cf54fc.
getchar();
return 0;
}
CPU 在访问内存中的数据时,会借助其芯片上名为“内存管理单元(MMU)”的硬件,首先将虚拟地址动态翻译为对应的物理地址,然后再进行实际的数据获取。你可以通过下图来直观地理解这个过程。
虚拟内存机制的一个最重要特征,就是为每一个应用程序进程都抽象出了独立于物理内存的虚拟地址空间。这意味着,从进程的角度来看,它可以独享整个计算机上的所有内存。现代操作系统通常采用 32 或 64 位地址空间,两者分别拥有 2^32 与 2^64 个地址。通过这种方式,编译器在构建应用时,便不需要考虑各二进制数据段应该被实际加载到内存中的何处,所有应用均可使用统一的静态文件结构。
比如,在 64 位 Linux 系统中,与应用代码相关的 Segment 会从 VAS 的固定地址 0x400000 处开始加载。而其他 Section 内容将在满足一定对齐要求的情况下,按顺序被连续加载到高地址方向的虚拟内存中。这样,无论是程序在二进制文件内的静态视图,还是被加载到 VAS 后的运行时视图,它们都可以在虚拟内存的隔离下,在表现层有着稳定一致的布局。
而通过下面这行命令,我们便可以查看某个运行进程的 VAS 布局情况。注意,其中的 “” 需要被替换为进程对应的 ID。
cat /proc/<pid>/maps
这里,我们尝试将开头处的那个 C 程序的 ID 替换到上述命令中。在运行该命令后,可以得到如下图所示的输出结果。
这里,命令按照地址由低到高的顺序打印出了进程 VAS 内,每一块已经被占用的连续虚拟内存地址,对应的映射信息。如最右侧一列所示,这些内存中的内容或是来自于某个具体文件(/www/workspace/main),或被用作其他用途([heap])。
将上图中的信息进行归类,我们可以得到如下图所示的 Linux 进程在 VAS 内的统一数据布局结构。这里我根据类别,将不同的数据用不同的颜色进行了标注。并且,为了方便你找到这两个图之间的内存段对应关系,我将上图中的一些关键性地址信息也选择性地标注在了下图中。
总的来看,Linux 进程 VAS 中的数据,按照地址由低到高的顺序,可以被分为下面这几个主要部分。
- LOAD Segments 这部分数据被加载到从地址 0x400000 开始的虚拟内存中。其主要内容为应用程序 ELF 二进制文件内定义的各种 LOAD Segment 结构。按照顺序,与代码相关的 Text Segment(包含 .text、.rodata 等多个 Section)位于最低地址处,紧接着为包含有已初始化和未初始化数据的 Data Segment(包含 .data,.bss 等多个 Section)。
- 堆(Heap)关于堆内存,我已经在 08 讲 中介绍过。随着动态数据的不断产生,它将向 VAS 的高地址方向不断增长。
- 共享库数据这部分内存中包含有与各类 .so 共享库相关的数据,程序会在运行时通过动态链接器来完成对它们的加载和处理。我会在第 29 讲再为你详细介绍。
- 栈(Stack)关于栈内存,我已经在 05 讲 中介绍过。随着各种局部变量的不断产生,它将向 VAS 的低地址方向不断增长。
- 用于系统调用加速的内核数据接下来的三个虚拟内存区域 [vvar]、[vdso],以及 [vsyscall] 中包含有操作系统内核的代码和数据结构,它们主要提供了用户进程可以直接与内核进行交互的接口。其中,[vvar] 中包含有只读的内核数据。而另外的 [vdso] 与 [vsyscall] 则包含有用于辅助操作系统,加速用户进程执行某些系统调用过程的信息。
- 其他内核数据除此之外,在进程 VAS 的高地址处,还可能包含有与当前进程相关的各种数据结构。甚至,该区域内的某段虚拟内存页还会被直接映射到某段被所有进程共享的物理内存页上(比如用于 MMIO)。
可以看到,得益于虚拟内存机制的抽象,进程可以使用完全统一、独立的内存数据布局,而不用考虑这些数据在真实物理内存中的具体存储细节。