Optimización del tiempo de arranque

Esta página proporciona un conjunto de consejos, que puede seleccionar, para mejorar el tiempo de arranque.

Quitar los símbolos de depuración de los módulos

De forma similar a cómo se eliminan los símbolos de depuración del kernel en un dispositivo de producción, asegúrese de eliminar también los símbolos de depuración de los módulos. La eliminación de los símbolos de depuración de los módulos ayuda al tiempo de arranque al reducir lo siguiente:

  • El tiempo que lleva leer los binarios desde flash.
  • El tiempo que se tarda en descomprimir el ramdisk.
  • El tiempo que se tarda en cargar los módulos.

Eliminar el símbolo de depuración de los módulos puede ahorrar varios segundos durante el arranque.

La eliminación de símbolos está habilitada de forma predeterminada en la compilación de la plataforma Android, pero para habilitarlos explícitamente, configure BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES en la configuración específica de su dispositivo en dispositivo/ vendor / device .

Use compresión LZ4 para kernel y ramdisk

Gzip genera una salida comprimida más pequeña en comparación con LZ4, pero LZ4 se descomprime más rápido que Gzip. Para el núcleo y los módulos, la reducción absoluta del tamaño de almacenamiento al usar Gzip no es tan significativa en comparación con el beneficio del tiempo de descompresión de LZ4.

Se ha agregado compatibilidad con la compresión ramdisk LZ4 a la compilación de la plataforma Android a través BOARD_RAMDISK_USE_LZ4 . Puede establecer esta opción en la configuración específica de su dispositivo. La compresión del kernel se puede configurar a través de kernel defconfig.

Cambiar a LZ4 debería dar un tiempo de arranque de 500ms a 1000ms más rápido.

Evite el inicio de sesión excesivo en sus controladores

En ARM64 y ARM32, las llamadas de función que se encuentran a más de una distancia específica del sitio de la llamada necesitan una tabla de salto (llamada tabla de vinculación de procedimientos o PLT) para poder codificar la dirección de salto completa. Dado que los módulos se cargan dinámicamente, estas tablas de salto deben corregirse durante la carga del módulo. Las llamadas que necesitan reubicación se denominan entradas de reubicación con sumandos explícitos (o RELA, para abreviar) entradas en formato ELF.

El kernel de Linux optimiza el tamaño de la memoria (como la optimización de aciertos de caché) al asignar el PLT. Con esta confirmación ascendente , el esquema de optimización tiene una complejidad O(N^2), donde N es el número de RELA de tipo R_AARCH64_JUMP26 o R_AARCH64_CALL26 . Por lo tanto, tener menos RELA de este tipo es útil para reducir el tiempo de carga del módulo.

Un patrón de codificación común que aumenta el número de R_AARCH64_CALL26 o R_AARCH64_JUMP26 RELA es el inicio de sesión excesivo en un controlador. Cada llamada a printk() o cualquier otro esquema de registro generalmente agrega una CALL26 / JUMP26 RELA. En el texto de confirmación en la confirmación ascendente , observe que, incluso con la optimización, los seis módulos tardan unos 250 ms en cargarse, eso se debe a que esos seis módulos fueron los seis módulos principales con la mayor cantidad de registro.

Reducir el registro puede ahorrar entre 100 y 300 ms en los tiempos de arranque, dependiendo de cuán excesivo sea el registro existente.

Habilite el sondeo asíncrono, de forma selectiva

Cuando se carga un módulo, si el dispositivo que admite ya se completó desde el DT (árbol de dispositivos) y se agregó al núcleo del controlador, la sonda del dispositivo se realiza en el contexto de la llamada 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 del módulo se serializa principalmente, un dispositivo que tarda relativamente mucho tiempo en sondear ralentiza el tiempo de arranque.

Para evitar tiempos de arranque más lentos, habilite el sondeo asíncrono para los módulos que tardan un tiempo en sondear sus dispositivos. Habilitar el 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 poner en marcha el sondeo puede ser tan alto como el tiempo que se tarda en sondear el dispositivo.

Los dispositivos que están conectados a través de un bus lento como I2C, los dispositivos que cargan el firmware en su función de sonda y los dispositivos que realizan una gran cantidad de inicialización de hardware pueden generar problemas de tiempo. La mejor manera de identificar cuándo sucede esto es recopilar el tiempo de sondeo de cada controlador y clasificarlo.

Para habilitar el sondeo asíncrono para un módulo, no es suficiente configurar solo el indicador PROBE_PREFER_ASYNCHRONOUS en el código del controlador. Para los módulos, también debe agregar module_name .async_probe=1 en la línea de comando del kernel o pasar async_probe=1 como parámetro del módulo al cargar el módulo usando modprobe o insmod .

Habilitar el sondeo asincrónico puede ahorrar entre 100 y 500 ms en los tiempos de arranque, según el hardware o los controladores.

Pruebe su controlador CPUfreq lo antes posible

