En esta página, se proporcionan sugerencias para mejorar el tiempo de inicio.
Quita los símbolos de depuración de los módulos
Al igual que los símbolos de depuración se quitan del kernel en un dispositivo de producción, asegúrate de quitarlos también de los módulos. Quitar los símbolos de depuración de los módulos ayuda a reducir el tiempo de inicio, ya que disminuye lo siguiente:
- Es el tiempo que se tarda en leer los objetos binarios de Flash.
- Es el tiempo que lleva descomprimir el ramdisk.
- Es el tiempo que lleva cargar los módulos.
Quitar el símbolo de depuración de los módulos puede ahorrar varios segundos durante el inicio.
La eliminación de símbolos está habilitada de forma predeterminada en la compilación de la plataforma de Android, pero para habilitarlos explícitamente, establece BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES
en la configuración específica del dispositivo en device/vendor/device.
Usa la compresión LZ4 para el kernel y el ramdisk
Gzip genera un resultado comprimido más pequeño en comparación con LZ4, pero LZ4 se descomprime más rápido que Gzip. En el caso del kernel y los módulos, la reducción absoluta del tamaño de almacenamiento por usar Gzip no es tan significativa en comparación con el beneficio de tiempo de descompresión de LZ4.
Se agregó compatibilidad con la compresión de ramdisk LZ4 a la compilación de la plataforma de Android a través de BOARD_RAMDISK_USE_LZ4
. Puedes establecer esta opción en la configuración específica del dispositivo. La compresión del kernel se puede configurar a través de defconfig del kernel.
Cambiar a LZ4 debería proporcionar un tiempo de inicio entre 500 ms y 1,000 ms más rápido.
Cómo evitar registros excesivos en tus controladores
En ARM64 y ARM32, las llamadas a función que están a más de una distancia específica del sitio de llamada necesitan una tabla de salto (llamada tabla de vinculación de procedimientos o PLT) para poder codificar la dirección de salto completa. Como los módulos se cargan de forma dinámica, estas tablas de salto deben corregirse durante la carga del módulo. Las llamadas que necesitan reasignación se denominan entradas de reasignación con complementos explícitos (o RELA, en resumen) en el formato ELF.
El kernel de Linux realiza algunas optimizaciones de tamaño de memoria (como la optimización de aciertos de caché) cuando asigna la PLT. Con esta confirmación upstream, el esquema de optimización tiene una complejidad de O(N^2)
, donde N
es la cantidad de RELA de tipo R_AARCH64_JUMP26
o R_AARCH64_CALL26
. Por lo tanto, tener menos RELA de estos tipos es útil para reducir el tiempo de carga del módulo.
Un patrón de codificación común que aumenta la cantidad de RELA R_AARCH64_CALL26
o R_AARCH64_JUMP26
es el registro excesivo en un controlador. Cada llamada a printk()
o cualquier otro esquema de registro suele agregar una entrada de RELA CALL26
/JUMP26
. En el texto de confirmación en la confirmación upstream, observa que, incluso con la optimización, los seis módulos tardan alrededor de 250 ms en cargarse, ya que esos seis módulos fueron los seis principales con la mayor cantidad de registros.
Reducir el registro puede ahorrar entre 100 y 300 ms en los tiempos de inicio, según cuán excesivo sea el registro existente.
Habilita la sondeo asíncrono de forma selectiva
Cuando se carga un módulo, si el dispositivo que admite ya se propagó desde el DT (árbol de dispositivos) y se agregó al núcleo del controlador, la prueba del dispositivo se realiza en el contexto de la llamada a module_init()
. Cuando se realiza un sondeo de dispositivo en el contexto de module_init()
, el módulo no puede terminar de cargarse hasta que se complete el sondeo. Dado que la carga de módulos está mayormente serializada, un dispositivo que tarda un tiempo relativamente largo en sondear ralentiza el tiempo de inicio.
Para evitar tiempos de inicio más lentos, habilita el sondeo asíncrono para los módulos que tardan un poco en sondear sus dispositivos. Habilitar la sondeo asíncrono para todos los módulos podría no ser beneficioso, ya que el tiempo que se tarda en bifurcar un subproceso y activar la sonda puede ser tan alto como el tiempo que se tarda en sondear el dispositivo.
Los dispositivos que se conectan a través de un bus lento, como I2C, los dispositivos que cargan firmware en su función de sonda y los dispositivos que realizan muchas inicializaciones de hardware pueden generar problemas de sincronización. La mejor manera de identificar cuándo ocurre esto es recopilar el tiempo de sondeo de cada controlador y ordenarlo.
Para habilitar el sondeo asíncrono para un módulo, no es suficiente configurar solo la marca PROBE_PREFER_ASYNCHRONOUS
en el código del controlador. En el caso de los módulos, también debes agregar module_name.async_probe=1
a la línea de comandos del kernel o pasar async_probe=1
como parámetro del módulo cuando lo cargues con modprobe
o insmod
.
Habilitar la sondeo asíncrono puede ahorrar entre 100 y 500 ms en los tiempos de inicio, según el hardware o los controladores.
Prueba el controlador de CPUfreq lo antes posible
Cuanto antes sondee el controlador de CPUfreq, antes podrás escalar la frecuencia de la CPU al máximo (o a un máximo limitado térmicamente) durante el inicio. Cuanto más rápido sea la CPU, más rápido será el inicio. Este lineamiento también se aplica a los controladores devfreq
que controlan la DRAM, la memoria y la frecuencia de interconexión.
Con los módulos, el orden de carga puede depender del nivel de initcall
y del orden de compilación o vinculación de los controladores. Usa un alias MODULE_SOFTDEP()
para asegurarte de que el controlador cpufreq
esté entre los primeros módulos que se carguen.
Además de cargar el módulo con anticipación, también debes asegurarte de que se hayan probado todas las dependencias para probar el controlador de CPUfreq. Por ejemplo, si necesitas un reloj o una manija de regulador para controlar la frecuencia de tu CPU, asegúrate de que se sondeen primero. También es posible que debas cargar los controladores térmicos antes del controlador de CPUfreq si es posible que las CPUs se calienten demasiado durante el inicio. Por lo tanto, haz lo posible para asegurarte de que los controladores de CPUfreq y devfreq relevantes realicen la prueba lo antes posible.
Los ahorros que se obtienen de sondear el controlador de CPUfreq con anticipación pueden ser muy pequeños o muy grandes, según la rapidez con la que puedas sondearlos y la frecuencia con la que el bootloader deje las CPUs.
Se movieron los módulos a la inicialización de la segunda etapa, la partición del proveedor o vendor_dlkm.
Debido a que el proceso de inicio de la primera etapa es serializado, no hay muchas oportunidades para paralelizar el proceso de inicio. Si no se necesita un módulo para que finalice la inicialización de la primera etapa, muévelo a la inicialización de la segunda etapa colocándolo en la partición del proveedor o vendor_dlkm
.
El init de la primera etapa no requiere el sondeo de varios dispositivos para llegar a la inicialización de la segunda etapa. Solo se necesitan las funciones de almacenamiento en consola y flash para un flujo de inicio normal.
Carga los siguientes controladores esenciales:
watchdog
reset
cpufreq
Para el modo fastbootd
de recuperación y espacio del usuario, el inicio de la primera etapa requiere más dispositivos para sondear (como USB) y visualizar. Mantén una copia de estos módulos en el ramdisk de la primera etapa y en la partición vendor_dlkm
o del proveedor. Esto les permite cargarse en la inicialización de la primera etapa para la recuperación o el flujo de inicio de fastbootd
. Sin embargo, no cargues los módulos del modo de recuperación en la inicialización de la primera etapa durante el flujo de inicio normal. Los módulos del modo de recuperación se pueden aplazar a la inicialización de la segunda etapa para disminuir el tiempo de inicio. Todos los demás módulos que no se necesitan en la inicialización de la primera etapa deben trasladarse a la partición del proveedor o vendor_dlkm
.
Dada una lista de dispositivos finales (por ejemplo, el UFS o el dispositivo en serie), la secuencia de comandos dev needs.sh
encuentra todos los controladores, dispositivos y módulos necesarios para que las dependencias o proveedores (por ejemplo, relojes, reguladores o gpio
) realicen la prueba.
Mover los módulos al init de la segunda etapa disminuye los tiempos de inicio de las siguientes maneras:
- Reducción del tamaño del ramdisk
- Esto genera lecturas de flash más rápidas cuando el bootloader carga el ramdisk (paso de inicio serializado).
- Esto genera velocidades de descompresión más rápidas cuando el kernel descomprime el ramdisk (paso de inicio serializado).
- La inicialización de la segunda etapa funciona en paralelo, lo que oculta el tiempo de carga del módulo con el trabajo que se realiza en la inicialización de la segunda etapa.
Mover módulos a la segunda etapa puede ahorrar entre 500 y 1,000 ms en los tiempos de inicio, según la cantidad de módulos que puedas mover a la inicialización de la segunda etapa.
Logística de carga de módulos
La compilación de Android más reciente incluye configuraciones de la placa que controlan qué módulos se copian en cada etapa y cuáles se cargan. En esta sección, nos enfocamos en el siguiente subconjunto:
BOARD_VENDOR_RAMDISK_KERNEL_MODULES
: Es la lista de módulos que se copiarán en el ramdisk.BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD
: Esta es una lista de módulos que se cargarán en la inicialización de la primera etapa.BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD
: Es la lista de módulos que se cargarán cuando se seleccione la recuperación ofastbootd
del disco RAM.BOARD_VENDOR_KERNEL_MODULES
: Es la lista de módulos que se copiarán en el proveedor o la particiónvendor_dlkm
en el directorio/vendor/lib/modules/
.BOARD_VENDOR_KERNEL_MODULES_LOAD
. Esta lista de módulos se cargará en el init de la segunda etapa.
Los módulos de arranque y recuperación en ramdisk también deben copiarse en el proveedor o en la partición vendor_dlkm
en /vendor/lib/modules
. Copiar estos módulos en la partición del proveedor garantiza que no sean invisibles durante la inicialización de la segunda etapa, lo que es útil para depurar y recopilar modinfo
para informes de errores.
La duplicación debería costar un espacio mínimo en el proveedor o la partición vendor_dlkm
, siempre y cuando se minimice el conjunto de módulos de inicio. Asegúrate de que el archivo modules.list
del proveedor tenga una lista filtrada de módulos en /vendor/lib/modules
.
La lista filtrada garantiza que los tiempos de inicio no se vean afectados por la carga nuevamente de los módulos (que es un proceso costoso).
Asegúrate de que los módulos del modo de recuperación se carguen como un grupo. La carga de módulos del modo de recuperación se puede realizar en el modo de recuperación o al comienzo de la inicialización de la segunda etapa en cada flujo de inicio.
Puedes usar los archivos Board.Config.mk
del dispositivo para realizar estas acciones, como se muestra en el siguiente ejemplo:
# 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)))
En este ejemplo, se muestra un subconjunto de BOOT_KERNEL_MODULES
y RECOVERY_KERNEL_MODULES
más fácil de administrar, que se especificará de forma local en los archivos de configuración de la placa. La secuencia de comandos anterior encuentra y completa cada uno de los módulos del subconjunto de los módulos de kernel disponibles seleccionados y deja los módulos restantes para la inicialización de la segunda etapa.
Para la inicialización de la segunda etapa, recomendamos ejecutar la carga del módulo como un servicio para que no bloquee el flujo de inicio. Usa una secuencia de comandos de shell para administrar la carga del módulo, de modo que otros aspectos logísticos, como el manejo y la mitigación de errores, o la finalización de la carga del módulo, se puedan informar (o ignorar) si es necesario.
Puedes ignorar una falla de carga del módulo de depuración que no está presente en las compilaciones del usuario.
Para ignorar esta falla, configura la propiedad vendor.device.modules.ready
para activar etapas posteriores del flujo de inicio de la secuencia de comandos init rc
y continuar en la pantalla de inicio. Consulta la siguiente secuencia de comandos de ejemplo, si tienes el siguiente código en /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
En el archivo rc de hardware, el servicio one shot
se puede especificar con lo siguiente:
service insmod-sh /vendor/etc/init.insmod.sh /vendor/etc/init.insmod.<hw>.cfg
class main
user root
group root system
Disabled
oneshot
Se pueden realizar optimizaciones adicionales después de que los módulos pasan de la primera a la segunda etapa. Puedes usar la función de lista de bloqueo de modprobe para dividir el flujo de inicio de la segunda etapa y, así, incluir la carga diferida de módulos no esenciales. La carga de los módulos que usa exclusivamente un HAL específico se puede aplazar para cargar los módulos solo cuando se inicia el HAL.
Para mejorar los tiempos de inicio aparentes, puedes elegir específicamente módulos en el servicio de carga de módulos que sean más propicios para la carga después de la pantalla de inicio. Por ejemplo, puedes cargar de forma tardía y explícita los módulos para el decodificador de video o Wi-Fi después de que se haya borrado el flujo de inicio de init (por ejemplo, el indicador de propiedad de Android sys.boot_complete
). Asegúrate de que los HAL de los módulos de carga tardía bloqueen el tiempo suficiente cuando no estén presentes los controladores de kernel.
Como alternativa, puedes usar el comando wait<file>[<timeout>]
de init en la secuencia de comandos de rc del flujo de inicio para esperar a que se muestren entradas sysfs
seleccionadas que indiquen que los módulos del controlador completaron las operaciones de sondeo. Un ejemplo de esto es esperar a que el controlador de pantalla complete la carga en segundo plano de la recuperación o fastbootd
, antes de presentar los gráficos del menú.
Inicializa la frecuencia de la CPU en un valor razonable en el bootloader
Es posible que no todos los SoC o productos puedan iniciar la CPU a la frecuencia más alta debido a problemas térmicos o de energía durante las pruebas de bucle de inicio. Sin embargo, asegúrate de que el bootloader configure la frecuencia de todas las CPUs en línea lo más alta posible de forma segura para un SoC o producto. Esto es muy importante porque, con un kernel completamente modular, la descompresión de init ramdisk se produce antes de que se pueda cargar el controlador de CPUfreq. Por lo tanto, si el bootloader deja la CPU en el extremo inferior de su frecuencia, el tiempo de descompresión del ramdisk puede tardar más que un kernel compilado de forma estática (después de ajustar la diferencia de tamaño del ramdisk) porque la frecuencia de la CPU sería muy baja cuando se realiza un trabajo intensivo en la CPU (descompresión). Lo mismo se aplica a la memoria y la frecuencia de interconexión.
Inicializa la frecuencia de la CPU de las CPUs grandes en el bootloader
Antes de que se cargue el controlador CPUfreq
, el kernel no conoce las frecuencias de la CPU y no escala la capacidad de programación de la CPU para su frecuencia actual. El kernel podría migrar subprocesos a la CPU grande si la carga es lo suficientemente alta en la CPU pequeña.
Asegúrate de que las CPU grandes tengan, al menos, el mismo rendimiento que las CPU pequeñas en relación con la frecuencia con la que las deja en el bootloader. Por ejemplo, si la CPU grande tiene el doble de rendimiento que la CPU pequeña para la misma frecuencia, pero el bootloader establece la frecuencia de la CPU pequeña en 1.5 GHz y la frecuencia de la CPU grande en 300 MHz, el rendimiento del inicio disminuirá si el kernel mueve un subproceso a la CPU grande. En este ejemplo, si es seguro iniciar la CPU grande a 750 MHz, debes hacerlo, incluso si no planeas usarla de forma explícita.
Los controladores no deben cargar el firmware en la inicialización de la primera etapa.
Puede haber algunos casos inevitables en los que se deba cargar el firmware en la inicialización de la primera etapa. Sin embargo, en general, los controladores no deben cargar ningún firmware en la inicialización de la primera etapa, en especial en el contexto de la prueba del dispositivo. La carga del firmware en la inicialización de la primera etapa hace que se detenga todo el proceso de inicio si el firmware no está disponible en el ramdisk de la primera etapa. Incluso si el firmware está presente en el ramdisk de la primera etapa, aún causa un retraso innecesario.