Оптимизация времени загрузки

На этой странице представлен набор советов, которые вы можете выбрать для улучшения времени загрузки.

Удаление символов отладки из модулей

Подобно тому, как символы отладки удаляются из ядра на рабочем устройстве, убедитесь, что вы также удаляете символы отладки из модулей. Удаление символов отладки из модулей помогает сократить время загрузки за счет уменьшения следующего:

  • Время, необходимое для чтения двоичных файлов из флэш-памяти.
  • Время, необходимое для распаковки RAM-диска.
  • Время, необходимое для загрузки модулей.

Удаление символа отладки из модулей может сэкономить несколько секунд во время загрузки.

Удаление символов включено по умолчанию в сборке платформы Android, но чтобы явно включить их, установите BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES в конфигурации вашего устройства в разделе device/ vendor / device .

Используйте сжатие LZ4 для ядра и RAM-диска

Gzip генерирует меньший сжатый вывод по сравнению с LZ4, но LZ4 распаковывает быстрее, чем Gzip. Для ядра и модулей абсолютное уменьшение размера хранилища от использования Gzip не так значительно по сравнению с преимуществом времени распаковки LZ4.

Поддержка сжатия RAM-диска LZ4 была добавлена ​​в сборку платформы Android через BOARD_RAMDISK_USE_LZ4 . Вы можете установить эту опцию в конфигурации вашего устройства. Сжатие ядра можно настроить через kernel defconfig.

Переключение на LZ4 должно сократить время загрузки на 500–1000 мс.

Избегайте чрезмерной регистрации в ваших драйверах

В ARM64 и ARM32 вызовы функций, которые находятся на большем, чем определенное расстояние от места вызова, нуждаются в таблице переходов (называемой таблицей связывания процедур или PLT), чтобы иметь возможность кодировать полный адрес перехода. Поскольку модули загружаются динамически, эти таблицы переходов должны быть исправлены во время загрузки модуля. Вызовы, требующие перемещения, называются записями перемещения с явными добавлениями (или RELA, для краткости) записями в формате ELF.

Ядро Linux выполняет некоторую оптимизацию размера памяти (например, оптимизацию попадания в кэш) при выделении PLT. С этой восходящей фиксацией схема оптимизации имеет сложность O(N^2), где N — количество RELA типа R_AARCH64_JUMP26 или R_AARCH64_CALL26 . Таким образом, меньшее количество RELA этих типов помогает сократить время загрузки модуля.

Одним из распространенных шаблонов кодирования, который увеличивает количество R_AARCH64_CALL26 или R_AARCH64_JUMP26 RELA, является чрезмерное ведение журнала в драйвере. Каждый вызов printk() или любой другой схемы регистрации обычно добавляет запись CALL26 / JUMP26 RELA. В тексте коммита в вышестоящем коммите обратите внимание, что даже с оптимизацией шесть модулей загружаются примерно за 250 мс — это потому, что эти шесть модулей были первыми шестью модулями с наибольшим объемом журналирования.

Сокращение ведения журнала может сэкономить около 100–300 мс времени загрузки в зависимости от того, насколько избыточным является существующее ведение журнала.

Включить асинхронное зондирование выборочно

Когда модуль загружается, если устройство, которое он поддерживает, уже заполнено из DT (devicetree) и добавлено в ядро ​​драйвера, то проверка устройства выполняется в контексте module_init() . Когда проверка устройства выполняется в контексте module_init() , модуль не может завершить загрузку, пока проверка не завершится. Поскольку загрузка модуля в основном сериализована, устройство, проверка которого занимает относительно много времени, замедляет время загрузки.

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

Устройства, подключенные через медленную шину, такую ​​как I2C, устройства, выполняющие загрузку встроенного ПО в своей функции зонда, и устройства, выполняющие большую часть аппаратной инициализации, могут привести к проблемам с синхронизацией. Лучший способ определить, когда это происходит, — собрать время проверки для каждого водителя и отсортировать его.

Чтобы включить асинхронное зондирование для модуля, недостаточно просто установить флаг PROBE_PREFER_ASYNCHRONOUS в коде драйвера. Для модулей вам также необходимо добавить module_name .async_probe=1 в командную строку ядра или передать async_probe=1 в качестве параметра модуля при загрузке модуля с помощью modprobe или insmod .

