长文101:内存管理-启动篇:从实模式到分页开启的内存布局

“内核代码链接在 1MB 以上,但启动时 CPU 还在实模式—— 今天,我们揭秘启动阶段的内存布局,看内核如何安全开启分页!”

在操作系统的启动过程中, 内存管理是最先面临的挑战 。 CPU 上电后处于 实模式(Real Mode) ,只能访问 1MB 以下内存, 但内核代码却链接在 1MB 以上 (如 0x100000 )。

更复杂的是: 分页(Paging)开启前,所有地址都是物理地址 ; 开启后,地址变为虚拟地址,硬件自动翻译。

今天,我们就来: ✅ 解析启动阶段的内存布局 ✅ 理解临时页表(Identity Mapping)的作用 ✅ 掌握安全开启分页的完整流程 ✅ 获取物理内存大小信息

让你彻底搞懂: 内核如何从实模式安全过渡到保护模式 + 分页 !


🗺️ 一、启动阶段内存布局全景图

内存区域划分(0x00000000 – 0xFFFFFFFF):

关键区域说明:

地址 用途 所有者
0x00000000 – 0x00000400 中断向量表 BIOS
0x00007C00 – 0x00007DFF 引导扇区(512B) Bootloader
0x00008000+ GRUB 传递的 Multiboot 信息 GRUB
0x0009FC00+ MP 表(多核 CPU 信息) BIOS
0x00100000+ 内核代码/数据 你的内核
1MB 以下 BIOS 保留区 BIOS

💡 内核绝不能使用 1MB 以下内存 !否则会覆盖 BIOS 数据,导致崩溃。


🚪 二、为什么内核要链接在 1MB 以上?

历史原因:

实模式限制

:20 位地址线 → 最大 1MB(0x00000 – 0xFFFFF)
保护模式需求

:32 位 CPU 需要更大地址空间
GRUB 约定

:Multiboot 规范要求内核加载到 1MB 以上

技术优势:

避免与 BIOS 冲突

:1MB 以下是 BIOS 的“领地”
简化内存管理

:1MB 以上可视为“干净”的物理内存
对齐分页

:1MB = 256 个 4KB 页,便于页表对齐

✅ 0x100000 (1MB)是 x86 内核的“安全起点” !


🔗 三、链接脚本:控制内核内存布局

内核的内存布局由 链接脚本(linker.ld) 决定:

关键点:

. = 0x100000

:设置加载地址(VMA)为 1MB
.multiboot

:必须位于内核前 8KB,GRUB 会扫描它
.bss

:不占 ELF 文件空间,但链接器分配内存并清零

⚠️ 若链接地址错误(如 0x0),内核会覆盖中断向量表,立即崩溃 !


🧱 四、临时页表:Identity Mapping

分页开启前,CPU 访问的是 物理地址 ; 开启后,访问的是 虚拟地址 ,需通过页表翻译。

但内核代码已链接在高地址(如 0xC0000000 ), 如何在开启分页 瞬间 不崩溃?

答案: 临时页表(Identity Mapping) !

什么是 Identity Mapping?

虚拟地址 = 物理地址

例如:虚拟
0x100000
→ 物理
0x100000
仅用于启动阶段

,后续会被高半内核映射替换

临时页表结构:

初始化页表:

🔑 页表项标志 0x3 = PRESENT(1) + RW(1<<1)


⚙️ 五、安全开启分页的完整流程

步骤 1:关闭中断

步骤 2:加载临时页表

步骤 3:开启分页

步骤 4:刷新流水线(关键!)

✅ 此流程确保:开启分页后,CPU 能正确翻译当前指令地址 !


📊 六、获取物理内存大小

GRUB 通过 Multiboot 信息结构 传递内存信息:

💡 mem_upper 是 Buddy 分配器初始化的关键输入 !


⚠️ 七、常见陷阱与避坑指南

  1. 页表未对齐

    页目录/页表必须
    4KB 对齐
    使用
    attribute((aligned(4096)))

  2. 开启分页后立即访问高地址

    临时页表仅映射低 4MB,访问
    0xC0000000
    会 Page Fault
    解决方案

    :先完成 Buddy 初始化,再建立高半内核映射

  3. 未刷新 TLB

    修改页表后需
    invlpg
    或重新加载 CR3
    启动阶段可忽略(TLB 为空)

  4. Multiboot 信息地址未验证

    检查
    magic == 0x2BADB002
    再使用
    mb_info


💬 写在最后

启动阶段的内存管理, 是操作系统安全的 第一道防线 。 临时页表虽简单, 却是后续所有内存操作的基石。

今天你构建的 Identity Mapping, 正是 Linux head_32.S 中 initial_page_table 的简化版。

🌟 好的开始,是成功的一半——尤其在内存管理中 。


📬 动手挑战 : 修改链接脚本,将内核链接到 0x200000 (2MB),并调整临时页表映射范围。 欢迎在评论区分享你的启动日志!

👇 下一篇你想看: Buddy 分配器深度解析 ,还是 Slab 分配器实现 ?


#操作系统 #内核开发 #内存管理 #分页 #启动 #x86 #从零开始

最后修改: 2026年7月1日

作者

评论

发表评论

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