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

发布时间:2026/9/22 12:54:33

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定义
延伸阅读

更多相关文章

2026/9/19 18:46:51

51单片机动态数码管显示学号:从I/O口、定时器到驱动电路全解析

1. 项目概述与核心价值最近在整理以前的学习笔记,翻到了一个特别经典的51单片机入门项目——用动态数码管显示自己的学号。这个项目看似简单,却是一个绝佳的“麻雀虽小,五脏俱全”的练手案例。它几乎串联了51单片机开发中所有最基础、最核心的…

2026/9/20 6:22:37

Python装饰器底层原理与5个实战用法

在Python编程中,装饰器是一个让许多初学者感到困惑,却又让资深开发者爱不释手的特性。简单来说,装饰器就是一个用于修改或增强函数行为的函数。如果把函数比作一部手机,那么装饰器就是给手机贴的防窥膜或保护壳,它在不…

2026/9/22 12:50:46

5个坑解决配置痛点,快用下载实战避坑指南

5个坑解决配置痛点,快用下载实战避坑指南 配置环境就卡半天,是不是你也经历过?明明照着教程一步步敲,结果依赖版本冲突、路径报错,半天没跑起来。更扎心的是,面试必问的工程化落地能力,往往就卡在这一步。今天不聊虚的,直接拆解一个用…

2026/9/22 12:50:46

3分钟搞懂破帽遮颜过闹市与手写实现避坑

3分钟搞懂破帽遮颜过闹市与手写实现避坑 面对满屏红色的报错堆栈,你盯着那个诡异的 Exception in thread "main" 发呆吗?别慌,这种“破帽遮颜过闹市”般的尴尬时刻,每个写代码的人都经历过。…

2026/9/22 12:50:46

国外旅游景点推荐系统慢?3个最佳实践解决性能瓶颈

国外旅游景点推荐系统慢?3个最佳实践解决性能瓶颈 复制来的国外旅游景点推荐算法代码,跑在测试环境飞快,一到生产环境直接卡死,日志里全是超时错误。这时候盲目加缓存或换服务器往往没用,因为问题出在数据聚合与排序逻辑的底层实现上。…

2026/9/22 10:02:42

GAMP 5 基于风险的计算机化系统验证:软件分类与审计追踪实践

简介:《A Risk-Based Approach to Compliant GxP Computerized Systems》即业内熟知的GAMP 5指南,面向制药企业质量与IT合规人员、验证工程师及计算机化系统管理者,用于解决GxP法规环境下系统合规性难以科学落地的问题。文档以风险管理为主线…

2026/9/22 9:07:39

安全托管MSSP实战:从静态防御到人机协同的攻防运营与应急响应

简介:这份PPT围绕互联网业务安全托管服务展开,面向企业安全负责人、IT运维人员及关注MSSP/MSS选型的读者,重点回应传统安全过度依赖人工、碎片化静态防御难以对抗产业化攻击等痛点。资源共1个pptx文件,包体约30.63MB,以…

2026/9/22 0:04:49

输电线路在线监测高频面试题拆解 3秒抓住官方文档重点

输电线路在线监测高频面试题拆解 3秒抓住官方文档重点 官方文档几百页翻到头还是懵?面试问到 输电线路在线监测 的数据链路时,脑子一片空白?别慌,这种 高频面试题 我整理了10年,专门治各种“文档太长抓不住重点”的毛病。…

2026/9/22 0:04:49

中介房源管理系统重构避坑:3个关键步骤搞定API变更

中介房源管理系统重构避坑:3个关键步骤搞定API变更 版本升级后 API 全变了,这种痛只有真做过的人懂。 很多团队在接手老旧房产项目时,最崩溃的不是代码烂,而是底层框架升级后,原本熟悉的接口调用方式彻底失效。 这份 保姆级教程…

2026/9/22 0:04:49

3个坑点带你一文搞懂55gg小游戏源码

3个坑点带你一文搞懂55gg小游戏源码 盯着控制台满屏的红色报错,看着那一长串 StackTrace ,是不是脑子瞬间宕机?别急,这种时候最忌讳的就是盲目改代码。很多刚入行的前端同学,面对 55gg 小游戏这类轻量级 H5…

2026/9/20 4:54:47

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/21 18:32:12

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/21 10:29:02

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码