从硬件到内核的内存寻址机制
(一)计算机内存寻址的底层逻辑
-
地址空间(Address Space)
- 每个程序(包括内核)运行时,看到的是一个 “虚拟地址空间”,就像一个巨大的连续内存区域,实际由 CPU 和内存管理单元(MMU)映射到物理内存。
- 32 位系统虚拟地址空间大小为
2^32 = 4GB
,64 位系统则是2^64
(理论上远超过实际物理内存)。
-
物理地址(Physical Address)vs 虚拟地址(Virtual Address)
- 物理地址:内存芯片上的真实地址,就像旅馆房间的真实位置。
- 虚拟地址:程序看到的 “假象地址”,通过 MMU 转换为物理地址,避免不同程序互相干扰(比如两个程序都用 0x1000 地址,实际指向不同的物理内存)。
(二)32 位系统的内存寻址限制史
-
早期 32 位 CPU:没有 PAE 技术(1995 年前)
- Intel x86 架构的 32 位 CPU(如 80386、80486),物理地址总线是 32 位,最多支持 4GB 物理内存。
- Linux 内核早期设计:为了兼容性和稳定性,将虚拟地址空间划分为 “3GB 用户空间 + 1GB 内核空间”(通过内核参数
CONFIG_PAGE_OFFSET
配置)。- 用户空间:程序运行的区域,不能直接访问内核数据。
- 内核空间:内核代码和数据专用,包含硬件寄存器、物理内存映射等。
- 问题:如果物理内存超过 4GB,内核根本无法寻址(因为地址总线只能到 4GB);即使物理内存只有 2GB,内核空间只有 1GB,也无法直接访问超过 1GB 的物理内存(因为内核空间地址不够用)。
-
PAE 技术登场(1995 年,Pentium Pro 引入)
- PAE(Physical Address Extension):扩展物理地址到 36 位,支持最多 64GB 物理内存。
- 原理:将 32 位虚拟地址转换为 36 位物理地址,通过 “分页机制” 分成多个 4KB 页面,用额外的页目录项记录扩展地址。
- Linux 内核支持:2.4.0 版本开始支持 PAE,但早期 32 位内核的虚拟地址空间仍为 4GB,内核空间只有 1GB,导致:
- 用户空间最多用 3GB,内核空间 1GB 需要映射所有物理内存、硬件设备、内核代码数据,严重不够用。
- 解决方法:引入 “高端内存(HighMem)” 机制,将超过内核空间的物理内存动态映射到内核空间的 “临时地址”,用完即释放(类似 “临时借用房间号”)。
-
32 位内核的困境:1GB 内核空间瓶颈
- 即使有 PAE,32 位内核的内核空间始终只有 1GB,需要存储:
- 内核代码(几十 MB)
- 内核数据结构(如进程表、文件系统缓存)
- 物理内存页表(管理所有物理内存的映射关系,内存越大,页表占用越多)
- 硬件设备映射(如显卡、网卡的寄存器地址)
- 当物理内存超过 1GB 时:
- 内核无法在 1GB 空间内同时映射所有物理内存,只能通过 HighMem 机制分批映射,导致性能下降。
- 极端情况:如果物理内存是 4GB,内核需要用 1GB 空间映射 4GB 物理内存,只能通过 “动态切换映射”,每次访问高端内存都要修改页表,效率极低。
- 即使有 PAE,32 位内核的内核空间始终只有 1GB,需要存储:
(三)技术细节:32 位内核如何处理 1GB 以上内存?
-
内存分页机制(Paging)
- 32 位系统默认使用 4KB 页面,虚拟地址分为三级:
- 页目录指针(PDPTR,仅 PAE 启用时存在)
- 页目录(PGD)
- 页表(PTE)
- PAE 模式下,页目录项扩展为 8 字节,支持 36 位物理地址,每个页目录项指向一个 4KB 的页中间目录(PUD),再指向页表。
- 32 位系统默认使用 4KB 页面,虚拟地址分为三级:
-
高端内存(HighMem)的实现
- Linux 将物理内存分为三部分:
- ZONE_DMA:0-16MB,供老式 ISA 设备使用(通过 DMA 传输数据)。
- ZONE_NORMAL:16MB-896MB(或 1GB,取决于内核配置),直接映射到内核空间。
- ZONE_HIGHMEM:896MB(或 1GB)以上,不直接映射,需要通过
kmap()
/kfree()
动态映射到内核空间的 “临时地址”。
- 映射过程:
- 内核从预留的 “临时地址空间”(属于内核空间,但未分配物理页)中找一个空闲地址。
- 创建页表项,将该虚拟地址映射到高端内存的物理页。
- 使用完毕后,删除页表项,释放临时地址(类似 “临时租借一个门牌号,用完还回去”)。
- Linux 将物理内存分为三部分:
-
内核空间地址不足的后果
- 当物理内存超过内核空间可映射范围(如 32 位内核的 1GB),内核必须频繁切换 HighMem 映射,导致:
- 上下文切换开销增加(修改页表需要 CPU 刷新 TLB 缓存)。
- 内存分配失败(内核空间没有足够的连续虚拟地址来映射物理页)。
- 这就是为什么 32 位 Linux 服务器通常建议物理内存不超过 4GB,且实际可用内存因 HighMem 机制进一步缩水(比如 32 位 Ubuntu 最多识别 3.25GB 内存)。
- 当物理内存超过内核空间可映射范围(如 32 位内核的 1GB),内核必须频繁切换 HighMem 映射,导致:
(四)64 位系统如何解决寻址问题?
-
64 位虚拟地址空间:不再有 1GB 限制
- 64 位内核默认虚拟地址空间为 128TB(Linux x86_64 采用 48 位地址,
2^48 = 256TB
,预留高位作为保护),分为:- 用户空间:低 128TB(0x0000000000000000 - 0x00007FFFFFFFFFFF)
- 内核空间:高 128TB(0xffff800000000000 - 0xFFFFFFFFFFFFFFFF)
- 内核空间足够大,可直接映射所有物理内存(即使服务器有 TB 级内存,内核空间也能轻松容纳页表和映射)。
- 64 位内核默认虚拟地址空间为 128TB(Linux x86_64 采用 48 位地址,
-
不再需要 HighMem 机制
- 64 位内核的内核空间远大于实际物理内存,物理内存可以全部直接映射到内核空间,无需动态切换映射,性能大幅提升。
- 例如:128GB 物理内存,在内核空间中只需要 128GB 地址范围,而内核空间有 128TB,完全够用。
-
PAE 的历史使命终结
- 64 位 CPU(如 x86_64、ARM64)不再需要 PAE,因为地址总线本身就是 64 位(或更高),物理地址直接支持 TB 级以上内存。
(五)现代 Linux 内核的内存管理
-
32 位内核的淘汰
- 随着 64 位 CPU 普及,32 位 Linux 内核逐渐被淘汰:
- Ubuntu 18.04 后不再支持 32 位用户空间;
- Linux 内核主线从 5.15 版本开始,不再维护 32 位 x86(i386)架构的 PAE 支持。
- 目前 32 位内核仅用于嵌入式设备(如 ARMv7),且这些架构通常有自己的扩展寻址方案(如 ARM 的 LPAE)。
- 随着 64 位 CPU 普及,32 位 Linux 内核逐渐被淘汰:
-
大内存系统的优化
- 透明大页(Huge Pages):将多个 4KB 页面合并为 2MB/1GB 大页,减少页表数量,提升 TLB 命中率。
- 内存热插拔(Memory Hotplug):动态添加 / 移除物理内存,内核自动更新页表。
- NUMA(非统一内存访问):针对多 CPU 服务器,内核根据 CPU 位置优化内存访问,避免跨 NUMA 节点的延迟。
(六)总结:为什么 32 位内核 “看不到” 1GB 以上内存?
- 早期设计限制:32 位内核将虚拟地址空间划分为 3GB 用户 + 1GB 内核,内核空间不足。
- 硬件寻址限制:无 PAE 时,32 位 CPU 最多支持 4GB 物理内存,且内核空间只有 1GB,无法直接映射超过 1GB 的物理内存。
- 解决方案的代价:PAE 和 HighMem 机制虽然让 32 位内核支持更大内存,但性能和稳定性问题显著,最终被 64 位架构取代。
形象比喻:把内存寻址比作 “房子找房间”
(一)先理解 “寻址” 是什么?
你可以把计算机的内存(RAM)想象成一栋超级大的 “数据旅馆”,每一个字节(Byte)的内存就是旅馆里的一个 “房间”,而 “寻址” 就是通过 “房间号” 找到对应的房间。
房间号在计算机里叫 “地址”,比如 32 位系统的 “房间号” 是 32 位的数字(二进制),最多能表示 2^32
个房间,也就是 4GB 的总房间数(因为 1GB=1024^3 字节,4GB≈40 亿字节)。
(二)为什么 32 位内核 “看不到” 1GB 以上的内存?
早期的 32 位 Linux 内核做了一个 “限制”:
- 它把 4GB 的 “房间号” 分成了两部分:
- 前 3GB(30 亿个房间) 留给用户程序(比如你打开的浏览器、文档编辑器等),相当于 “用户住的客房”。
- 后 1GB(10 亿个房间) 留给内核自己用,比如管理硬件、运行系统核心功能,相当于 “旅馆的后台服务区”。
- 这样一来,内核虽然理论上能 “看到” 4GB 地址,但实际只给用户程序留了 3GB,自己占了 1GB。
- 但更关键的是:早期 32 位 CPU 和内核没有 “扩展寻址” 技术时,物理内存超过 1GB 后,内核可能连这些额外的内存都 “认不出来”,就像旅馆扩建了新楼,但门牌号没升级,前台(内核)找不到新房间的位置。
(三)类比:就像 “老式小区的门牌号不够用”
假设你住在一个小区,门牌号是 3 位数(000-999),最多只能有 1000 户。
- 如果你家小区扩建到 1500 户,但门牌号还是 3 位数,新来的 500 户就没有合法门牌号,快递员(内核)根本找不到它们,只能干瞪眼。
- 后来有了 “扩展门牌号” 技术(比如在门牌号前加楼号,变成 4 位数),才能找到更多住户。这就是 32 位系统后来用 PAE(物理地址扩展)技术支持更大内存的原理。