Включение асинхронного зондирования может сократить время загрузки примерно на 100–500 мс в зависимости от вашего оборудования/драйверов.

Проверьте драйвер CPUfreq как можно раньше

Чем раньше ваш драйвер CPUfreq проверит, тем быстрее вы сможете масштабировать частоту ЦП до максимума (или до некоторого предела, ограниченного температурой) во время загрузки. Чем быстрее процессор, тем быстрее загрузка. Это руководство также относится к драйверам devfreq , которые управляют DRAM, памятью и частотой межсоединений.

Для модулей порядок загрузки может зависеть от уровня initcall и порядка компиляции или компоновки драйверов. Используйте псевдоним MODULE_SOFTDEP() , чтобы драйвер cpufreq одним из первых модулей.

Помимо ранней загрузки модуля, вам также необходимо убедиться, что все зависимости для проверки драйвера CPUfreq также были проверены. Например, если вам нужны часы или ручка регулятора для управления частотой вашего процессора, сначала убедитесь, что они опробованы. Или вам может потребоваться загрузка тепловых драйверов перед драйвером CPUfreq, если ваши процессоры могут перегреваться во время загрузки. Итак, сделайте все возможное, чтобы как можно раньше проверить CPUfreq и соответствующие драйверы devfreq.

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

Переместите модули в раздел init второго этапа, поставщика или поставщика_dlkm.

Поскольку процесс инициализации первого этапа сериализуется, возможностей распараллелить процесс загрузки не так много. Если модуль не нужен для завершения инициализации первого этапа, переместите модуль на второй этап инициализации, поместив его в раздел vendor или vendor_dlkm .

Инициализация первого этапа не требует проверки нескольких устройств, чтобы перейти ко второму этапу инициализации. Для нормальной загрузки требуются только функции консоли и флэш-памяти.

Загрузите следующие основные драйверы:

  • сторожевая собака
  • перезагрузить
  • частота процессора

Для режима fastbootd восстановления и пользовательского пространства на первом этапе инициализации требуется больше устройств для проверки (например, USB) и отображения. Сохраните копию этих модулей на виртуальном диске первого этапа и в разделе vendor или vendor_dlkm . Это позволяет загружать их на первом этапе инициализации для восстановления или процесса загрузки fastbootd . Однако не загружайте модули режима восстановления на первом этапе инициализации во время обычной загрузки. Модули режима восстановления можно отложить до второго этапа инициализации, чтобы сократить время загрузки. Все остальные модули, которые не нужны на первом этапе инициализации, следует переместить в раздел vendor или vendor_dlkm .

Учитывая список конечных устройств (например, UFS или последовательный порт), сценарий dev needs.sh находит все драйверы, устройства и модули, необходимые для зависимостей или поставщиков (например, часы, регуляторы или gpio ) для исследования.

Перемещение модулей на вторую стадию инициализации сокращает время загрузки следующим образом:

  • Уменьшение размера RAM-диска.
    • Это обеспечивает более быстрое чтение флэш-памяти, когда загрузчик загружает виртуальный диск (шаг последовательной загрузки).
    • Это обеспечивает более высокую скорость распаковки, когда ядро ​​распаковывает виртуальный диск (шаг сериализованной загрузки).
  • Второй этап инициализации работает параллельно, что скрывает время загрузки модуля работой, выполняемой на втором этапе инициализации.

Перемещение модулей на вторую стадию может сэкономить 500–1000 мс времени загрузки в зависимости от того, сколько модулей вы можете переместить на вторую стадию инициализации.

Логистика загрузки модулей

В последней сборке Android есть конфигурации плат, которые контролируют, какие модули копируются на каждый этап и какие модули загружаются. В этом разделе основное внимание уделяется следующему подмножеству:

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES . Этот список модулей необходимо скопировать в рамдиск.
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD . Этот список модулей для загрузки на первом этапе инициализации.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD . Это список модулей, которые будут загружаться при выборе recovery или fastbootd с ramdisk.
  • BOARD_VENDOR_KERNEL_MODULES . Этот список модулей необходимо скопировать в раздел vendor или vendor_dlkm в каталоге /vendor/lib/modules/ .
  • BOARD_VENDOR_KERNEL_MODULES_LOAD . Этот список модулей для загрузки на втором этапе инициализации.

