Linux 核心裁剪框架初探

roc_guo發表於2022-08-05

大約是在2000年的時候,老碼農還很年輕,當時希望將Linux 作為手機的作業系統, 於是才有了進行核心裁剪的想法並輔助實踐,效果尚好,已經能在PDA上執行手機的功能了。一晃20多年過去了,Linux 已經有了太大的變化,核心裁剪的技術和方式也有了較大的不同。
Linux 的核心裁剪是為了減少目標應用中不需要的核心程式碼,在安全性和高效能(快速啟動時間和減少記憶體佔用)方面有著顯著的好處。但是,現有的核心裁剪技術有其侷限性,有沒有核心裁剪的框架化方法呢?

1. 關於核心裁剪

近年來,Linux作業系統在複雜性和規模上都在增長。然而,一個應用程式通常只需要一部分 OS 功能,眾多的應用需求導致了Linux核心的膨脹。作業系統的核心膨脹同樣導致了安全性隱患、啟動時間變長和記憶體使用的增加。
隨著服務化和微服務的流行,進一步提出了對核心裁剪的需求。在這些場景中,虛擬機器執行小型應用程式,每個應用程式往往是“微型”的,核心佔用較小,一些虛擬化技術要為目標應用程式提供最簡單的 Linux 核心。
鑑於作業系統的複雜性,透過手工挑選核心特性來裁剪核心有些不切實際。例如,Linux 有超過14,000+個配置選項(截至 v4.14) ,每年都會引入數百個新選項。核心配置器(例如 KConfig)只提供用於選擇配置選項的使用者介面。鑑於糟糕的可用性和文件的不完整性,使用者很難選擇最小且實用的核心配置。
現有的核心裁剪技術一般遵循三個步驟:

  1. 執行目標應用程式的工作負載並跟蹤在應用程式執行期間執行的核心程式碼;
  2. 分析跟蹤並確定目標應用程式所需的核心程式碼,
  3. 組裝一個只包含應用程式所需程式碼的核心裁剪。

配置驅動的是核心裁剪的一般方法,大多數現有的工具使用配置驅動技術,因為它們是為數不多的可以產生穩定核心的技術之一。配置驅動的核心過載根據功能特性減少了核心程式碼,配置選項對應於核心的功能,裁剪後的核心只包含用於支援目標應用程式工作負載的功能。
然而,儘管核心裁剪技術在安全性和效能方面非常吸引人,但在實踐中並沒有得到廣泛採用。這並不是因為缺乏需求,實際上,許多雲供應商手工編寫 Linux 核心來減少程式碼,但一般不如核心裁剪技術有效。

2. 現有核心裁剪技術的限制

現有核心裁剪技術有五個主要的侷限性。

在引導階段不可見。現有技術只能在核心引導後啟動,依賴於 ftrace,因此無法觀察在引導階段載入了哪些核心程式碼。如果核心中缺少關鍵模組,核心通常無法啟動,而大量的核心功能特性只能透過觀察引導階段來捕獲。此外,關於效能和安全性同樣只在引導時載入(例如,用於多核支援的 CONFIGSCHEDMC 和 CONFIGSECURITYNETWORK) ,導致了效能和安全性降低。
缺乏對應用程式部署的快速支援。使用現有的工具,面向核心裁剪來部署一個新的應用程式需要完成跟蹤、分析和組裝這三個步驟。這個過程非常耗時,有可能需要幾個小時甚至幾天,阻礙了應用部署的敏捷性。
粒度較粗。使用ftrace 只能在函式級跟蹤核心程式碼,粒度太粗,無法跟蹤影響函式內程式碼的配置選項。
覆蓋不完全。因為使用動態跟蹤,所以需要應用程式工作負載來驅動核心的程式碼執行,以最大限度地擴大覆蓋範圍。然而,基準測試覆蓋是具有挑戰性的,而且,如果應用程式有在跟蹤期間沒有觀察到的核心程式碼,那麼裁剪後的核心可能會在執行時崩潰。
沒有區分執行依賴,可能存在冗餘。即使實際上可能並不需要執行的程式碼,也可能包含在了核心功能特性中,例如,可能初始化了第二個檔案系統。
前三個限制是可以克服的,可以透過改進設計和工具加以解決,而後兩個限制是在所難免,需要在具體的技術之外作出努力。

3. Linux 的核心配置
3.1配置選項

