Optimisation du temps de démarrage

Cette page fournit un ensemble de conseils parmi lesquels vous pouvez sélectionner pour améliorer le temps de démarrage.

Supprimer les symboles de débogage des modules

De la même manière que les symboles de débogage sont supprimés du noyau sur un périphérique de production, assurez-vous de également supprimer les symboles de débogage des modules. Supprimer les symboles de débogage des modules facilite le démarrage en réduisant les éléments suivants :

  • Le temps nécessaire pour lire les binaires à partir du flash.
  • Le temps nécessaire pour décompresser le disque virtuel.
  • Le temps nécessaire au chargement des modules.

Supprimer le symbole de débogage des modules peut faire gagner plusieurs secondes lors du démarrage.

La suppression des symboles est activée par défaut dans la version de la plate-forme Android, mais pour les activer explicitement, définissez BOARD_DO_NOT_STRIP_VENDOR_RAMDISK_MODULES dans la configuration spécifique à votre appareil sous périphérique/ vendor / device .

Utiliser la compression LZ4 pour le noyau et le disque virtuel

Gzip génère une sortie compressée plus petite que LZ4, mais LZ4 se décompresse plus rapidement que Gzip. Pour le noyau et les modules, la réduction absolue de la taille de stockage grâce à l'utilisation de Gzip n'est pas si significative par rapport au gain de temps de décompression de LZ4.

La prise en charge de la compression du disque virtuel LZ4 a été ajoutée à la plate-forme Android via BOARD_RAMDISK_USE_LZ4 . Vous pouvez définir cette option dans la configuration spécifique à votre appareil. La compression du noyau peut être définie via le noyau defconfig.

Le passage à LZ4 devrait donner un temps de démarrage de 500 ms à 1 000 ms plus rapide.

Évitez la journalisation excessive dans vos pilotes

Dans ARM64 et ARM32, les appels de fonction qui se trouvent à plus d'une distance spécifique du site d'appel nécessitent une table de saut (appelée table de liaison de procédure, ou PLT) pour pouvoir coder l'adresse de saut complète. Étant donné que les modules sont chargés dynamiquement, ces tables de sauts doivent être corrigées lors du chargement du module. Les appels qui nécessitent une relocalisation sont appelés entrées de relocalisation avec des ajouts explicites (ou RELA, en abrégé) entrées au format ELF.

