Загружаемые модули ядра

Согласно требованиям к ядру, введенным в Android 8.0, все ядра систем на кристалле должны поддерживать загружаемые модули ядра.

Варианты конфигурации ядра

Чтобы поддерживать загружаемые модули ядра, файл android-base.config во всех распространенных ядрах содержит следующие параметры конфигурации ядра (или их эквиваленты для определенной версии ядра):

CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_MODVERSIONS=y

Эти параметры должны быть включены во всех ядрах устройств. Модули ядра также должны поддерживать выгрузку и перезагрузку, если это возможно.

Подписание модулей

Подпись модулей не поддерживается для модулей поставщика GKI. На устройствах, которые должны поддерживать проверку при запуске, ОС Android требует, чтобы модули ядра находились в разделах, в которых включена функция dm-verity. Это избавляет от необходимости подписывать отдельные модули для подтверждения их подлинности. В Android 13 появились модули GKI. Модули GKI используют инфраструктуру подписи времени сборки ядра, чтобы различать GKI и другие модули во время выполнения. Неподписанные модули могут загружаться, если они используют только символы, которые есть в белом списке или предоставлены другими неподписанными модулями. Чтобы упростить подписание модулей GKI во время сборки GKI с помощью пары ключей времени сборки ядра, в конфигурации ядра GKI включен параметр CONFIG_MODULE_SIG_ALL=y. Чтобы не подписывать модули, не относящиеся к GKI, во время сборки ядра устройства, добавьте # CONFIG_MODULE_SIG_ALL is not set в фрагменты конфигурации ядра.

Местоположения файлов

В Android 7.x и более ранних версий использование модулей ядра не запрещено (и поддерживаются insmod и rmmod), а в Android 8.x и более поздних версий рекомендуется использовать модули ядра в экосистеме. В таблице ниже показана поддержка периферийных устройств для разных платформ в трех режимах загрузки Android.

Режим загрузки Хранилище Экран Клавиатура Батарея PMIC Сенсорный экран NFC, Wi-Fi,
Bluetooth
Датчики Камера
Восстановление
Зарядное устройство
Android

Помимо доступности в режимах загрузки Android, модули ядра также можно классифицировать по их владельцу (поставщик SoC или ODM). Если используются модули ядра, их размещение в файловой системе должно соответствовать следующим требованиям:

  • Все ядра должны поддерживать загрузку и монтирование разделов.
  • Модули ядра должны загружаться из раздела, доступного только для чтения.
  • На устройствах, для которых требуется проверка при запуске, модули ядра должны загружаться из проверенных разделов.
  • Модули ядра не должны находиться в каталоге /system.
  • Модули GKI, необходимые для устройства, должны загружаться из каталога /system/lib/modules, который является символической ссылкой на каталог /system_dlkm/lib/modules.
  • Модули ядра от поставщика SoC, необходимые для работы Android или режима зарядки, должны находиться в каталоге /vendor/lib/modules.
  • Если раздел ODM существует, модули ядра из ODM, необходимые для работы Android или режима зарядки, должны находиться в /odm/lib/modules. В противном случае эти модули должны находиться в /vendor/lib/modules.
  • Модули ядра от поставщика SoC и ODM, необходимые для режима восстановления, должны находиться в ramfs восстановления по пути /lib/modules.
  • Модули ядра, необходимые для режима восстановления, а также для полной версии Android или режима зарядки, должны быть в разделах rootfs и /vendor или /odm (как описано выше).
  • Модули ядра, используемые в режиме восстановления, не должны зависеть от модулей, расположенных только в /vendor или /odm, поскольку эти разделы не монтируются в режиме восстановления.
  • Модули ядра поставщика процессора не должны зависеть от модулей ядра ODM.

В Android 7.x и более ранних версий разделы /vendor и /odm не монтируются на раннем этапе. В Android 8.x и более поздних версий для загрузки модулей из этих разделов предусмотрено раннее монтирование разделов как для устройств с A/B-разделами, так и без них. Это также гарантирует, что разделы будут смонтированы как в режиме Android, так и в режиме зарядки.

Поддержка системы сборки 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, чтобы создать необходимые файлы modules.dep в каталогах /vendor/lib/modules и /lib/modules (recovery ramfs).

Загрузка модулей и управление версиями

Загрузите все модули ядра за один проход из init.rc*, вызвав modprobe -a. Это позволяет избежать избыточных расходов на многократную инициализацию среды выполнения C для двоичного файла modprobe. Событие 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, изменяются (например, set_cpus_allowed_ptr в kernel/sched/core.c). Совместимость с ранее скомпилированными модулями ядра, которые используют эти интерфейсы, нарушается, поэтому эти модули необходимо пересобрать с новой конфигурацией ядра.

Подробную информацию об атрибуте CONFIG_MODVERSIONS можно найти в документации в дереве ядра по адресу Documentation/kbuild/modules.rst.