На этой странице приведены советы по сокращению времени загрузки.
Удалить отладочные символы из модулей
Подобно тому, как отладочные символы удаляются из ядра на рабочем устройстве, убедитесь, что вы также удаляете отладочные символы из модулей. Удаление отладочных символов из модулей сокращает время загрузки, сокращая следующее:
- Время, необходимое для чтения двоичных файлов из флэш-памяти.
- Время, необходимое для распаковки RAM-диска.
- Время, необходимое для загрузки модулей.
Удаление отладочного символа из модулей может сэкономить несколько секунд во время загрузки.
Отключение символов включено по умолчанию в сборке платформы Android, но чтобы явно включить их, установите BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES в конфигурации вашего устройства в разделе device/ vendor / device .
Использовать сжатие LZ4 для ядра и ramdisk
Gzip генерирует меньший объём сжатого файла по сравнению с LZ4, но LZ4 распаковывает его быстрее. Для ядра и модулей абсолютное сокращение объёма хранилища при использовании Gzip не так существенно по сравнению с выигрышем во времени распаковки, который обеспечивает LZ4.
Поддержка сжатия RAM-диска LZ4 добавлена в сборку для платформы Android через BOARD_RAMDISK_USE_LZ4 . Эту опцию можно настроить в настройках вашего устройства. Сжатие ядра можно настроить через 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 , является избыточное логирование в драйвере. Каждый вызов printk() или любой другой схемы логирования обычно добавляет запись связи CALL26 / JUMP26 . В тексте коммита в исходном коммите обратите внимание, что даже с учётом оптимизации загрузка шести модулей занимает около 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, раздел vendor или vendor_dlkm
Поскольку процесс инициализации первого этапа выполняется последовательно, возможностей для распараллеливания процесса загрузки немного. Если модуль не требуется для завершения инициализации первого этапа, переместите его на этап инициализации второго этапа, разместив его в разделе vendor или vendor_dlkm .
На первом этапе инициализации не требуется проверять несколько устройств для перехода ко второму этапу инициализации. Для нормальной загрузки требуются только возможности консоли и флеш-накопителя.
Загрузите следующие необходимые драйверы:
-
watchdog -
reset -
cpufreq
Для режима восстановления и fastbootd в пользовательском пространстве на первом этапе инициализации требуется больше устройств для проверки (например, USB) и отображения. Сохраните копию этих модулей на RAM-диске первого этапа и в разделе vendor или vendor_dlkm . Это позволит загружать их на первом этапе инициализации для восстановления или загрузки fastbootd . Однако не загружайте модули режима восстановления на первом этапе инициализации во время обычной загрузки. Модули режима восстановления можно отложить на второй этап инициализации для сокращения времени загрузки. Все остальные модули, не нужные на первом этапе инициализации, следует переместить в раздел vendor или vendor_dlkm .
Учитывая список конечных устройств (например, UFS или последовательный), скрипт dev needs.sh находит все драйверы, устройства и модули, необходимые для проверки зависимостей или поставщиков (например, часов, регуляторов или gpio ).
Перемещение модулей на второй этап инициализации сокращает время загрузки следующими способами:
- Уменьшение размера RAM-диска.
- Это обеспечивает более быстрое чтение флэш-памяти, когда загрузчик загружает RAM-диск (шаг последовательной загрузки).
- Это обеспечивает более высокую скорость распаковки, когда ядро распаковывает ramdisk (шаг последовательной загрузки).
- Второй этап инициализации выполняется параллельно, что скрывает время загрузки модуля по сравнению с работой, выполняемой на втором этапе инициализации.
Перемещение модулей на второй этап может сэкономить 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 . Отфильтрованный список гарантирует, что время загрузки не будет зависеть от повторной загрузки модулей (что является дорогостоящим процессом).
Убедитесь, что модули режима восстановления загружаются группой. Загрузка модулей режима восстановления может выполняться либо в режиме восстановления, либо в начале второго этапа инициализации в каждом потоке загрузки.
Для выполнения этих действий можно использовать файлы 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 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 ядро не знает о частотах процессора и не масштабирует выделенную ему мощность для текущей частоты. Ядро может перенести потоки на большой процессор, если нагрузка на маленький достаточно высока.
Убедитесь, что производительность больших процессоров как минимум не ниже производительности малых процессоров на частоте, заданной загрузчиком. Например, если большой процессор в два раза производительнее малого процессора на той же частоте, но загрузчик устанавливает частоту малого процессора 1,5 ГГц, а большого — 300 МГц, то производительность загрузки снизится, если ядро перенесёт поток на большой процессор. В этом примере, если загрузка большого процессора на частоте 750 МГц безопасна, следует сделать это, даже если вы не планируете использовать его явно.
Драйверы не должны загружать прошивку на первом этапе инициализации.
В некоторых неизбежных случаях требуется загрузка прошивки на первом этапе инициализации. Но, как правило, драйверы не должны загружать прошивку на первом этапе инициализации, особенно в контексте проверки устройств. Загрузка прошивки на первом этапе инициализации приводит к остановке всего процесса загрузки, если прошивка отсутствует в RAM-диске первого этапа. И даже если прошивка присутствует в RAM-диске первого этапа, это всё равно приводит к ненужной задержке.