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

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

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

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

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

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

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

Используйте сжатие LZ4 для ядра и виртуального диска.

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 , является чрезмерная регистрация в драйвере. Каждый вызов printk() или любой другой схемы журналирования обычно добавляет запись CALL26 / JUMP26 RELA. В тексте коммита восходящего потока обратите внимание, что даже с учетом оптимизации загрузка шести модулей занимает около 250 мс — это потому, что эти шесть модулей были шестью модулями с наибольшим количеством журналов.

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

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

Если при загрузке модуля устройство, которое он поддерживает, уже было заполнено из DT (дерева устройств) и добавлено в ядро ​​драйвера, то проверка устройства выполняется в контексте вызова 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 .

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

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

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

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

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

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

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

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

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

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

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

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

Дублирование должно занимать минимальное пространство в разделе поставщика или 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/продукта. Это очень важно, поскольку в полностью модульном ядре распаковка начального виртуального диска происходит до загрузки драйвера CPUfreq. Таким образом, если загрузчик оставляет процессор на нижнем пределе своей частоты, время распаковки виртуального диска может занять больше времени, чем у статически скомпилированного ядра (после корректировки разницы в размере виртуального диска), поскольку частота процессора будет очень низкой при интенсивной работе процессора. работа (декомпрессия). То же самое относится и к частоте памяти/межсоединения.

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

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

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

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

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