2.2 内存映射与执行环境


2.2 内存映射与执行环境

本节摘要:内存映射并非一张静态图表,而是 CPU、芯片组、固件、设备四方在通电后持续数毫秒的空间主权谈判结果。本节讲解 UEFI 内存类型体系里最要紧的五类各归谁管,拆开实模式→保护模式→长模式的切换仪式,并说明运行时服务与内核同处 Ring 0 却各守各的"宪法"这一隔离设计。

核心问题

读完后请自测四点:能否用"动态宪章"的视角还原内存映射的生成过程;能否说出五类内存(启动服务区、运行时服务区、常规内存、ACPI NVS、不可用内存)各自的语义与限制;能否复述三级模式切换的步骤链及其不可逆性;能否讲清 ExitBootServices 失败与内存映射冲突之间的因果。

一、内存映射:一张不断修订的活宪章

教科书爱把内存映射画成一张静止的表:低 640KB 是常规内存,0xA0000 往上是显存,0xC0000 起是各 ROM,0xF0000 起是系统 BIOS。可这张表只是立宪前的草稿。真实情况是:CPU、芯片组、固件、设备控制器四方从通电一刻起,展开一场持续几毫秒的主权谈判与划界仪式。

谈判的核心矛盾在于:物理地址空间是一根有限的直线,而逻辑需求却分层异构、彼此打架。CPU 要放代码的区间,GPU 要帧缓冲的直通车道,NVMe 控制器要 DMA 用的大块连续内存,TPM 要隔离的受保护页,UEFI 运行时服务还得在 OS 接管之后仍可被调用。这些诉求无法按"谁先到谁得"裁决,只能交给一套分阶段、分权限、可验证的地址治理机制。

机制的地基是内存类型划分。它不是"RAM 与 ROM"的二元切分,而是按访问语义、缓存策略、一致性模型与安全域归属做的多维分类。UEFI 规范列了十二种内存类型,实践中最有分量的五类构成理解现代固件内存治理的棱镜:

启动服务代码/数据区。 UEFI 启动期的"经济特区",固件装载的驱动、协议实现、Shell 程序都在这里跑。要害在于:退出启动服务之后,操作系统有权把整块区域收归可用物理内存重新分配。这不是简单的释放,而是一次语义主权移交——固件主动交出对这段地址的解释权,把它降格为裸物理页。

运行时服务代码/数据区。 固件留给操作系统的"驻外使馆"。系统即便被 OS 全面接管,仍要经它读写变量、查询时间。这块内存背上两个苛刻条件:物理上不得被 OS 分页换出(必须钉在物理内存里);还得支持缓存一致性的 DMA 访问(S3 唤醒时固件要直接读写这些区域)。Intel 平台要求其映射符合 MTRR 的写合并或不可缓存类型,AMD 平台对应页属性表的相应设置。这份映射,是硬件、固件、OS 三方在内存描述符结构体里签下的跨生命周期契约。

常规内存区。 操作系统眼里的"无主荒地"。启动服务结束前,固件交给 OS 一份按 4KB 页粒度描述的可用内存清单。注意,这份清单绝不是物理内存的全貌——被占用、被保留、被设备 MMIO 占据、被 ACPI 表驻留、被 SMM 代码锁定的区间都已被固件剔除。它是一份经过政治过滤的资源白名单。

ACPI 非易失存储区。 ACPI 规范压给固件的"宪法附件"。系统进 S3 挂起时,电源管理器切断大部分供电,但这一区的内容必须原样保住,供唤醒后恢复上下文。固件必须保证它退出启动服务后仍被 OS 视作保留,且不能被任何 DMA 波及——否则一次网卡中断就可能抹掉唤醒向量。它的位置由 ACPI 固定硬件描述表的字段间接指明,串起一条从 ACPI 表到固件保留区再到 OS 保留区的跨栈信任链。

不可用内存区。 这不是报错,而是防御。部分 PCIe 配置空间、某些 SoC 内部寄存器窗口、有硬件缺陷的内存段,会被固件主动标记为不可用:不参与分配、不暴露给 OS。这是固件作为硬件抽象层行使的最终否决权——一次主动的空间封禁。

内存类型 主权归属 OS 接管后命运 关键约束
启动服务代码/数据 固件(临时) 整体回收为可用页 退出启动服务即失效
运行时服务代码/数据 固件(永久使馆) 必须保留映射 不可换出,S3 供电保持
常规内存 待分配白名单 纳入 OS 内存管理 已过滤保留与占用区
ACPI NVS 平台恢复区 识别为保留 不可被 DMA 覆盖
不可用内存 固件封禁 不可见 防御性剔除缺陷段

