Moduły jądra do załadowania

Zgodnie z wymaganiami dotyczącymi modułów jądra wprowadzonymi w Androidzie 8.0 wszystkie jądra systemów na chipie (SoC) muszą obsługiwać ładowalne moduły jądra.

Opcje konfiguracji jądra

Aby obsługiwać ładowalne moduły jądra, android-base.config we wszystkich popularnych jądrach zawiera te opcje konfiguracji jądra (lub ich odpowiedniki w wersji jądra):

CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_MODVERSIONS=y

Te opcje muszą być włączone we wszystkich jądrach urządzeń. Moduły jądra powinny też obsługiwać wyładowywanie i ponowne wczytywanie, o ile to możliwe.

Podpisywanie modułów

Podpisywanie modułów nie jest obsługiwane w przypadku modułów dostawców GKI. Na urządzeniach, które muszą obsługiwać uruchamianie w trybie zweryfikowanym, Android wymaga, aby moduły jądra znajdowały się na partycjach z włączoną funkcją dm-verity. Dzięki temu nie trzeba podpisywać poszczególnych modułów w celu potwierdzenia ich autentyczności. Android 13 wprowadził koncepcję modułów GKI. Moduł GKI korzysta z infrastruktury podpisywania w czasie kompilacji jądra, aby rozróżniać GKI od innych modułów w czasie wykonywania. Moduły bez podpisu mogą się wczytywać, o ile używają tylko symboli znajdujących się na liście dozwolonych lub dostarczonych przez inne moduły bez podpisu. Aby ułatwić podpisywanie modułów GKI podczas kompilacji GKI za pomocą pary kluczy kompilacji jądra, w konfiguracji jądra GKI włączono CONFIG_MODULE_SIG_ALL=y. Aby uniknąć podpisywania modułów innych niż GKI podczas kompilowania jądra urządzenia, musisz dodać # CONFIG_MODULE_SIG_ALL is not set jako część fragmentów konfiguracji jądra.

Lokalizacje plików

Chociaż Android 7.x i starsze nie wymagają korzystania z modułów jądra (i obsługują insmodrmmod), w Androidzie 8.x i nowszych zalecamy używanie modułów jądra w ekosystemie. Tabela poniżej przedstawia potencjalne wymagania dotyczące obsługi urządzeń peryferyjnych w ramach trzech trybów uruchamiania Androida.

Tryb uruchamiania Miejsce na dane Wyświetlacz Klawiatura Bateria PMIC Ekran dotykowy NFC, Wi-Fi,
Bluetooth
Czujniki Aparat
Odzyskiwanie
Ładowarka
Android

Oprócz dostępności w trybach rozruchu Androida moduły jądra mogą być też kategoryzowane według tego, kto jest ich właścicielem (dostawca SoC lub ODM). Jeśli używane są moduły jądra, wymagania dotyczące ich umieszczenia w systemie plików są następujące:

  • Wszystkie jądra powinny mieć wbudowane wsparcie dla uruchamiania i montowania partycji.
  • Moduły jądra muszą być ładowane z partycji tylko do odczytu.
  • W przypadku urządzeń wymagających uruchamiania w trybie zweryfikowanym moduły jądra powinny być ładowane z partycji zweryfikowanych.
  • Moduły jądra nie powinny znajdować się w katalogu /system.
  • Moduły GKI wymagane na urządzeniu powinny być ładowane z /system/lib/modules, który jest linkiem symbolicznym do /system_dlkm/lib/modules.
  • Moduły jądra od dostawcy układu SoC, które są wymagane do pełnego systemu Android lub trybów ładowarki, powinny znajdować się w /vendor/lib/modules.
  • Jeśli partycja ODM istnieje, moduły jądra z ODM, które są wymagane w trybie pełnego Androida lub trybie ładowania, powinny znajdować się w /odm/lib/modules. W przeciwnym razie te moduły powinny znajdować się w folderze /vendor/lib/modules.
  • Moduły jądra od dostawcy SoC i ODM, które są wymagane w trybie odzyskiwania, powinny znajdować się w pliku ramfs w katalogu /lib/modules.
  • Moduły jądra wymagane zarówno w trybie odzyskiwania, jak i w pełnym systemie Android lub w trybie ładowania powinny znajdować się zarówno w partycji rootfs, jak i w partycji /vendor lub /odm (jak opisano powyżej).
  • Moduły jądra używane w trybie odzyskiwania nie powinny zależeć od modułów znajdujących się tylko w /vendor lub /odm, ponieważ te partycje nie są montowane w tym trybie.
  • Moduł jądra dostawcy SoC nie powinien zależeć od modułów jądra ODM.

