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定义

相关新闻

【全栈实战】AI + Python + CMIP6:气候变化数据分析与降尺度技术完整指南

【全栈实战】AI + Python + CMIP6:气候变化数据分析与降尺度技术完整指南

气候变化已成为全球性挑战,对农业、生态系统、水资源、人类健康和社会经济系统产生深远影响。科学研究表明,自工业革命以来,人类活动导致的温室气体排放与全球气温上升、极端天气事件增加、冰川融化和海平面上升等现象密切相关。为科学理解和…

2026/10/25 23:58:51 阅读更多 →
基于Scrapy的拉勾网招聘数据爬取与Python数据分析实战

基于Scrapy的拉勾网招聘数据爬取与Python数据分析实战

1. 项目缘起:从招聘数据中洞察市场脉搏最近在帮一个做技术猎头的朋友分析市场趋势,他经常问我:“现在哪个技术栈最火?”“Python后端和Java后端,哪个岗位需求更大,薪资更高?” 这类问题&#xf…

2026/10/25 19:22:11 阅读更多 →
Taste Skill:88KB 提示词如何让 AI 写的 UI 不再像流水线罐头

Taste Skill:88KB 提示词如何让 AI 写的 UI 不再像流水线罐头

Taste Skill:88KB 提示词如何让 AI 写的 UI 不再像流水线罐头 让 AI 写个 Landing Page,出来永远是深色背景加紫色渐变,三个等宽 Feature Card 整齐排列,Inter 字体配 slate-900 文字颜色,再点缀一层 glassmorphism 玻…

2026/10/21 5:51:22 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/26 12:00:21 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/25 22:36:10 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/26 4:52:12 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/26 12:00:21 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/26 5:00:37 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/26 3:16:46 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/26 12:00:19 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/25 13:21:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/26 8:34:37 阅读更多 →