ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

BIOS BDS阶段详解:启动项、设备路径与故障排查

BIOS BDS阶段详解:启动项、设备路径与故障排查 有人问我为什么同一块主板厂商固件里U盘一插就能认出来自己照着开源工程编出来的固件怎么插都不认为什么Setup里能看见U盘启动菜单里却死活没有为什么F2键按到手酸也进不去设置界面。这些问题八成都落在同一个地方——BIOS的BDS阶段。BDSBoot Device Selection这个名字听着像是挑个启动项这么点事实际上它是整个固件里最像操作系统的一段代码要建控制台、要连驱动、要枚举启动项、要处理热键、要验签、要交棒给OS Loader任何一环出岔子表现出来都只是没反应三个字。这篇就把BDS从头到尾拆开讲清楚包括它在整条启动链路里的位置、启动项变量的底层结构、设备路径的匹配规则、常见故障的排查路径以及平台移植时那些文档里不会写的坑。写过DXE驱动、做过平台移植、或者正被启动项丢失折磨的人应该都能从里面捞到点能直接用的东西。1. BIOS的BDS阶段在启动链路里的确切位置1.1 从SEC到DXE交棒之前固件已经攒下了什么要理解BDS得先知道它接手的时候手里有什么牌。上电复位之后CPU从复位向量开始跑这是SEC阶段通常只有几百KB的可用空间做的是Cache As RAM、建立临时栈、找到固件卷FV、然后跳到PEI。PEI阶段干的是最脏最累的活内存初始化、芯片组早期配置、把关键信息打包成HOBHand-Off Block交给下一棒。这两个阶段基本和用户能看见的东西无关出了问题就是黑屏加蜂鸣连串口都未必有输出。到了DXE阶段世界才开始变得像样。DxeCore从FV里把所有DXE驱动捞出来按照依赖表达式排队派遣PCI枚举、内存管理、变量服务、Runtime服务、BDS服务这些Arch Protocol陆续发布。等所有驱动派遣完、APRIORI表也走完了DxeCore会去检查BDS Arch Protocol有没有就位就位就调用它的Entry()控制权正式交给BdsDxe。这个交接点非常关键在BDS之前所有驱动是被发现的从BDS开始所有驱动是被连接的。发现和连接是两回事前者是DxeCore按FV里的顺序把驱动装进内存后者是用ConnectController把驱动和硬件真正绑在一起USB、网卡、存储控制器的功能都是在这一步才真正活过来的。1.2 BDS阶段的四项核心任务把BDS的工作拆开大概是四件事而且顺序不能乱。第一件是建立控制台让固件有地方输出、有地方收键盘输入包括ConIn/ConOut/ErrOut三个变量指向的设备路径、Console Splitter的虚拟句柄、串口重定向终端、图形控制台等等。第二件是连接驱动递归调用ConnectController把所有控制器连一遍让USB键盘、U盘、NVMe盘、网卡在BDS阶段完成枚举。第三件是启动项决策读取BootOrder、Boot####、BootNext这些变量结合平台自己的策略生成启动菜单处理超时、热键、菜单显示。第四件是加载和交棒用LoadImage和StartImage把选中的启动项镜像跑起来也就是把bootmgfw.efi或者grubx64.efi之类的东西拉进内存执行。第一件和第二件是基础第三件是策略第四件是执行。真正麻烦的是第三件因为它是纯粹的平台策略没有任何标准规定你必须把网络启动放最后、也没规定超时必须十五秒、更没规定F12是启动菜单还是F2是Setup。同一个BDS框架配上不同的PlatformBootManagerLib行为可以差得离谱。这也是为什么网上那些BIOS设置图解教程互相抄来抄去到了具体机型上还是有人照着按不出来——截图里的按键提示是平台自己画的不是UEFI规范规定的。1.3 为什么这个阶段最容易被误判成玄学BDS出问题的表现有个共同点不报错。驱动连不上不报错启动项被清理掉不报错热键没注册还是不报错。用户看到的就是菜单里少了一项按了没反应重启又进BIOS了。更麻烦的是BDS阶段默认几乎不打日志串口安静得像没上电一样于是很多人第一反应是怀疑硬件、怀疑电源、怀疑自己编错了DXE驱动白白浪费掉几天时间。我的经验是凡是设备在Setup里能看到、在启动菜单里看不到或者启动菜单里能看到、点进去没反应这类现象先把怀疑范围锁死在BDS然后按控制台→驱动连接→启动项变量→镜像加载这条链路顺序排查不要跳步。后面第5节会把这条排查链路完整展开。2. 控制台建立与驱动连接BDS前半段那些看不见的活2.1 ConIn/ConOut变量和Console Splitter到底在做什么很多人以为BDS第一件事是读启动项其实它先干的是搭控制台。UEFI里有三个全局变量ConIn、ConOut、ErrOut内容是一串设备路径比如PciRoot(0x0)/Pci(0x1,0x0)/USB(0x3,0x0)/USB(0x1,0x0)后面再接键盘或显示设备的节点。Console Splitter驱动会读这些变量把路径对应的设备句柄聚合成虚拟的Simple Text Input/Output这样上层调用者不用关心到底是USB键盘、PS/2键盘还是串口终端只管往ConIn上注册事件就行。这里有个特别容易踩的点ConIn和ConOut是变量是会被保存的。很多平台出厂时只塞了一个串口或者内置键盘用户插了USB键盘发现不能用本质上是ConIn里根本没有USB键盘那条设备路径而平台在BDS阶段又没有把新发现的键盘追加进去的逻辑。EDK II的UefiBootManagerLib里提供了连接默认控制台的接口平台需要在AfterConsole阶段调用它才会把新枚举出来的键盘、显示器补进控制台列表。我见过不止一次有人的固件在串口上一切正常插上显示器却什么都不显示就是因为ConOut里只有Terminal那条路径GraphicsConsole压根没被加进去。串口重定向本身也值得说一句。Terminal驱动配合SerialPortLib把1600×900像素的图形界面翻译成ANSI字符流字符宽度得按80×25算菜单布局全是平台自己画的字符画。如果你在串口上看到Setup界面排版错乱那通常不是BDS的问题而是UI画字符的坐标计算没考虑终端宽度。2.2 ConnectController的递归连接与USB枚举时序BDS里最容易被低估的是驱动连接。DXE阶段只是把驱动装进内存控制器和驱动的绑定要等BDS来做。UefiBootManagerLib提供了两个层次的接口一个是连接全部控制器内部循环调用ConnectController递归标志打开一个是按设备路径连接指定设备。这两个接口什么时候调、调几次直接决定USB设备什么时候能被认出来。注意这里的递归二字。打开递归之后ConnectController会把新产生的子句柄继续往下连USB控制器就是这样一层层连到Hub、再连到U盘的。顺序上大致是PCI控制器先连上→XHCI驱动挂上去→Root Hub出现→连Root Hub→Port出现→插着的设备被枚举→设备的Block I/O或文件系统协议出现→U盘才能真正出现在启动菜单里。整条链路少一环U盘就是不存在。我在实际项目里最常遇到的坑是XHCI驱动没进FV。做减法裁固件的时候为了省空间把USB驱动删了结果Setup界面里键盘还能用因为走的是PS/2或者内置ECU盘启动就彻底废了。还有一种情况是驱动进了FV但依赖不满足DxeCore在派遣阶段就把它跳过了日志里只有一行被忽略的提示不留心根本看不见。所以裁剪固件之后务必用Shell确认map -r能不能列出U盘的FS句柄那是对整条USB链路最直接的验证。2.3 热键检测的窗口期与按键时序怎么按都进不去Setup是BDS相关投诉里的头号问题。它的机制其实不复杂BDS在进Boot Manager之前会进入一个等待循环同时在ConIn的WaitForKey事件和一个定时器事件上等。定时器到了就按默认启动项继续走按键先到就处理热键。问题出在这个窗口的长度和起点上。起点问题热键注册是在控制台建好之后才做的如果你在USB键盘枚举完成之前就开始计时用户按的时候按键事件还没人接。超时又设得很短有些消费级机型为了追求开机速度设到两三秒等你看到屏幕亮起来再伸手窗口已经关了。这就是为什么很多人会觉得新版BIOS启动太快按不进去本质不是BIOS变快了是等待窗口被压缩了。终点问题等待循环里如果每次都重新查一遍设备连接状态会拖慢响应。有些平台实现得比较糙按键回调里做了耗时的设备枚举结果按一次要等一秒才响应用户以为自己没按上又按一次反而触发了别的功能。我自己的做法是平台固件里给热键留一个足够长的窗口比如五到八秒并且在平台层加一个检测到任意按键就把窗口重置一次的逻辑避免用户按到一半窗口过期。同时把等待循环里的事件处理写得足够轻只做置标志位真正的处理放到循环外。2.4 GOP与VBT屏幕花屏为什么不是BDS的锅有个现象值得单独提一下串口日志一切正常BDS的各种阶段都打出来了启动项也正确但屏幕从头到尾是黑的或者花的。这时候别急着怀疑BDS的逻辑先去查图形输出协议GOP驱动。GOP驱动要靠平台提供的显示配置块Intel平台上就是VBT来初始化显示控制器里面包含面板时序、背光参数、接口类型这些信息。VBT不对显示控制器初始化就是错的GOP句柄要么出不来要么出来了但分辨率、时序全是乱的。BDS会照常调GraphicsConsole输出只是输出到了一块没配好的屏幕上。所以碰到串口有、屏幕没有先确认GOP句柄存不存在Shell里可以用dh -p GraphicsOutput之类的命令看再去改VBT别在BDS里绕圈子。3. 启动项从哪来变量结构与设备路径匹配规则3.1 Boot####的结构一个被严重低估的变量BDS的启动项都存在变量里命名规则是Boot0000到BootFFFF另外还有BootOrder、BootNext、BootCurrent三个全局变量。每条Boot####的内容是一个EFI_LOAD_OPTION结构字段顺序是UINT32的Attributes、UINT16的FilePathListLength、以空字符结尾的Description字符串、设备路径列表、以及可选的OptionalData。这个结构非常直白直白到很多人以为它没什么可研究的实际上坑全在Attributes和设备路径这两块。Attributes里最低位是LOAD_OPTION_ACTIVE表示这条启动项是否有效第3位是LOAD_OPTION_HIDDEN置位之后这条启动项在菜单里就不显示了但依然存在、依然可以被BootNext指向。这就解释了那种启动项明明存在菜单里却看不到的现象——大概率是被标了HIDDEN。还有一个LOAD_OPTION_FORCE_RECONNECT位用于可移动介质提示BDS在加载这条启动项之前先重新连一次对应设备。另外Attributes的高位里还有category字段用来区分启动项和应用项部分平台会把Setup、Device Manager这类东西也做成一条Boot####用category区分不进正常启动列表。变量属性也是坑。Boot####、BootOrder、BootNext这些必须是NVBSRT三种属性齐全NV保证掉电不丢BS保证BDS阶段能读写RT保证OS起来之后还能改Windows的bcdedit和bcfg命令能改启动项靠的就是RT属性。如果平台移植时变量驱动写得不对刷完ROM之后启动项变成了只有RT属性一次重启就全丢了表现出来就是改完启动顺序重启又变回去了。3.2 短格式设备路径U盘换口还能启动的秘密设备路径是理解BDS行为的关键。一条完整的启动项设备路径写起来大概是这样的PciRoot(0x0)/Pci(0x14,0x0)/USB(0x5,0x0)/USB(0x1,0x0)/HD(1,GPT,3F2504E0-4F89-11D3-9A0C-0305E82C3301,0x800,0x100000)这里面既有物理位置哪个PCI、哪个USB端口也有分区信息分区类型、分区表类型、分区GUID、起止扇区。完整格式的好处是精确坏处是太精确——你把U盘从第二个口换到第三个口路径就匹配不上了启动项直接失效。这也是很多人换了个USB口就启动不了的真实原因。于是UEFI引入了短格式设备路径的概念只保留到控制器那一级的节点比如只写到PciRoot(0x0)/Pci(0x14,0x0)/USB(0x5,0x0)后面靠BDS在启动时按前缀去匹配当前枚举出来的所有子设备找到第一个能匹配上的再补全成完整路径。可移动介质的启动项通常就用这种形式创建所以U盘换个口、换台机器只要控制器层级还能对上BDS就能重新匹配出来。这里有个推论很有用启动项失效的原因八成就写在设备路径的哪一段失配了。硬盘换过之后分区GUID变了HD那段就失配主板走线改了导致PCI设备号变了Pci那段就失配。用Shell里的bcfg boot dump -v把完整路径打出来跟当前设备树对一下问题基本一眼就能看出来比在BDS代码里加一堆日志高效得多。3.3 启动项自动消失与自动增加的真相很多人抱怨启动项会自己消失或者插过的U盘启动项一直在。这两个现象的根源是同一个BDS在做启动项刷新。UefiBootManagerLib提供的刷新接口会把当前枚举到的所有设备和已有启动项做交叉比对规则大致是——当前存在且合理的启动项保留指向已不存在设备的启动项删除新发现的可启动设备补一条进去。这个逻辑本身是合理的问题在于它依赖枚举结果。如果刷新发生在设备还没连上之前枚举结果是空的已有启动项就会被全部判为无效而清掉。这就是开机进一次BIOS启动顺序就乱了的经典成因。正确的做法是在驱动连接完成之后、菜单生成之前做刷新而且在平台层要保证连接动作真的把存储设备都连上再往下走。另一个来源是默认变量。固件里通常会烧一份出厂默认变量数据一份打包好的二进制第一次开机或者恢复默认设置时灌入。如果这份默认数据里带着一堆Boot####那恢复默认设置之后启动项就会复活成出厂状态。做平台的时候默认变量数据最好只保留必要项别把开发机上的启动项一起打包进去。3.4 BootNext、Timeout与一次性语义的陷阱BootNext是个UINT16存的是下一
返回列表