目录
- 概述
- 历史背景
- [BIOS INT 15h E820 调用协议](#BIOS INT 15h E820 调用协议)
- [EFI 系统的 E820 构建](#EFI 系统的 E820 构建)
- [E820 条目类型](#E820 条目类型)
- 数据结构
- [E820 表的清洗:e820__update_table()](#E820 表的清洗:e820__update_table())
- [E820 → iomem_resource 树的映射](#E820 → iomem_resource 树的映射)
- [E820 → memblock 的映射](#E820 → memblock 的映射)
- [真实系统的 E820 表](#真实系统的 E820 表)
- [E820 内核修正](#E820 内核修正)
- [完整启动流程中的 E820](#完整启动流程中的 E820)
- 关键设计洞察
1. 概述
E820 表是 iomem_resource 树的初始数据源 。系统启动时,BIOS/UEFI 通过 E820 接口
向操作系统报告物理内存布局,内核据此构建 iomem_resource 树的第一批节点。理解 E820
是理解整个物理地址空间管理的起点。
固件 (BIOS/UEFI) Linux 内核
┌──────────────┐ ┌──────────────────────┐
│ E820 表 │ ──boot_params→ │ e820_table │
│ (物理内存 │ │ ↓ e820__reserve_ │
│ 布局声明) │ │ ↓ resources() │
│ │ │ iomem_resource 树 │
│ │ │ ↓ │
│ │ │ memblock → buddy │
│ │ │ → struct page │
└──────────────┘ └──────────────────────┘
2. 历史背景
E820 的名字来自 x86 BIOS 的 INT 15h, AX=E820h 中断调用。这是 1990 年代中期
为解决"BIOS 如何告诉操作系统内存分布"而制定的标准。在此之前,INT 15h 只有两个简陋的
内存检测方式:
| 接口 | 调用方式 | 能力 | 限制 |
|---|---|---|---|
| INT 15h, AH=88h | 最古老 | 返回 1MB 以上扩展内存大小 | 最大 64MB |
| INT 15h, AX=E801h | 中期 | 分 15MB 以下和以上两段报告 | 最大 4GB |
| INT 15h, AX=E820h | 现代标准 | 任意多段、任意类型、64位地址 | 无实质限制 |
E820 胜出的原因是它用逐条枚举 的方式描述地址空间,每条记录有独立的起始地址、大小和
类型,完美适配现代系统中物理地址空间的碎片化布局。
3. BIOS INT 15h E820 调用协议
在 x86 实模式下(CPU 加电后的 16 位模式),bootloader 通过循环调用 INT 15h 来逐条
获取内存映射表:
调用约定:
EAX = 0x0000E820 ; 功能号
EBX = 续传标记 ; 首次调用为 0,后续使用上次返回的 EBX
ECX = 缓冲区大小 ; 通常 20 字节
EDX = 0x534D4150 ; 签名 "SMAP"(ASCII)
ES:DI = 缓冲区指针 ; 用于接收一条 e820_entry
返回:
EAX = 0x534D4150 ; 确认签名 "SMAP"
EBX = 下一条的续传标记 ; 为 0 表示最后一条
ECX = 实际写入字节数
CF = 0 表示成功
缓冲区被填充:
[0..7] : 起始物理地址 (u64)
[8..15] : 区域大小 (u64)
[16..19]: 类型 (u32)
Linux 的实现在 arch/x86/boot/memory.c:
c
#define SMAP 0x534d4150 /* ASCII "SMAP" */
static void detect_memory_e820(void)
{
int count = 0;
struct biosregs ireg, oreg;
struct boot_e820_entry *desc = boot_params.e820_table;
static struct boot_e820_entry buf;
initregs(&ireg);
ireg.ax = 0xe820;
ireg.cx = sizeof(buf);
ireg.edx = SMAP;
ireg.di = (size_t)&buf;
do {
intcall(0x15, &ireg, &oreg); // 真正的 BIOS 中断调用
ireg.ebx = oreg.ebx; // 续传标记传给下次调用
if (oreg.eflags & X86_EFLAGS_CF) // CF=1 表示结束
break;
if (oreg.eax != SMAP) { // 签名不对,放弃
count = 0;
break;
}
*desc++ = buf;
count++;
} while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_table));
boot_params.e820_entries = count;
}
这段代码在 CPU 还处于实模式 时执行(在 bootloader 阶段),结果存入 boot_params
结构的 e820_table[] 数组。最多存储 128 条(E820_MAX_ENTRIES_ZEROPAGE),因为
zero page 空间有限。
4. EFI 系统的 E820 构建
现代 UEFI 系统不再使用 BIOS 中断。EFI 提供了自己的内存映射接口
GetMemoryMap(),返回 efi_memory_desc_t 描述符数组。Linux 会将 EFI 内存类型
转换为 E820 类型,统一用 E820 表表示:
c
/* arch/x86/platform/efi/efi.c */
static void __init do_add_efi_memmap(void)
{
efi_memory_desc_t *md;
for_each_efi_memory_desc(md) {
unsigned long long start = md->phys_addr;
unsigned long long size = md->num_pages << EFI_PAGE_SHIFT;
int e820_type;
switch (md->type) {
case EFI_LOADER_CODE:
case EFI_LOADER_DATA:
case EFI_BOOT_SERVICES_CODE:
case EFI_BOOT_SERVICES_DATA:
case EFI_CONVENTIONAL_MEMORY:
if (md->attribute & EFI_MEMORY_WB)
e820_type = E820_TYPE_RAM; // 可用内存
else
e820_type = E820_TYPE_RESERVED;
break;
case EFI_ACPI_RECLAIM_MEMORY:
e820_type = E820_TYPE_ACPI; // ACPI 可回收
break;
case EFI_ACPI_MEMORY_NVS:
e820_type = E820_TYPE_NVS; // ACPI NVS
break;
case EFI_PERSISTENT_MEMORY:
e820_type = E820_TYPE_PMEM; // 持久内存
break;
default:
e820_type = E820_TYPE_RESERVED; // 其他都标为保留
break;
}
e820__range_add(start, size, e820_type);
}
e820__update_table(e820_table); // 合并、排序、去重
}
EFI 内存类型 → E820 类型映射
| EFI 内存类型 | E820 类型 | 说明 |
|---|---|---|
EFI_CONVENTIONAL_MEMORY (WB) |
E820_TYPE_RAM |
可用内存 |
EFI_LOADER_CODE/DATA (WB) |
E820_TYPE_RAM |
Bootloader 用完即可用 |
EFI_BOOT_SERVICES_CODE/DATA (WB) |
E820_TYPE_RAM |
Boot services 退出后可用 |
EFI_ACPI_RECLAIM_MEMORY |
E820_TYPE_ACPI |
ACPI 表,可回收 |
EFI_ACPI_MEMORY_NVS |
E820_TYPE_NVS |
ACPI NVS,不可回收 |
EFI_PERSISTENT_MEMORY |
E820_TYPE_PMEM |
NVDIMM 持久内存 |
EFI_RESERVED_TYPE |
E820_TYPE_RESERVED |
固件保留 |
EFI_RUNTIME_SERVICES_CODE/DATA |
E820_TYPE_RESERVED |
运行时服务,保留 |
EFI_MEMORY_MAPPED_IO |
E820_TYPE_RESERVED |
设备 MMIO |
EFI MMIO 修正
EFI 还会修正某些 MMIO 区域的 E820 标记------系统 dmesg 中可以看到:
efi: Remove mem61: MMIO range=[0xe0000000-0xefffffff] (256MB) from e820 map
efi: Remove mem66: MMIO range=[0xc80000000-0xca01fffff] (514MB) from e820 map
这些是 EFI 发现 BIOS 错误地将 PCI ECAM/MMIO 区域标记为 reserved,
需要从 E820 中移除以避免内核误认为可用资源。
5. E820 条目类型
c
/* arch/x86/include/asm/e820/types.h */
enum e820_type {
E820_TYPE_RAM = 1, // 可用系统内存 → "System RAM"
E820_TYPE_RESERVED = 2, // 保留区域 → "Reserved"
E820_TYPE_ACPI = 3, // ACPI 可回收表 → "ACPI Tables"
E820_TYPE_NVS = 4, // ACPI NVS → "ACPI Non-volatile Storage"
E820_TYPE_UNUSABLE = 5, // 包含错误的内存 → "Unusable memory"
E820_TYPE_PMEM = 7, // 持久内存 → "Persistent Memory"
E820_TYPE_PRAM = 12, // 非标准持久内存
E820_TYPE_SOFT_RESERVED = 0xefffffff, // EFI 软保留
};
各类型的含义和操作系统应如何处理:
| 类型值 | 名称 | 含义 | OS 处理 |
|---|---|---|---|
| 1 | RAM (usable) | 可自由使用的物理内存 | 加入 memblock → 构建 page 数组 → 可分配 |
| 2 | Reserved | 固件保留,不可使用 | 不加入 memblock,但登记在 iomem 树中 |
| 3 | ACPI data | ACPI 表数据 | 内核读取后可回收为普通 RAM |
| 4 | ACPI NVS | ACPI Non-Volatile Storage | 永久保留,不可回收(挂起/恢复需要) |
| 5 | Unusable | 已知有错误的内存 | 完全不使用 |
| 7 | PMEM | 持久内存(NVDIMM) | 可作为 DAX/fsdax 设备 |
| 12 | PRAM | 非标准持久内存 | 需要 CONFIG_X86_PMEM_LEGACY |
6. 数据结构
6.1 单条记录
c
/* BIOS 原始格式 (20 字节, packed) --- 通过 zero page 传递 */
struct boot_e820_entry {
__u64 addr; // 起始物理地址
__u64 size; // 区域大小(字节)
__u32 type; // 类型(e820_type 枚举)
} __attribute__((packed));
/* 内核内部格式 */
struct e820_entry {
u64 addr;
u64 size;
enum e820_type type;
} __attribute__((packed));
20 字节紧凑布局是 BIOS ABI 要求的,内核在编译时会断言:
c
BUILD_BUG_ON(sizeof(struct boot_e820_entry) != 20);
6.2 完整表
c
struct e820_table {
__u32 nr_entries;
struct e820_entry entries[E820_MAX_ENTRIES];
};
E820_MAX_ENTRIES 的计算:
c
// 基础容量(zero page 限制)
#define E820_MAX_ENTRIES_ZEROPAGE 128
// 内核扩展容量(支持大型 NUMA 系统)
#define E820_MAX_ENTRIES (E820_MAX_ENTRIES_ZEROPAGE + 3 * MAX_NUMNODES)
每个 NUMA 节点额外预留 3 条,因为 EFI 系统可能为每个节点报告多段不连续内存。
6.3 三份副本
c
extern struct e820_table *e820_table; // 工作副本(会被修改)
extern struct e820_table *e820_table_kexec; // kexec 用的副本
extern struct e820_table *e820_table_firmware; // 固件原始副本(不修改)
| 副本 | 用途 | 是否会被修改 |
|---|---|---|
e820_table |
内核日常使用的工作副本 | ✅ 频繁修改(清洗、EFI 追加、内核修正) |
e820_table_firmware |
保存固件原始报告 | ❌ 不修改(用于调试和 sysfs 导出) |
e820_table_kexec |
kexec 新内核时传递 | ❌ 不修改 |
7. E820 表的清洗:e820__update_table()
BIOS 报告的 E820 表可能有重叠、乱序 等问题。内核的 e820__update_table() 函数
使用一个优雅的change-point 算法来清洗数据。
7.1 算法思路
- 将每条记录拆成两个 change-point:起始地址(标记为"进入")和结束地址(标记为"离开")
- 将所有 change-point 按地址排序
- 扫描排序后的 change-point,维护一个"当前活跃区域"的重叠列表
- 在重叠处取类型值最大者胜出(数值大 = 更受保护)
7.2 类型优先级
E820_TYPE_UNUSABLE(5) > E820_TYPE_NVS(4) > E820_TYPE_ACPI(3) >
E820_TYPE_RESERVED(2) > E820_TYPE_RAM(1)
这意味着如果 BIOS 同时声明一段地址是 RAM 和 Reserved,内核会将其视为 Reserved。
这是安全原则:宁可少用内存,也不要覆盖固件保留区域。
7.3 可视化示例
输入(有重叠的 BIOS 原始数据):
[0x000-0x0FF] type=1 (RAM)
[0x080-0x1FF] type=2 (Reserved)
[0x100-0x2FF] type=1 (RAM)
Change-points 排序后:
0x000 (进入 RAM)
0x080 (进入 Reserved)
0x100 (离开 RAM) ← 第一段 RAM 的 end
0x100 (进入 RAM) ← 第二段 RAM 的 start
0x200 (离开 Reserved)
0x300 (离开 RAM)
清洗后(重叠区域取更高优先级):
[0x000-0x07F] type=1 (RAM) ← RAM 独占
[0x080-0x1FF] type=2 (Reserved) ← Reserved 优先级高,覆盖了 RAM
[0x200-0x2FF] type=1 (RAM) ← RAM 独占
7.4 内核实现核心
c
/* arch/x86/kernel/e820.c */
struct change_member {
struct e820_entry *entry; // 指向原始条目
unsigned long long addr; // 这个 change-point 的地址
};
int __init e820__update_table(struct e820_table *table)
{
// 1. 为每条记录创建两个 change-point(进入和离开)
for (i = 0; i < table->nr_entries; i++) {
if (entries[i].size != 0) {
change_point[chg_idx]->addr = entries[i].addr;
change_point[chg_idx++]->entry = &entries[i];
change_point[chg_idx]->addr = entries[i].addr + entries[i].size;
change_point[chg_idx++]->entry = &entries[i];
}
}
// 2. 按地址排序
sort(change_point, chg_nr, sizeof(*change_point), cpcompare, NULL);
// 3. 扫描 change-points,维护重叠列表,取最高类型值
for (chg_idx = 0; chg_idx < chg_nr; chg_idx++) {
// 如果是"进入"→ 加入 overlap_list
// 如果是"离开"→ 从 overlap_list 移除
// 在所有重叠条目中取 max(type) 作为当前类型
// 类型变化时生成新的输出条目
}
}
8. E820 → iomem_resource 树的映射
E820 表清洗完成后,通过 e820__reserve_resources() 注册到 iomem_resource 树中:
c
void __init e820__reserve_resources(void)
{
for (i = 0; i < e820_table->nr_entries; i++) {
struct e820_entry *entry = e820_table->entries + i;
res->start = entry->addr;
res->end = entry->addr + entry->size - 1;
res->name = e820_type_to_string(entry); // "System RAM" 等
res->flags = e820_type_to_iomem_type(entry); // IORESOURCE_SYSTEM_RAM 等
res->desc = e820_type_to_iores_desc(entry); // IORES_DESC_* 枚举
if (do_mark_busy(entry->type, res)) {
res->flags |= IORESOURCE_BUSY;
insert_resource(&iomem_resource, res); // ← 插入到 iomem 树
}
}
}
类型映射关系
| E820 类型 | iomem name | iomem flags | iomem desc |
|---|---|---|---|
E820_TYPE_RAM |
"System RAM" | `IORESOURCE_SYSTEM_RAM | BUSY` |
E820_TYPE_RESERVED |
"Reserved" | IORESOURCE_MEM |
IORES_DESC_RESERVED |
E820_TYPE_ACPI |
"ACPI Tables" | `IORESOURCE_MEM | BUSY` |
E820_TYPE_NVS |
"ACPI Non-volatile Storage" | `IORESOURCE_MEM | BUSY` |
E820_TYPE_PMEM |
"Persistent Memory" | IORESOURCE_MEM |
IORES_DESC_PERSISTENT_MEMORY |
转换函数
c
static const char *e820_type_to_string(struct e820_entry *entry)
{
switch (entry->type) {
case E820_TYPE_RAM: return "System RAM";
case E820_TYPE_ACPI: return "ACPI Tables";
case E820_TYPE_NVS: return "ACPI Non-volatile Storage";
case E820_TYPE_UNUSABLE: return "Unusable memory";
case E820_TYPE_PMEM: return "Persistent Memory";
case E820_TYPE_RESERVED: return "Reserved";
default: return "Unknown E820 type";
}
}
do_mark_busy() 逻辑
c
static bool do_mark_busy(enum e820_type type, struct resource *res)
{
/* 低于 1MB 的区域总是标记 BUSY */
if (res->start < (1ULL << 20))
return true;
switch (type) {
case E820_TYPE_RESERVED:
case E820_TYPE_SOFT_RESERVED:
case E820_TYPE_PRAM:
case E820_TYPE_PMEM:
return false; // 不标记 BUSY → 允许驱动后续认领
case E820_TYPE_RAM:
case E820_TYPE_ACPI:
case E820_TYPE_NVS:
case E820_TYPE_UNUSABLE:
default:
return true; // 标记 BUSY → 防止被重复认领
}
}
Reserved、PMEM 等类型不设 BUSY 标志 ,因为它们可能还需要被 PCI 子系统
或专用驱动进一步认领。
9. E820 → memblock 的映射
除了 iomem_resource 树,E820 还驱动了内核的物理内存分配器初始化:
c
void __init e820__memblock_setup(void)
{
for (i = 0; i < e820_table->nr_entries; i++) {
struct e820_entry *entry = &e820_table->entries[i];
if (entry->type == E820_TYPE_SOFT_RESERVED)
memblock_reserve(entry->addr, entry->size);
if (entry->type != E820_TYPE_RAM)
continue; // 只有 RAM 才加入 memblock
memblock_add(entry->addr, entry->size); // ← 告诉 memblock 这段可用
}
memblock_trim_memory(PAGE_SIZE); // 对齐到页边界
}
memblock 是内核早期的物理内存分配器(buddy allocator 建立之前),只接收
E820_TYPE_RAM 类型的区域。后续由 memblock 驱动 struct page 数组的创建和
buddy 分配器的初始化。
E820 的两条消费路径
E820 表
┌───────┐
│ RAM │──→ memblock_add() → buddy allocator → 可分配物理页
│ RES │──→ (跳过)
│ ACPI │──→ (跳过)
│ ... │
└───┬───┘
│
↓ (所有类型)
e820__reserve_resources()
│
↓
insert_resource(&iomem_resource, ...)
│
↓
/proc/iomem 可见
10. 真实系统的 E820 表
以下是本系统(ASUS TUF B650M-PLUS, AMD Ryzen, 48GB RAM, AMD GPU)启动时
BIOS 报告的完整 E820 表(来自 dmesg | grep BIOS-e820):
BIOS-e820: [mem 0x0000000000000000-0x000000000009ffff] usable # 640KB 常规内存
BIOS-e820: [mem 0x00000000000a0000-0x00000000000fffff] reserved # 384KB VGA+ROM 区
BIOS-e820: [mem 0x0000000000100000-0x00000000099fefff] usable # ~9.6MB 扩展内存
BIOS-e820: [mem 0x00000000099ff000-0x0000000009ffffff] reserved # ~6MB 保留
BIOS-e820: [mem 0x000000000a000000-0x000000000a1fffff] usable # 2MB
BIOS-e820: [mem 0x000000000a200000-0x000000000a21dfff] ACPI NVS # ~120KB
BIOS-e820: [mem 0x000000000a21e000-0x000000000affffff] usable # ~5.9MB
BIOS-e820: [mem 0x000000000b000000-0x000000000b020fff] reserved # ~132KB
BIOS-e820: [mem 0x000000000b021000-0x000000006b0a9fff] usable # ~1.54GB ← 低端主内存
BIOS-e820: [mem 0x000000006b0aa000-0x00000000718a6fff] reserved # ~104MB UEFI 运行时
BIOS-e820: [mem 0x00000000718a7000-0x0000000071a46fff] ACPI data # ~1.6MB ACPI 表
BIOS-e820: [mem 0x0000000071a47000-0x0000000073a46fff] ACPI NVS # 32MB ACPI NVS
BIOS-e820: [mem 0x0000000073a47000-0x00000000783fefff] reserved # ~73MB
BIOS-e820: [mem 0x00000000783ff000-0x0000000079ff6fff] usable # ~28MB
BIOS-e820: [mem 0x0000000079ff7000-0x0000000079ffbfff] reserved # 20KB
BIOS-e820: [mem 0x0000000079ffc000-0x0000000079ffffff] usable # 16KB
BIOS-e820: [mem 0x000000007a000000-0x000000007bffffff] reserved # 32MB
BIOS-e820: [mem 0x000000007d7f3000-0x000000007fffffff] reserved # ~40MB
BIOS-e820: [mem 0x00000000e0000000-0x00000000efffffff] reserved # 256MB PCI ECAM
BIOS-e820: [mem 0x00000000f7000000-0x00000000ffffffff] reserved # 144MB legacy 高区
BIOS-e820: [mem 0x0000000100000000-0x0000000c5dd7ffff] usable # ~47GB ← 高端主内存
BIOS-e820: [mem 0x0000000c5edc0000-0x0000000ca01fffff] reserved # ~516MB
BIOS-e820: [mem 0x000000fd00000000-0x000000ffffffffff] reserved # 12GB PCI MMIO
图形化表示
物理地址
0 640K 1M ~1.7GB 2GB 3.5GB 4GB
├─ usable ──┤res├─ usable ──┤─ usable ──────┤ res ├─res──┤
│ 常规内存 │VGA│ 扩展内存 │ 主内存 (低) │UEFI │ECAM │
│ │ROM│(碎片化) │ │/ACPI │ │
4GB ~48GB
├──── usable ─────────────────────────────────────┤ res ┤
│ 主内存 (高端, ~47GB) │ │
│ ← Kernel code/data 在这里面 │ │
~64GB ~1TB
├─── reserved ────────────┤
│ PCI 64位 MMIO 窗口 │
│ GPU VRAM BAR 在这里面 │
为什么低端内存如此碎片化?
4GB 以下的 usable 内存被切成了 6 块碎片。这是因为:
- VGA 兼容区 (0xA0000-0xFFFFF) --- 历史遗留的 640KB-1MB ISA 孔洞
- ACPI NVS --- 固件需要在 S3 睡眠/恢复期间保持的非易失存储
- UEFI 运行时服务 --- EFI Runtime Services 代码和数据需要保留
- ACPI 表 --- DSDT/SSDT 等表需要内核可读
- SMM/保留 --- System Management Mode 和其他固件保留区域
相比之下,4GB 以上的内存几乎是一整块连续的 47GB usable,
因为高端地址不受 legacy 兼容性约束。
11. E820 内核修正
内核不会原样使用 BIOS E820 表,而是会进行多轮修正:
第一轮:EFI MMIO 修正
efi: Remove mem61: MMIO range=[0xe0000000-0xefffffff] (256MB) from e820 map
efi: Remove mem66: MMIO range=[0xc80000000-0xca01fffff] (514MB) from e820 map
EFI 固件检查哪些 reserved 区间实际上是 MMIO(内存映射 I/O),需要从 E820 中移除
以避免被误认为可用资源。
第二轮:低端内存保护
e820: update [mem 0x00000000-0x00000fff] usable ==> reserved
e820: remove [mem 0x000a0000-0x000fffff] usable
- 第 0 页(0x000-0xFFF)包含实模式中断向量表(IVT)和 BIOS 数据区(BDA),必须保护
- ISA 孔洞(0xA0000-0xFFFFF)是 VGA frame buffer 和 ROM 区域,标记为不可用
第三轮:内核特定保留
e820: update [mem 0x80000000-0xffffffff] usable ==> reserved
e820: update [mem 0x65766000-0x65766fff] usable ==> reserved
e820: update [mem 0x64fa3000-0x65153fff] usable ==> reserved
内核将 32 位 PCI MMIO 窗口(2GB-4GB)标记为 reserved,以及一些由 BIOS 遗漏的保留页
(可能是 EFI Memory Attributes Table 标识的特殊页)。
第四轮:RAM buffer 保留
e820: reserve RAM buffer [mem 0x099ff000-0x0bffffff]
e820: reserve RAM buffer [mem 0xc5dd80000-0xc5fffffff]
在 RAM 区域的尾部留出缓冲区,避免设备 DMA 访问到超出实际 DRAM 的地址。
缓冲区大小由 ram_alignment() 根据地址范围决定:
c
static unsigned long __init ram_alignment(resource_size_t pos)
{
unsigned long mb = pos >> 20;
if (!mb) return 64*1024; // 第一个 MB:对齐到 64KB
if (mb < 16) return 1024*1024; // 前 16MB:对齐到 1MB
return 64*1024*1024; // 其他:对齐到 64MB
}
12. 完整启动流程中的 E820
CPU 加电
│
├── BIOS/UEFI POST
│ │
│ ├── 枚举物理内存(SPD 读取、DRAM 训练)
│ ├── 配置 PCI BAR(分配 MMIO 地址)
│ └── 构建 E820 表 / EFI Memory Map
│
├── Bootloader (GRUB)
│ │
│ ├── 调用 INT 15h, AX=E820h(Legacy BIOS)
│ │ 或解析 EFI GetMemoryMap()
│ │
│ └── 存入 boot_params.e820_table[](最多 128 条)
│ └── 通过 zero page 传递给内核
│
├── 内核早期启动 (实模式 → 保护模式)
│ │
│ ├── detect_memory()
│ │ ├── detect_memory_e820() ← 调用 INT 15h
│ │ ├── detect_memory_e801() ← 回退方案
│ │ └── detect_memory_88() ← 最终回退
│ │
│ └── boot_params.e820_entries = count
│
├── 内核 setup_arch() [保护模式]
│ │
│ ├── e820__memory_setup()
│ │ ├── 从 boot_params 复制到 e820_table
│ │ ├── e820__update_table() ← 清洗:去重、合并、解决重叠
│ │ ├── 复制到 e820_table_kexec
│ │ └── 复制到 e820_table_firmware
│ │
│ ├── [EFI 系统] do_add_efi_memmap()
│ │ └── 将 EFI memory map 追加到 e820_table
│ │
│ ├── e820__memblock_setup()
│ │ └── 对每条 E820_TYPE_RAM → memblock_add()
│ │ → 内核开始有可用物理内存
│ │
│ └── e820__reserve_resources()
│ └── 对每条 E820 entry → insert_resource(&iomem_resource, ...)
│ → iomem_resource 树建立完成
│
├── PCI 子系统初始化
│ └── pcibios_resource_survey()
│ └── 将 PCI BAR 插入 iomem_resource 树(E820 空洞区域)
│
├── 驱动加载 (amdgpu)
│ └── devm_request_free_mem_region(&iomem_resource, ...)
│ └── 在 E820 未覆盖的空洞中找到空闲位置
│ 为 ZONE_DEVICE 占位
│
└── 系统运行
└── /proc/iomem 展示完整的 iomem_resource 树
13. 关键设计洞察
13.1 E820 是"声明式"而非"命令式"
BIOS 只是声明 每段地址的类型,不负责分配。操作系统根据声明自行决定如何使用。
这意味着操作系统可以选择不使用某些 usable 内存(如内核的 mem= 参数限制),
也可以回收 ACPI data 类型的内存。
13.2 E820 有"空洞"
E820 表不覆盖 整个地址空间。表中未列出的区域是"空洞"------不是 RAM、不是 Reserved、
什么也不是。这些空洞正是 PCI BAR 和 devm_request_free_mem_region 可以使用的空间。
在真实系统中,最大的空洞通常在:
| 位置 | 示例范围 | 大小 | 用途 |
|---|---|---|---|
| 32位 MMIO 窗口 | 0x80000000 - 0xDFFFFFFF | ~1.5GB | PCI 32位 BAR |
| 4GB gap | 0xCA020000 - 0xFFFFFFFF | ~860MB | 各种保留 |
| 64位 MMIO 窗口 | 0xC80000000 - ... | 数百 GB | PCI 64位 BAR |
| 高端空洞 | 远超实际 RAM 的高地址 | 数 TB | ZONE_DEVICE 占位 |
13.3 E820 与 iomem_resource 的关系
E820 表 (固件层) iomem_resource 树 (OS 层)
┌──────────────┐ ┌─────────────────────┐
│ usable │ ──→ │ "System RAM" │ → memblock → buddy → 可用内存
│ reserved │ ──→ │ "Reserved" │ → 不分配
│ ACPI data │ ──→ │ "ACPI Tables" │ → 读取后可回收
│ ACPI NVS │ ──→ │ "ACPI NVS" │ → 永久保留
│ (空洞) │ │ (空洞 → 后续填充) │
│ │ │ └→ "PCI Bus ..." │ ← PCI 枚举后填充
│ │ │ └→ "0000:03:00.0" │ ← GPU DEVICE_PRIVATE 占位
└──────────────┘ └─────────────────────┘
E820 是种子数据,iomem_resource 树在其基础上不断生长------PCI 枚举添加设备 BAR,
驱动加载添加功能区域,devm_request_free_mem_region 添加 ZONE_DEVICE 占位。
但所有后续添加都必须不与 E820 已声明的区域冲突。
13.4 向后兼容的典范
即使在 UEFI 完全取代 BIOS 的今天,Linux 仍然将 EFI 内存映射转换为 E820 格式。
这体现了内核的设计哲学:在接口层做适配,让核心逻辑保持统一 。
无论固件接口如何变化,内核的 E820 处理逻辑(清洗 → memblock → iomem_resource)
始终保持稳定。