Ładowalne moduły jądra

W ramach wymagań jądra modułów wprowadzonych w systemie Android 8.0 wszystkie jądra systemu na chipie (SoC) muszą obsługiwać ładowalne moduły jądra.

Opcje konfiguracji jądra

Aby obsługiwać ładowalne moduły jądra, plik android-base.config we wszystkich popularnych jądrach zawiera następujące opcje kernel-config (lub ich odpowiedniki w wersji jądra):

CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_MODVERSIONS=y

Wszystkie jądra urządzeń muszą włączyć te opcje. Moduły jądra powinny również obsługiwać rozładowywanie i ponowne ładowanie, gdy tylko jest to możliwe.

Podpisanie modułu

Podpisywanie modułów nie jest obsługiwane w przypadku modułów dostawcy GKI. Na urządzeniach wymaganych do obsługi zweryfikowanego rozruchu system Android wymaga, aby moduły jądra znajdowały się na partycjach z włączoną funkcją dm-verity. Eliminuje to potrzebę podpisywania poszczególnych modułów w celu sprawdzenia ich autentyczności. W Androidzie 13 wprowadzono koncepcję modułów GKI. Moduły GKI korzystają z infrastruktury podpisywania czasu kompilacji jądra, aby odróżnić GKI od innych modułów w czasie wykonywania. Niepodpisane moduły mogą być ładowane pod warunkiem, że używają tylko symboli znajdujących się na liście dozwolonych lub dostarczonych przez inne niepodpisane moduły. Aby ułatwić podpisywanie modułów GKI podczas kompilacji GKI przy użyciu pary kluczy kompilacji jądra, konfiguracja jądra GKI włączyła CONFIG_MODULE_SIG_ALL=y . Aby uniknąć podpisywania modułów innych niż GKI podczas kompilacji jądra urządzenia, musisz dodać # CONFIG_MODULE_SIG_ALL is not set jako część fragmentów konfiguracji jądra.

Lokalizacje plików

Podczas gdy Android 7.x i starsze wersje nie wymagają modułów jądra (i obejmują obsługę insmod i rmmod ), Android 8.x i nowsze zalecają używanie modułów jądra w ekosystemie. Poniższa tabela przedstawia potencjalną obsługę urządzeń peryferyjnych specyficznych dla danej płyty, wymaganą w trzech trybach rozruchu systemu Android.

Tryb rozruchu Składowanie Wyświetlacz Klawiatura Bateria PMIC Ekran dotykowy NFC, Wi-Fi,
Bluetooth
Czujniki Kamera
Powrót do zdrowia
Ładowarka
Android

Oprócz dostępności w trybach rozruchu systemu Android, moduły jądra można również kategoryzować 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ć wbudowaną obsługę uruchamiania i montowania partycji.
  • Moduły jądra muszą być ładowane z partycji tylko do odczytu.
  • W przypadku urządzeń wymagających zweryfikowanego rozruchu moduły jądra powinny być ładowane ze zweryfikowanych partycji.
  • Moduły jądra nie powinny znajdować się w /system .
  • Wymagane dla urządzenia moduły GKI należy załadować z /system/lib/modules , który jest dowiązaniem symbolicznym do /system_dlkm/lib/modules .
  • Moduły jądra od dostawcy SoC, które są wymagane do pełnego trybu Androida lub ładowarki, powinny znajdować się w /vendor/lib/modules .
  • Jeśli istnieje partycja ODM, moduły jądra z ODM wymagane do pełnego trybu Androida lub ładowarki powinny znajdować się w /odm/lib/modules . W przeciwnym razie moduły te powinny znajdować się w /vendor/lib/modules .
  • Moduły jądra od dostawcy SoC i ODM wymagane do trybu odzyskiwania powinny znajdować się w ramfs odzyskiwania w /lib/modules .
  • Moduły jądra wymagane zarówno w trybie odzyskiwania, jak i w trybie pełnego systemu Android lub w trybie ładowarki powinny znajdować się zarówno w plikach rootfs odzyskiwania, jak i na partycjach /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 trybie odzyskiwania.
  • Moduły jądra dostawcy SoC nie powinny zależeć od modułów jądra ODM.

