from: http://www.linuxsky.org/doc/admin/200712/174.html
在上次完成嵌入式應用的Linux裁減後,Linux的啟動時間仍需要 7s 左右,雖然勉強可以接受,但仍然沒有達到我個人所追求的目标——2s 以内。況且,在實際的商用環境中,裝置可靠性的要求可是“5個9”(99.999%,即OOS時間低于5分鐘/年),這就意味着每減少一秒鐘Linux啟動(裝置複位)時間,對可靠性都是一個明顯的提升。
言歸正傳,如何着手對Linux的啟動時間進行優化呢?
CELF(The Consumer Electronics Linux Forum)論壇為我們指引了一個方向。
(1)首先是對Linux啟動過程的跟蹤和分析,生成詳細的啟動時間報告。
較為簡單可行的方式是通過PrintkTime功能為啟動過程的所有核心資訊增加時間戳,便于彙總分析。PrintkTime最早為CELF所提供的一個核心更新檔,在後來的Kernel 2.6.11版本中正式納入标準核心。是以大家可能在新版本的核心中直接啟用該功能。如果你的Linux核心因為某些原因不能更新為2.6.11之後的版本,那麼可以參考CELF提供的方法修改或直接下載下傳它們提供的更新檔:http://tree.celinuxforum.org/CelfPubWiki /PrintkTimes
開啟PrintkTime功能的方法很簡單,隻需在核心啟動參數中增加“time”即可。當然,你也可以選擇在編譯核心時直接指定“Kernel hacking”中的“Show timing information on printks”來強制每次啟動均為核心資訊增加時間戳。這一種方式還有另一個好處:你可以得到核心在解析啟動參數前所有資訊的時間。是以,我選擇後一種方式。
當完成上述配置後,重新啟動Linux,然後通過以下指令将核心啟動資訊輸出到檔案:
dmesg -s 131072 >ktime
然後利用一個腳本“show_delta”(位于Linux源碼的scripts檔案夾下)将上述輸出的檔案轉換為時間增量顯示格式:
/usr/src/linux-x.xx.xx/scripts/show_delta ktime >dtime
這樣,你就得到了一份關于Linux啟動時間消耗的詳細報告。
(2)然後,我們就來通過這份報告,找出啟動中相對耗時的過程。
必須明确一點:報告中的時間增量和核心資訊之間沒有必然的對應關系,真正的時間消耗必須從核心源碼入手分析。
這一點對于稍微熟悉程式設計的朋友來說都不難了解,因為時間增量隻是兩次調用printk之間的時間內插補點。通常來說,核心啟動過程中在完成一些耗時的任務,如建立hash索引、probe硬體裝置等操作後會通過printk将結果列印出來,這種情況下,時間增量往往反映的是資訊對應過程的耗時;但有些時候,核心是在調用printk輸出資訊後才開始相應的過程,那麼報告中核心資訊相應過程的時間消耗對應的是其下一行的時間增量;還有一些時候,時間消耗在了兩次核心資訊輸出之間的某個不确定的時段,這樣時間增量可能就完全無法通過核心資訊反應出來了。
是以,為了準确判斷真正的時間消耗,我們需要結合核心源碼進行分析。必要的時候,例如上述第三種情形下,還得自己在源碼中插入printk列印,以進一步确定實際的時間消耗過程。
以下是我上次裁減後Linux核心的啟動分析:
核心啟動總時間: 6.188s
關鍵的耗時部分:
1) 0.652s - Timer,IRQ,Cache,Mem Pages等核心部分的初始化
2) 0.611s - 核心與RTC時鐘同步
3) 0.328s - 計算Calibrating Delay(4個CPU核心的總消耗)
4) 0.144s - 校準APIC時鐘
5) 0.312s - 校準Migration Cost
6) 3.520s - Intel E1000網卡初始化
下面,将針對上述各部分進行逐一分析和化解。
(3)接下來,進行具體的分項優化。
CELF已經提出了一整套針對消費類電子産品所使用的嵌入式Linux的啟動優化方案,但是由于面向不同應用,是以我們隻能部分借鑒他們的經驗,針對自己面對的問題作出具體的分析和嘗試。
核心關鍵部分(Timer、IRQ、Cache、Mem Pages……)的初始化目前暫時沒有比較可靠和可行的優化方案,是以暫不考慮。
對于上面分析結果中的 2、3 兩項,CELF已有專項的優化方案:“RTCNoSync”和“PresetLPJ”。
前者通過屏蔽啟動過程中所進行的RTC時鐘同步或者将這一過程放到啟動後進行(視具體應用對時鐘精度的需求而定),實作起來比較容易,但需要為核心打更新檔。似乎CELF目前的工作僅僅是去掉了該過程,而沒有實作所提到的“延後”處理RTC時鐘的同步。考慮到這個原因,我的方案中暫時沒有引入這一優化(畢竟它所帶來的時間漂移已經達到了“秒”級),繼續關注中。
後者是通過在啟動參數中強制指定LPJ值而跳過實際的計算過程,這是基于LPJ值在硬體條件不變的情況下不會變化的考慮。是以在正常啟動後記錄下核心資訊中的“Calibrating Delay”數值後就可以在啟動參數中以下面的形式強制指定LPJ值了:
lpj=9600700
上面分析結果中的 4、5 兩項都是SMP初始化的一部分,是以不在CELF研究的範疇(或許将來會有采用多核的MP4出現?……),隻能自力更生了。研究了一下SMP的初始化代碼,發現“Migration Cost”其實也可以像“Calibrating Delay”采用預置的方式跳過校準時間。方法類似,最後在核心啟動參數中增加:
migration_cost=4000,4000
而Intel的網卡驅動初始化優化起來就比較麻煩了,雖然也是開源,但讀硬體驅動完全不比讀一般的C代碼,況且建立在如此膚淺了解基礎上的“優化”修改也實在難保萬全。基于可靠性的考慮,我最終在兩次嘗試均告失敗後放棄了這一條路。那麼,換一個思維角度,可以借鑒CELF在“ParallelRCScripts”方案中的“并行初始化”思想,将網卡驅動獨立編譯為子產品,放在初始化腳本中與其它子產品和應用同步加載,進而消除Probe阻塞對啟動時間的影響。考慮到應用初始化也可能使用到網絡,而在我們的實際硬體環境中,隻有eth0是供應用使用的,是以需要将第一個網口初始化的0.3s時間計算在内。
除了在我的方案中所遇到的上述各優化點,CELF還提出了一些你可能會感興趣的有特定針對性的專項優化,如:
ShortIDEDelays - 縮短IDE探測時長(我的應用場景中不包含硬碟,是以用不上)
KernelXIP - 直接在ROM或Flash中運作核心(考慮到相容性因素,未采用)
IDENoProbe - 跳過未連接配接裝置的IDE口
OptimizeRCScripts - 優化initrd中的linuxrc腳本(我采用了BusyBox更簡潔的linuxrc)