Le noyau Linux effectue une certaine optimisation de la taille de la mémoire (telle que l'optimisation des accès au cache) lors de l'allocation du PLT. Avec ce commit en amont , le schéma d'optimisation a une complexité O(N^2), où N est le nombre de RELA de type R_AARCH64_JUMP26 ou R_AARCH64_CALL26 . Avoir moins de RELA de ces types est donc utile pour réduire le temps de chargement du module.

Un modèle de codage courant qui augmente le nombre de R_AARCH64_CALL26 ou R_AARCH64_JUMP26 est la journalisation excessive dans un pilote. Chaque appel à printk() ou à tout autre schéma de journalisation ajoute généralement une entrée CALL26 / JUMP26 RELA. Dans le texte de validation du commit en amont , notez que même avec l'optimisation, le chargement des six modules prend environ 250 ms, car ces six modules étaient les six principaux modules avec le plus de journalisation.

La réduction de la journalisation peut permettre d'économiser environ 100 à 300 ms sur les temps de démarrage , en fonction du caractère excessif de la journalisation existante.

Activer le sondage asynchrone, de manière sélective

Lorsqu'un module est chargé, si le périphérique qu'il prend en charge a déjà été renseigné à partir du DT (devicetree) et ajouté au noyau du pilote, alors la sonde du périphérique est effectuée dans le contexte de l'appel module_init() . Lorsqu'une sonde de périphérique est effectuée dans le contexte de module_init() , le module ne peut pas terminer le chargement tant que la sonde n'est pas terminée. Étant donné que le chargement des modules est principalement sérialisé, un périphérique dont la sonde prend un temps relativement long ralentit le temps de démarrage.

Pour éviter des temps de démarrage plus lents, activez la détection asynchrone pour les modules qui mettent un certain temps à tester leurs périphériques. L'activation de la détection asynchrone pour tous les modules peut ne pas être bénéfique, car le temps nécessaire pour créer un thread et lancer la sonde peut être aussi long que le temps nécessaire pour tester le périphérique.

Les appareils connectés via un bus lent tel que I2C, les appareils qui chargent le micrologiciel dans leur fonction de sonde et les appareils qui effectuent de nombreuses initialisations matérielles peuvent entraîner un problème de synchronisation. La meilleure façon d'identifier quand cela se produit est de collecter le temps de sonde pour chaque conducteur et de le trier.

Pour activer le sondage asynchrone pour un module, il ne suffit pas de définir uniquement l'indicateur PROBE_PREFER_ASYNCHRONOUS dans le code du pilote. Pour les modules, vous devez également ajouter module_name .async_probe=1 dans la ligne de commande du noyau ou transmettre async_probe=1 comme paramètre de module lors du chargement du module à l'aide modprobe ou insmod .

L'activation du sondage asynchrone peut permettre d'économiser environ 100 à 500 ms sur les temps de démarrage en fonction de votre matériel/pilotes.

Sondez votre pilote CPUfreq le plus tôt possible

Plus votre pilote CPUfreq sonde tôt, plus tôt vous pourrez mettre à l'échelle la fréquence du processeur au maximum (ou à un maximum limité thermiquement) pendant le démarrage. Plus le processeur est rapide, plus le démarrage est rapide. Cette directive s'applique également aux pilotes devfreq qui contrôlent la DRAM, la mémoire et la fréquence d'interconnexion.

Avec les modules, l'ordre de chargement peut dépendre du niveau initcall et de l'ordre de compilation ou de liaison des pilotes. Utilisez un alias MODULE_SOFTDEP() pour vous assurer que le pilote cpufreq est parmi les premiers modules à charger.

Outre le chargement anticipé du module, vous devez également vous assurer que toutes les dépendances à tester sur le pilote CPUfreq ont également été testées. Par exemple, si vous avez besoin d'une horloge ou d'une poignée de régulateur pour contrôler la fréquence de votre processeur, assurez-vous qu'ils sont d'abord sondés. Ou vous devrez peut-être charger les pilotes thermiques avant le pilote CPUfreq s'il est possible que vos processeurs deviennent trop chauds pendant le démarrage. Alors, faites ce que vous pouvez pour vous assurer que les pilotes CPUfreq et devfreq concernés sont détectés le plus tôt possible.

Les économies réalisées grâce à l'analyse précoce de votre pilote CPUfreq peuvent être très faibles, voire très importantes, en fonction de la rapidité avec laquelle vous pouvez les tester et de la fréquence à laquelle le chargeur de démarrage laisse les processeurs.

Déplacer les modules vers la partition d'initialisation de deuxième étape, le vendeur ou le vendeur_dlkm

Étant donné que le processus d'initialisation de la première étape est sérialisé, il n'existe pas beaucoup de possibilités de paralléliser le processus de démarrage. Si un module n'est pas nécessaire à la fin de l'initialisation de la première étape, déplacez le module vers l'initialisation de la deuxième étape en le plaçant dans la partition supplier ou vendor_dlkm .

L'initialisation de la première étape ne nécessite pas de sonder plusieurs appareils pour accéder à l'initialisation de la deuxième étape. Seules les fonctionnalités de console et de stockage flash sont nécessaires pour un flux de démarrage normal.

Chargez les pilotes essentiels suivants :

  • chien de garde
  • réinitialiser
  • cpufréq

Pour la récupération et le mode fastbootd de l'espace utilisateur, l'initialisation de la première étape nécessite la détection et l'affichage de davantage de périphériques (tels que l'USB). Conservez une copie de ces modules dans le disque virtuel de premier étage et dans la partition supplier ou vendor_dlkm . Cela leur permet d'être chargés lors de la première étape d'initialisation pour la récupération ou le flux de démarrage fastbootd . Cependant, ne chargez pas les modules du mode de récupération lors de la première étape d'initialisation pendant le flux de démarrage normal. Les modules du mode de récupération peuvent être reportés à l'initialisation de la deuxième étape pour réduire le temps de démarrage. Tous les autres modules qui ne sont pas nécessaires lors de la première étape d'initialisation doivent être déplacés vers la partition supplier ou vendor_dlkm .