五类合在一起,织成一张持续演化的地址空间拓扑:POST 早期粗划,DXE 阶段各驱动依设备枚举结果细填,启动设备选择阶段由加载器裁边,退出启动服务那一刻完成法律意义上的主权交割。它不画在纸上,而是刻进 MTRR、页属性表、运行时服务数据结构与 ACPI 表的字节流——是硬件意志、固件策略与软件契约三方角力后凝固下来的时空结构。

二、执行模式切换:一场权力交接的完整仪式

内存映射决定"在哪跑",模式切换决定"以什么身份、什么权限、按什么规矩跑"。这不是一条 jmp 指令的事,而是牵动 CPU 架构、特权等级、地址转换与安全边界的系统级改朝换代。x86/x64 上,这场更替横跨三条线:保护模式与长模式的切换、特权环的语义重载、分页的逐级启用。UEFI 正是这场更替的总设计师兼首席公证。

第一次让渡:实模式到保护模式。 通电瞬间 x86 处于 16 位实模式:1MB 空间、没有保护、没有特权分级。固件的第一段代码只能在这里跑,而它的头等大事竟是亲手关掉自己的摇篮——置起控制寄存器的保护允许位,把 CPU 推进 32 位保护模式。切换看似一步,实则凶险:保护模式一生效,CPU 立刻不再解释 16 位段描述符,转而索要一张有效的 GDT;表没备好,通用保护异常,系统锁死。所以固件安全阶段的汇编代码必须在跳转前,用加载描述符表的指令精确装入一张预置的、含 32 位代码与数据描述符的表。这张表就是新政权的第一部宪法草案——写明代码段的基址、限长与特权级。

更巧的是固件对一项硬件特性的利用:保护模式下代码段寄存器的低两位(RPL)仍可改动,从而临时调低当前特权级。固件因此能在最高环里安全模拟低特权环境,给后续装载、未必可信的 Option ROM 提供沙箱。这种"特权级弹性",是固件超越纯硬件抽象、展露软件治理手腕的漂亮一笔。

第二次升维:保护模式到长模式。 驱动执行环境就绪后,系统要进 64 位长模式解锁 4GB 以上的寻址。四步缺一不可:打开物理地址扩展(为 36 位物理地址铺路);往扩展功能寄存器写入长模式开关;启用分页(此刻四级页表必须已经建好,否则页错误异常);用 64 位代码段描述符做一次远跳转,强制切换。四步是一条不可逆的单行道——长模式与分页同时生效后,CPU 拒绝再执行 16 位或 32 位兼容代码。固件在其中扮演"架构翻译官":把遗留 Option ROM 的 16 位实模式代码,经由 SMM 或 CSM 做二进制翻译与沙箱封装,让旧生态在新政权下获得特赦式生存。对历史包袱的包容治理,比一刀切的淘汰更能看出系统工程的成熟度。

⚠️ 常见坑:长模式切换翻车的三大经典原因——页表没有按四级结构对齐、PAE 没有先于长模式开启、远跳用的选择子指向了 32 位段。排查先看异常向量号:页错误多半是页表问题,通用保护异常多半是描述符问题。

Ring 0 的两副面孔。 长模式下,固件运行时服务与内核同坐最高特权环,执行上下文与内存视图却完全两样。运行时服务住在受限的、静态映射的空间里:代码段可执行,数据段严格只读写、禁止执行;页表项管理位设成监管级,意味着内核即便换掉页表,也绕不过固件设定的访问权限——这是硬件级的宪法隔离。内核则住在一套完全自建自管的、动态的、支持地址随机化与管理模式访问防护的页表体系里,能摸到全部常规内存,但对运行时服务数据的访问必须经由预注册函数指针、由硬件强制跳到固件保留的已签名入口。这不是普通函数调用,而是一次受控的、硬件背书的跨主权域调用。这层设计让运行时服务成为 OS 之下、硬件之上的可信执行子系统:TPM 要在 S3 唤醒时核对固件完整性、Secure Boot 要在运行期动态换平台密钥、ACPI 要在休眠期间维持计时精度——全靠这个在 Ring 0 里保持独立宪法的"影子政府"。

三、时空基座:两层信任的分层结构

至此可以点出本节最内核的命题:内存映射与执行环境合起来,是整个计算系统的时空基座。它规定信息在物理空间(地址)里的合法落点,也规定指令在时间序列(执行流)里的合法身份。没有这个基座,Secure Boot 只是白纸,SMM 只是一段无法调度的代码,ACPI 只是一堆解不开的二进制,OS 的虚拟内存更是无源之水。

