На этой странице приведены советы по сокращению времени загрузки.
Удалить отладочные символы из модулей
Подобно тому, как отладочные символы удаляются из ядра на производственном устройстве, убедитесь, что вы также удаляете отладочные символы из модулей. Удаление отладочных символов из модулей помогает сократить время загрузки, сокращая следующее:
- Время, необходимое для чтения двоичных файлов из флэш-памяти.
- Время, необходимое для распаковки RAM-диска.
- Время, необходимое для загрузки модулей.
Удаление отладочного символа из модулей может сэкономить несколько секунд во время загрузки.
Отключение символов включено по умолчанию в сборке платформы Android, но чтобы явно включить его, установите BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES
в конфигурации вашего устройства в разделе device/ vendor / device .
Использовать сжатие LZ4 для ядра и ramdisk
Gzip генерирует меньший сжатый вывод по сравнению с LZ4, но LZ4 распаковывает быстрее, чем Gzip. Для ядра и модулей абсолютное уменьшение размера хранилища от использования Gzip не так уж и существенно по сравнению с преимуществом времени распаковки LZ4.
Поддержка сжатия ramdisk LZ4 была добавлена в сборку платформы Android через BOARD_RAMDISK_USE_LZ4
. Вы можете установить эту опцию в конфигурации вашего устройства. Сжатие ядра можно установить через kernel defconfig.
Переход на LZ4 должен сократить время загрузки на 500–1000 мс.
Избегайте чрезмерного входа в систему ваших драйверов.
В ARM64 и ARM32 вызовы функций, которые находятся дальше определенного расстояния от места вызова, нуждаются в таблице переходов (называемой таблицей связывания процедур, или PLT), чтобы иметь возможность кодировать полный адрес перехода. Поскольку модули загружаются динамически, эти таблицы переходов должны быть исправлены во время загрузки модуля. Вызовы, которые требуют перемещения, называются записями перемещения с явными дополнениями (или RELA, для краткости) в формате ELF.
Ядро Linux выполняет некоторую оптимизацию размера памяти (например, оптимизацию попадания в кэш) при выделении PLT. С этим upstream commit схема оптимизации имеет сложность O(N^2)
, где N
— это количество RELA типа R_AARCH64_JUMP26
или R_AARCH64_CALL26
. Поэтому наличие меньшего количества RELA этих типов полезно для сокращения времени загрузки модуля.
Одним из распространенных шаблонов кодирования, который увеличивает количество R_AARCH64_CALL26
или R_AARCH64_JUMP26
RELA, является чрезмерное ведение журнала в драйвере. Каждый вызов printk()
или любой другой схемы ведения журнала обычно добавляет запись CALL26
/ JUMP26
RELA. В тексте коммита в upstream commit , ,обратите внимание, что даже с оптимизацией загрузка шести модулей занимает около 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, раздел vendor или vendor_dlkm
Поскольку процесс init первой стадии является сериализованным, не так много возможностей для распараллеливания процесса загрузки. Если модуль не нужен для завершения init первой стадии, переместите модуль на init второй стадии, поместив его в раздел vendor или vendor_dlkm
.
Первая стадия init не требует проверки нескольких устройств для перехода ко второй стадии init. Для нормального потока загрузки необходимы только возможности консоли и флэш-накопителя.
Загрузите следующие основные драйверы:
-
watchdog
-
reset
-
cpufreq
Для режима восстановления и пользовательского пространства fastbootd
, первый этап init требует больше устройств для зондирования (например, USB) и отображения. Сохраните копию этих модулей в ramdisk первого этапа и в разделе vendor или vendor_dlkm
. Это позволяет загружать их на первом этапе init для восстановления или потока загрузки fastbootd
. Однако не загружайте модули режима восстановления на первом этапе init во время обычного потока загрузки. Модули режима восстановления можно отложить до второго этапа init, чтобы сократить время загрузки. Все остальные модули, которые не нужны на первом этапе init, следует переместить в раздел vendor или vendor_dlkm
.
Учитывая список конечных устройств (например, UFS или последовательный), скрипт dev needs.sh
находит все драйверы, устройства и модули, необходимые для проверки зависимостей или поставщиков (например, часов, регуляторов или gpio
).
Перемещение модулей на второй этап инициализации сокращает время загрузки следующими способами:
- Уменьшение размера RAM-диска.
- Это обеспечивает более быстрое чтение флэш-памяти, когда загрузчик загружает RAM-диск (шаг последовательной загрузки).
- Это обеспечивает более высокую скорость распаковки, когда ядро распаковывает RAM-диск (шаг последовательной загрузки).
- Второй этап инициализации работает параллельно, что скрывает время загрузки модуля по сравнению с работой, выполняемой на втором этапе инициализации.
Перемещение модулей на второй этап может сэкономить 500–1000 мс времени загрузки в зависимости от того, сколько модулей вы сможете переместить на второй этап инициализации.
Логистика загрузки модулей
Последняя сборка Android содержит конфигурации платы, которые контролируют, какие модули копируются на каждую стадию и какие модули загружаются. В этом разделе рассматривается следующее подмножество:
-
BOARD_VENDOR_RAMDISK_KERNEL_MODULES
. Это список модулей, которые необходимо скопировать в RAM-диск. -
BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD
. Это список модулей, которые будут загружены на первом этапе инициализации. -
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD
. Это список модулей, которые будут загружены при выборе восстановления илиfastbootd
из ramdisk. -
BOARD_VENDOR_KERNEL_MODULES
. Это список модулей, которые необходимо скопировать в раздел vendor илиvendor_dlkm
в каталоге/vendor/lib/modules/
. -
BOARD_VENDOR_KERNEL_MODULES_LOAD
. Это список модулей, которые будут загружены на втором этапе инициализации.
Модули загрузки и восстановления в ramdisk также должны быть скопированы в раздел vendor или vendor_dlkm
в /vendor/lib/modules
. Копирование этих модулей в раздел vendor гарантирует, что модули не будут невидимыми во время второго этапа инициализации, что полезно для отладки и сбора modinfo
для отчетов об ошибках.
Дублирование должно занимать минимальное пространство на разделе vendor или vendor_dlkm
, пока набор загрузочных модулей минимизирован. Убедитесь, что файл modules.list
поставщика содержит отфильтрованный список модулей в /vendor/lib/modules
. Отфильтрованный список гарантирует, что время загрузки не будет затронуто повторной загрузкой модулей (что является дорогостоящим процессом).
Убедитесь, что модули режима восстановления загружаются как группа. Загрузка модулей режима восстановления может быть выполнена либо в режиме восстановления, либо в начале второго этапа init в каждом потоке загрузки.
Для выполнения этих действий можно использовать файлы 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
, которое должно быть указано локально в файлах конфигурации платы. Предыдущий скрипт находит и заполняет каждый из модулей подмножества из выбранных доступных модулей ядра, оставляя модули reamining для второй стадии инициализации.
Для второго этапа инициализации мы рекомендуем запустить загрузку модуля как службу, чтобы она не блокировала поток загрузки. Используйте скрипт оболочки для управления загрузкой модуля, чтобы при необходимости можно было сообщить о другой логистике, например, об обработке и смягчении ошибок или завершении загрузки модуля (или проигнорировать ее).
Вы можете игнорировать сбой загрузки отладочного модуля, который отсутствует в пользовательских сборках. Чтобы игнорировать этот сбой, установите свойство vendor.device.modules.ready
для запуска более поздних стадий init rc
scripting bootflow для продолжения на экране запуска. Ссылайтесь на следующий пример скрипта, если у вас есть следующий код в /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 blocklist для разделения потока загрузки второго этапа, чтобы включить отложенную загрузку несущественных модулей. Загрузка модулей, используемых исключительно определенным HAL, может быть отложена для загрузки модулей только при запуске HAL.
Чтобы улучшить видимое время загрузки, вы можете специально выбрать модули в службе загрузки модулей, которые более подходят для загрузки после экрана запуска. Например, вы можете явно поздно загрузить модули для видеодекодера или Wi-Fi после очистки потока загрузки init (например, сигнал свойства Android sys.boot_complete
). Убедитесь, что HAL для модулей поздней загрузки блокируются достаточно долго, когда драйверы ядра отсутствуют.
В качестве альтернативы можно использовать команду init wait<file>[<timeout>]
в скрипте rc потока загрузки, чтобы дождаться выбранных записей sysfs
, чтобы показать, что модули драйвера завершили операции зондирования. Примером этого является ожидание завершения загрузки драйвера дисплея в фоновом режиме восстановления или fastbootd
перед представлением графики меню.
Инициализируйте частоту процессора до разумного значения в загрузчике.
Не все SoC/продукты могут загружать ЦП на самой высокой частоте из-за проблем с температурой или питанием во время тестов цикла загрузки. Однако убедитесь, что загрузчик устанавливает частоту всех онлайн-ЦП на максимально возможное безопасное значение для SoC или продукта. Это очень важно, поскольку при полностью модульном ядре декомпрессия init ramdisk происходит до того, как может быть загружен драйвер CPUfreq. Таким образом, если загрузчик оставляет ЦП на нижнем пределе его частоты, время декомпрессии ramdisk может занять больше времени, чем статически скомпилированное ядро (после корректировки разницы в размере ramdisk), поскольку частота ЦП будет очень низкой при выполнении интенсивной работы ЦП (декомпрессии). То же самое относится к частоте памяти и межсоединений.
Инициализация частоты ЦП больших ЦП в загрузчике
До загрузки драйвера CPUfreq
ядро не знает о частотах ЦП и не масштабирует емкость ЦП для их текущей частоты. Ядро может перенести потоки на большой ЦП, если нагрузка на маленький ЦП достаточно высока.
Убедитесь, что большие ЦП по крайней мере столь же производительны, как и маленькие ЦП для частоты, на которой их оставляет загрузчик. Например, если большой ЦП в 2 раза производительнее маленького ЦП для той же частоты, но загрузчик устанавливает частоту маленького ЦП на 1,5 ГГц, а частоту большого ЦП на 300 МГц, то производительность загрузки упадет, если ядро переместит поток на большой ЦП. В этом примере, если безопасно загружать большой ЦП на частоте 750 МГц, вам следует это сделать, даже если вы не планируете явно его использовать.
Драйверы не должны загружать прошивку на первом этапе инициализации
Могут быть некоторые неизбежные случаи, когда необходимо загрузить прошивку на первом этапе init. Но в целом драйверы не должны загружать никакую прошивку на первом этапе init, особенно в контексте проверки устройства. Загрузка прошивки на первом этапе init приводит к остановке всего процесса загрузки, если прошивка недоступна на первом этапе ramdisk. И даже если прошивка присутствует на первом этапе ramdisk, это все равно вызывает ненужную задержку.