На этой странице представлены советы по улучшению времени загрузки.
Удалять отладочные символы из модулей
Подобно тому, как отладочные символы удаляются из ядра на рабочем устройстве, убедитесь, что вы также удаляете отладочные символы из модулей. Удаление отладочных символов из модулей улучшает время загрузки, уменьшая следующие параметры:
- Время, необходимое для чтения бинарных файлов с флеш-накопителя.
- Время, необходимое для декомпрессии виртуального диска в оперативной памяти.
- Время, необходимое для загрузки модулей.
Удаление отладочного символа из модулей может сэкономить несколько секунд во время загрузки.
Удаление символов включено по умолчанию в сборке платформы Android, но для явного включения этой функции установите параметр BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES в конфигурации вашего устройства в разделе device/ vendor / device .
Используйте сжатие LZ4 для ядра и виртуального диска в оперативной памяти.
Gzip генерирует меньший сжатый файл по сравнению с LZ4, но LZ4 распаковывает его быстрее. Для ядра и модулей абсолютное уменьшение размера памяти при использовании Gzip не столь значительно по сравнению с преимуществом LZ4 во времени распаковки.
В сборку платформы Android добавлена поддержка сжатия LZ4 через BOARD_RAMDISK_USE_LZ4 . Вы можете установить этот параметр в конфигурации вашего устройства. Сжатие ядра можно настроить через параметр 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) в ядро драйвера, то проверка устройства выполняется в контексте вызова функции 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 может быть как очень небольшой, так и очень значительной, в зависимости от того, насколько рано вам удастся это сделать и на какой частоте загрузчик оставит процессоры включенными.
Переместите модули во второй этап инициализации, раздел vendor или vendor_dlkm.
Поскольку первый этап инициализации выполняется последовательно, возможностей для распараллеливания процесса загрузки не так много. Если модуль не требуется для завершения первого этапа инициализации, переместите его на второй этап инициализации, поместив в раздел vendor или vendor_dlkm .
Для инициализации первого этапа не требуется проверка нескольких устройств для перехода ко второму этапу. Для нормальной загрузки необходимы только возможности консоли и флэш-памяти.
Загрузите следующие необходимые драйверы:
-
watchdog -
reset -
cpufreq
Для режима восстановления и пользовательского пространства fastbootd на первом этапе инициализации требуется больше устройств для проверки (например, USB) и отображения. Сохраните копии этих модулей в оперативной памяти первого этапа и в разделе vendor или vendor_dlkm . Это позволит загрузить их на первом этапе инициализации для процесса восстановления или загрузки fastbootd . Однако не загружайте модули режима восстановления на первом этапе инициализации во время обычной загрузки. Модули режима восстановления можно отложить до второго этапа инициализации, чтобы сократить время загрузки. Все остальные модули, которые не нужны на первом этапе инициализации, следует переместить в раздел vendor или vendor_dlkm .
Получив список конечных устройств (например, UFS или последовательный порт), скрипт dev needs.sh находит все драйверы, устройства и модули, необходимые для зависимостей или поставщиков (например, тактовые генераторы, регуляторы или gpio ), которые необходимо проверить.
Перемещение модулей на второй этап инициализации сокращает время загрузки следующим образом:
- Уменьшение размера оперативной памяти.
- Это обеспечивает более быстрое чтение флэш-памяти, когда загрузчик загружает ramdisk (последовательный этап загрузки).
- Это обеспечивает более высокую скорость декомпрессии, когда ядро выполняет декомпрессию виртуального диска в оперативной памяти (последовательный этап загрузки).
- Инициализация на втором этапе выполняется параллельно, что позволяет скрыть время загрузки модуля от работы, выполняемой на этом этапе.
Перемещение модулей на второй этап инициализации может сэкономить 500–1000 мс времени загрузки, в зависимости от того, сколько модулей вам удастся переместить на второй этап инициализации.
логистика загрузки модулей
В последней сборке Android реализованы конфигурации платы, которые управляют тем, какие модули копируются на каждый этап и какие модули загружаются. В этом разделе рассматривается следующее подмножество:
-
BOARD_VENDOR_RAMDISK_KERNEL_MODULES. Этот список модулей будет скопирован в RAM-диск. -
BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD. Этот список модулей должен быть загружен на первом этапе инициализации. -
BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD. Этот список модулей будет загружен при выборе режима восстановления илиfastbootdиз оперативной памяти. -
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 scripting и перейти к экрану запуска. Обратитесь к следующему примеру скрипта, если у вас есть следующий код в /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 позже, после завершения инициализации загрузки (например, сигнал свойства Android sys.boot_complete ). Убедитесь, что HAL для загружаемых позже модулей блокируются достаточно долго, когда драйверы ядра отсутствуют.
В качестве альтернативы, вы можете использовать команду ` wait<file>[<timeout>] в скрипте загрузки (rc) для ожидания завершения операций проверки модулей драйвера в выбранных записях sysfs . Примером этого может служить ожидание завершения загрузки драйвера дисплея в фоновом режиме восстановления или fastbootd перед отображением графики меню.
В загрузчике установите для процессора разумное значение частоты.
Не все SoC/продукты могут загрузить процессор на максимальной частоте из-за проблем с перегревом или энергопотреблением во время тестов цикла загрузки. Однако убедитесь, что загрузчик устанавливает частоту всех работающих процессоров на максимально возможную для SoC или продукта. Это очень важно, поскольку в полностью модульном ядре декомпрессия init ramdisk происходит до загрузки драйвера CPUfreq. Таким образом, если загрузчик оставляет процессор на нижнем пределе его частоты, время декомпрессии ramdisk может занять больше времени, чем в статически скомпилированном ядре (после корректировки на разницу в размере ramdisk), поскольку частота процессора будет очень низкой при выполнении ресурсоемких операций (декомпрессии). То же самое относится к частоте памяти и межсоединений.
Инициализация частоты процессоров с большими процессорами в загрузчике.
До загрузки драйвера CPUfreq ядро не знает частот процессоров и не масштабирует пропускную способность ЦП в соответствии с их текущей частотой. Ядро может перенести потоки на более мощный процессор, если нагрузка на более слабый процессор достаточно высока.
Убедитесь, что производительность больших процессоров как минимум не уступает производительности маленьких процессоров на той частоте, на которой их оставляет загрузчик. Например, если большой процессор в два раза производительнее маленького при той же частоте, но загрузчик устанавливает частоту маленького процессора на 1,5 ГГц, а частоту большого процессора на 300 МГц, то производительность загрузки снизится, если ядро переместит поток на большой процессор. В этом примере, если безопасно загружать большой процессор на частоте 750 МГц, следует это делать, даже если вы не планируете его явно использовать.
Драйверы не должны загружать прошивку на первом этапе инициализации.
В некоторых неизбежных случаях может потребоваться загрузка прошивки на первом этапе инициализации. Но в целом, драйверы не должны загружать прошивку на первом этапе инициализации, особенно в контексте проверки устройства. Загрузка прошивки на первом этапе инициализации приводит к зависанию всего процесса загрузки, если прошивка отсутствует в оперативной памяти первого этапа. И даже если прошивка присутствует в оперативной памяти первого этапа, это все равно вызывает ненужную задержку.