Optimización del tiempo de arranque

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

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

De manera 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. Eliminar 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 lleva descomprimir el disco RAM.
  • El tiempo que se tarda en cargar los módulos.

Quitar 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 habilitarla explícitamente, configure BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES en la configuración específica de su dispositivo en dispositivo/ vendor / device .

Utilice 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 kernel y los módulos, la reducción absoluta del tamaño de almacenamiento mediante el uso de Gzip no es tan significativa en comparación con el beneficio del tiempo de descompresión de LZ4.

Se agregó soporte para la compresión de disco RAM LZ4 a la plataforma Android mediante BOARD_RAMDISK_USE_LZ4 . Puede configurar esta opción en la configuración específica de su dispositivo. La compresión del kernel se puede configurar mediante kernel defconfig.

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

Evite el registro excesivo en sus controladores

En ARM64 y ARM32, las llamadas a funciones 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 arreglarse durante la carga del módulo. Las llamadas que necesitan reubicación se denominan entradas de reubicación con entradas de sumandos explícitos (o RELA, para abreviar) en formato ELF.

El kernel de Linux realiza alguna optimización del tamaño de la memoria (como la optimización del acceso al 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 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 R_AARCH64_CALL26 o R_AARCH64_JUMP26 RELA es el registro excesivo en un controlador. Cada llamada a printk() o cualquier otro esquema de registro generalmente agrega una entrada RELA CALL26 / JUMP26 . En el texto de confirmación en la confirmación ascendente , observe que incluso con la optimización, los seis módulos tardan aproximadamente 250 ms en cargarse; eso se debe a que esos seis módulos fueron los seis módulos principales con la mayor cantidad de registros.

La reducción del registro puede ahorrar entre 100 y 300 ms en tiempos de arranque , dependiendo de qué tan excesivo sea el registro existente.

Habilite el sondeo asincrónico, 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, entonces la sonda del dispositivo se realiza en el contexto de la llamada module_init() . Cuando se realiza una prueba de dispositivo en el contexto de module_init() , el módulo no puede terminar de cargarse hasta que se complete la prueba. Dado que la carga de módulos se realiza principalmente en forma serializada, un dispositivo que tarda relativamente mucho en sondearse ralentiza el tiempo de arranque.

Para evitar tiempos de arranque más lentos, habilite el sondeo asincrónico para los módulos que tardan un poco en sondear sus dispositivos. Es posible que habilitar el sondeo asincrónico para todos los módulos no sea beneficioso, ya que el tiempo que lleva bifurcar un subproceso e iniciar el sondeo puede ser tan alto como el tiempo que lleva 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 inicializan mucho el hardware pueden provocar problemas de sincronización. La mejor manera de identificar cuándo sucede esto es recopilar el tiempo de sondeo de cada conductor y ordenarlo.

Para habilitar el sondeo asincrónico para un módulo, no es suficiente establecer únicamente 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 asíncrono puede ahorrar entre 100 y 500 ms en tiempos de arranque , dependiendo de su hardware/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 initcall y del orden de compilación o enlace de los controladores. Utilice un alias MODULE_SOFTDEP() para asegurarse de que el controlador cpufreq esté entre los primeros módulos en cargarse.

Además de cargar el módulo temprano, también debe asegurarse de que todas las dependencias para probar el controlador CPUfreq también se hayan probado. Por ejemplo, si necesita un reloj o un regulador para controlar la frecuencia de su CPU, asegúrese de probarlos primero. O es posible que necesite cargar controladores térmicos 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 CPUfreq y los controladores devfreq relevantes sean probados lo antes posible.

Los ahorros al probar el controlador CPUfreq con anticipación pueden ser de muy pequeños a muy grandes, dependiendo de qué tan temprano pueda hacerlos sondear y con qué frecuencia el gestor de arranque deja las CPU.

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

Debido a que el proceso de inicio 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 finalice el inicio de la primera etapa, mueva el módulo al inicio de la segunda etapa colocándolo en la partición proveedor o vendor_dlkm .

El inicio de la primera etapa no requiere sondear varios dispositivos para llegar al inicio de la segunda etapa. Sólo se necesitan 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. Mantenga una copia de estos módulos en el disco RAM de la primera etapa y en la partición proveedor o vendor_dlkm . Esto permite que se carguen 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 inicio normal. Los módulos del modo de recuperación se pueden diferir al inicio de la segunda etapa para disminuir el tiempo de arranque. Todos los demás módulos que no sean necesarios en la primera etapa de inicio deben moverse a la partición proveedor o vendor_dlkm .

Dada una lista de dispositivos hoja (por ejemplo, UFS o serial), 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 al init de segunda etapa reduce los tiempos de arranque de las siguientes maneras:

  • Reducción del tamaño del disco ram.
    • Esto produce lecturas flash más rápidas cuando el gestor de arranque carga el disco ram (paso de arranque serializado).
    • Esto produce velocidades de descompresión más rápidas cuando el kernel descomprime el disco ram (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 módulos a la segunda etapa puede ahorrar entre 500 y 1000 ms en tiempos de arranque , dependiendo de cuántos módulos pueda mover al inicio de la segunda etapa.

Logística de carga de módulos

La última versió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 se copiarán en el disco RAM.
  • 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 el disco ram.
  • BOARD_VENDOR_KERNEL_MODULES . Esta lista de módulos se copiará en la partición proveedor o vendor_dlkm en el directorio /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 se deben copiar a la partición proveedor o vendor_dlkm en /vendor/lib/modules . Copiar estos módulos a la partición del proveedor garantiza que los módulos no sean invisibles durante el inicio de la segunda etapa, lo cual 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 proveedor o vendor_dlkm siempre que se minimice el conjunto de módulos de inicio. 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 modo de recuperación o al comienzo de la segunda etapa de inicio en cada flujo de arranque.

Puede utilizar 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 que se especificarán localmente en los archivos de configuración de la placa. El script anterior busca y completa cada uno de los subconjuntos de módulos de los módulos del kernel 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. Utilice un script de shell para gestionar la carga del módulo de modo que se pueda informar (o ignorar) de otras logísticas, como el manejo y la mitigación de errores o la finalización de la carga del módulo, si es necesario.

Puede ignorar un error de carga del módulo de depuración que no esté presente en las compilaciones de los usuarios. Para ignorar este error, configure la propiedad vendor.device.modules.ready para activar etapas posteriores del flujo de arranque del scripting 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 one shot 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 utilizar la función de lista de bloqueo de modprobe para dividir el flujo de inicio de la segunda etapa e incluir la carga diferida de módulos no esenciales. La carga de módulos utilizados exclusivamente por un HAL específico se puede aplazar para cargar los módulos solo cuando se inicia el 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 la carga después de la pantalla de inicio. Por ejemplo, puede cargar explícitamente tarde 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 los HAL de los módulos de carga tardía se bloqueen el tiempo suficiente cuando los controladores del kernel no estén presentes.

Alternativamente, puede usar el comando wait<file>[<timeout>] de init en el scripting rc de flujo de arranque para esperar a que las entradas sysfs seleccionadas muestren 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 segundo plano de recuperación o fastbootd , antes de presentar los gráficos del menú.

Inicialice la frecuencia de la CPU a un valor razonable en el gestor 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 del bucle de arranque. Sin embargo, asegúrese de que el gestor de arranque establezca la frecuencia de todas las CPU en línea en el nivel más alto posible de forma segura para un SoC/producto. Esto es muy importante porque, con un kernel completamente modular, la descompresión inicial del disco ram tiene lugar antes de que se pueda cargar el controlador CPUfreq. Por lo tanto, si el gestor de arranque deja la CPU en el extremo inferior de su frecuencia, el tiempo de descompresión del disco ram puede tardar más que el de un kernel compilado estáticamente (después de ajustar la diferencia de tamaño del disco ram) 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 CPU grandes en el gestor de arranque

Antes de cargar el controlador CPUfreq , el kernel desconoce las frecuencias grandes y pequeñas 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 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 con la que las deja el gestor de arranque. Por ejemplo, si la CPU grande tiene el doble de rendimiento que la CPU pequeña para la misma frecuencia, pero el gestor de arranque establece el Si la frecuencia de la CPU pequeña es de 1,5 GHz y la frecuencia de la CPU grande es de 300 MHz, entonces el rendimiento de arranque disminuirá si el núcleo 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 usarlo explícitamente.

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

Puede haber algunos casos inevitables en los que sea necesario cargar el firmware en la primera etapa de inicio. Pero, en general, los controladores no deben cargar ningún firmware en el inicio de la primera etapa, especialmente en el contexto de la sonda 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 disco RAM de la primera etapa. E incluso si el firmware está presente en el disco RAM de la primera etapa, todavía causa un retraso innecesario.