Étant donné une liste de périphériques feuilles (par exemple, l'UFS ou la série), le script dev needs.sh trouve tous les pilotes, périphériques et modules nécessaires aux dépendances ou aux fournisseurs (par exemple, les horloges, les régulateurs ou gpio ) à sonder.

Le déplacement des modules vers la deuxième étape d'initialisation réduit les temps de démarrage des manières suivantes :

  • Réduction de la taille du disque RAM.
    • Cela permet d'obtenir des lectures flash plus rapides lorsque le chargeur de démarrage charge le disque virtuel (étape de démarrage sérialisé).
    • Cela permet d'obtenir des vitesses de décompression plus rapides lorsque le noyau décompresse le disque virtuel (étape de démarrage sérialisé).
  • L'initialisation de la deuxième étape fonctionne en parallèle, ce qui masque le temps de chargement du module avec le travail effectué lors de l'initialisation de la deuxième étape.

Le déplacement des modules vers la deuxième étape peut permettre d'économiser 500 à 1 000 ms sur les temps de démarrage en fonction du nombre de modules que vous pouvez déplacer vers l'initialisation de la deuxième étape.

Logistique de chargement des modules

La dernière version d'Android propose des configurations de carte qui contrôlent quels modules sont copiés à chaque étape et quels modules se chargent. Cette section se concentre sur le sous-ensemble suivant :

  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES . Cette liste de modules à copier dans le disque virtuel.
  • BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD . Cette liste de modules à charger lors de la première étape d'initialisation.
  • BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD . Cette liste de modules à charger lorsque recovery ou fastbootd est sélectionné à partir du disque virtuel.
  • BOARD_VENDOR_KERNEL_MODULES . Cette liste de modules doit être copiée dans la partition supplier ou vendor_dlkm du répertoire /vendor/lib/modules/ .
  • BOARD_VENDOR_KERNEL_MODULES_LOAD . Cette liste de modules à charger lors de la deuxième étape d'initialisation.

Les modules de démarrage et de récupération du disque virtuel doivent également être copiés sur la partition supplier ou vendor_dlkm dans /vendor/lib/modules . La copie de ces modules sur la partition du fournisseur garantit que les modules ne sont pas invisibles lors de la deuxième étape d'initialisation, ce qui est utile pour le débogage et la collecte modinfo pour les rapports de bogues.

La duplication devrait coûter un minimum d'espace sur la partition supplier ou vendor_dlkm tant que l'ensemble des modules de démarrage est réduit au minimum. Assurez-vous que le fichier modules.list du fournisseur contient une liste filtrée de modules dans /vendor/lib/modules . La liste filtrée garantit que les temps de démarrage ne sont pas affectés par le nouveau chargement des modules (ce qui est un processus coûteux).

Assurez-vous que les modules du mode de récupération se chargent en groupe. Le chargement des modules en mode de récupération peut être effectué soit en mode de récupération, soit au début de la deuxième étape d'initialisation de chaque flux de démarrage.

Vous pouvez utiliser les fichiers Board.Config.mk du périphérique pour effectuer ces actions, comme le montre l'exemple suivant :

# 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)))

Cet exemple présente un sous-ensemble plus facile à gérer de BOOT_KERNEL_MODULES et RECOVERY_KERNEL_MODULES à spécifier localement dans les fichiers de configuration de la carte. Le script précédent recherche et remplit chacun des modules de sous-ensemble à partir des modules de noyau disponibles sélectionnés, laissant les modules restants pour l'initialisation de la deuxième étape.

Pour l'initialisation de la deuxième étape, nous vous recommandons d'exécuter le chargement du module en tant que service afin qu'il ne bloque pas le flux de démarrage. Utilisez un script shell pour gérer le chargement du module afin que d'autres aspects logistiques, tels que la gestion et l'atténuation des erreurs, ou l'achèvement du chargement du module, puissent être signalés (ou ignorés) si nécessaire.

Vous pouvez ignorer un échec de chargement du module de débogage qui n’est pas présent sur les builds utilisateur. Pour ignorer cet échec, définissez la propriété vendor.device.modules.ready pour déclencher les étapes ultérieures du flux de démarrage du script init rc afin de continuer sur l'écran de lancement. Faites référence à l'exemple de script suivant, si vous avez le code suivant dans /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

Dans le fichier rc du matériel, le service one shot peut être spécifié avec :

service insmod-sh /vendor/etc/init.insmod.sh /vendor/etc/init.insmod.<hw>.cfg
    class main
    user root
    group root system
    Disabled
    oneshot

Des optimisations supplémentaires peuvent être apportées une fois que les modules sont passés de la première à la deuxième étape. Vous pouvez utiliser la fonctionnalité de liste de blocage de modprobe pour diviser le flux de démarrage de la deuxième étape afin d'inclure le chargement différé des modules non essentiels. Le chargement des modules utilisés exclusivement par une HAL spécifique peut être différé pour charger les modules uniquement au démarrage de la HAL.

Pour améliorer les temps de démarrage apparents, vous pouvez spécifiquement choisir des modules dans le service de chargement de modules qui sont plus propices au chargement après l'écran de lancement. Par exemple, vous pouvez charger explicitement tardivement les modules pour le décodeur vidéo ou le wifi après que le flux de démarrage d'initialisation ait été effacé (signal de propriété Android sys.boot_complete , par exemple). Assurez-vous que les HAL des modules à chargement tardif se bloquent suffisamment longtemps lorsque les pilotes du noyau ne sont pas présents.

Vous pouvez également utiliser la commande wait<file>[<timeout>] d'init dans le script rc du flux de démarrage pour attendre que les entrées sysfs sélectionnées indiquent que les modules de pilote ont terminé les opérations de sonde. Un exemple de ceci est d'attendre que le pilote d'affichage termine le chargement en arrière-plan de la récupération ou fastbootd , avant de présenter les graphiques du menu.

Initialisez la fréquence du CPU à une valeur raisonnable dans le bootloader

Tous les SoC/produits peuvent ne pas être en mesure de démarrer le processeur à la fréquence la plus élevée en raison de problèmes thermiques ou d'alimentation lors des tests de boucle de démarrage. Cependant, assurez-vous que le chargeur de démarrage définit la fréquence de tous les processeurs en ligne aussi élevée que possible en toute sécurité pour un SoC/produit. Ceci est très important car, avec un noyau entièrement modulaire, la décompression du disque virtuel d'initialisation a lieu avant que le pilote CPUfreq puisse être chargé. Ainsi, si le processeur est laissé à l'extrémité inférieure de sa fréquence par le chargeur de démarrage, le temps de décompression du disque virtuel peut prendre plus de temps qu'un noyau compilé statiquement (après ajustement pour la différence de taille du disque virtuel) car la fréquence du processeur serait très faible lors d'une utilisation intensive du processeur. travail (décompression). Il en va de même pour la fréquence mémoire/interconnexion.

Initialiser la fréquence du processeur des gros processeurs dans le chargeur de démarrage

Avant le chargement du pilote CPUfreq , le noyau ignore les petites et grandes fréquences du processeur et n'adapte pas la capacité planifiée des processeurs à leur fréquence actuelle. Le noyau peut migrer les threads vers le gros processeur si la charge est suffisamment élevée sur le petit processeur.

Assurez-vous que les gros processeurs sont au moins aussi performants que les petits processeurs pour la fréquence à laquelle le chargeur de démarrage les laisse. Par exemple, si le gros processeur est 2 fois plus performant que le petit processeur pour la même fréquence, mais que le chargeur de démarrage définit le la fréquence du petit processeur à 1,5 GHz et la fréquence du gros processeur à 300 MHz, alors les performances de démarrage vont chuter si le noyau déplace un thread vers le gros processeur. Dans cet exemple, s'il est possible de démarrer en toute sécurité le gros processeur à 750 MHz, vous devez le faire même si vous ne prévoyez pas de l'utiliser explicitement.

Les pilotes ne doivent pas charger le micrologiciel lors de la première étape d'initialisation

Il peut y avoir des cas inévitables où le micrologiciel doit être chargé lors de la première étape d'initialisation. Mais en général, les pilotes ne doivent charger aucun micrologiciel lors de la première étape d'initialisation, en particulier dans le contexte de la sonde de périphérique. Le chargement du micrologiciel lors de l'initialisation de la première étape entraîne le blocage de l'ensemble du processus de démarrage si le micrologiciel n'est pas disponible sur le disque virtuel de la première étape. Et même si le firmware est présent dans le disque virtuel du premier étage, cela entraîne toujours un retard inutile.