核心配置由一組配置選項組成。一個核心模組可以有多個選項,每個選項都控制哪些程式碼將包含在最終的核心二進位制檔案中。
配置選項控制核心程式碼的不同粒度,例如由 C 前處理器實現的語句和函式,以及基於 Makefile 實現的物件檔案。C 前處理器根據 #ifdef/#ifndef 選擇程式碼塊,配置選項用作宏定義,以確定是否在編譯後的核心中包含這樣條件的程式碼塊,可以是語句粒度或者函式粒度。Makefile 用於確定是否在編譯後的核心中包含某些物件檔案,例如, CONFIG_CACHEFILES 就是 Makefile 中的配置選項。
語句級配置選項不能透過現有核心裁剪工具所使用的函式級跟蹤來識別。事實上,Linux 4.14 中30%左右 的 C 前處理器是語句級選項。
隨著核心程式碼和功能特性的快速增長,核心中的配置選項數量也在迅速增加,以 Linux核心3.0以上版本都有1萬多個配置選項。

3.2. 配置語言

Linux核心使用KConfig 配置語言來指示編譯器在編譯後的核心中包含哪些程式碼,允許定義配置選項以及它們之間的依賴關係。
KConfig 中配置選項的值可能是 bool、 tristate 或 constant。bool 意味著程式碼要麼被靜態編譯成核心二進位制檔案,要麼被排除在外,而 tristate 允許程式碼被編譯成一個可載入核心模組,即一個可以在執行時載入的獨立物件。constant可以為核心程式碼變數提供字串或數值。一個選項可以依賴於另一個選項,KConfig 使用了一個遞迴過程,透過遞迴選擇和取消依賴項。最終的核心配置具有有效的依賴關係,但可能與使用者輸入不同。

3.3. 配置模板

Linux 核心附帶了許多手工製作的配置模板。但是,由於配置模板的硬編碼特性並且需要人工干預,它們不能適應不同的硬體平臺,也不瞭解應用程式的需求。例如,由 tinyconfig 構建的核心不能在標準硬體上啟動,更不用說支援其他應用了。有些工具將 localmodconfig 視為最小化的配置,但是,localmodconfig 與靜態配置模板具有相同的侷限性,它不會啟動控制語句級或函式級 C 前處理器的配置選項,也不會處理可載入的核心模組。
kvmconfig 和 xenconfig 模板是為在 KVM 和 Xen 上執行的核心而定製的。它們提供例如底層虛擬化和硬體環境的領域知識。

3.4. 雲中的 Linux 核心配置

Linux 是雲服務中占主導地位的作業系統核心,雲供應商都在一定程度上放棄了普通的 Linux 核心。雲廠商的定製通常是透過直接刪除可載入的核心模組來完成的,手工修剪核心模組二進位制檔案的問題是可能會違反依賴關係。重要的是,基於應用程式需求可以進一步裁剪核心。例如,Amazon FireCracker 核心是一個專門用於函式即服務的微型虛擬機器,使用 HTTPD 作為目標應用程式,在保證功能和效能提升的同時,使核心裁剪實現了更大程度的最小化。

4. 核心裁剪的思考

針對侷限一,是否可以使用來自 QEMU 的指令級跟蹤來實現引導階段的可見性呢?這樣,就可以跟蹤核心程式碼並將其對映到核心配置選項。既然引導階段對於生成可引導核心至關重要,使用 hypervisor 提供的跟蹤特性來獲得端到端的可觀察性並生成穩定的核心。

針對侷限二,根據在NLP深度學習中的經驗,可以使用離線和線上結合的方法,給定一組目標應用程式,可以直接離線生成的App 配置,再和基線配置組合成完整的核心配置,從而生成一個裁剪後的核心。這種可組合效能夠透過重用應用配置和以前構建的檔案(例如核心模組)來增量地構建新核心。如果目標應用程式的配置已知,就可以在幾十秒內完成核心裁剪。

針對侷限三,使用指令級跟蹤可以解決控制函式內部功能特性的核心配置選項,指令級跟蹤的開銷對於執行測試套件和效能基準來說是可以接受的。

針對侷限四,使用基於動態跟蹤的一個基本限制是測試套件和基準的不完善,許多開源應用程式測試套件的程式碼覆蓋率較低。組合不同的工作負載來驅動應用程式可以在一定程度上減輕這種限制。

針對侷限五,透過刪除在基線核心中執行但在實際部署執行時不需要的核心模組,可以使用特定於領域的資訊進一步載入核心。以 Xen 和 KVM 為例,可以基於 xenconfig 和 kvmconfig 配置模板進一步減少核心大小。面向應用程式的核心裁剪可以進一步減少核心大小甚至廣泛地定製的核心程式碼。

5 核心裁剪框架初探

核心裁剪框架的原理沒有變,仍然是跟蹤目標應用工作負載的核心佔用情況,以確定所需的核心選項。

5.1 核心裁剪框架的核心特性