Модули загрузки и восстановления в ramdisk также должны быть скопированы в раздел vendor или vendor_dlkm в /vendor/lib/modules . Копирование этих модулей в раздел поставщика гарантирует, что модули не будут невидимы во время второй стадии инициализации, что полезно для отладки и сбора информации о модификациях для modinfo об ошибках.

Дублирование должно занимать минимальное место в разделе vendor или vendor_dlkm , если набор загрузочных модулей сведен к минимуму. Убедитесь, что в файле modules.list поставщика есть отфильтрованный список модулей в /vendor/lib/modules . Отфильтрованный список гарантирует, что повторная загрузка модулей не повлияет на время загрузки (что является дорогостоящим процессом).

Убедитесь, что модули режима восстановления загружаются как группа. Загрузка модулей режима восстановления может производиться либо в режиме восстановления, либо в начале второго этапа инициализации в каждом потоке загрузки.

Вы можете использовать файлы Board.Config.mk устройства для выполнения этих действий, как показано в следующем примере:

# All kernel modules
KERNEL_MODULES := $(wildcard $(KERNEL_MODULE_DIR)/*.ko)
KERNEL_MODULES_LOAD := $(strip $(shell cat $(KERNEL_MODULE_DIR)/modules.load)

# First stage ramdisk modules
BOOT_KERNEL_MODULES_FILTER := $(foreach m,$(BOOT_KERNEL_MODULES),%/$(m))

# Recovery ramdisk modules
RECOVERY_KERNEL_MODULES_FILTER := $(foreach m,$(RECOVERY_KERNEL_MODULES),%/$(m))
BOARD_VENDOR_RAMDISK_KERNEL_MODULES += \
     $(filter $(BOOT_KERNEL_MODULES_FILTER) \
                $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES))

# ALL modules land in /vendor/lib/modules so they could be rmmod/insmod'd,
# and modules.list actually limits us to the ones we intend to load.
BOARD_VENDOR_KERNEL_MODULES := $(KERNEL_MODULES)
# To limit /vendor/lib/modules to just the ones loaded, use:
# BOARD_VENDOR_KERNEL_MODULES := $(filter-out \
#     $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES))

# Group set of /vendor/lib/modules loading order to recovery modules first,
# then remainder, subtracting both recovery and boot modules which are loaded
# already.
BOARD_VENDOR_KERNEL_MODULES_LOAD := \
        $(filter-out $(BOOT_KERNEL_MODULES_FILTER), \
        $(filter $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD)))
BOARD_VENDOR_KERNEL_MODULES_LOAD += \
        $(filter-out $(BOOT_KERNEL_MODULES_FILTER) \
            $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))

# NB: Load order governed by modules.load and not by $(BOOT_KERNEL_MODULES)
BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD := \
        $(filter $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))

# Group set of /vendor/lib/modules loading order to boot modules first,
# then the remainder of recovery modules.
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD := \
    $(filter $(BOOT_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD))
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD += \
    $(filter-out $(BOOT_KERNEL_MODULES_FILTER), \
    $(filter $(RECOVERY_KERNEL_MODULES_FILTER),$(KERNEL_MODULES_LOAD)))

В этом примере демонстрируется более простое в управлении подмножество BOOT_KERNEL_MODULES и RECOVERY_KERNEL_MODULES , которое должно быть указано локально в файлах конфигурации платы. Предыдущий сценарий находит и заполняет каждый из модулей подмножества из выбранных доступных модулей ядра, оставляя модули повторного анализа для второго этапа инициализации.

Для инициализации второго этапа мы рекомендуем запускать загрузку модуля как службу, чтобы она не блокировала процесс загрузки. Используйте сценарий оболочки для управления загрузкой модуля, чтобы при необходимости можно было сообщить (или проигнорировать) другую логистику, такую ​​как обработка и устранение ошибок или завершение загрузки модуля.

Вы можете игнорировать сбой загрузки модуля отладки, которого нет в пользовательских сборках. Чтобы проигнорировать этот сбой, установите свойство vendor.device.modules.ready , чтобы инициировать более поздние этапы загрузки сценария init rc для продолжения на экране запуска. Ссылайтесь на следующий пример сценария, если у вас есть следующий код в /vendor/etc/init.insmod.sh :

#!/vendor/bin/sh
. . .
if [ $# -eq 1 ]; then
  cfg_file=$1
else
  # Set property even if there is no insmod config
  # to unblock early-boot trigger
  setprop vendor.common.modules.ready
  setprop vendor.device.modules.ready
  exit 1
fi

if [ -f $cfg_file ]; then
  while IFS="|" read -r action arg
  do
    case $action in
      "insmod") insmod $arg ;;
      "setprop") setprop $arg 1 ;;
      "enable") echo 1 > $arg ;;
      "modprobe") modprobe -a -d /vendor/lib/modules $arg ;;
     . . .
    esac
  done < $cfg_file
fi

В аппаратном файле rc one shot служба может быть указана с помощью:

service insmod-sh /vendor/etc/init.insmod.sh /vendor/etc/init.insmod.<hw>.cfg
    class main
    user root
    group root system
    Disabled
    oneshot

Дополнительные оптимизации могут быть сделаны после перехода модулей с первого на второй этап. Вы можете использовать функцию черного списка modprobe, чтобы разделить поток загрузки второго этапа, чтобы включить отложенную загрузку ненужных модулей. Загрузка модулей, используемых исключительно конкретным HAL, может быть отложена для загрузки модулей только при запуске HAL.

Чтобы улучшить видимое время загрузки, вы можете специально выбрать модули в службе загрузки модулей, которые более благоприятны для загрузки после экрана запуска. Например, вы можете явно отложить загрузку модулей для видеодекодера или Wi-Fi после того, как поток начальной загрузки был очищен (например, сигнал свойства Android sys.boot_complete ). Убедитесь, что HAL для модулей с поздней загрузкой блокируются достаточно долго, когда отсутствуют драйверы ядра.

В качестве альтернативы вы можете использовать команду init wait<file>[<timeout>] в сценарии rc потока загрузки, чтобы дождаться выбранных записей sysfs , чтобы показать, что модули драйверов завершили операции проверки. Примером этого является ожидание завершения загрузки драйвера дисплея в фоновом режиме восстановления или fastbootd перед отображением графики меню.

Инициализируйте частоту ЦП до разумного значения в загрузчике.

Не все SoC/продукты могут загружать ЦП с максимальной частотой из-за проблем с температурой или питанием во время тестов цикла загрузки. Однако убедитесь, что загрузчик устанавливает частоту всех онлайн-процессоров на максимально безопасное значение для SoC/продукта. Это очень важно, потому что с полностью модульным ядром распаковка RAM-диска инициализации происходит до загрузки драйвера CPUfreq. Таким образом, если загрузчик оставляет ЦП на нижнем пределе своей частоты, время распаковки виртуального диска может занять больше времени, чем для статически скомпилированного ядра (после корректировки на разницу в размере виртуального диска), потому что частота ЦП будет очень низкой при интенсивном использовании ЦП. работа (декомпрессия). То же самое относится к частоте памяти/интерконнекта.

Инициализировать частоту процессора больших процессоров в загрузчике

До загрузки драйвера CPUfreq ядро ​​не знает о малых и больших частотах ЦП и не масштабирует запланированную мощность ЦП для их текущей частоты. Ядро может перенаправить потоки на большой процессор, если нагрузка на маленький процессор достаточно высока.

Убедитесь, что производительность больших процессоров не меньше, чем у маленьких процессоров на той частоте, на которой их оставляет загрузчик. Например, если производительность большого процессора в 2 раза выше, чем у маленького процессора на той же частоте, но частоту маленького процессора до 1,5 ГГц, а частоту большого процессора до 300 МГц, тогда производительность загрузки упадет, если ядро ​​​​перенесет поток на большой процессор. В этом примере, если безопасно загружать большой ЦП на частоте 750 МГц, вы должны сделать это, даже если вы не планируете использовать его явно.

Драйверы не должны загружать прошивку на первом этапе инициализации.

Могут быть некоторые неизбежные случаи, когда прошивка должна быть загружена на первом этапе инициализации. Но в целом драйверы не должны загружать никакую прошивку на первом этапе инициализации, особенно в контексте проверки устройства. Загрузка микропрограммы на первом этапе инициализации приводит к остановке всего процесса загрузки, если микропрограмма недоступна на виртуальном диске первого этапа. И даже если прошивка присутствует в рамдиске первой очереди, это все равно вызывает ненужную задержку.