Linux 內(nèi)核裁剪框架初探
大約是在2000年的時(shí)候,老碼農(nóng)還很年輕,當(dāng)時(shí)希望將Linux 作為手機(jī)的操作系統(tǒng), 于是才有了進(jìn)行內(nèi)核裁剪的想法并輔助實(shí)踐,效果尚好,已經(jīng)能在PDA上執(zhí)行手機(jī)的功能了。一晃20多年過去了,Linux 已經(jīng)有了太大的變化,內(nèi)核裁剪的技術(shù)和方式也有了較大的不同。
Linux 的內(nèi)核裁剪是為了減少目標(biāo)應(yīng)用中不需要的內(nèi)核代碼,在安全性和高性能(快速啟動(dòng)時(shí)間和減少內(nèi)存占用)方面有著顯著的好處。但是,現(xiàn)有的內(nèi)核裁剪技術(shù)有其局限性,有沒有內(nèi)核裁剪的框架化方法呢?
1. 關(guān)于內(nèi)核裁剪
近年來,Linux操作系統(tǒng)在復(fù)雜性和規(guī)模上都在增長(zhǎng)。然而,一個(gè)應(yīng)用程序通常只需要一部分 OS 功能,眾多的應(yīng)用需求導(dǎo)致了Linux內(nèi)核的膨脹。操作系統(tǒng)的內(nèi)核膨脹同樣導(dǎo)致了安全性隱患、啟動(dòng)時(shí)間變長(zhǎng)和內(nèi)存使用的增加。
隨著服務(wù)化和微服務(wù)的流行,進(jìn)一步提出了對(duì)內(nèi)核裁剪的需求。在這些場(chǎng)景中,虛擬機(jī)運(yùn)行小型應(yīng)用程序,每個(gè)應(yīng)用程序往往是“微型”的,內(nèi)核占用較小,一些虛擬化技術(shù)要為目標(biāo)應(yīng)用程序提供最簡(jiǎn)單的 Linux 內(nèi)核。
鑒于操作系統(tǒng)的復(fù)雜性,通過手工挑選內(nèi)核特性來裁剪內(nèi)核有些不切實(shí)際。例如,Linux 有超過14,000+個(gè)配置選項(xiàng)(截至 v4.14) ,每年都會(huì)引入數(shù)百個(gè)新選項(xiàng)。內(nèi)核配置器(例如 KConfig)只提供用于選擇配置選項(xiàng)的用戶界面。鑒于糟糕的可用性和文檔的不完整性,用戶很難選擇最小且實(shí)用的內(nèi)核配置。
現(xiàn)有的內(nèi)核裁剪技術(shù)一般遵循三個(gè)步驟:
- 運(yùn)行目標(biāo)應(yīng)用程序的工作負(fù)載并跟蹤在應(yīng)用程序運(yùn)行期間執(zhí)行的內(nèi)核代碼;
- 分析跟蹤并確定目標(biāo)應(yīng)用程序所需的內(nèi)核代碼,
- 組裝一個(gè)只包含應(yīng)用程序所需代碼的內(nèi)核裁剪。
配置驅(qū)動(dòng)的是內(nèi)核裁剪的一般方法,大多數(shù)現(xiàn)有的工具使用配置驅(qū)動(dòng)技術(shù),因?yàn)樗鼈兪菫閿?shù)不多的可以產(chǎn)生穩(wěn)定內(nèi)核的技術(shù)之一。配置驅(qū)動(dòng)的內(nèi)核重載根據(jù)功能特性減少了內(nèi)核代碼,配置選項(xiàng)對(duì)應(yīng)于內(nèi)核的功能,裁剪后的內(nèi)核只包含用于支持目標(biāo)應(yīng)用程序工作負(fù)載的功能。
然而,盡管內(nèi)核裁剪技術(shù)在安全性和性能方面非常吸引人,但在實(shí)踐中并沒有得到廣泛采用。這并不是因?yàn)槿狈π枨?,?shí)際上,許多云供應(yīng)商手工編寫 Linux 內(nèi)核來減少代碼,但一般不如內(nèi)核裁剪技術(shù)有效。
2. 現(xiàn)有內(nèi)核裁剪技術(shù)的限制
現(xiàn)有內(nèi)核裁剪技術(shù)有五個(gè)主要的局限性。
在引導(dǎo)階段不可見?,F(xiàn)有技術(shù)只能在內(nèi)核引導(dǎo)后啟動(dòng),依賴于 ftrace,因此無法觀察在引導(dǎo)階段加載了哪些內(nèi)核代碼。如果內(nèi)核中缺少關(guān)鍵模塊,內(nèi)核通常無法啟動(dòng),而大量的內(nèi)核功能特性只能通過觀察引導(dǎo)階段來捕獲。此外,關(guān)于性能和安全性同樣只在引導(dǎo)時(shí)加載(例如,用于多核支持的 CONFIGSCHEDMC 和 CONFIGSECURITYNETWORK) ,導(dǎo)致了性能和安全性降低。
缺乏對(duì)應(yīng)用程序部署的快速支持。使用現(xiàn)有的工具,面向內(nèi)核裁剪來部署一個(gè)新的應(yīng)用程序需要完成跟蹤、分析和組裝這三個(gè)步驟。這個(gè)過程非常耗時(shí),有可能需要幾個(gè)小時(shí)甚至幾天,阻礙了應(yīng)用部署的敏捷性。
粒度較粗。使用ftrace 只能在函數(shù)級(jí)跟蹤內(nèi)核代碼,粒度太粗,無法跟蹤影響函數(shù)內(nèi)代碼的配置選項(xiàng)。
覆蓋不完全。因?yàn)槭褂脛?dòng)態(tài)跟蹤,所以需要應(yīng)用程序工作負(fù)載來驅(qū)動(dòng)內(nèi)核的代碼執(zhí)行,以最大限度地?cái)U(kuò)大覆蓋范圍。然而,基準(zhǔn)測(cè)試覆蓋是具有挑戰(zhàn)性的,而且,如果應(yīng)用程序有在跟蹤期間沒有觀察到的內(nèi)核代碼,那么裁剪后的內(nèi)核可能會(huì)在運(yùn)行時(shí)崩潰。
沒有區(qū)分執(zhí)行依賴,可能存在冗余。即使實(shí)際上可能并不需要執(zhí)行的代碼,也可能包含在了內(nèi)核功能特性中,例如,可能初始化了第二個(gè)文件系統(tǒng)。
前三個(gè)限制是可以克服的,可以通過改進(jìn)設(shè)計(jì)和工具加以解決,而后兩個(gè)限制是在所難免,需要在具體的技術(shù)之外作出努力。
3. Linux 的內(nèi)核配置
3.1配置選項(xiàng)
內(nèi)核配置由一組配置選項(xiàng)組成。一個(gè)內(nèi)核模塊可以有多個(gè)選項(xiàng),每個(gè)選項(xiàng)都控制哪些代碼將包含在最終的內(nèi)核二進(jìn)制文件中。
配置選項(xiàng)控制內(nèi)核代碼的不同粒度,例如由 C 預(yù)處理器實(shí)現(xiàn)的語句和函數(shù),以及基于 Makefile 實(shí)現(xiàn)的對(duì)象文件。C 預(yù)處理器根據(jù) #ifdef/#ifndef 選擇代碼塊,配置選項(xiàng)用作宏定義,以確定是否在編譯后的內(nèi)核中包含這樣條件的代碼塊,可以是語句粒度或者函數(shù)粒度。Makefile 用于確定是否在編譯后的內(nèi)核中包含某些對(duì)象文件,例如, CONFIG_CACHEFILES 就是 Makefile 中的配置選項(xiàng)。
語句級(jí)配置選項(xiàng)不能通過現(xiàn)有內(nèi)核裁剪工具所使用的函數(shù)級(jí)跟蹤來識(shí)別。事實(shí)上,Linux 4.14 中30%左右 的 C 預(yù)處理器是語句級(jí)選項(xiàng)。
隨著內(nèi)核代碼和功能特性的快速增長(zhǎng),內(nèi)核中的配置選項(xiàng)數(shù)量也在迅速增加,以 Linux內(nèi)核3.0以上版本都有1萬多個(gè)配置選項(xiàng)。
3.2. 配置語言
Linux內(nèi)核使用KConfig 配置語言來指示編譯器在編譯后的內(nèi)核中包含哪些代碼,允許定義配置選項(xiàng)以及它們之間的依賴關(guān)系。
KConfig 中配置選項(xiàng)的值可能是 bool、 tristate 或 constant。bool 意味著代碼要么被靜態(tài)編譯成內(nèi)核二進(jìn)制文件,要么被排除在外,而 tristate 允許代碼被編譯成一個(gè)可載入核心模組,即一個(gè)可以在運(yùn)行時(shí)加載的獨(dú)立對(duì)象。constant可以為內(nèi)核代碼變量提供字符串或數(shù)值。一個(gè)選項(xiàng)可以依賴于另一個(gè)選項(xiàng),KConfig 使用了一個(gè)遞歸過程,通過遞歸選擇和取消依賴項(xiàng)。最終的內(nèi)核配置具有有效的依賴關(guān)系,但可能與用戶輸入不同。
3.3. 配置模板
Linux 內(nèi)核附帶了許多手工制作的配置模板。但是,由于配置模板的硬編碼特性并且需要人工干預(yù),它們不能適應(yīng)不同的硬件平臺(tái),也不了解應(yīng)用程序的需求。例如,由 tinyconfig 構(gòu)建的內(nèi)核不能在標(biāo)準(zhǔn)硬件上啟動(dòng),更不用說支持其他應(yīng)用了。有些工具將 localmodconfig 視為最小化的配置,但是,localmodconfig 與靜態(tài)配置模板具有相同的局限性,它不會(huì)啟動(dòng)控制語句級(jí)或函數(shù)級(jí) C 預(yù)處理器的配置選項(xiàng),也不會(huì)處理可加載的內(nèi)核模塊。
kvmconfig 和 xenconfig 模板是為在 KVM 和 Xen 上運(yùn)行的內(nèi)核而定制的。它們提供例如底層虛擬化和硬件環(huán)境的領(lǐng)域知識(shí)。
3.4. 云中的 Linux 內(nèi)核配置
Linux 是云服務(wù)中占主導(dǎo)地位的操作系統(tǒng)內(nèi)核,云供應(yīng)商都在一定程度上放棄了普通的 Linux 內(nèi)核。云廠商的定制通常是通過直接刪除可加載的內(nèi)核模塊來完成的,手工修剪內(nèi)核模塊二進(jìn)制文件的問題是可能會(huì)違反依賴關(guān)系。重要的是,基于應(yīng)用程序需求可以進(jìn)一步裁剪內(nèi)核。例如,Amazon FireCracker 內(nèi)核是一個(gè)專門用于函數(shù)即服務(wù)的微型虛擬機(jī),使用 HTTPD 作為目標(biāo)應(yīng)用程序,在保證功能和性能提升的同時(shí),使內(nèi)核裁剪實(shí)現(xiàn)了更大程度的最小化。
4. 內(nèi)核裁剪的思考
針對(duì)局限一,是否可以使用來自 QEMU 的指令級(jí)跟蹤來實(shí)現(xiàn)引導(dǎo)階段的可見性呢?這樣,就可以跟蹤內(nèi)核代碼并將其映射到內(nèi)核配置選項(xiàng)。既然引導(dǎo)階段對(duì)于生成可引導(dǎo)內(nèi)核至關(guān)重要,使用 hypervisor 提供的跟蹤特性來獲得端到端的可觀察性并生成穩(wěn)定的內(nèi)核。
針對(duì)局限二,根據(jù)在NLP深度學(xué)習(xí)中的經(jīng)驗(yàn),可以使用離線和在線結(jié)合的方法,給定一組目標(biāo)應(yīng)用程序,可以直接離線生成的App 配置,再和基線配置組合成完整的內(nèi)核配置,從而生成一個(gè)裁剪后的內(nèi)核。這種可組合性能夠通過重用應(yīng)用配置和以前構(gòu)建的文件(例如內(nèi)核模塊)來增量地構(gòu)建新內(nèi)核。如果目標(biāo)應(yīng)用程序的配置已知,就可以在幾十秒內(nèi)完成內(nèi)核裁剪。
針對(duì)局限三,使用指令級(jí)跟蹤可以解決控制函數(shù)內(nèi)部功能特性的內(nèi)核配置選項(xiàng),指令級(jí)跟蹤的開銷對(duì)于運(yùn)行測(cè)試套件和性能基準(zhǔn)來說是可以接受的。
針對(duì)局限四,使用基于動(dòng)態(tài)跟蹤的一個(gè)基本限制是測(cè)試套件和基準(zhǔn)的不完善,許多開源應(yīng)用程序測(cè)試套件的代碼覆蓋率較低。組合不同的工作負(fù)載來驅(qū)動(dòng)應(yīng)用程序可以在一定程度上減輕這種限制。
針對(duì)局限五,通過刪除在基線內(nèi)核中執(zhí)行但在實(shí)際部署運(yùn)行時(shí)不需要的內(nèi)核模塊,可以使用特定于領(lǐng)域的信息進(jìn)一步加載內(nèi)核。以 Xen 和 KVM 為例,可以基于 xenconfig 和 kvmconfig 配置模板進(jìn)一步減少內(nèi)核大小。面向應(yīng)用程序的內(nèi)核裁剪可以進(jìn)一步減少內(nèi)核大小甚至廣泛地定制的內(nèi)核代碼。
5 內(nèi)核裁剪框架初探
內(nèi)核裁剪框架的原理沒有變,仍然是跟蹤目標(biāo)應(yīng)用工作負(fù)載的內(nèi)核占用情況,以確定所需的內(nèi)核選項(xiàng)。
5.1 內(nèi)核裁剪框架的核心特性
內(nèi)核裁剪框架大概可以具備以下特性:
- 端到端的可見性。利用虛擬機(jī)監(jiān)控程序的可見性來實(shí)現(xiàn)端到端的觀察,可以跟蹤內(nèi)核引導(dǎo)階段和應(yīng)用程序工作負(fù)載,可以嘗試在QEMU 的基礎(chǔ)上建造Linux內(nèi)核的裁剪框架。
- 可組合性。一個(gè)核心思想是通過將內(nèi)核配置劃分為若干組配置集,使內(nèi)核配置可以組合,用于在給定的部署環(huán)境上引導(dǎo)內(nèi)核,也可以用于目標(biāo)應(yīng)用程序所需的配置選項(xiàng)。配置集分為兩種:基線配置和應(yīng)用配置?;€配置不一定是在特定硬件上引導(dǎo)所需的最小配置集,而是在引導(dǎo)階段跟蹤的一組配置選項(xiàng)?;€配置可以與一個(gè)或多個(gè)應(yīng)用配置組合在一起,以生成最終的內(nèi)核配置。
- 可重用性?;€配置和應(yīng)用配置都可以存儲(chǔ)在數(shù)據(jù)庫中,并且只要部署環(huán)境和應(yīng)用程序的二進(jìn)制文件不變就可以重用。這種可重用性避免了重復(fù)跟蹤工作負(fù)載的運(yùn)行,使得配置集的創(chuàng)建成為一次性的工作。
- 支持快速應(yīng)用部署。給定一個(gè)部署環(huán)境和目標(biāo)應(yīng)用程序,內(nèi)核裁剪框架可以有效地檢索基線配置和 應(yīng)用配置,并將它們組合成所需的內(nèi)核配置,然后使用生成的配置構(gòu)建廢棄的內(nèi)核。
- 細(xì)粒度配置跟蹤,基于程序計(jì)數(shù)器的跟蹤來識(shí)別基于低級(jí)代碼模式的配置選項(xiàng)。
5.2 內(nèi)核裁剪框架的體系結(jié)構(gòu)
內(nèi)核裁剪框架應(yīng)該同時(shí)具備離/在線系統(tǒng),體系結(jié)構(gòu)如下圖所示:
通過離線系統(tǒng), 配置跟蹤器用于跟蹤部署環(huán)境和應(yīng)用程序所需的配置選項(xiàng),并記錄下來。配置生成器將這些選項(xiàng)處理成基線配置和應(yīng)用配置選項(xiàng),并將它們存儲(chǔ)在配置數(shù)據(jù)庫中。
通過在線系統(tǒng),配置組合器使用基線配置和應(yīng)用配置來生成目標(biāo)內(nèi)核配置,然后,內(nèi)核構(gòu)建器生成裁剪后的Linux內(nèi)核.
5.3 內(nèi)核裁剪框架的實(shí)現(xiàn)可行性
配置跟蹤
內(nèi)核裁剪框架的配置跟蹤器在目標(biāo)應(yīng)用程序驅(qū)動(dòng)的內(nèi)核執(zhí)行期間跟蹤配置選項(xiàng),使用 PC 寄存器捕獲正在執(zhí)行的指令的地址。為了確保被跟蹤的 PC 屬于目標(biāo)應(yīng)用程序,而不是其他進(jìn)程(例如,后臺(tái)服務(wù)) ,可以使用了一個(gè)定制的 init 腳本,該腳本不啟動(dòng)任何其他應(yīng)用程序,只掛載文件系統(tǒng)/tmp、/proc 和/sys ,啟用網(wǎng)絡(luò)接口(lo 和 eth0) ,最后在內(nèi)核引導(dǎo)后直接啟動(dòng)應(yīng)用程序。
同時(shí),可能需要禁用內(nèi)核位址空間配置隨機(jī)載入 ,以便能夠正確地將地址映射到源代碼,但在裁剪后的內(nèi)核中仍然可以使用。然后,將 PC 映射到源代碼語句??杉虞d的內(nèi)核模塊需要額外的處理,可以使用/proc/module 獲取每個(gè)加載的內(nèi)核模塊的起始地址,將這些 PC 映射到內(nèi)核模塊二進(jìn)制中的語句。另一種方法是利用 localmodconfig,但是,localmodconfig 只提供模塊粒度級(jí)別的信息。
最后,將語句歸屬于配置。對(duì)于基于 C 預(yù)處理器的模式 ,分析 C 源文件以提取預(yù)處理器指令,然后檢查這些指令中的語句是否被執(zhí)行。對(duì)于基于 Makefile 的模式 ,確定是否應(yīng)該在對(duì)象文件的粒度上選擇配置選項(xiàng)。例如,如果使用了任何相應(yīng)的文件(bind.o、 achefiles.o 或 daemon.o) ,則需要選擇 CONFIG_CACHEFILES。
配置生成
基線配置和應(yīng)用配置是在離線系統(tǒng)中生成的。如何判斷啟動(dòng)階段結(jié)束呢?可以使用 mmap 將一個(gè)空的存根函數(shù)映射到一個(gè)預(yù)定義地址段,上述的初始化腳本在運(yùn)行目標(biāo)應(yīng)用程序之前調(diào)用調(diào)用存根函數(shù),因此,可能根據(jù) PC 跟蹤中的預(yù)定義地址來識(shí)別引導(dǎo)階段的結(jié)束。
內(nèi)核裁剪框架從應(yīng)用程序中獲取配置選項(xiàng),并過濾掉在引導(dǎo)階段觀察到的與硬件相關(guān)的選項(xiàng)。這些硬件特性是根據(jù)它們?cè)趦?nèi)核源代碼中的位置定義的。不排除這樣的可能性,即與硬件相關(guān)的選項(xiàng)只能在應(yīng)用程序執(zhí)行期間觀察到,例如,它根據(jù)需要加載新的設(shè)備驅(qū)動(dòng)程序。
配置組裝
將基線配置與一個(gè)或多個(gè)應(yīng)用配置組合在一起,可以以生成用于構(gòu)建內(nèi)核的最終配置。首先,將所有 配置選項(xiàng)并入一個(gè)初始配置,然后使用SAT求解器解決它們之間的依賴關(guān)系。嘗試將配置依賴性建模為一個(gè)布爾可滿足性問題,有效配置是指滿足配置選項(xiàng)之間所有指定依賴性的配置。因?yàn)?KConfig 并不確保包含所有選定的選項(xiàng),而是取消選擇未滿足的依賴項(xiàng),所以才要基于 SAT 求解器對(duì)內(nèi)核配置進(jìn)行建模。
內(nèi)核構(gòu)建
使用于Linux的KBuild基于組裝后的配置選項(xiàng)構(gòu)建裁剪內(nèi)核,利用現(xiàn)代make的增量構(gòu)建可以優(yōu)化構(gòu)建時(shí)間,也可以緩存以前的構(gòu)建結(jié)果(例如,目標(biāo)文件和內(nèi)核模塊) ,以避免冗余的編譯和鏈接。當(dāng)發(fā)生配置更改時(shí),只有對(duì)配置選項(xiàng)進(jìn)行更改的模塊重新構(gòu)建,而其他文件可以重用。
6. 小結(jié)
由于操作系統(tǒng)內(nèi)核的不穩(wěn)定性、時(shí)效性較差、完整性問題以及需要人工干預(yù)等原因,Linux內(nèi)核裁剪技術(shù)沒有得到廣泛的應(yīng)用。了解了現(xiàn)有技術(shù)的局限性,嘗試提出一個(gè)Linux內(nèi)核裁剪框架,或許可以解決這些問題。