W systemie Android 7.xi starszym partycje /vendor i /odm nie są montowane wcześniej. Aby umożliwić ładowanie modułów z tych partycji w systemie Android 8.x i nowszych wersjach, wprowadzono możliwość wcześniejszego montowania partycji zarówno dla urządzeń innych niż A/B, jak i A/B . Zapewnia to również, że partycje są montowane zarówno w trybie Android, jak i Charger.

Obsługa systemu kompilacji Androida

W BoardConfig.mk kompilacja systemu Android definiuje zmienną BOARD_VENDOR_KERNEL_MODULES , która udostępnia pełną listę modułów jądra przeznaczonych dla obrazu dostawcy. Moduły wymienione w tej zmiennej są kopiowane do obrazu dostawcy w /lib/modules/ , a po zamontowaniu w systemie Android pojawiają się w /vendor/lib/modules (zgodnie z powyższymi wymaganiami). Przykładowa konfiguracja modułów jądra dostawcy:

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 gotowe repozytorium modułu jądra dostawcy jest mapowane na kompilację systemu Android w lokalizacji wymienionej powyżej.

Obraz odzyskiwania może zawierać podzbiór modułów dostawcy. Wersja 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 depmod w celu wygenerowania wymaganych plików modules.dep w /vendor/lib/modules i /lib/modules ( recovery ramfs ).

Ładowanie modułów i wersjonowanie

Załaduj wszystkie moduły jądra w jednym przebiegu z init.rc* , wywołując modprobe -a . Pozwala to uniknąć narzutu związanego z wielokrotnym inicjowaniem środowiska wykonawczego C dla pliku binarnego modprobe . Zdarzenie early-init można zmodyfikować, aby wywołać 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 moduł ma być używany (w przeciwnym razie jądro odmówi załadowania modułu). CONFIG_MODVERSIONS zapewnia obejście polegające na wykrywaniu usterek w binarnym interfejsie aplikacji (ABI). Ta funkcja oblicza wartość cyklicznej kontroli nadmiarowej (CRC) dla prototypu każdego eksportowanego 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. Po załadowaniu modułu wartości symboli używanych przez moduł są porównywane z tymi w jądrze. Jeśli wartości się zgadzają, moduł zostaje załadowany; w przeciwnym razie obciążenie nie powiedzie się.

Aby umożliwić aktualizację obrazu jądra niezależnie od obrazu dostawcy, włącz CONFIG_MODVERSIONS . Dzięki temu możliwe jest wprowadzanie niewielkich aktualizacji jądra (takich jak poprawki błędów z LTS) przy jednoczesnym zachowaniu zgodności z istniejącymi modułami jądra w obrazie dostawcy. Jednak CONFIG_MODVERSIONS sama w sobie nie naprawia awarii ABI. Jeśli prototyp wyeksportowanego symbolu w jądrze ulegnie zmianie, albo z powodu modyfikacji źródła, albo z powodu zmiany konfiguracji jądra, powoduje to zerwanie kompatybilności z modułami jądra, które używają tego symbolu. W takich przypadkach moduł jądra musi zostać przekompilowany.

Na przykład struktura task_struct w jądrze (zdefiniowana w include/linux/sched.h ) zawiera wiele pól dołączanych warunkowo w zależności od konfiguracji jądra. Pole sched_info jest obecne tylko wtedy, gdy włączone jest CONFIG_SCHED_INFO (co ma miejsce, gdy włączone są CONFIG_SCHEDSTATS lub CONFIG_TASK_DELAY_ACCT ). Jeśli stan tych opcji konfiguracyjnych ulegnie zmianie, układ struktury task_struct ulegnie zmianie i wszelkie wyeksportowane interfejsy z jądra korzystające z task_struct ulegną zmianie (na przykład set_cpus_allowed_ptr w kernel/sched/core.c ). Kompatybilność z wcześniej skompilowanymi modułami jądra, które korzystają z tych interfejsów, ulega awarii, co wymaga przebudowania tych modułów z nową konfiguracją jądra.

Więcej szczegółów na temat CONFIG_MODVERSIONS można znaleźć w dokumentacji drzewa jądra w Documentation/kbuild/modules.rst .