作為 Android 8.0 中引入的模塊內核要求的一部分,所有片上系統 (SoC) 內核都必須支持可加載內核模塊。
內核配置選項
為了支持可加載內核模塊,所有常見內核中的android-base.cfg都包含以下內核配置選項(或其內核版本等效項):
CONFIG_MODULES=y CONFIG_MODULE_UNLOAD=y CONFIG_MODVERSIONS=y
所有設備內核都必須啟用這些選項。內核模塊還應盡可能支持卸載和重新加載。
模塊簽名
GKI 供應商模塊不支持模塊簽名。在需要支持驗證啟動的設備上,Android 要求內核模塊位於啟用了 dm-verity 的分區中。這消除了對單個模塊的真實性進行簽名的需要。
文件位置
雖然 Android 7.x 及更低版本不強制要求內核模塊(包括對insmod
和rmmod
的支持),但 Android 8.x 及更高版本建議在生態系統中使用內核模塊。下表顯示了三種 Android 啟動模式所需的潛在板特定外設支持。
引導模式 | 貯存 | 展示 | 鍵盤 | 電池 | PMIC | 觸摸屏 | NFC、無線網絡、 藍牙 | 傳感器 | 相機 |
---|---|---|---|---|---|---|---|---|---|
恢復 | |||||||||
充電器 | |||||||||
安卓 |
除了在 Android 啟動模式下的可用性之外,內核模塊還可以按擁有者(SoC 供應商或 ODM)進行分類。如果使用內核模塊,它們在文件系統中的放置要求如下:
- 所有內核都應該具有對引導和掛載分區的內置支持。
- 內核模塊必須從只讀分區加載。
- 對於需要驗證啟動的設備,內核模塊應該從驗證分區加載。
- 內核模塊不應位於
/system
。 - 完整 Android 或 Charger 模式所需的 SoC 供應商的內核模塊應位於
/vendor/lib/modules
中。 - 如果存在 ODM 分區,完整 Android 或 Charger 模式所需的 ODM 內核模塊應位於
/odm/lib/modules
中。否則,這些模塊應位於/vendor/lib/modules
中。 - 恢復模式所需的 SoC 供應商和 ODM 的內核模塊應位於
/lib/modules
的恢復ramfs
中。 - 恢復模式和完整 Android 或 Charger 模式所需的內核模塊應該存在於恢復
rootfs
和/vendor
或/odm
分區中(如上所述)。 - 在恢復模式下使用的內核模塊不應依賴於僅位於
/vendor
或/odm
的模塊,因為這些分區不會在恢復模式下掛載。 - SoC 供應商內核模塊不應依賴於 ODM 內核模塊。
在 Android 7.x 及更低版本中, /vendor
和/odm
分區不會提前掛載。在 Android 8.x 及更高版本中,為了使從這些分區加載模塊成為可能,已經為非 A/B 和 A/B 設備提前安裝了分區。這也確保了分區在 Android 和 Charger 模式下都掛載。
Android 構建系統支持
在BoardConfig.mk
中,Android 構建定義了一個BOARD_VENDOR_KERNEL_MODULES
變量,該變量提供了用於供應商映像的內核模塊的完整列表。此變量中列出的模塊被複製到/lib/modules/
的供應商映像中,並在 Android 中掛載後出現在/vendor/lib/modules
中(根據上述要求)。供應商內核模塊的示例配置:
vendor_lkm_dir := device/$(vendor)/lkm-4.x BOARD_VENDOR_KERNEL_MODULES := \ $(vendor_lkm_dir)/vendor_module_a.ko \ $(vendor_lkm_dir)/vendor_module_b.ko \ $(vendor_lkm_dir)/vendor_module_c.ko
在此示例中,供應商內核模塊預構建存儲庫被映射到上面列出的位置的 Android 構建中。
恢復映像可能包含供應商模塊的子集。 Android 構建為這些模塊定義了變量BOARD_RECOVERY_KERNEL_MODULES
。例子:
vendor_lkm_dir := device/$(vendor)/lkm-4.x BOARD_RECOVERY_KERNEL_MODULES := \ $(vendor_lkm_dir)/vendor_module_a.ko \ $(vendor_lkm_dir)/vendor_module_b.ko
Android 構建負責運行depmod
以在/vendor/lib/modules
和/lib/modules
( recovery ramfs
) 中生成所需的modules.dep
文件。
模塊加載和版本控制
通過調用modprobe -a
從init.rc*
一次性加載所有內核模塊。這避免了為modprobe
二進製文件重複初始化 C 運行時環境的開銷。可以修改early-init
事件以調用modprobe
:
on early-init exec u:r:vendor_modprobe:s0 -- /vendor/bin/modprobe -a -d \ /vendor/lib/modules module_a module_b module_c ...
通常,內核模塊必須與要使用該模塊的內核一起編譯(否則內核拒絕加載該模塊)。 CONFIG_MODVERSIONS
通過檢測應用程序二進制接口 (ABI) 中的損壞來提供解決方法。此功能為內核中每個導出符號的原型計算循環冗餘校驗 (CRC) 值,並將值存儲為內核的一部分;對於內核模塊使用的符號,值也存儲在內核模塊中。加載模塊時,模塊使用的符號值與內核中的值進行比較。如果值匹配,則加載模塊;否則加載失敗。
要獨立於供應商映像啟用內核映像的更新,請啟用CONFIG_MODVERSIONS
。這樣做允許對內核進行小幅更新(例如來自 LTS 的錯誤修復),同時保持與供應商映像中現有內核模塊的兼容性。但是, CONFIG_MODVERSIONS
本身並不能修復 ABI 損壞。如果內核中導出符號的原型發生更改,無論是由於源代碼的修改還是內核配置的更改,都會破壞與使用該符號的內核模塊的兼容性。在這種情況下,必須重新編譯內核模塊。
例如,內核中的task_struct
結構(在include/linux/sched.h
中定義)包含許多根據內核配置有條件地包含的字段。 sched_info
字段僅在啟用CONFIG_SCHED_INFO
時出現(這在啟用CONFIG_SCHEDSTATS
或CONFIG_TASK_DELAY_ACCT
時發生)。如果這些配置選項改變了狀態, task_struct
結構的佈局也會改變,並且任何從內核導出的使用task_struct
的接口都會改變(例如kernel/sched/core.c
set_cpus_allowed_ptr
。與使用這些接口的先前編譯的內核模塊的兼容性中斷,需要使用新的內核配置重新構建這些模塊。
有關CONFIG_MODVERSIONS
的更多詳細信息,請參閱位於Documentation/kbuild/modules.rst
的內核樹中的文檔。