基座的稳固来自三层嵌套的信任。硬件层:控制寄存器、MTRR、页属性表给出不可篡改的执行策略与内存类型语义,是信任的物理锚。固件层:UEFI 规范定死内存描述符的格式、退出启动服务的原子语义、运行时服务重映射的严格流程,把硬件能力翻译成可编程、可验证、可审计的软件契约。协议层:各协议接口不只是函数表,而是在特定内存映射与执行模式下、被授权触碰特定硬件资源的数字凭证——一个驱动只有在被调度器装进启动服务代码区并成功安装协议之后,才算领到与显卡、键盘、磁盘对话的"外交护照"。

💡 关键直觉:分析启动失败,表象在软件、病灶常在契约缝隙。日志里报退出启动服务失败,往往不是代码 bug,而是内存映射的深层冲突:也许某块 ACPI NVS 被 OS 误当常规内存释放重用;也许某 PCIe 设备的地址窗口与运行时服务数据区撞车;也可能虚拟地址重映射违反了 MTRR 对运行时代码的缓存类型要求。排障次序应是:先取固件内存图,再对照内核的映射转换逻辑,最后查设备地址窗口分配。

从 BIOS 钉死的 1MB,到 UEFI 的弹性空间;从实模式的混沌初开,到长模式的秩序井然;从 Option ROM 的野蛮生长,到驱动模型的协议治理——这一切演化的底层动力,都是对同一个问题的不懈求解:在有限的物理世界里,造出可预测、可验证的确定性秩序。

要点串联

  • 动态宪章:内存映射是四方谈判的产物,POST 粗划、DXE 细填、BDS 裁边,退出启动服务时完成交割。
  • 五类内存:启动服务区(临时特区)、运行时服务区(永久使馆)、常规内存(过滤后白名单)、ACPI NVS(恢复保护区)、不可用内存(防御性封禁)。
  • 切换链:实模式→保护模式(GDT 是宪法草案)→长模式(PAE→开关→分页→远跳),不可逆单行道。
  • 特权弹性:固件在 Ring 0 内模拟低特权沙箱收容遗留 Option ROM。
  • 宪法隔离:运行时服务与内核同环不同宪,跨域调用经硬件门控到已签名入口。
  • 排障心法:退出启动服务失败先查内存映射冲突,再查地址窗口重叠,最后查缓存类型约束。

下一节看固件在这套时空基座上开了哪些服务,Boot 与 Runtime 两个域又是怎么分权的。

四、把内存映射打印出来验证

上一节的地址表可以现场复现。Linux 把 EFI 启动时的完整内存映射留在 efi 内存表里,内核日志一行一条,直接对照:

$ sudo dmesg | grep efi.*mem | head -12 efi: mem000: type=7 phys=0x0000000000000000-0x000000000009FFFF ...Conventional efi: mem001: type=3 phys=0x0000000000100000-0x000000007A6BFFFF ...BootServicesCode efi: mem002: type=4 phys=0x000000007A6C0000-0x000000007A7EFFFF ...BootServicesData efi: mem003: type=7 phys=0x000000007A7F0000-0x000000009FBFFFFF ...Conventional efi: mem004: type=5 phys=0x0000000100000000-0x000000026FFFFFFF ...RuntimeCode/ACPI efi: mem005: type=10 phys=0x0000000270000000-0x000000026F5FFFFF ...MMIO efi: mem032: type=6 phys=0x00000000FEE00000-0x00000000FEE00FFF ...APIC efi: mem040: type=7 phys=0x0000001000000000-0x00000011DFFFFFFF ...Conventional (PCIEXBAR above) efi: 43 memory map entries

type=7(Conventional)的空闲区就是引导加载器分配内存的来源;type=5 的 Runtime 区小而关键——它们在 ExitBootServices 之后仍被页表保留,第 3 章讲运行时服务时还会回到这几个区间。

执行环境差异也能用一段最小代码量出来。同一个"读 RTC 时间"的需求,两种环境下写出的是两份截然不同的代码:

/* BIOS/实模式:INT 1AH,AH=02H 读 CMOS RTC,结果放 BCD 寄存器 */ /* NASM,运行于 0x7C00 的引导扇区 */ mov ah, 0x02 int 0x1A ; CF=0 成功;CH=时 CL=分 DH=秒(BCD 码) jc .fail ; 把 BCD 拆成二进制还得手工 al>>4*10 + al&0xF /* UEFI:ConOut->OutputString 直接拿时间没有 BCD,用 gRT->GetTime */ EFI_TIME t; gRT->GetTime(&t, NULL); /* 返回已是二进制,附带世纪与时区字段 */ AsciiPrint("now %04d-%02d-%02d\n", t.Year, t.Month, t.Day);

一边要自己拆 BCD、处理世纪翻转,一边拿到的是带时区的结构体——"执行环境即服务"不是口号,是两段并排的代码。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U