Cuanto antes pruebe el controlador CPUfreq, antes podrá escalar la frecuencia de la CPU al máximo (o algún máximo limitado térmicamente) durante el arranque. Cuanto más rápida sea la CPU, más rápido será el arranque. Esta directriz 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 el orden de compilación o enlace de los controladores. Use un alias MODULE_SOFTDEP() para asegurarse de que el controlador cpufreq se encuentre entre los primeros módulos en cargarse.

Además de cargar el módulo antes, también debe asegurarse de que todas las dependencias para sondear el controlador CPUfreq también lo hayan sido. Por ejemplo, si necesita un reloj o un regulador para controlar la frecuencia de su CPU, asegúrese de probarlos primero. O puede necesitar que los controladores térmicos se carguen antes que el controlador CPUfreq si es posible que sus CPU se calienten demasiado durante el arranque. Por lo tanto, haga lo que pueda para asegurarse de que los controladores CPUfreq y devfreq relevantes sondeen lo antes posible.

Los ahorros de sondear su controlador CPUfreq temprano pueden ser muy pequeños o muy grandes dependiendo de qué tan temprano pueda hacer que estos sondeen y con qué frecuencia el cargador de arranque deja las CPU.

Mover módulos a la partición de inicio de segunda etapa, proveedor o proveedor_dlkm

Debido a que el proceso de inicialización de la primera etapa está serializado, no hay muchas oportunidades para paralelizar el proceso de inicio. Si no se necesita un módulo para que termine la inicialización de la primera etapa, mueva el módulo a la inicialización de la segunda etapa colocándolo en la partición de proveedor o vendor_dlkm .

La inicialización de la primera etapa no requiere sondear varios dispositivos para llegar a la inicialización de la segunda etapa. Solo se necesita la funcionalidad de consola y almacenamiento flash para un flujo de arranque normal.

Cargue los siguientes controladores esenciales:

  • perro guardián
  • Reiniciar
  • cpufreq

Para el modo fastbootd de recuperación y espacio de usuario, el inicio de la primera etapa requiere más dispositivos para sondear (como USB) y mostrar. Guarde una copia de estos módulos en el ramdisk de la primera etapa y en la partición del vendor_dlkm o del proveedor_dlkm. Esto les permite cargarse en el inicio de la primera etapa para la recuperación o el flujo de arranque fastbootd . Sin embargo, no cargue los módulos del modo de recuperación en el inicio de la primera etapa durante el flujo de arranque normal. Los módulos del modo de recuperación se pueden diferir a la segunda etapa de inicio para disminuir el tiempo de arranque. Todos los demás módulos que no se necesitan en la primera etapa de inicio deben moverse a la partición de proveedor o vendor_dlkm .

Dada una lista de dispositivos hoja (por ejemplo, UFS o serie), el script dev needs.sh encuentra todos los controladores, dispositivos y módulos necesarios para las dependencias o proveedores (por ejemplo, relojes, reguladores o gpio ) para sondear.

Mover módulos a la segunda etapa init reduce los tiempos de arranque de las siguientes maneras:

  • Reducción de tamaño de ramdisk.
    • Esto produce lecturas flash más rápidas cuando el gestor de arranque carga el ramdisk (paso de arranque serializado).
    • Esto produce velocidades de descompresión más rápidas cuando el núcleo descomprime el ramdisk (paso de arranque serializado).
  • El inicio de la segunda etapa funciona en paralelo, lo que oculta el tiempo de carga del módulo con el trabajo que se realiza en el inicio de la segunda etapa.

Mover los módulos a la segunda etapa puede ahorrar entre 500 y 1000 ms en los tiempos de arranque, según la cantidad de módulos que pueda mover a la segunda etapa.

Logística de carga de módulos

La última compilación de Android presenta configuraciones de placa que controlan qué módulos se copian en cada etapa y qué módulos se cargan. Esta sección se centra en el siguiente subconjunto:

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES . Esta lista de módulos que se copiarán en el ramdisk.
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD . Esta lista de módulos que se cargarán en la primera etapa init.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD . Esta lista de módulos que se cargarán cuando se seleccione recovery o fastbootd desde ramdisk.
  • BOARD_VENDOR_KERNEL_MODULES . Esta lista de módulos se copiará en la partición del proveedor o del proveedor_dlkm en el vendor_dlkm /vendor/lib/modules/ .
  • BOARD_VENDOR_KERNEL_MODULES_LOAD . Esta lista de módulos que se cargarán en la segunda etapa init.

Los módulos de arranque y recuperación en ramdisk también deben vendor_dlkm en la partición del proveedor o del proveedor_dlkm en /vendor/lib/modules . Copiar estos módulos en la partición del proveedor garantiza que los módulos no sean invisibles durante la segunda etapa de inicio, lo que es útil para depurar y recopilar modinfo para informes de errores.

La duplicación debería costar un espacio mínimo en la partición del vendor_dlkm o del proveedor_dlkm, siempre que se minimice el conjunto de módulos de arranque. Asegúrese 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 arranque no se vean afectados por la carga de los módulos nuevamente (lo cual es un proceso costoso).

