1. 概述
Alibaba Cloud Linux 2(原Aliyun Linux 2,簡稱Alinux 2)是阿里雲操作系統團隊基於開源Linux內核4.19 LTS版本打造的一款針對雲應用場景的下一代Linux OS發行,不僅提供Linux社區的最新增強功能,在提供雲上最佳用戶體驗的同時,也針對阿里雲基礎設施做了深度的優化。今日Alinux 2 LTS 正式發佈,是Alinux 2的一個重要里程碑。這標誌著阿里雲操作系統團隊將為Alinux 2提供長期技術支持、穩定的更新、更好服務,為Alinux 2的用戶提供更多保障。
Alinux 2 LTS 版本不僅增加了更多社區新功能的支持,對系統啟動時間、運行時性能及穩定性都做了許多優化。更詳細的更新優化可參考發佈記錄,推薦直接上手試用體驗。
Alinux 2在快速啟動優化上取得一些不錯的效果,同時推出“Alinux 2 qboot快速啟動版”鏡像(公測中),內核部分啟動性能提升40%:
這裡分享一下Alinux 2 LTS在系統快速啟動優化上的所做的一些實踐。
2. Linux系統啟動流程
首先定義Linux系統啟動,這裡我們定義為從系統上電(虛擬機開啟),到用戶能夠登陸(ssh login)為系統啟動。通用Linux系統啟動大致分為三個階段:引導階段(phase#1),內核啟動階段(phase#2)及用戶態啟動階段(phase#3):
其中,
- 對物理機產品,開機後運行固件中的BIOS程序,完成基本硬件初始化及上電自檢(POST),通過後跳轉至系統磁盤引導扇區;
- 對虛擬機(Qemu + KVM)產品,Qemu運行後模擬BIOS,加載系統鏡像文件虛擬出系統盤,跳轉至系統盤引導扇區;
下面來看看各階段大致的啟動流程。
2.1 Boot Loader
Bootloader是位於系統引導扇區的一段獨立的系統程序,用於系統啟動初期的硬件初始化,系統分區識別,系統內核加載及跳轉執行。目前應用比較廣泛的bootloader是用於通用系統的grub2和嵌入式系統的uboot。Grub2是多重引導器(multiboot),提供交互界面,默認配置下有5s交互超時,啟動耗時較長。
2.2 Kernel
Bootloader加載Linux內核(一般為壓縮內核vmlinuz)到內存,並運行內核自解壓縮程序,解壓後跳轉至start_kernel,開始內核初始化流程:
2.3 User Space
Linux內核完成一系列初始化動作之後,開始運行init程序,創建PID為1的用戶態進程,將系統控制權從內核態跳轉到用戶態。init進程會繼續進行用戶態啟動流程,開啟各種必要的,或是預先配置的系統服務,最後啟動登陸服務,完成整個系統的啟動。
Initrd與Switch Root
init是用戶態程序,存放在系統根文件系統(rootfs)裡。內核需要先掛載rootfs,才能運行init程序。通用Linux發行需要支持多種磁盤設備,多種文件系統,因此內核必須能夠識別不同的磁盤設備,不同的文件系統。這需要內核預加載多種可能的磁盤設備驅動以及多種文件系統相關用戶態工具軟件才能正確識別rootfs。而這些驅動及用戶態工具一般都存放在rootfs中,形成一個循環依賴。
為解決這個問題,initrd應運而生,將掛載rootfs必要的驅動,用戶態工具以及其他需要預加載的代碼從rootfs總抽取出來,並依照rootfs的文件結構,打包成一個小的rootfs,做成一個內存盤(ram disk)。內核在掛載最終的rootfs之前,先從內存中掛載initrd,加載必要的驅動後,先運行initrd中的init程序,掛載最終的rootfs。然後執行switch root動作,切換至最終的rootfs。
Alinux 2系統採用systemd來管理用戶空間啟動流程,systemd就是init程序,initrd使用壓縮格式的initramfs文件。因此在加載initrd之前,內核需要先解壓縮initramfs。
Cloud Init
Cloud init是雲環境中的虛擬實例初始化配置工具,實例啟動階段能從多種數據源讀取相關數據並據此對虛擬機進行配置,如用戶密碼,主機名,網絡,用戶數據等等一些配置。
3. 啟動耗時畫像
優化系統啟動時間,自然需要先對系統啟動畫像,瞭解啟動時間分佈情況,找出系統啟動耗時熱點。
3.1 啟動時間測量
Linux系統有如下常見的啟動時間測量統計方法:
-
systemd-analyze
systemd自帶的啟動分析工具,能夠給出總的啟動時間消耗,已經用戶態服務啟動耗時統計。
-
dmesg
dmesg輸出內核啟動日誌,時間戳能夠幫助分析內核初始化各階段耗時情況。配合-d
選項計算出日誌間的時間差,方便快速定位內核啟動過程中耗時熱點。
-
initcall_debug
內核啟動參數,開啟後會統計內核各初始化函數的耗時情況,相比dmesg -d
更加精確。 -
printk/trace_printk
要分析一些啟動熱點的細化耗時情況時,手動增加一些printk/trace_printk探針能夠幫助獲取時間統計信息。 -
ftrace
必要時也可開啟內核早期ftrace功能,幫助分析熱點耗時。不過需要注意開啟ftrace後可能會導致函數延時增加,因此不宜參考ftrace得出函數絕對耗時,可以參照trace結果幫助分析熱點函數的耗時邏輯。
還有其他一些時間測試方法,以及圖形化畫像工具,這裡不一一介紹。
3.2 啟動耗時熱點分析
對Alinux 2系統啟動畫像後,按耗時排序,得到如下耗時熱點:
(這裡以2C8G虛擬機為例,內核耗時1000ms,總體耗時5000ms)
熱點 | 耗時(ms) | 內核啟動佔比 | 總啟動佔比 |
---|---|---|---|
mem init | ~35 | 3.5% | 0.7% |
ORC unwind init | ~90 | 9% | 1.8% |
buddy init | 250 | 25% | 5% |
console enable | ~60 | 6% | 1.2 % |
initramfs unpack | 250 | 25% | 5% |
free initmem | 270 | 27% | 5.4% |
mouse probe | 650 | 65% | 13% |
systemd initrd | 600 | N/A | 12% |
mount rootfs | 200 | N/A | 4% |
cloud init | 2740 | N/A | 54.8% |
可見:
- 總體啟動耗時中,一半以上的時間消耗在用戶態cloud-init進程上;
- 內核啟動階段,鼠標探測耗時佔比較高。
4. 快速啟動優化
4.1 啟動優化方法
常用的啟動優化方法大致如下:
-
瘦身
- 移除不必要的代碼,如模塊,服務等,縮減啟動初始化步驟;
- 移除不必要的測試,調式及打印
- 精簡共享庫
-
異步、並行
- 將耗時動作從關鍵路徑移除,延後執行
- 將順序動作並行化執行
-
原地執行(XIP)
- 多用於嵌入式系統
-
定製化
- 將通用初始化程序定製化
-
算法優化
- 改進算法,加速初始化時間
4.2 去initrd
從前面的啟動耗時熱點分析結果可以看出,initrd解壓縮及initrd systemd耗時佔Alinux 2啟動較大比率。
Alinux 2系統主要面向雲環境虛擬實例,系統盤設備基本固定為virtio-blk設備,根文件系統格式基本固定為ext4文件系統,應該不需要通過initrd來加載rootfs,可以去掉initrd,直接掛載系統磁盤,即對內核啟動瘦身。
理論上會優化掉initramfs unpack(270) + initrd systemd(560) ~ 800ms的啟動耗時。去掉initrd測試結果如下:
可見initrd systemd時間確實優化掉了,但總的啟動時間並沒有理論優化收益。原因是內核啟動耗時增加了約400ms。進一步分析發現,啟動耗時熱點之一的mouse probe(600ms),去initrd之前是與initrd systemd並行執行的。
去掉initrd後,這部分時間就直接計入內核啟動時間了。抵去優化掉的initramfs unpacking的200ms,內核實際增加了400ms左右。
因此,要最大化去initrd的優化收益,必須同時解決mouse probe的耗時。
4.3 延遲probe
通用Linux系統需要支持多種IO設備,而鼠標鍵盤是比較常用的輸入設備,特別是鼠標,產品繁多,接口多樣。系統啟動過程中加載鼠標驅動後,需要掃描多種IO總線來探測鼠標設備,這一過程非常耗時。
依據前面提到的優化方法,我們有兩種方案:
- 對雲環境定製鼠標驅動,固定探測virtio設備;
- 將鼠標探測從啟動關鍵路徑剝離,延遲探測,與後面系統啟動服務並行;
第一種方案需要重構相關代碼,成本較高;而且定製化限制較多,無法與開源社區協作。因此需要思考第二種方法:延遲探測。一種簡單可行的方法是將原本內置(built-in)的設備驅動重新編譯為內核模塊(kernel module),因內核模塊存放在根文件系統,所以加載時機被動推遲到根文件系統掛載之後,此時內核已經啟動完成,自然與用戶態初始化進程並行執行。
測試結果如下:(注意這是優化後的內核本地測,cloud-init被禁用)
帶initrd啟動:
不帶initrd啟動:
可見,內核啟動時間縮減約200ms,優化掉initrd systemd時間;鼠標設備探測延後至userspace初始化階段,導致userspace啟動時間略有增加。獲得預期的啟動時間優化。
4.4 內存初始化優化
內存初始化也是內核啟動熱點之一,特別是在大規格實例上,內存初始化耗時佔比較高。圖中為750GB實例內存初始化耗時:
meminit耗時近2s
buddy init耗時1.8s
內存初始化動作是在內核啟動的關鍵路徑上,優化思路是並行初始化。因內存初始化時機較早,系統多CPU還未初始化完成,所以需要將內存初始化延後至CPU初始化完成之後,採用多線程並行執行內存初始化。這部分工作社區已經完成,通過內核配置CONFIG_DEFERRED_STRUCT_PAGE_INIT
來開啟。
開啟後,內存初始化延後,按NUMA node並行執行:
前半部耗時約0.2s
後半部耗時約1.3s
4.5 free initmem修復
Alinux 2 內核啟動優化前有一個概率性的啟動熱點,free initmem到buddy系統時,會大概率(超過70%)出現200ms以上延時,dmesg日誌顯示如下,耗時超過200ms:
[ 0.687494] rtc_cmos 00:00: setting system clock to 2020-03-03 15:09:38 UTC (1583248178)
[ 0.915315] Freeing unused kernel image memory: 1836K
經分析,發現是社區已知問題,並在新內核已經修復 於是backport回Alinux 2 LTS內核,修復後耗時約5ms,基本消除這部分延時:
[ 0.482477] rtc_cmos 00:00: setting system clock to 2020-03-03 15:01:41 UTC (1583247701)
[ 0.487438] Freeing unused kernel image memory: 1856K
4.6 ORC unwind初始化
內核中有一些靜態表,需要在內核初始化階段排序,有些表體積較大,初始化耗時佔比也不容小覷。如ORC unwind表格初始化排序,耗時約90ms:
[ 0.087330] clocksource: refined-jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 1910969940391419 ns
[ 0.179718] random: get_random_bytes called from start_kernel+0x8b/0x563 with crng_init=0
這些靜態表格是在內核構建階段生成,因此可以將排序動作從內核初始化階段移除,放到內核構建階段完成,以節省內核初始化時間。經調查發現社區已經有類似的優化方案,異常處理表(exception table)排序移植到了內核構建階段完成。於是對異常處理表改進,增加了ORC unwind表格構建階段排序優化,系列patch已經合入主線。
優化後基本削減了這部分耗時:
[ 0.037253] clocksource: refined-jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 1910969940391419 ns
[ 0.040714] random: get_random_bytes called from start_kernel+0x8b/0x563 with crng_init=0
4.7 Cloud-init優化
Alinux 2採用systemd啟動用戶態系統服務進程,以實現最大化並行啟動。在到達啟動完成的ssh登陸狀態前,依賴一系列必要的系統服務,其中cloud-init是關鍵鏈路中一個耗時熱點,啟動畫像中可以看出cloud-init幾乎佔整個系統啟動時間的一半,因此優化cloud-init能夠獲得較大的啟動性能收益。
在cloud-init服務中,一個耗時的配置任務是用戶密碼配置,需要從metadata服務器獲取賬號密碼,完成配置。Alinux 2 LTS 內核開啟了 Qemu firmware configuration
功能,能夠通過qemu透傳一些諸如賬號密碼的配置到虛擬機內部,使得cloud-init能夠本地讀取配置信息,加快cloud-init配置動作。感謝阿里雲鏡像團隊跟阿里雲虛擬化團隊共同努力,即將推出InnerPasswd功能,加速Alinux 2 LTS 實例的cloud-init配置,提升實例啟動時間,敬請期待!
4.8 其它優化
另外,啟動階段的console輸出也是一個相對耗時的動作,因為串行口的波特率是固定的,大量是輸出會形成阻塞導致console enabled延時較大。例如:
開啟console output,console耗時2.6s!:
配置內核參數quiet
,關閉console output:
5. 下一步工作
雖然Alinux 2 LTS在啟動優化已經取得了不錯的效果,啟動性能得到進一步提升,但仍然還有進一步挖掘的空間。特別是內存初始化這塊,仍然是大規格實例啟動熱點。即便已經開啟的deferred page init特性,但內存初始仍然限於node間並行,而node內並行初始化值得進一步挖掘,特別對當前ECS實例大都為單node實例(NUMA關閉)的場景下,理論上有更大的收益。
據瞭解社區已經有貢獻者在著手進行相關工作,值得期待。