
1. 老主板上的corebootSeaBIOS為什么會“卡在啟動早期”1.1 一臺“古董平臺”的特殊處境先說清楚我在折騰什么。手頭有一臺很久以前的老機器南橋是ICH7時代的產(chǎn)物主板本身完全不支持AHCISATA控制器只能跑在IDE兼容模式下。為了榨干這臺機器的剩余價值我給它刷了corebootpayload用的是SeaBIOS引導(dǎo)的是GNU/Linux。刷完coreboot之后系統(tǒng)能正常起來但有一個讓人非常難受的現(xiàn)象從按下電源鍵到GRUB菜單出現(xiàn)時間還算正常真正讓人抓狂的是選定內(nèi)核之后從內(nèi)核開始加載到initramfs解壓完成、systemd真正接管系統(tǒng)那段過程的耗時長得離譜。整臺機器在啟動早期給人一種“磁盤在拼命讀但數(shù)據(jù)就是吐不出來”的感覺磁盤指示燈亮得很勤但速度肉眼可見地慢。我一開始以為是發(fā)行版的問題換過輕量發(fā)行版、換過不同的initramfs生成器問題依舊。后來在啟動過程中仔細(xì)掐表發(fā)現(xiàn)從GRUB加載內(nèi)核到initramfs完畢用時居然能到一分多鐘。這絕對不是正常水平哪怕是一塊老機械硬盤也不該慢到這種程度。1.2 問題現(xiàn)象不是整體慢是“卡”在某個特定階段這里要特別強調(diào)一個細(xì)節(jié)這臺機器用corebootSeaBIOS啟動的時候系統(tǒng)整體慢的區(qū)域非常集中。具體來說UEFI/傳統(tǒng)BIOS自檢階段coreboot跑得飛快幾乎一閃而過SeaBIOS初始化硬件并準(zhǔn)備引導(dǎo)設(shè)備也很快GRUB加載內(nèi)核速度正常內(nèi)核開始執(zhí)行早期代碼通過BIOS接口讀取initramfs這里開始急劇變慢systemd接管、udev掃描設(shè)備之后一切又恢復(fù)正常速度這個“一段快、一段慢、然后再正?!钡哪J椒浅jP(guān)鍵。它說明CPU、內(nèi)存、主板初始化都沒有大問題瓶頸就出在內(nèi)核早期的磁盤訪問路徑上。而這條路徑在corebootSeaBIOS環(huán)境下恰恰有文章可做。2. 排查啟動慢的完整鏈路從initramfs到DMA缺失2.1 先排除幾個“看起來很像”的常見慢因在把矛頭指向DMA之前我花了不少時間排除其他可能。這里列出我排查過的幾個方向給同樣遇到啟動慢問題的人一個參考USB設(shè)備枚舉緩慢。有些老主板在coreboot下USB初始化有問題會導(dǎo)致啟動時反復(fù)枚舉設(shè)備每多一個USB設(shè)備就多等幾秒。我拔掉所有不必要的外設(shè)只保留鍵盤問題沒有改善。ACPI表配置異常。某些coreboot版本生成的ACPI表不完善Linux內(nèi)核啟動時會等待較長超時。我在內(nèi)核命令行加了acpioff測試啟動時間沒有明顯變化說明ACPI不是主要瓶頸。GRUB等待超時。檢查過GRUB配置超時時間設(shè)的是0不會造成額外等待。顯卡初始化。老主板的VGA BIOS加載有時會卡很久但我的啟動日志里顯卡初始化占用時間很少。這些常規(guī)方向排除之后我才把注意力放回磁盤本身。2.2 dmesg里的關(guān)鍵線索ata2.00 一直提示 PIO真正讓我意識到問題出在傳輸模式上是看dmesg啟動日志的時候發(fā)現(xiàn)的。那幾行日志如果有心人看過其實已經(jīng)非常直白了ata2.00: ATA-7: WDC WD3200AAJS-00L7A0, 01.03E01, max UDMA/133 ata2.00: configured for UDMA/133看到configured for UDMA/133的時候我一度以為這臺老機械硬盤已經(jīng)是跑在DMA模式下了。但仔細(xì)想想不對這是Linux內(nèi)核的libata驅(qū)動接管之后的配置它覆蓋了BIOS階段的工作模式。真正讓我起疑的是另一個現(xiàn)象。我在內(nèi)核命令行臨時加了init/bin/sh想看看早期根文件系統(tǒng)掛載前的狀態(tài)結(jié)果發(fā)現(xiàn)從內(nèi)核開始執(zhí)行到/bin/sh跑起來中間還是花了非常長的時間。這個階段的內(nèi)核代碼還在使用BIOS提供的磁盤訪問服務(wù)Int 13h尚未切換到自己的ata驅(qū)動也就是說這段時間的磁盤讀寫效率完全取決于SeaBIOS的實現(xiàn)水平。2.3 定量的啟動時間拆分看看時間到底花在哪為了不靠感覺折騰我用systemd-analyze把啟動時間拆了一遍firmwarecorebootSeaBIOS3.2秒loaderGRUB1.8秒kernel78.6秒userspacesystemd初始化6.1秒kernel階段78.6秒這個數(shù)字太扎眼了。正常機器上kernel階段一般也就5到15秒多出來的60多秒幾乎都是在讀取和解壓initramfs。而且仔細(xì)看systemd-analyze的輸出initrd部分占掉了大頭等initrd結(jié)束之后根文件系統(tǒng)的掛載和udev設(shè)備掃描反而非???。也就是說這臺機器的時間黑洞就在“內(nèi)核早期讀取initramfs”這一步。而這個階段磁盤的每次讀取都在走最原始、最慢的程序化I/O路徑。3. 根源分析AHCI缺失只是起點真正的問題是固件傳輸路徑上的DMA被掐斷3.1 IDE兼容模式與AHCI的差別軟件接口決定上層能力不支持AHCI的主板SATA控制器運行在IDE兼容模式下這不是什么罕見事。AHCIAdvanced Host Controller Interface高級主機控制器接口本質(zhì)上定義了一套標(biāo)準(zhǔn)化的寄存器接口和行為規(guī)范它最核心的價值之一就是原生支持NCQ、熱插拔和高效的DMA傳輸。但I(xiàn)DE兼容模式本身并不等于沒有DMA——IDE模式同樣支持總線主控DMABus Mastering DMA否則早年那些跑在PXE、DOS、傳統(tǒng)BIOS環(huán)境下的機器讀盤速度會慢到?jīng)]法用。問題在于IDE模式的DMA能力不像AHCI那樣是“控制器自帶、驅(qū)動發(fā)現(xiàn)即可用”的它需要上下兩級配合固件/驅(qū)動需要主動初始化IDE控制器的總線主控DMA相關(guān)寄存器上層代碼需要明確選擇使用DMA命令而非PIO命令來傳輸數(shù)據(jù)只要這兩級有一級偷懶整個鏈路就會退回到PIO模式。SeaBIOS作為BIOS階段的固件它的ATA驅(qū)動代碼直接決定了“內(nèi)核早期通過BIOS接口讀盤時到底是走DMA還是走PIO”。3.2 BIOS階段的磁盤讀寫SeaBIOS不啟用DMA內(nèi)核也只能跟著遭殃這里有個很多人容易忽略的細(xì)節(jié)Linux內(nèi)核在啟動的極早期尤其是解壓initramfs之前并不能使用自己完整的驅(qū)動程序棧。它依賴固件提供的Int 13h例程來完成磁盤訪問這個階段被稱為“早期引導(dǎo)環(huán)境”。等到內(nèi)核把initramfs加載進(jìn)內(nèi)存解壓完成掛載好臨時的根文件系統(tǒng)才會切換到內(nèi)核自己的libata/ata_piix驅(qū)動。也就是說initramfs這個文件本身有多大BIOS階段就要通過Int 13h接口讀多少數(shù)據(jù)。如果SeaBIOS的ATA驅(qū)動在傳輸時選擇的是PIO模式那每次讀一個扇區(qū)都要CPU反復(fù)輪詢狀態(tài)寄存器等待I/O完成效率低下到令人發(fā)指。特別是現(xiàn)在發(fā)行版的initramfs動輒幾十MB里面塞滿了各種硬件驅(qū)動模塊、固件文件、加密工具體量遠(yuǎn)超十年前的標(biāo)準(zhǔn)PIO模式讀這些數(shù)據(jù)就成了一場災(zāi)難。CONFIG_ATA_DMA這個選項的作用就是在SeaBIOS編譯時啟用ATA控制器的DMA傳輸支持。打開它之后SeaBIOS初始化ATA設(shè)備時會把控制器切換到總線主控DMA模式后續(xù)Int 13h讀盤操作會使用DMA而不是PIO每一步數(shù)據(jù)傳輸都不再需要CPU逐字節(jié)地搬運數(shù)據(jù)。量變引起質(zhì)變這個差異對啟動耗時的影響是數(shù)量級的。3.3 為什么舊版本SeaBIOS默認(rèn)不啟用CONFIG_ATA_DMA看到這里肯定有人要問這么明顯的性能差異為什么CONFIG_ATA_DMA不是默認(rèn)開啟這個問題我研究過挺有意思。SeaBIOS在默認(rèn)配置里其實有很多為了“兼容最大化”而做的保守選擇。CONFIG_ATA_DMA本質(zhì)上默認(rèn)是開啟的但如果你用的coreboot版本較老、或者習(xí)慣了用coreboot提供的SeaBIOS配置模板某些預(yù)編譯配置或者精簡配置可能把它關(guān)掉了。另一個更隱蔽的情況是有些自定義構(gòu)建為了省ROM空間會把一些“看起來并非必需”的選項關(guān)掉——畢竟BIOS只要能把系統(tǒng)引導(dǎo)起來就算完成任務(wù)沒人會想到BIOS階段的磁盤性能會影響到整個系統(tǒng)啟動速度。還有一個兼容性層面的考量IDE模式的DMA在某些極度老舊的芯片組上存在兼容性問題極少數(shù)情況下會引發(fā)數(shù)據(jù)錯亂或者傳輸中斷。為了穩(wěn)妥起見部分發(fā)行版或精簡固件在構(gòu)建SeaBIOS時干脆禁用了DMA支持。但對于絕大多數(shù)支持總線主控DMA的IDE控制器來說這個選項帶來的收益遠(yuǎn)大于風(fēng)險。4. 解決辦法手動打開SeaBIOS的CONFIG_ATA_DMA并重建payload4.1 在coreboot集成SeaBIOS時改配置defconfig或menuconfig兩種路線搞清楚原因之后剩下的就是動手解決。這里有兩種做法取決于你的coreboot是用來獨立編譯SeaBIOS還是把它作為內(nèi)嵌payload整體構(gòu)建。先說第一種也是最簡單的方式如果你已經(jīng)用了coreboot的菜單配置也就是運行make menuconfig的那個界面找到Payload配置相關(guān)選項。在coreboot較新版本里SeaBIOS作為payload集成時會提供一個指向額外配置文件的入口比較常見的做法是在coreboot的配置里通過CONFIG_SEABIOS_EXTRA_CONFIG或者舊版中的CONFIG_PAYLOAD_SEABIOS_EXTRA_CONFIG指向一個額外的配置片段。你可以在這個配置片段里寫一行CONFIG_ATA_DMAy具體來說你在coreboot源碼根目錄編譯的時候可以先make menuconfig進(jìn)入Payload那一欄選擇“SeaBIOS”作為payload。然后找到類似“SeaBIOS extra config”或者“Additional SeaBIOS configuration”的選項填上配置文件的路徑。那個文件的內(nèi)容不必很復(fù)雜加上對應(yīng)的選項即可。第二種做法是完全獨立編譯SeaBIOS生成自己想要的payload文件再交給coreboot使用。這種方式控制力最強也最容易排查問題。操作步驟大致如下git clone https://review.coreboot.org/seabios.git cd seabios make menuconfig make在make menuconfig中進(jìn)入“ATA support”相關(guān)菜單確保CONFIG_ATA_DMA處于開啟狀態(tài)。編譯完成后會生成out/bios.bin.bin如果你需要的是ELF格式payloadcoreboot通常使用這個可以用make命令生成后看到out/bios.bin.elf。把這個文件交給coreboot在coreboot配置里指定外部payload路徑。4.2 完整編譯與刷寫流程示意如果你用的是coreboot直接內(nèi)嵌SeaBIOS整體流程整理如下準(zhǔn)備好coreboot源碼進(jìn)入make menuconfig在“Payload”中啟用SeaBIOS保留默認(rèn)版本或指定你常用的SeaBIOS版本通過config片段強制打開CONFIG_ATA_DMAy重新編譯corebootmake生成新的build/coreboot.rom用flashrom或其他工具刷入主板如果你的主板不支持軟件刷寫還得外接編程器這一步因板而異。我這邊刷寫過程一共花了不到一分鐘啟動后的變化卻是立竿見影的。需要特別提醒的一點是CONFIG_ATA_DMA只是SeaBIOS層面的配置開關(guān)。你在coreboot的配置界面里是找不到這個名字的必須在給SeaBIOS的額外配置文件中寫入或者在實際編譯SeaBIOS的時候通過它自身的Kconfig系統(tǒng)打開。很多人在這步卡住在coreboot的menuconfig里搜ATA_DMA搜不到就放棄了這是正常的因為它本來就是SeaBIOS自己的配置項。4.3 驗證是否生效看dmesg中的傳輸模式變化刷寫完新固件之后怎么確認(rèn)DMA真的生效了最簡單的方法還是看dmesg。重啟進(jìn)入系統(tǒng)執(zhí)行dmesg | grep -i ata.*dma\|ata.*pio\|ata.*UDMA如果SeaBIOS階段成功啟用了DMA你在dmesg中可能看不到BIOS階段的直接記錄因為那個階段發(fā)生在內(nèi)核接管之前但可以對比啟動速度和早期的ata初始化日志。內(nèi)核的ata驅(qū)動會報告它檢測到的最大傳輸模式和當(dāng)前使用的模式如configured for UDMA/133。更重要的是整個kernel階段的啟動時間會大幅縮短。另外如果你用的內(nèi)核開啟了dynamic_debug可以查看更多底層細(xì)節(jié)。不過我個人的經(jīng)驗是最直接的驗證方式就是重新開啟那個大體積initramfs發(fā)行版的啟動計時前后對比一下差距有多大數(shù)據(jù)比任何日志都更有說服力。5. 開啟DMA之后實測提升和其他補充優(yōu)化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秒整個系統(tǒng)的啟動總時間從接近一分半鐘縮短到半分鐘以內(nèi)。這個提升幅度完全達(dá)到了“像換了一臺機器”的程度而且整體系統(tǒng)進(jìn)入桌面之后的流暢度也有微妙改善因為啟動初期的I/O壓力不再拖累后續(xù)進(jìn)程加載了。順帶一提我用的是機械硬盤如果換成SSD或者更高轉(zhuǎn)速的硬盤這個差距可能還會更大。因為SSD在PIO模式下的性能瓶頸更明顯DMA模式才能發(fā)揮出設(shè)備的真實速度。5.2 其他可以配合的啟動提速項CONFIG_ATA_DMA解決了最核心的問題但如果你還在折騰老平臺的啟動速度下面這幾個優(yōu)化項也可以一并考慮縮小initramfs體積。即使開啟了DMA早期BIOS階段的PIO讀取依然存在只是時間縮短了。initramfs的體積越小這個階段的耗時越短??梢杂胢kinitcpioArch系或者update-initramfsDebian系時只保留必要模塊或者改用dracut --hostonly生成僅包含當(dāng)前硬件驅(qū)動的最小initramfs。開啟GRUB的truecrypt和native圖形模式。GRUB自身讀取內(nèi)核文件也會經(jīng)過固件接口讓GRUB使用自己原生的磁盤驅(qū)動insmod ata、insmod ahci可以直接繞過BIOS接口讀取速度更快。在GRUB的/boot/grub/grub.cfg中的load_video附近加一段insmod ata和insmod ahci可以顯著縮短GRUB加載內(nèi)核的時間。確認(rèn)內(nèi)核命令行沒有多余的等待參數(shù)。比如rootdelay、rootwait這些參數(shù)會根據(jù)場景增加額外等待時間如果確實需要保留至少確認(rèn)它們沒有被不必要地放大??紤]終端輸出的速度影響。老主板在串口控制臺或慢速幀緩沖設(shè)備上輸出大量內(nèi)核日志也可能放大啟動耗時。如果不需要詳細(xì)日志可以在內(nèi)核命令行加上quiet loglevel3減少啟動階段的終端輸出壓力。5.3 給同樣折騰老硬件的人留個備忘整個排查和解決過程走下來我給同樣喜歡折騰老平臺、玩coreboot和SeaBIOS的人留幾個備忘點都是實打?qū)嵅冗^的坑第一不要假設(shè)“BIOS階段慢一點無所謂”。在傳統(tǒng)BIOS啟動流程里BIOS階段的磁盤讀取能力直接影響內(nèi)核早期初始化速度?,F(xiàn)在的Linux發(fā)行版為了兼容所有硬件initramfs越做越大BIOS階段慢吞吞地讀幾十MB數(shù)據(jù)放大了這個問題。尤其是那顆老態(tài)龍鐘的機械硬盤還在堅持服役的場景下這個差距更加顯著。第二CONFIG_ATA_DMA不是萬能的開關(guān)但值得先試。它解決的是“BIOS階段的磁盤傳輸模式”問題。如果你的啟動慢點不在這個階段而是集中在systemd初始化、網(wǎng)絡(luò)等待、服務(wù)超時等環(huán)節(jié)開啟它不會有任何效果需要另找原因。但“先試這個”的成本極低一次重新編譯刷寫就能確認(rèn)收益卻可能非常大。第三一定要保留一個好的啟動時間度量工具。我強烈建議在折騰之前先用systemd-analyze記錄基線數(shù)據(jù)修改之后再記錄一次做對比。沒有量化數(shù)據(jù)全憑“感覺變快了”很容易誤判也容易在后續(xù)改動中引入新問題而不自知。第四老硬件上兼容性永遠(yuǎn)要放在性能前面。萬一開了DMA之后出現(xiàn)偶爾的讀盤錯誤、啟動不穩(wěn)定、文件系統(tǒng)校驗失敗不用急著排除DMA模式先確認(rèn)是不是電源供電不穩(wěn)、SATA線老化、硬盤本身有壞道。DMA只是把數(shù)據(jù)通路打開了并不能解決硬件本身的老化問題。如果確實出現(xiàn)DMA相關(guān)問題也可以考慮退回PIO模式不過以我的經(jīng)驗絕大多數(shù)老ICH芯片組在DMA模式下都非常穩(wěn)定。折騰完這臺老機器最大的感慨是有些問題時隔多年依然會咬人但底層原理搞清楚之后解決起來就是改一個配置、重新編譯一次固件的事。如果你也正被類似的不支持AHCI的老主板加coreboot組合折磨著不妨花二十分鐘試試這個方案多半不會讓你失望。