ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

coreboot+SeaBIOS启动慢?打开CONFIG_ATA_DMA让老主板提速

coreboot+SeaBIOS启动慢?打开CONFIG_ATA_DMA让老主板提速 1. 老主板上的corebootSeaBIOS为什么会“卡在启动早期”1.1 一台“古董平台”的特殊处境先说清楚我在折腾什么。手头有一台很久以前的老机器南桥是ICH7时代的产物主板本身完全不支持AHCISATA控制器只能跑在IDE兼容模式下。为了榨干这台机器的剩余价值我给它刷了corebootpayload用的是SeaBIOS引导的是GNU/Linux。刷完coreboot之后系统能正常起来但有一个让人非常难受的现象从按下电源键到GRUB菜单出现时间还算正常真正让人抓狂的是选定内核之后从内核开始加载到initramfs解压完成、systemd真正接管系统那段过程的耗时长得离谱。整台机器在启动早期给人一种“磁盘在拼命读但数据就是吐不出来”的感觉磁盘指示灯亮得很勤但速度肉眼可见地慢。我一开始以为是发行版的问题换过轻量发行版、换过不同的initramfs生成器问题依旧。后来在启动过程中仔细掐表发现从GRUB加载内核到initramfs完毕用时居然能到一分多钟。这绝对不是正常水平哪怕是一块老机械硬盘也不该慢到这种程度。1.2 问题现象不是整体慢是“卡”在某个特定阶段这里要特别强调一个细节这台机器用corebootSeaBIOS启动的时候系统整体慢的区域非常集中。具体来说UEFI/传统BIOS自检阶段coreboot跑得飞快几乎一闪而过SeaBIOS初始化硬件并准备引导设备也很快GRUB加载内核速度正常内核开始执行早期代码通过BIOS接口读取initramfs这里开始急剧变慢systemd接管、udev扫描设备之后一切又恢复正常速度这个“一段快、一段慢、然后再正常”的模式非常关键。它说明CPU、内存、主板初始化都没有大问题瓶颈就出在内核早期的磁盘访问路径上。而这条路径在corebootSeaBIOS环境下恰恰有文章可做。2. 排查启动慢的完整链路从initramfs到DMA缺失2.1 先排除几个“看起来很像”的常见慢因在把矛头指向DMA之前我花了不少时间排除其他可能。这里列出我排查过的几个方向给同样遇到启动慢问题的人一个参考USB设备枚举缓慢。有些老主板在coreboot下USB初始化有问题会导致启动时反复枚举设备每多一个USB设备就多等几秒。我拔掉所有不必要的外设只保留键盘问题没有改善。ACPI表配置异常。某些coreboot版本生成的ACPI表不完善Linux内核启动时会等待较长超时。我在内核命令行加了acpioff测试启动时间没有明显变化说明ACPI不是主要瓶颈。GRUB等待超时。检查过GRUB配置超时时间设的是0不会造成额外等待。显卡初始化。老主板的VGA BIOS加载有时会卡很久但我的启动日志里显卡初始化占用时间很少。这些常规方向排除之后我才把注意力放回磁盘本身。2.2 dmesg里的关键线索ata2.00 一直提示 PIO真正让我意识到问题出在传输模式上是看dmesg启动日志的时候发现的。那几行日志如果有心人看过其实已经非常直白了ata2.00: ATA-7: WDC WD3200AAJS-00L7A0, 01.03E01, max UDMA/133 ata2.00: configured for UDMA/133看到configured for UDMA/133的时候我一度以为这台老机械硬盘已经是跑在DMA模式下了。但仔细想想不对这是Linux内核的libata驱动接管之后的配置它覆盖了BIOS阶段的工作模式。真正让我起疑的是另一个现象。我在内核命令行临时加了init/bin/sh想看看早期根文件系统挂载前的状态结果发现从内核开始执行到/bin/sh跑起来中间还是花了非常长的时间。这个阶段的内核代码还在使用BIOS提供的磁盘访问服务Int 13h尚未切换到自己的ata驱动也就是说这段时间的磁盘读写效率完全取决于SeaBIOS的实现水平。2.3 定量的启动时间拆分看看时间到底花在哪为了不靠感觉折腾我用systemd-analyze把启动时间拆了一遍firmwarecorebootSeaBIOS3.2秒loaderGRUB1.8秒kernel78.6秒userspacesystemd初始化6.1秒kernel阶段78.6秒这个数字太扎眼了。正常机器上kernel阶段一般也就5到15秒多出来的60多秒几乎都是在读取和解压initramfs。而且仔细看systemd-analyze的输出initrd部分占掉了大头等initrd结束之后根文件系统的挂载和udev设备扫描反而非常快。也就是说这台机器的时间黑洞就在“内核早期读取initramfs”这一步。而这个阶段磁盘的每次读取都在走最原始、最慢的程序化I/O路径。3. 根源分析AHCI缺失只是起点真正的问题是固件传输路径上的DMA被掐断3.1 IDE兼容模式与AHCI的差别软件接口决定上层能力不支持AHCI的主板SATA控制器运行在IDE兼容模式下这不是什么罕见事。AHCIAdvanced Host Controller Interface高级主机控制器接口本质上定义了一套标准化的寄存器接口和行为规范它最核心的价值之一就是原生支持NCQ、热插拔和高效的DMA传输。但IDE兼容模式本身并不等于没有DMA——IDE模式同样支持总线主控DMABus Mastering DMA否则早年那些跑在PXE、DOS、传统BIOS环境下的机器读盘速度会慢到没法用。问题在于IDE模式的DMA能力不像AHCI那样是“控制器自带、驱动发现即可用”的它需要上下两级配合固件/驱动需要主动初始化IDE控制器的总线主控DMA相关寄存器上层代码需要明确选择使用DMA命令而非PIO命令来传输数据只要这两级有一级偷懒整个链路就会退回到PIO模式。SeaBIOS作为BIOS阶段的固件它的ATA驱动代码直接决定了“内核早期通过BIOS接口读盘时到底是走DMA还是走PIO”。3.2 BIOS阶段的磁盘读写SeaBIOS不启用DMA内核也只能跟着遭殃这里有个很多人容易忽略的细节Linux内核在启动的极早期尤其是解压initramfs之前并不能使用自己完整的驱动程序栈。它依赖固件提供的Int 13h例程来完成磁盘访问这个阶段被称为“早期引导环境”。等到内核把initramfs加载进内存解压完成挂载好临时的根文件系统才会切换到内核自己的libata/ata_piix驱动。也就是说initramfs这个文件本身有多大BIOS阶段就要通过Int 13h接口读多少数据。如果SeaBIOS的ATA驱动在传输时选择的是PIO模式那每次读一个扇区都要CPU反复轮询状态寄存器等待I/O完成效率低下到令人发指。特别是现在发行版的initramfs动辄几十MB里面塞满了各种硬件驱动模块、固件文件、加密工具体量远超十年前的标准PIO模式读这些数据就成了一场灾难。CONFIG_ATA_DMA这个选项的作用就是在SeaBIOS编译时启用ATA控制器的DMA传输支持。打开它之后SeaBIOS初始化ATA设备时会把控制器切换到总线主控DMA模式后续Int 13h读盘操作会使用DMA而不是PIO每一步数据传输都不再需要CPU逐字节地搬运数据。量变引起质变这个差异对启动耗时的影响是数量级的。3.3 为什么旧版本SeaBIOS默认不启用CONFIG_ATA_DMA看到这里肯定有人要问这么明显的性能差异为什么CONFIG_ATA_DMA不是默认开启这个问题我研究过挺有意思。SeaBIOS在默认配置里其实有很多为了“兼容最大化”而做的保守选择。CONFIG_ATA_DMA本质上默认是开启的但如果你用的coreboot版本较老、或者习惯了用coreboot提供的SeaBIOS配置模板某些预编译配置或者精简配置可能把它关掉了。另一个更隐蔽的情况是有些自定义构建为了省ROM空间会把一些“看起来并非必需”的选项关掉——毕竟BIOS只要能把系统引导起来就算完成任务没人会想到BIOS阶段的磁盘性能会影响到整个系统启动速度。还有一个兼容性层面的考量IDE模式的DMA在某些极度老旧的芯片组上存在兼容性问题极少数情况下会引发数据错乱或者传输中断。为了稳妥起见部分发行版或精简固件在构建SeaBIOS时干脆禁用了DMA支持。但对于绝大多数支持总线主控DMA的IDE控制器来说这个选项带来的收益远大于风险。4. 解决办法手动打开SeaBIOS的CONFIG_ATA_DMA并重建payload4.1 在coreboot集成SeaBIOS时改配置defconfig或menuconfig两种路线搞清楚原因之后剩下的就是动手解决。这里有两种做法取决于你的coreboot是用来独立编译SeaBIOS还是把它作为内嵌payload整体构建。先说第一种也是最简单的方式如果你已经用了coreboot的菜单配置也就是运行make menuconfig的那个界面找到Payload配置相关选项。在coreboot较新版本里SeaBIOS作为payload集成时会提供一个指向额外配置文件的入口比较常见的做法是在coreboot的配置里通过CONFIG_SEABIOS_EXTRA_CONFIG或者旧版中的CONFIG_PAYLOAD_SEABIOS_EXTRA_CONFIG指向一个额外的配置片段。你可以在这个配置片段里写一行CONFIG_ATA_DMAy具体来说你在coreboot源码根目录编译的时候可以先make menuconfig进入Payload那一栏选择“SeaBIOS”作为payload。然后找到类似“SeaBIOS extra config”或者“Additional SeaBIOS configuration”的选项填上配置文件的路径。那个文件的内容不必很复杂加上对应的选项即可。第二种做法是完全独立编译SeaBIOS生成自己想要的payload文件再交给coreboot使用。这种方式控制力最强也最容易排查问题。操作步骤大致如下git clone https://review.coreboot.org/seabios.git cd seabios make menuconfig make在make menuconfig中进入“ATA support”相关菜单确保CONFIG_ATA_DMA处于开启状态。编译完成后会生成out/bios.bin.bin如果你需要的是ELF格式payloadcoreboot通常使用这个可以用make命令生成后看到out/bios.bin.elf。把这个文件交给coreboot在coreboot配置里指定外部payload路径。4.2 完整编译与刷写流程示意如果你用的是coreboot直接内嵌SeaBIOS整体流程整理如下准备好coreboot源码进入make menuconfig在“Payload”中启用SeaBIOS保留默认版本或指定你常用的SeaBIOS版本通过config片段强制打开CONFIG_ATA_DMAy重新编译corebootmake生成新的build/coreboot.rom用flashrom或其他工具刷入主板如果你的主板不支持软件刷写还得外接编程器这一步因板而异。我这边刷写过程一共花了不到一分钟启动后的变化却是立竿见影的。需要特别提醒的一点是CONFIG_ATA_DMA只是SeaBIOS层面的配置开关。你在coreboot的配置界面里是找不到这个名字的必须在给SeaBIOS的额外配置文件中写入或者在实际编译SeaBIOS的时候通过它自身的Kconfig系统打开。很多人在这步卡住在coreboot的menuconfig里搜ATA_DMA搜不到就放弃了这是正常的因为它本来就是SeaBIOS自己的配置项。4.3 验证是否生效看dmesg中的传输模式变化刷写完新固件之后怎么确认DMA真的生效了最简单的方法还是看dmesg。重启进入系统执行dmesg | grep -i ata.*dma\|ata.*pio\|ata.*UDMA如果SeaBIOS阶段成功启用了DMA你在dmesg中可能看不到BIOS阶段的直接记录因为那个阶段发生在内核接管之前但可以对比启动速度和早期的ata初始化日志。内核的ata驱动会报告它检测到的最大传输模式和当前使用的模式如configured for UDMA/133。更重要的是整个kernel阶段的启动时间会大幅缩短。另外如果你用的内核开启了dynamic_debug可以查看更多底层细节。不过我个人的经验是最直接的验证方式就是重新开启那个大体积initramfs发行版的启动计时前后对比一下差距有多大数据比任何日志都更有说服力。5. 开启DMA之后实测提升和其他补充优化5.1 前后对比从78秒到17秒改完配置、刷完BIOS之后我又用systemd-analyze做了一次完整的启动时间记录阶段修改前秒修改后秒firmwarecorebootSeaBIOS3.23.1loaderGRUB1.81.7kernel含initramfs读取78.617.4userspacesystemd初始化6.15.9总计89.728.1kernel阶段从78.6秒降到17.4秒整个系统的启动总时间从接近一分半钟缩短到半分钟以内。这个提升幅度完全达到了“像换了一台机器”的程度而且整体系统进入桌面之后的流畅度也有微妙改善因为启动初期的I/O压力不再拖累后续进程加载了。顺带一提我用的是机械硬盘如果换成SSD或者更高转速的硬盘这个差距可能还会更大。因为SSD在PIO模式下的性能瓶颈更明显DMA模式才能发挥出设备的真实速度。5.2 其他可以配合的启动提速项CONFIG_ATA_DMA解决了最核心的问题但如果你还在折腾老平台的启动速度下面这几个优化项也可以一并考虑缩小initramfs体积。即使开启了DMA早期BIOS阶段的PIO读取依然存在只是时间缩短了。initramfs的体积越小这个阶段的耗时越短。可以用mkinitcpioArch系或者update-initramfsDebian系时只保留必要模块或者改用dracut --hostonly生成仅包含当前硬件驱动的最小initramfs。开启GRUB的truecrypt和native图形模式。GRUB自身读取内核文件也会经过固件接口让GRUB使用自己原生的磁盘驱动insmod ata、insmod ahci可以直接绕过BIOS接口读取速度更快。在GRUB的/boot/grub/grub.cfg中的load_video附近加一段insmod ata和insmod ahci可以显著缩短GRUB加载内核的时间。确认内核命令行没有多余的等待参数。比如rootdelay、rootwait这些参数会根据场景增加额外等待时间如果确实需要保留至少确认它们没有被不必要地放大。考虑终端输出的速度影响。老主板在串口控制台或慢速帧缓冲设备上输出大量内核日志也可能放大启动耗时。如果不需要详细日志可以在内核命令行加上quiet loglevel3减少启动阶段的终端输出压力。5.3 给同样折腾老硬件的人留个备忘整个排查和解决过程走下来我给同样喜欢折腾老平台、玩coreboot和SeaBIOS的人留几个备忘点都是实打实踩过的坑第一不要假设“BIOS阶段慢一点无所谓”。在传统BIOS启动流程里BIOS阶段的磁盘读取能力直接影响内核早期初始化速度。现在的Linux发行版为了兼容所有硬件initramfs越做越大BIOS阶段慢吞吞地读几十MB数据放大了这个问题。尤其是那颗老态龙钟的机械硬盘还在坚持服役的场景下这个差距更加显著。第二CONFIG_ATA_DMA不是万能的开关但值得先试。它解决的是“BIOS阶段的磁盘传输模式”问题。如果你的启动慢点不在这个阶段而是集中在systemd初始化、网络等待、服务超时等环节开启它不会有任何效果需要另找原因。但“先试这个”的成本极低一次重新编译刷写就能确认收益却可能非常大。第三一定要保留一个好的启动时间度量工具。我强烈建议在折腾之前先用systemd-analyze记录基线数据修改之后再记录一次做对比。没有量化数据全凭“感觉变快了”很容易误判也容易在后续改动中引入新问题而不自知。第四老硬件上兼容性永远要放在性能前面。万一开了DMA之后出现偶尔的读盘错误、启动不稳定、文件系统校验失败不用急着排除DMA模式先确认是不是电源供电不稳、SATA线老化、硬盘本身有坏道。DMA只是把数据通路打开了并不能解决硬件本身的老化问题。如果确实出现DMA相关问题也可以考虑退回PIO模式不过以我的经验绝大多数老ICH芯片组在DMA模式下都非常稳定。折腾完这台老机器最大的感慨是有些问题时隔多年依然会咬人但底层原理搞清楚之后解决起来就是改一个配置、重新编译一次固件的事。如果你也正被类似的不支持AHCI的老主板加coreboot组合折磨着不妨花二十分钟试试这个方案多半不会让你失望。
返回列表