B11 —— 现代计算机的内存空间布局:虚拟地址和物理地址的完整映射
金句:理解内存布局,是理解系统设计的钥匙。
从物理地址到虚拟地址的演进
早期计算机(80286 之前)没有 MMU,直接用物理地址访问内存。后来引入了段机制,再到分页,内存布局越来越复杂。
内存布局的演进:
1. 纯物理地址(80286 之前):
- 没有 MMU
- 物理地址 = 访问地址
- 无法实现进程隔离
2. 实模式(8086):
- 段寄存器 × 16 + 偏移 = 20-bit 物理地址
- 1MB 地址空间
- 无保护,无隔离
3. 保护模式(80286+):
- GDT/LDT 定义段描述符
- 段选择子 + 偏移 = 线性地址
- 特权级(Ring 0/1/2/3)
4. 分页保护模式(80386+):
- 线性地址通过页表翻译成物理地址
- 虚拟内存支持
- 每个进程独立的页表
5. x86-64 长模式(AMD64):
- 48-bit 虚拟地址(用户 128TB,内核 128TB)
- 四级页表
- 不再使用段寄存器(CS/DS 等仍是选择子,但基址=0)x86-64 完整虚拟地址空间布局
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
→ 固件保留
→ 未使用物理地址到虚拟地址的转换
物理地址到虚拟地址的几种方式:
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 内核内存布局详解
内核内存布局的关键区域(x86-64,4-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 查看物理地址布局
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 系统的内存布局
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不着急」,回复”资料”获取内存学习路线图。
评论