1. 虚拟地址空间概述

1.1 什么是虚拟地址空间

虚拟地址空间是操作系统为每个进程提供的一个独立的、连续的、逻辑上的内存地址范围。对于 64 位 Linux 系统,理论上每个进程拥有 2^48 字节(256 TB)的虚拟地址空间;32 位系统则为 4 GB。进程中的所有代码、数据、堆、栈以及动态库都映射到这个虚拟空间中,而实际物理内存则由内核按需分配。

核心概念:虚拟地址空间不是物理内存,而是一个抽象层。进程“看到”的地址是虚拟地址(VA),CPU 通过 MMU(内存管理单元)将其转换为物理地址(PA)。

1.2 为什么需要虚拟地址空间

引入虚拟地址空间解决了以下关键问题:

问题 无虚拟地址空间时的后果 虚拟地址空间的解决方案
进程隔离 进程 A 可以读写进程 B 的内存,导致数据泄露或崩溃 每个进程拥有独立的虚拟地址空间,物理页表不共享,天然隔离
内存碎片 物理内存分配后产生大量外部碎片,大块连续内存难以分配 虚拟地址连续,物理页可以分散存放,MMU 完成映射
程序简化 程序员必须手动管理物理内存布局和地址重定位 程序只需关心虚拟地址,链接器假定固定基址,OS 负责映射
内存共享 共享内存需要复杂的物理地址协商 不同进程的虚拟页可映射到同一物理页,实现高效共享
按需加载 程序必须全部加载到物理内存才能运行 虚拟空间可大于物理内存,OS 通过缺页中断按需加载
关键收益:虚拟地址空间让每个进程都感觉自己独占整个内存系统,极大地简化了编程模型,同时提供了硬件级的内存保护。

1.3 物理地址与虚拟地址的映射关系

映射的核心机制是分页(Paging)。虚拟地址空间和物理地址空间都被划分为固定大小的块:虚拟地址空间中的块称为页(Page),物理地址空间中的块称为页框(Page Frame)。Linux 默认页大小为 4 KB。

映射过程由 MMU(内存管理单元) 硬件完成,操作系统负责维护页表(Page Table)。页表是一个多级索引结构,记录了每个虚拟页到物理页框的转换关系。

注意:并非所有虚拟页都映射到物理页框。未映射的页访问会触发缺页异常(Page Fault),由内核决定是分配物理页、从磁盘换入,还是向进程发送 SIGSEGV 信号。

映射关系示意图

虚拟地址到物理地址映射流程 虚拟地址 → 物理地址映射流程 虚拟地址空间 虚拟页 0x7f00_0000 虚拟页 0x7f00_1000 虚拟页 0x7f00_2000 虚拟页 0x7f00_3000 多级页表 页全局目录 (PGD) 页上级目录 (PUD) 页中间目录 (PMD) 页表条目 (PTE) 物理地址空间 页框 0x1a00_0000 页框 0x1a00_1000 页框 0x1a00_2000 页框 0x1a00_3000 MMU 根据虚拟地址的高位索引多级页表,逐级查找,最终得到物理页框号 + 页内偏移 = 物理地址 缺页时 PTE 标记为无效,MMU 触发缺页异常,内核介入处理

1.4 地址转换的硬件与软件协作

一次完整的虚拟地址到物理地址的转换涉及以下步骤:

  1. CPU 发出虚拟地址:程序指令中的地址(如 mov rax, [0x7f00_1000])被送到 MMU。
  2. MMU 解析页表:MMU 从控制寄存器(如 x86 的 CR3)中读取页表基址,按虚拟地址的各级索引遍历页表。
  3. TLB 加速:MMU 首先检查 TLB(转译后备缓冲器),如果命中则直接得到物理地址,避免内存访问。
  4. 缺页处理:如果 PTE 无效,MMU 触发缺页异常,操作系统在异常处理程序中分配物理页框、更新页表,然后恢复进程执行。
  5. 返回物理地址:MMU 将物理地址发送到内存控制器,完成数据读写。
性能关键:TLB 命中率直接影响程序性能。大页(Huge Pages)可以减少页表层级和 TLB 压力,是数据库和虚拟化场景的常用优化手段。

1.5 虚拟地址空间布局概览

以 64 位 Linux 为例,用户空间(0x0000_0000_0000_0000 ~ 0x0000_7FFF_FFFF_FFFF)和内核空间(0xFFFF_8000_0000_0000 ~ 0xFFFF_FFFF_FFFF_FFFF)的典型布局如下:

区域 起始地址(典型) 内容
代码段(.text) 0x400000 可执行指令,只读
数据段(.data/.bss) 0x600000 全局变量、静态变量
堆(Heap) 0x601000 起向上增长 动态分配(malloc/new)
内存映射区(mmap) 0x7f00_0000 附近 动态库、共享内存、匿名映射
栈(Stack) 0x7fff_xxxx_xxxx 向下增长 局部变量、函数调用帧
内核空间 0xffff_8000_0000_0000 以上 内核代码、模块、直接映射区
实战提示:使用 cat /proc/<pid>/maps 可以查看任意进程的虚拟地址空间布局。这是调试内存问题、分析 mmap 行为的第一手工具。

1.6 本章小结