PEI阶段的“搬家“——UEFI启动流程中你的代码是怎么从临时内存搬到真内存的

发布时间:2026/9/27 23:39:11

PEI阶段的“搬家“——UEFI启动流程中你的代码是怎么从临时内存搬到真内存的
本文基于EDK2源码MdeModulePkg/Core/Pei逐行拆解不是翻译spec。先说结论UEFI启动流程中PEI阶段的核心矛盾只有一句话早期代码在临时RAM里跑但临时RAM只有几十KB迟早要搬到真正的DRAM里去。这个搬迁过程——从临时内存到永久内存的迁移——是PEI Core最精巧也最容易出bug的地方。❌ 搬错了 → 指针全乱 → 机器直接死✅ 搬对了 → 后续所有模块才能在真正的内存里舒展手脚一、为什么需要临时内存按下电源键之后CPU从SPI Flash里读出第一段代码SEC阶段然后跳进PEI Core。但这时候DRAM还没初始化——内存控制器还没配置DDR还没训练你连一根内存条都不能用。那代码在哪跑CPU内部有一小块SRAM叫Temporary RAM临时RAM。在Intel平台上这通常是CPU Cache-as-RAMCAR机制——把L2/L3 Cache当RAM用。属性数值临时RAM大小一般只有64KB~128KBStack区域给PEI Core和早期PEIM跑代码用Heap区域放HOB列表和早期内存分配SEC把这些信息打包成EFI_SEC_PEI_HAND_OFF结构体交给PEI Coretypedefstruct_EFI_SEC_PEI_HAND_OFF{UINT16 DataSize;// 结构体大小VOID*BootFirmwareVolumeBase;// BFV起始地址SPI Flash映射区UINTN BootFirmwareVolumeSize;// BFV大小VOID*TemporaryRamBase;// 临时RAM起始地址UINTN TemporaryRamSize;// 临时RAM总大小VOID*PeiTemporaryRamBase;// PEI Heap起始地址UINTN PeiTemporaryRamSize;// PEI Heap大小VOID*StackBase;// Stack起始地址UINTN StackSize;// Stack大小}EFI_SEC_PEI_HAND_OFF;类比临时RAM就像酒店临时寄存柜——你刚到酒店行李先塞进寄存柜房间还没准备好。柜子很小64KB但够你先洗个脸换身衣服。二、PEI Core第一次进场——只做最轻的事看PeiMain.c里的PeiCore()函数入口当OldCoreData NULL时第一次进PEI Core代码非常克制if(OldCoreDataNULL){// 第一次进PEI Core临时RAM还没搬家ZeroMem(PrivateData,sizeof(PEI_CORE_INSTANCE));PrivateData.SignaturePEI_CORE_HANDLE_SIGNATURE;CopyMem(PrivateData.ServiceTableShadow,gPs,sizeof(gPs));}它做了什么步骤操作含义①ZeroMem (PrivateData)把PEI Core的私有数据结构清零②CopyMem ServiceTableShadow把PEI Services Table拷贝到临时RAM③建HOB列表InitializeMemoryServices()把HobList指向临时RAM关键代码——HOB账本建在临时RAM上PrivateData.HobList.RawSecCoreData-PeiTemporaryRamBase;PeiCoreBuildHobHandoffInfoTable(BOOT_WITH_FULL_CONFIGURATION,(EFI_PHYSICAL_ADDRESS)(UINTN)SecCoreData-PeiTemporaryRamBase,(UINTN)SecCoreData-PeiTemporaryRamSize);HOB的整个账本——记录内存分布、资源分配、固件卷信息——全部写在临时RAM上。然后PEI Core开始dispatch PEIM逐个执行早期模块。这些模块也在临时RAM的Stack上跑HOB也在临时RAM的Heap上增长。⚠️一切都在那64KB的寄存柜里进行。三、谁触发搬家——PeiInstallPeiMemory()某个早期PEIM通常是Memory Init PEIM比如Intel FSP中的MrcInit完成DRAM训练后调用PeiInstallPeiMemory(PeiServices,MemoryBegin,MemoryLength);看MemoryServices.c里这个函数的实现EFI_STATUS EFIAPIPeiInstallPeiMemory(IN CONST EFI_PEI_SERVICES**PeiServices,IN EFI_PHYSICAL_ADDRESS MemoryBegin,IN UINT64 MemoryLength){PrivateDataPEI_CORE_INSTANCE_FROM_PS_THIS(PeiServices);// 只能调用一次调两次直接ASSERTif(PrivateData-PeiMemoryInstalled){ASSERT(FALSE);returnEFI_SUCCESS;}PrivateData-PhysicalMemoryBeginMemoryBegin;PrivateData-PhysicalMemoryLengthMemoryLength;PrivateData-FreePhysicalMemoryTopMemoryBeginMemoryLength;// 关键设这个flag告诉PEI Core该搬家了PrivateData-SwitchStackSignalTRUE;returnEFI_SUCCESS;}这个函数做的事情极其简单——只是记下永久内存的地址和大小然后设一个flag。它不搬任何东西不切换Stack不迁移HOB。类比你告诉前台房间准备好了前台在账本上记下房间号但不会立刻让你冲进去——因为你的行李还在寄存柜里没搬你现在手上还拿着寄存柜的钥匙。为什么不能立刻搬因为调用PeiInstallPeiMemory()的PEIM此刻还在临时RAM的Stack上跑。你不能在自己脚底下拆地板——立刻搬迁Stack当前函数的返回地址、局部变量全部失效直接crash。四、搬家的时机——Dispatcher回来之后PEI Dispatcher在每次dispatch完一个PEIM之后会检查SwitchStackSignal。如果flag被设了调用PeiCheckAndSwitchStack()——这才是真正搬家的地方。8个步骤按顺序执行Step 1 · 在永久内存里预留新StackNewStackSizeRShiftU64(Private-PhysicalMemoryLength,1);NewStackSizeALIGN_VALUE(NewStackSize,EFI_PAGE_SIZE);NewStackSizeMIN(PcdGet32(PcdPeiCoreMaxPeiStackSize),NewStackSize);新Stack大小 内存总大小的一半但不超过PcdPeiCoreMaxPeiStackSize限制从永久内存底部开始分配。Step 2 · 计算偏移量TopOfOldStack(UINTN)SecCoreData-StackBaseSecCoreData-StackSize;TopOfNewStackPrivate-PhysicalMemoryBeginNewStackSize;if(TopOfNewStackTopOfOldStack){StackOffsetPositiveTRUE;StackOffset(UINTN)(TopOfNewStack-TopOfOldStack);}else{StackOffsetPositiveFALSE;StackOffset(UINTN)(TopOfOldStack-TopOfNewStack);}新旧Stack顶的偏移量。偏移方向正/负很重要——临时RAM和永久RAM的地址谁高谁低取决于平台实现。Step 3 · Heap搬迁HeapTemporaryRamSize(UINTN)(Private-HobList.HandoffInformationTable-EfiFreeMemoryBottom-Private-HobList.HandoffInformationTable-EfiMemoryBottom);CopyMem((UINT8*)(UINTN)BaseOfNewHeap,PeiTemporaryRamBase,HeapTemporaryRamSize);直接把整个临时RAM的Heap内容包括HOB列表拷贝到永久内存。一个CopyMem把寄存柜里的账本和行李搬到新房间。Step 4 · Stack搬迁CopyMem((UINT8*)(UINTN)(TopOfNewStack-TemporaryStackSize),TemporaryStackBase,TemporaryStackSize);把旧Stack上的内容完整拷贝到新Stack。这包括了当前PEI Core函数的返回地址、局部变量、函数参数——全部。Step 5 · 迁移已分配的Memory PagesMigrateMemoryPages(Private,FALSE);如果早期PEIM在临时RAM阶段已通过AllocatePages分过内存页这些页也要搬Private-MemoryPages.Size(UINTN)(Private-HobList.HandoffInformationTable-EfiMemoryTop-Private-HobList.HandoffInformationTable-EfiFreeMemoryTop);NewMemPagesBasePrivate-FreePhysicalMemoryTop-Private-MemoryPages.Size;NewMemPagesBase~(UINT64)EFI_PAGE_MASK;CopyMem((VOID*)(UINTN)NewMemPagesBase,(VOID*)(UINTN)Private-MemoryPages.Base,Private-MemoryPages.Size);Step 6 · 修正所有指针——这是最危险的一步搬迁之后所有指向临时RAM的指针都失效了。PEI Core必须逐一修正修正HOB列表指针if(OldCoreData-HeapOffsetPositive){OldCoreData-HobList.RawOldCoreData-HeapOffset;}else{OldCoreData-HobList.Raw-OldCoreData-HeapOffset;}修正PPI数据库指针OldCoreData-PpiData.PpiList.PpiPtrs(PEI_PPI_LIST_POINTERS*)((UINT8*)OldCoreData-PpiData.PpiList.PpiPtrsHeapOffset);修正FV Handle指针、PeimState数组、FileHandles数组……一行一行修每个都不能漏。修正Memory Allocation HOB里的地址if((MemAllocHob-MemoryBaseAddressOldMemPagesBase)(MemAllocHob-MemoryBaseAddress(OldMemPagesBaseOldMemPagesSize))){if(PrivateData-MemoryPages.OffsetPositive){MemAllocHob-MemoryBaseAddressPrivateData-MemoryPages.Offset;}else{MemAllocHob-MemoryBaseAddress-PrivateData-MemoryPages.Offset;}}类比搬家之后纸笔记的所有东西在第3号寄存柜都得改成东西在301房第2个柜子。漏改一条你就找不到那件东西了。在固件里漏改一个指针机器就死了。Step 7 · Shadow PeiCore到永久内存OldCoreData-ShadowedPeiCoreShadowPeiCore(OldCoreData);把PEI Core自身从Flash拷到永久内存执行——XIP变成内存执行速度大幅提升。Step 8 · 切换Stack重入PeiCoreOldCoreData-ShadowedPeiCore(SecCoreData,PpiList,OldCoreData);// Should never reach here.ASSERT(FALSE);旧Stack上的PeiCore函数到此为止不再返回。新的PeiCore在永久内存的新Stack上重新开始执行。五、第二次进场——Permanent Memory上的新生活第二次进PeiCore()OldCoreData ! NULL且ShadowedPeiCore已经有值。走另一个分支CopyMem(NewSecCoreData,SecCoreDataPtr,sizeof(NewSecCoreData));CopyMem(PrivateData,OldCoreData,sizeof(PrivateData));然后安装最重要的PPI// 安装Memory Discovered PPI——告诉所有人真正的内存来了StatusPeiServicesInstallPpi(mMemoryDiscoveredPpi);ProcessDispatchNotifyList(PrivateData);gEfiPeiMemoryDiscoveredPpiGuid是PEI阶段最重要的PPI之一。所有注册了Notify的PEIM会被唤醒——比如需要大块内存的模块现在终于可以干活了。然后继续PeiDispatcherdispatch剩余的PEIM。这次它们在真正的DRAM上跑有充足的Stack和Heap不再受64KB限制。最终PEI Core找到gEfiDxeIplPpiGuid把HOB列表交给DXE IPLPEI阶段结束。六、实战踩坑点坑1临时RAM溢出临时RAM只有64KB左右。如果早期PEIM分配太多HOB或PoolPeiCreateHob会返回EFI_OUT_OF_RESOURCESFreeMemoryHandOffHob-EfiFreeMemoryTop-HandOffHob-EfiFreeMemoryBottom;if(FreeMemoryLength){DEBUG((DEBUG_ERROR,PeiCreateHob fail: Length - 0x%08x\n,(UINTN)Length));returnEFI_OUT_OF_RESOURCES;}经验临时RAM阶段的代码要极致精简。PeiAllocatePool有硬限制——最大不能超过0xFFF8 - sizeof(EFI_HOB_MEMORY_POOL)大约64KB不到。任何大数组、大数据结构都不要在这里分配。坑2Stack偏移方向搞错HeapOffsetPositive和StackOffsetPositive决定指针修正的方向。不同平台上临时RAM地址可能高于也可能低于永久RAM地址。如果平台代码把这两个值搞反了 → 所有指针修正都是反的 → 机器直接死 → 死得很深 → JTAG调试器都不一定能抓到。坑3PeiInstallPeiMemory只能调一次if(PrivateData-PeiMemoryInstalled){DEBUG((DEBUG_ERROR,ERROR: PeiInstallPeiMemory is called more than once!\n));ASSERT(FALSE);returnEFI_SUCCESS;}Debug版直接断死Release版忽略。如果有两个PEIM都试图注册永久内存——要么是平台配置错了要么是FSP和自定义Memory Init PEIM冲突了。坑4TemporaryRamSupportPPI vs 直接CopyMem搬迁Stack有两种路径条件搬迁方式注意事项平台提供gEfiTemporaryRamSupportPpiGuid调用TemporaryRamMigration()Intel平台一般提供因为CAR需要特殊Cache刷新平台不提供该PPIPEI Core自己CopyMem搬⚠️ 必须确保Cache一致性CAR模式下非常危险七、从源码学什么这段搬迁代码的价值不只是知道怎么搬。它展示了一个固件设计原则在资源受限的早期阶段所有操作都要可逆、可迁移。HOB列表就是一个可搬迁的账本——它不直接引用临时RAM的绝对地址而是通过偏移量修正来实现搬迁。PEI Core的私有数据结构也设计成了可以被整体拷贝和偏移修正的形态。这个原则在BIOS开发的很多地方都有体现场景类似机制PCI资源分配先临时分配再根据冲突调整ACPI表构建在临时缓冲区建好再拷到最终位置SMM通信缓冲区先在SMRAM外构建再迁入理解了PEI的搬迁机制后面遇到类似设计就能一眼看出本质。源码路径索引文件内容MdeModulePkg/Core/Pei/PeiMain/PeiMain.cPeiCore主入口搬迁总控MdeModulePkg/Core/Pei/Memory/MemoryServices.cInstallPeiMemory、AllocatePages、内存迁移MdeModulePkg/Core/Pei/Dispatcher/Dispatcher.cDispatcher和Stack切换逻辑MdeModulePkg/Core/Pei/Hob/Hob.cHOB操作创建、安装SEC HOBMdeModulePkg/Core/Pei/PeiMain.hPEI_CORE_INSTANCE结构体定义MdePkg/Include/Pi/PiPeiCis.hEFI_SEC_PEI_HAND_OFF定义

相关新闻

2:大模型深度对比 | 五大场景实测

2:大模型深度对比 | 五大场景实测

2026/8/22 22:55:37

2026年7月,榜单数字早已不再等于生产选择——真正决定胜负的,是在真实任务中的单任务成本与工作流适配度。 引言:当“跑分”不再等于“好用” 上一篇文章,我们梳理了2026年大模型的全景格局。但站在选型决策前,还有一…

空调集群等效储能建模与微电网经济调度MATLAB实现

空调集群等效储能建模与微电网经济调度MATLAB实现

2026/9/8 20:50:01

1. 项目背景与核心价值空调集群作为建筑能耗的主要组成部分,在微电网系统中具有显著的负荷调节潜力。传统调度方法往往将空调视为刚性负荷,忽略了其热储能特性。这项研究创新性地提出等效储能聚合模型,将分散的空调设备虚拟为集中式储能单元&…

NUMA-04 显存迁回内存:AMD/Intel 的 SVM 与Eviction的实现是否考虑了NUMA

NUMA-04 显存迁回内存:AMD/Intel 的 SVM 与Eviction的实现是否考虑了NUMA

2026/9/25 2:28:06

前三篇的迁移都在“普通内存 ↔ 普通内存”。但 GPU 显存 CPU 根本访问不到,它是怎么被纳入进程地址空间、又怎么在缺页时自动迁回内存,以及回迁时怎么选择numa node。这将是本文的主题。 0. 本章要回答的问题 GPU 显存这种 CPU 不能直接读的内存&#…

CANN/GE ACL数据集缓冲区添加函数

CANN/GE ACL数据集缓冲区添加函数

2026/9/26 19:14:12

aclmdlAddDatasetBuffer 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE 提供对 PyTorch、Te…

用ffmpeg高效批量调整图片尺寸的实战指南

用ffmpeg高效批量调整图片尺寸的实战指南

2026/9/27 1:30:29

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

Transformers 音频特征提取工具库 audio_utils 全解析:从 Mel 刻度换算到对数 Mel 频谱

Transformers 音频特征提取工具库 audio_utils 全解析:从 Mel 刻度换算到对数 Mel 频谱

2026/9/27 1:30:37

Transformers 音频特征提取工具库 audio_utils 全解析:从 Mel 刻度换算到对数 Mel 频谱 【免费下载链接】transformers 🤗 Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and mu…

RustFS 多节点集群重启与滚动升级实战:Readiness、Quorum 与 Degraded 模式完全指南

RustFS 多节点集群重启与滚动升级实战:Readiness、Quorum 与 Degraded 模式完全指南

2026/9/27 1:30:35

RustFS 多节点集群重启与滚动升级实战:Readiness、Quorum 与 Degraded 模式完全指南 【免费下载链接】rustfs 🚀2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system sup…

Java Integer缓存揭秘:128陷阱原理、避坑与面试全解

Java Integer缓存揭秘:128陷阱原理、避坑与面试全解

2026/9/27 1:30:34

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

RustFS Scanner 数据用量发布权威性决策:配额准入如何获得可用的权威依据

RustFS Scanner 数据用量发布权威性决策:配额准入如何获得可用的权威依据

2026/9/26 16:36:51

RustFS Scanner 数据用量发布权威性决策:配额准入如何获得可用的权威依据 【免费下载链接】rustfs 🚀2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system supporting mi…

远程协作的工作台整理

远程协作的工作台整理

2026/9/26 14:29:04

远程协作的工作台整理远程协作的核心不是再加一个工具,而是让交接信息足够完整。异步任务要写明目标、输入位置、完成标准和需要决策的人。 工作台的最小配置 将日程、待办、代码和沟通入口收拢到少数固定位置;通知按紧急程度分层。工作台不需要模仿办公…

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

2026/9/26 13:57:22

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

2026/9/26 23:35:16

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…