W Androidzie 7.x i starszych partycje /vendor/odm nie są montowane wcześniej. W Androidzie 8.x i nowszych, aby umożliwić wczytywanie modułów z tych partycji, wprowadzono postanowienia dotyczące wczesnego montowania partycji na urządzeniach z wsparciem i bez obsługi testów A/B. Dzięki temu partycje są montowane zarówno w trybie Androida, jak i w trybie ładowania.

Obsługa systemu kompilacji Androida

BoardConfig.mk kompilacja Androida definiuje zmienną BOARD_VENDOR_KERNEL_MODULES, która zawiera pełną listę modułów jądra przeznaczonych dla obrazu dostawcy. Modułów wymienionych w tej zmiennej kopia jest tworzona w obrazie dostawcy w miejscu /lib/modules/, a po zamontowaniu w Androidzie są one widoczne w miejscu /vendor/lib/modules (zgodnie z wymaganiami wymienionymi powyżej). Przykładowa konfiguracja modułów dostawcy jądra:

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

W tym przykładzie repozytorium z gotowym modułem jądra dostawcy jest mapowane na kompilację Androida w wymienionej powyżej lokalizacji.

Obraz odzyskiwania może zawierać podzbiór modułów dostawcy. Kompilacja Androida definiuje zmienną BOARD_RECOVERY_KERNEL_MODULES dla tych modułów. Przykład:

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

Kompilacja Androida uruchamia narzędzie depmod, aby wygenerować wymagane pliki modules.dep w katalogach /vendor/lib/modules/lib/modules (recovery ramfs).

Ładowanie modułów i uwzględnianie wersji

Załaduj wszystkie moduły jądra w jednym przejściu z poziomu init.rc*, wywołując modprobe -a. Dzięki temu unikniesz nadmiernego obciążania procesora podczas wielokrotnego inicjowania środowiska wykonawczego C dla binarnego pliku modprobe. Zdarzenie early-init można zmodyfikować, aby wywołać zdarzenie modprobe:

on early-init
    exec u:r:vendor_modprobe:s0 -- /vendor/bin/modprobe -a -d \
        /vendor/lib/modules module_a module_b module_c ...

Zazwyczaj moduł jądra musi być skompilowany z jądrem, z którym ma być używany (w przeciwnym razie jądro odmówi załadowania modułu). CONFIG_MODVERSIONS oferuje obejście problemu przez wykrywanie awarii w interfejsie binarnym aplikacji (ABI). Ta funkcja oblicza wartość CRC dla prototypu każdego wyeksportowanego symbolu w jądrze i przechowuje wartości jako część jądra. W przypadku symboli używanych przez moduł jądra wartości są również przechowywane w module jądra. Podczas ładowania modułu wartości symboli używanych przez moduł są porównywane z wartościami w jądrze. Jeśli wartości są zgodne, moduł jest wczytywany. W przeciwnym razie wczytywanie kończy się niepowodzeniem.

Aby umożliwić aktualizowanie obrazu jądra oddzielnie od obrazu dostawcy, włącz CONFIG_MODVERSIONS. Dzięki temu można wprowadzać drobne aktualizacje jądra (np. poprawki błędów z wersji LTS) przy zachowaniu zgodności z dotychczasowymi modułami jądra w obrazie dostawcy. Jednak CONFIG_MODVERSIONS nie naprawia samodzielnie naruszenia ABI. Jeśli prototyp wyeksportowanego symbolu w jądrze ulegnie zmianie (z powodu modyfikacji źródła lub zmiany konfiguracji jądra), spowoduje to utratę zgodności z modułami jądra, które używają tego symbolu. W takich przypadkach należy ponownie skompilować moduł jądra.

Na przykład struktura task_struct w rdzeniu (zdefiniowana w include/linux/sched.h) zawiera wiele pól uwzględnianych warunkowo w zależności od konfiguracji rdzenia. Pole sched_info jest obecne tylko wtedy, gdy włączona jest zasada CONFIG_SCHED_INFO (co ma miejsce, gdy włączone są zasady CONFIG_SCHEDSTATS lub CONFIG_TASK_DELAY_ACCT). Jeśli te opcje konfiguracji zmienią stan, układ struktury task_struct ulegnie zmianie, a wszystkie wyeksportowane interfejsy jądra, które używają task_struct, zostaną zmienione (na przykład set_cpus_allowed_ptrkernel/sched/core.c). Spowoduje to utratę zgodności z wcześniej skompilowanymi modułami jądra, które używają tych interfejsów. Konieczne będzie ich ponowne skompilowanie z nową konfiguracją jądra.

Więcej informacji o CONFIG_MODVERSIONS znajdziesz w dokumentacji w drzewie jądra na stronie Documentation/kbuild/modules.rst.