核心裁剪框架大概可以具備以下特性:
端到端的可見性。利用虛擬機器監控程式的可見性來實現端到端的觀察,可以跟蹤核心引導階段和應用程式工作負載,可以嘗試在QEMU 的基礎上建造Linux核心的裁剪框架。
可組合性。一個核心思想是透過將核心配置劃分為若干組配置集,使核心配置可以組合,用於在給定的部署環境上引導核心,也可以用於目標應用程式所需的配置選項。配置集分為兩種:基線配置和應用配置。基線配置不一定是在特定硬體上引導所需的最小配置集,而是在引導階段跟蹤的一組配置選項。基線配置可以與一個或多個應用配置組合在一起,以生成最終的核心配置。
可重用性。基線配置和應用配置都可以儲存在資料庫中,並且只要部署環境和應用程式的二進位制檔案不變就可以重用。這種可重用性避免了重複跟蹤工作負載的執行,使得配置集的建立成為一次性的工作。
支援快速應用部署。給定一個部署環境和目標應用程式,核心裁剪框架可以有效地檢索基線配置和 應用配置,並將它們組合成所需的核心配置,然後使用生成的配置構建廢棄的核心。
細粒度配置跟蹤,基於程式計數器的跟蹤來識別基於低階程式碼模式的配置選項。

5.2 核心裁剪框架的體系結構

核心裁剪框架應該同時具備離/線上系統,體系結構如下圖所示:
Linux 核心裁剪框架初探Linux 核心裁剪框架初探
透過離線系統, 配置跟蹤器用於跟蹤部署環境和應用程式所需的配置選項,並記錄下來。配置生成器將這些選項處理成基線配置和應用配置選項,並將它們儲存在配置資料庫中。
透過線上系統,配置組合器使用基線配置和應用配置來生成目標核心配置,然後,核心構建器生成裁剪後的Linux核心.

5.3 核心裁剪框架的實現可行性

配置跟蹤
核心裁剪框架的配置跟蹤器在目標應用程式驅動的核心執行期間跟蹤配置選項,使用 PC 暫存器捕獲正在執行的指令的地址。為了確保被跟蹤的 PC 屬於目標應用程式,而不是其他程式(例如,後臺服務) ,可以使用了一個定製的 init ,該 不啟動任何其他應用程式,只掛載檔案系統/tmp、/proc 和/sys ,啟用網路介面(lo 和 eth0) ,最後在核心引導後直接啟動應用程式。
同時,可能需要禁用核心位址空間配置隨機載入 ,以便能夠正確地將地址對映到原始碼,但在裁剪後的核心中仍然可以使用。然後,將 PC 對映到原始碼語句。可載入的核心模組需要額外的處理,可以使用/proc/module 獲取每個載入的核心模組的起始地址,將這些 PC 對映到核心模組二進位制中的語句。另一種方法是利用 localmodconfig,但是,localmodconfig 只提供模組粒度級別的資訊。
最後,將語句歸屬於配置。對於基於 C 前處理器的模式 ,分析 C 原始檔以提取前處理器指令,然後檢查這些指令中的語句是否被執行。對於基於 Makefile 的模式 ,確定是否應該在物件檔案的粒度上選擇配置選項。例如,如果使用了任何相應的檔案(bind.o、 achefiles.o 或 daemon.o) ,則需要選擇 CONFIG_CACHEFILES。
配置生成
基線配置和應用配置是在離線系統中生成的。如何判斷啟動階段結束呢?可以使用 mmap 將一個空的存根函式對映到一個預定義地址段,上述的初始化指令碼在執行目標應用程式之前呼叫呼叫存根函式,因此,可能根據 PC 跟蹤中的預定義地址來識別引導階段的結束。
核心裁剪框架從應用程式中獲取配置選項,並過濾掉在引導階段觀察到的與硬體相關的選項。這些硬體特性是根據它們在核心原始碼中的位置定義的。不排除這樣的可能性,即與硬體相關的選項只能在應用程式執行期間觀察到,例如,它根據需要載入新的裝置驅動程式。
配置組裝
將基線配置與一個或多個應用配置組合在一起,可以以生成用於構建核心的最終配置。首先,將所有 配置選項併入一個初始配置,然後使用SAT求解器解決它們之間的依賴關係。嘗試將配置依賴性建模為一個布林可滿足性問題,有效配置是指滿足配置選項之間所有指定依賴性的配置。因為 KConfig 並不確保包含所有選定的選項,而是取消選擇未滿足的依賴項,所以才要基於 SAT 求解器對核心配置進行建模。
核心構建
使用於Linux的KBuild基於組裝後的配置選項構建裁剪核心,利用現代make的增量構建可以最佳化構建時間,也可以快取以前的構建結果(例如,目標檔案和核心模組) ,以避免冗餘的編譯和連結。當發生配置更改時,只有對配置選項進行更改的模組重新構建,而其他檔案可以重用。

6. 小結

由於作業系統核心的不穩定性、時效性較差、完整性問題以及需要人工干預等原因,Linux核心裁剪技術沒有得到廣泛的應用。瞭解了現有技術的侷限性,嘗試提出一個Linux核心裁剪框架,或許可以解決這些問題。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2909216/,如需轉載,請註明出處,否則將追究法律責任。

相關文章