B11 —— 现代计算机的内存空间布局:虚拟地址和物理地址的完整映射

金句:理解内存布局,是理解系统设计的钥匙。


从物理地址到虚拟地址的演进

早期计算机(80286 之前)没有 MMU,直接用物理地址访问内存。后来引入了段机制,再到分页,内存布局越来越复杂。

Bash
内存布局的演进:

1. 纯物理地址(80286 之前):
   - 没有 MMU
   - 物理地址 = 访问地址
   - 无法实现进程隔离

2. 实模式(8086):
   - 段寄存器 × 16 + 偏移 = 20-bit 物理地址
   - 1MB 地址空间
   - 无保护,无隔离

3. 保护模式(80286+):
   - GDT/LDT 定义段描述符
   - 段选择子 + 偏移 = 线性地址
   - 特权级(Ring 0/1/2/34. 分页保护模式(80386+):
   - 线性地址通过页表翻译成物理地址
   - 虚拟内存支持
   - 每个进程独立的页表

5. x86-64 长模式(AMD64):
   - 48-bit 虚拟地址(用户 128TB,内核 128TB)
   - 四级页表
   - 不再使用段寄存器(CS/DS 等仍是选择子,但基址=0

x86-64 完整虚拟地址空间布局

Bash
x86-64 虚拟地址空间布局(Linux 5.x+):

用户空间(User Space,0x0000000000000000 ~ 0x00007FFFFFFFFFFF):
  0x0000000000000000:NULL 指针陷阱(防止 NULL deref)
  0x0000000000400000:可执行文件加载地址(ELF)
  0x0000004000000000:程序堆(brk 增长)
  0x00007F0000000000:mmap 区域(匿名映射/共享库)
  0x00007FF000000000:栈(向低地址增长)
  0xFFFF800000000000:内核空间起始(符号扩展)

内核空间(Kernel Space,0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF):
  0xFFFF800000000000 ~ 0xFFFF88007FFFFFFF:
    直接映射区(Direct Map)
    → 线性映射:物理地址 + 0xFFFF880000000000 = 虚拟地址
    → 物理 0~896MB 直接映射到这个区域(低端 896MB)
    → 适用于 kmalloc/vmalloc(物理连续)

  0xFFFF880100000000 ~ 0xFFFFC3FFFFFFFFFFFF:
    vmalloc 区域
    → 映射不连续的物理内存(vmalloc)
    → module 空间(0xFFFF880000000000 ~ 0xFFFF8BFFFFFFFFFF)
    → ioremap(0xFFFFC40000000000 开始)

  0xFFFFC40000000000 ~ 0xFFFFC7FFFFFFFFFFF:
    固定映射区(Fixed Address Mapping)
    → early boot、ACPI tables、物理内存映射

  0xFFFFC80000000000 ~ 0xFFFFFFFFFFFFFFFF:
    保留区/硬件区
    → ACPI NVS
    → 固件保留
    → 未使用

物理地址到虚拟地址的转换

Bash
物理地址到虚拟地址的几种方式:

1. 直接映射(0xFFFF880000000000):
   PA 0x1000000 → VA = 0x1000000 + 0xFFFF880000000000
               = 0xFFFF880100000000

   用于低端 896MB DRAM(kmalloc/vmalloc)

2. kmap(kmap_atomic):
   用于高端内存(HIGHMEM,> 896MB)
   - 临时映射
   -kunmap_atomic 释放

3. vmalloc 映射:
   PA 可以不连续
   VA 在 vmalloc 区域动态分配

4. ioremap:
   外设 MMIO 寄存器
   VA 在 ioremap 区域

物理地址范围的定义(arch/x86/include/asm/page_32_types.h):
  #define __PAGE_OFFSET      0xFFFF880000000000
  #define PHYSICAL_PAGE_MASK  (~0xFFFUL)

Linux 内核内存布局详解

Bash
内核内存布局的关键区域(x86-644-level pagetable):

1. 直接映射区(ZONE_NORMAL,896MB):
   - 物理地址 0 ~ 896MB 映射到这里
   - 用于 kmalloc / 伙伴系统
   - 虚拟 = 物理 + __PAGE_OFFSET
   - 高效:无页表开销

2. vmalloc 区(动态映射):
   - 大约 32TB 虚拟空间(0xFFFF880100000000 ~ 0xFFFFC3FFFFFFFFFF)
   - 模块加载(0xFFFF880000000000 ~ 0xFFFF8BFFFFFFFFFF)
   - 适用:vmalloc / ioremap

3. 固定映射区:
   - 编译时确定的虚拟地址
   - 用途:early boot(VMALLOC_START ~ VMALLOC_END)
   - 编译时固定,不动态分配

4. 高端内存(HIGHMEM,32-bit x86):
   - 物理 > 896MB 的部分
   - 不能直接映射,需要临时映射(kmap)

物理内存 > 物理地址空间时:
  - 64-bit 系统不需要高端内存概念
  - 256TB 虚拟空间 >> 实际物理内存

/proc/iomem 查看物理地址布局

Bash
Linux 查看物理地址布局:

$ cat /proc/iomem

00000000-00000fff : Reserved
00001000-0009ffff : System RAM          ← 传统 640KB
000a0000-000fffff : Reserved            ← VGA/ROM
00100000-bfffffff : System RAM          ← 主内存区域
c0000000-feafffff : Reserved            ← MMIO 区域
  c0000000-cfffffff : PCI Bus 0000:01  ← PCIe BAR
  d0000000-dfffffff : RAM (系统内存,但 MMIO 占用)
  fe000000-fe7fffff : Local APIC        ← APIC
  fe800000-fe9fffff : Reserved          ← firmware
ff800000-ffffffff : Reserved            ← BIOS ROM

# 查看虚拟地址映射(/proc/self/maps)
$ cat /proc/self/maps
00400000-00405000 : r-xp /bin/cat         ← 可执行代码
00600000-00606000 : rw-p [heap]          ← 堆
7fffc8c00000-7fffc8c20000 : rw-p [stack] ← 栈
ffff8800000000-ffff8880000000 : vmalloc   ← vmalloc 区域

NUMA 系统的内存布局

Bash
NUMA(Non-Uniform Memory Access)系统的内存布局:

2 路服务器(2 NUMA nodes):

Node 0(CPU 0~7 本地):
  物理地址 0x0 ~ 0x100_0000_0000(64GB)
  本地内存,访问延迟 ~60ns

Node 1(CPU 8~15 本地):
  物理地址 0x100_0000_0000 ~ 0x200_0000_0000(64GB)
  本地内存,访问延迟 ~60ns
  跨节点访问延迟 ~100ns

NUMA 配置的虚拟地址映射:
  - 内核直接映射区(0xFFFF880000000000)跨越所有节点
  - 但某个虚拟地址对应哪个节点取决于物理地址

Linux NUMA API:
  $ numactl --hardware
  available: 2 nodes (0-1)
  node 0 size: 65536 MB
  node 1 size: 65536 MB

  $ numactl --membind=0 ./program
  强制程序在 Node 0 分配内存(本地访问)

总结

  • 内存布局演进:物理地址 → 实模式段→保护模式段→分页→x86-64长模式
  • x86-64 虚拟空间:用户 128TB(低)+ 内核 128TB(高),段基址=0(不用段)
  • 内核直接映射区:物理 + 0xFFFF880000000000,用于 kmalloc(低端 896MB)
  • vmalloc 区域:0xFFFF880100000000 ~ 0xFFFFC3FFFFFFFFFF,动态映射,模块/ioremap
  • 固定映射区:编译时固定,用于 early boot/ACPI
  • NUMA:每个节点独立物理地址空间,本地访问快,跨节点访问慢(~100ns)

B 层总结:B01~B11 覆盖了从 BIOS/UEFI → Bootloader → e820 → SPD → Boot流程 → 实模式→保护模式→GDT/CR0 → MMU/分页 → Buddy System → memblock → vmalloc/kswapd → COW → 内存布局的完整链路。

下篇预告(K01):Buddy System 分配物理页后,内核对象是怎么分配的?SLAB/SLUB 分配器的设计——为什么需要对象池,怎么减少分配延迟。


关注公众号「AI不着急」,回复”资料”获取内存学习路线图。

最后修改: 2024年8月14日

作者

评论

发表评论

您的邮箱地址不会被公开。