Asegúrese de que los módulos del modo de recuperación se carguen como un grupo. La carga de los módulos del modo de recuperación se puede realizar en el modo de recuperación o al comienzo de la segunda etapa init en cada flujo de arranque.

Puede usar los archivos Board.Config.mk del dispositivo para realizar estas acciones, como se ve 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)))

Este ejemplo muestra un subconjunto más fácil de administrar de BOOT_KERNEL_MODULES y RECOVERY_KERNEL_MODULES para especificarse localmente en los archivos de configuración de la placa. La secuencia de comandos anterior encuentra y completa cada uno de los módulos de subconjunto de los módulos de núcleo disponibles seleccionados, dejando los módulos restantes para el inicio de la segunda etapa.

Para el inicio de la segunda etapa, recomendamos ejecutar la carga del módulo como un servicio para que no bloquee el flujo de inicio. Use un script de shell para administrar la carga del módulo de modo que se pueda informar (o ignorar) otra logística, como el manejo y la mitigación de errores, o la finalización de la carga del módulo, si es necesario.

Puede ignorar una falla de carga del módulo de depuración que no está presente en las compilaciones del usuario. Para ignorar este error, configure la propiedad vendor.device.modules.ready para activar las etapas posteriores del flujo de arranque de secuencias de comandos init rc para continuar en la pantalla de inicio. Consulte el siguiente script de ejemplo, si tiene 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 de one shot vez podría especificarse con:

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 pasen de la primera a la segunda etapa. Puede usar la función de lista de bloqueo de modprobe para dividir el flujo de arranque de la segunda etapa para incluir la carga diferida de módulos de módulos no esenciales. La carga de módulos utilizados exclusivamente por una HAL específica se puede diferir para cargar los módulos solo cuando se inicia la HAL.

Para mejorar los tiempos de arranque aparentes, puede elegir específicamente módulos en el servicio de carga de módulos que sean más propicios para cargar después de la pantalla de inicio. Por ejemplo, puede cargar explícitamente los módulos para el decodificador de video o wifi después de que se haya borrado el flujo de inicio de inicio (señal de propiedad de Android sys.boot_complete , por ejemplo). Asegúrese de que las HAL para los módulos de carga tardía se bloqueen durante el tiempo suficiente cuando los controladores del kernel no estén presentes.

De manera alternativa, puede usar el comando wait<file>[<timeout>] de init en el flujo de arranque rc scripting para esperar las entradas seleccionadas de sysfs para mostrar que los módulos del controlador han completado las operaciones de sondeo. Un ejemplo de esto es esperar a que el controlador de pantalla complete la carga en el fondo de recovery o fastbootd , antes de presentar los gráficos del menú.

Inicialice la frecuencia de la CPU a un valor razonable en el cargador de arranque

Es posible que no todos los SoC/productos puedan arrancar la CPU a la frecuencia más alta debido a problemas térmicos o de energía durante las pruebas de bucle de arranque. Sin embargo, asegúrese de que el cargador de arranque establezca la frecuencia de todas las CPU en línea al nivel más alto posible para un SoC/producto. Esto es muy importante porque, con un núcleo totalmente modular, la descompresión del ramdisk de inicio tiene lugar antes de que se pueda cargar el controlador CPUfreq. Por lo tanto, si el cargador de arranque deja la CPU en el extremo inferior de su frecuencia, el tiempo de descompresión del ramdisk puede llevar más tiempo que un kernel compilado estáticamente (después de ajustar la diferencia de tamaño del ramdisk) porque la frecuencia de la CPU sería muy baja cuando se hace un uso intensivo de la CPU. trabajo (descompresión). Lo mismo se aplica a la memoria/frecuencia de interconexión.

Inicialice la frecuencia de la CPU de las CPU grandes en el cargador de arranque

Antes de que se cargue el controlador CPUfreq , el núcleo desconoce las frecuencias pequeñas y grandes de la CPU y no escala la capacidad programada de las 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úrese de que las CPU grandes tengan al menos el mismo rendimiento que las CPU pequeñas para la frecuencia a la que el cargador de arranque las deja. Por ejemplo, si la CPU grande tiene el doble de rendimiento que la CPU pequeña para la misma frecuencia, pero el cargador de arranque establece el frecuencia de la CPU pequeña a 1,5 GHz y la frecuencia de la CPU grande a 300 MHz, entonces el rendimiento de arranque disminuirá si el kernel mueve un subproceso a la CPU grande. En este ejemplo, si es seguro arrancar la CPU grande a 750 MHz, debe hacerlo incluso si no planea usarla explícitamente.

Los controladores no deben cargar el firmware en la primera etapa de inicio

Puede haber algunos casos inevitables en los que el firmware deba cargarse en el inicio de la primera etapa. Pero, en general, los controladores no deberían cargar ningún firmware en el inicio de la primera etapa, especialmente en el contexto de sondeo del dispositivo. La carga del firmware en el inicio de la primera etapa hace que todo el proceso de arranque se detenga si el firmware no está disponible en el ramdisk de la primera etapa. E incluso si el firmware está presente en el ramdisk de la primera etapa, todavía causa una demora innecesaria.