E820 内存映射表深度解析

目录

  1. 概述
  2. 历史背景
  3. [BIOS INT 15h E820 调用协议](#BIOS INT 15h E820 调用协议)
  4. [EFI 系统的 E820 构建](#EFI 系统的 E820 构建)
  5. [E820 条目类型](#E820 条目类型)
  6. 数据结构
  7. [E820 表的清洗:e820__update_table()](#E820 表的清洗:e820__update_table())
  8. [E820 → iomem_resource 树的映射](#E820 → iomem_resource 树的映射)
  9. [E820 → memblock 的映射](#E820 → memblock 的映射)
  10. [真实系统的 E820 表](#真实系统的 E820 表)
  11. [E820 内核修正](#E820 内核修正)
  12. [完整启动流程中的 E820](#完整启动流程中的 E820)
  13. 关键设计洞察

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 算法思路

  1. 将每条记录拆成两个 change-point:起始地址(标记为"进入")和结束地址(标记为"离开")
  2. 将所有 change-point 按地址排序
  3. 扫描排序后的 change-point,维护一个"当前活跃区域"的重叠列表
  4. 在重叠处取类型值最大者胜出(数值大 = 更受保护)

7.2 类型优先级

复制代码
E820_TYPE_UNUSABLE(5) > E820_TYPE_NVS(4) > E820_TYPE_ACPI(3) >
E820_TYPE_RESERVED(2) > E820_TYPE_RAM(1)

这意味着如果 BIOS 同时声明一段地址是 RAMReserved,内核会将其视为 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 → 防止被重复认领
    }
}

ReservedPMEM 等类型不设 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 块碎片。这是因为:

  1. VGA 兼容区 (0xA0000-0xFFFFF) --- 历史遗留的 640KB-1MB ISA 孔洞
  2. ACPI NVS --- 固件需要在 S3 睡眠/恢复期间保持的非易失存储
  3. UEFI 运行时服务 --- EFI Runtime Services 代码和数据需要保留
  4. ACPI 表 --- DSDT/SSDT 等表需要内核可读
  5. 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)

始终保持稳定。