Cette page fournit des conseils pour améliorer le temps de démarrage.
Supprimer les symboles de débogage des modules
Tout comme les symboles de débogage sont supprimés du noyau sur un appareil de production, assurez-vous de supprimer également les symboles de débogage des modules. La suppression des symboles de débogage des modules permet d'améliorer le temps de démarrage en réduisant les éléments suivants :
- Temps nécessaire pour lire les binaires à partir de la mémoire flash.
- Temps nécessaire pour décompresser le ramdisk.
- Temps nécessaire pour charger les modules.
La suppression des symboles de débogage des modules peut faire gagner plusieurs secondes au démarrage.
La suppression des symboles est activée par défaut dans la compilation 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 device/vendor/device.
Utiliser la compression LZ4 pour le noyau et le disque RAM
Gzip génère une sortie compressée plus petite que LZ4, mais LZ4 décompresse plus rapidement que Gzip. Pour le noyau et les modules, la réduction absolue de la taille de stockage obtenue avec Gzip n'est pas aussi importante que le gain de temps de décompression de LZ4.
La prise en charge de la compression LZ4 du ramdisk a été ajoutée à la compilation de 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 la configuration par défaut du noyau.
Le passage à LZ4 devrait permettre de gagner entre 500 ms et 1 000 ms sur le temps de démarrage.
Évitez la journalisation excessive dans vos pilotes
Dans ARM64 et ARM32, les appels de fonction qui se trouvent à une distance spécifique du site d'appel ont besoin d'une table de saut (appelée table de liaison de procédure ou PLT) pour pouvoir encoder l'adresse de saut complète. Étant donné que les modules sont chargés de manière dynamique, ces tables de saut 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 compléments explicites (ou entrées RELA, en abrégé) au format ELF.
Le noyau Linux effectue une optimisation de la taille de la mémoire (comme l'optimisation des accès au cache) lors de l'allocation de la PLT. Avec ce commit en amont, le schéma d'optimisation présente une complexité O(N^2), où N correspond au nombre de Rela de type R_AARCH64_JUMP26 ou R_AARCH64_CALL26. Il est donc utile de réduire le nombre de RELA de ce type pour diminuer le temps de chargement du module.
Un schéma de codage courant qui augmente le nombre de RELA 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 RELA CALL26/JUMP26. Dans le texte du commit dans le commit en amont, notez que même avec l'optimisation, les six modules mettent environ 250 ms à se charger. En effet, ces six modules étaient les six premiers modules avec le plus grand nombre de journaux.
La réduction de la journalisation peut permettre de gagner environ 100 à 300 ms sur les temps de démarrage, en fonction de l'excès de journalisation existant.
Activer le probing asynchrone de manière sélective
Lorsqu'un module est chargé, si l'appareil qu'il prend en charge a déjà été renseigné à partir du DT (devicetree) et ajouté au cœur du pilote, la sonde de l'appareil est effectuée dans le contexte de l'appel module_init(). Lorsqu'une analyse de l'appareil est effectuée dans le contexte de module_init(), le module ne peut pas finir de se charger tant que l'analyse n'est pas terminée. Étant donné que le chargement des modules est principalement sérialisé, un appareil qui met relativement longtemps à sonder ralentit le temps de démarrage.
Pour éviter de ralentir le temps de démarrage, activez l'analyse asynchrone pour les modules qui mettent du temps à analyser leurs appareils. L'activation de l'analyse asynchrone pour tous les modules peut ne pas être avantageuse, car le temps nécessaire pour forker un thread et lancer l'analyse peut être aussi long que le temps nécessaire pour analyser l'appareil.
Les appareils connectés via un bus lent tel qu'I2C, les appareils qui chargent le micrologiciel dans leur fonction de sonde et les appareils qui effectuent beaucoup d'initialisation matérielle peuvent entraîner des problèmes de timing. La meilleure façon de l'identifier est de collecter le temps de sondage pour chaque pilote et de le trier.
Pour activer l'analyse asynchrone d'un module, il ne suffit pas de définir 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 en tant que paramètre de module lors du chargement du module à l'aide de modprobe ou insmod.
L'activation de l'analyse asynchrone peut permettre de gagner entre 100 et 500 ms sur les temps de démarrage, selon votre matériel/vos pilotes.
Sondez votre pilote CPUfreq le plus tôt possible
Plus tôt votre pilote CPUfreq sonde, plus tôt vous pouvez mettre à l'échelle la fréquence du processeur au maximum (ou à un maximum limité thermiquement) lors du démarrage. Plus le processeur est rapide, plus le démarrage est rapide. Cette règle s'applique également aux devfreqpilotes qui contrôlent la fréquence de la DRAM, de la mémoire et de l'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 fait partie des premiers modules à charger.
En plus de charger le module de manière anticipée, vous devez également vous assurer que toutes les dépendances pour sonder le pilote CPUfreq ont également été sondé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'elles sont sondées en premier. Vous devrez peut-être charger les pilotes thermiques avant le pilote CPUfreq si vos processeurs risquent de surchauffer au démarrage. Faites donc tout votre possible pour que les pilotes CPUfreq et devfreq concernés soient sondés le plus tôt possible.
Les économies réalisées en sondant votre pilote CPUfreq de manière précoce peuvent être très faibles ou très importantes, selon la précocité de la sonde et la fréquence à laquelle le bootloader laisse les processeurs.
Déplacer les modules vers la deuxième étape d'initialisation, la partition vendor ou vendor_dlkm
Étant donné que le processus d'initialisation de la première étape est sérialisé, il n'y a pas beaucoup de possibilités de paralléliser le processus de démarrage. Si un module n'est pas nécessaire pour que l'initialisation de la première phase se termine, déplacez-le vers l'initialisation de la deuxième phase en le plaçant dans la partition du fournisseur ou vendor_dlkm.
L'initialisation de la première étape ne nécessite pas de sondage de plusieurs appareils pour passer à l'initialisation de la deuxième étape. Seules les fonctionnalités de la console et du stockage flash sont nécessaires pour un flux de démarrage normal.
Chargez les pilotes essentiels suivants :
watchdogresetcpufreq
Pour le mode de récupération et d'espace utilisateur fastbootd, l'initialisation de la première étape nécessite de sonder davantage d'appareils (comme l'USB) et l'écran. Conservez une copie de ces modules dans le ramdisk de la première étape et dans la partition du fournisseur ou vendor_dlkm. Cela leur permet d'être chargés lors de l'initialisation de la première étape pour la récupération ou le flux de démarrage fastbootd. Toutefois, ne chargez pas les modules du mode Recovery dans l'initialisation de la première étape lors du flux de démarrage normal. Les modules du mode Récupération peuvent être différé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 à l'initialisation de la première étape doivent être déplacés vers la partition du fournisseur ou vendor_dlkm.
À partir d'une liste d'appareils feuilles (par exemple, UFS ou série), le script dev needs.sh trouve tous les pilotes, appareils 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 l'initialisation de la deuxième étape réduit les temps de démarrage des manières suivantes :
- Réduction de la taille du ramdisk.
- Cela permet des lectures flash plus rapides lorsque le bootloader charge le ramdisk (étape de démarrage sérialisée).
- Cela permet d'accélérer la décompression lorsque le noyau décompresse le disque RAM (étape de démarrage sérialisée).
- 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 de modules vers la deuxième étape peut permettre de gagner entre 500 et 1 000 ms sur les temps de démarrage, selon le 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 inclut des configurations de carte qui contrôlent les modules à copier dans chaque étape et les modules à charger. Cette section se concentre sur le sous-ensemble suivant :
BOARD_VENDOR_RAMDISK_KERNEL_MODULES. Liste des modules à copier dans le ramdisk.BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD: liste des modules à charger lors de l'initialisation de la première étape.BOARD_VENDOR_RAMDISK_RECOVERY_KERNEL_MODULES_LOAD: liste des modules à charger lorsque la récupération oufastbootdest sélectionnée à partir du disque RAM.BOARD_VENDOR_KERNEL_MODULES. Cette liste de modules doit être copiée dans la partition du fournisseur ouvendor_dlkmau répertoire/vendor/lib/modules/.BOARD_VENDOR_KERNEL_MODULES_LOAD. Liste des modules à charger lors de l'initialisation de la deuxième étape.
Les modules de démarrage et de récupération du ramdisk doivent également être copiés dans la partition du fournisseur ou vendor_dlkm à /vendor/lib/modules. La copie de ces modules dans la partition du fournisseur permet de s'assurer qu'ils ne sont pas invisibles lors de l'initialisation de la deuxième étape, ce qui est utile pour le débogage et la collecte de modinfo pour les rapports de bug.
La duplication devrait occuper un espace minimal sur la partition du fournisseur ou vendor_dlkm, à condition que l'ensemble de modules de démarrage soit 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 permet de s'assurer que les temps de démarrage ne sont pas affectés par le rechargement des modules (qui est un processus coûteux).
Assurez-vous que les modules du mode Récupération se chargent en tant que groupe. Le chargement des modules du mode Recovery peut être effectué en mode Recovery ou au début de la deuxième étape de l'initialisation dans chaque flux de démarrage.
Vous pouvez utiliser les fichiers Board.Config.mk de l'appareil pour effectuer ces actions, comme illustré dans 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, en 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 dans les versions utilisateur.
Pour ignorer cet échec, définissez la propriété vendor.device.modules.ready afin de déclencher les étapes ultérieures du bootflow de script init rc pour continuer sur l'écran de lancement. Reportez-vous à 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 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 effectué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 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 un HAL spécifique peut être différé pour ne charger les modules que lorsque le HAL est démarré.
Pour améliorer les temps de démarrage apparents, vous pouvez choisir spécifiquement les modules du service de chargement de modules qui sont plus propices au chargement après l'écran de lancement. Par exemple, vous pouvez charger explicitement les modules de décodeur vidéo ou de Wi-Fi après l'effacement du flux de démarrage init (signal de propriété Android sys.boot_complete, par exemple). Assurez-vous que les HAL pour les modules à chargement tardif 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 certaines entrées sysfs indiquent que les modules de pilote ont terminé les opérations de sonde. Par exemple, le système attend que le pilote d'affichage ait fini de se charger en arrière-plan de la récupération ou de fastbootd avant d'afficher les graphiques du menu.
Initialiser la fréquence du processeur sur une valeur raisonnable dans le bootloader
Il est possible que tous les SoC/produits ne puissent pas démarrer le CPU à la fréquence la plus élevée en raison de problèmes thermiques ou d'alimentation lors des tests de boucle de démarrage. Toutefois, assurez-vous que le bootloader définit la fréquence de tous les processeurs en ligne sur la valeur la plus élevée possible pour un SoC ou un produit, sans danger. C'est très important, car avec un noyau entièrement modulaire, la décompression du ramdisk d'initialisation a lieu avant le chargement du pilote CPUfreq. Ainsi, si le bootloader laisse le processeur à la limite inférieure de sa fréquence, le temps de décompression du ramdisk peut être plus long qu'avec un noyau compilé statiquement (après ajustement de la différence de taille du ramdisk), car la fréquence du processeur serait très faible lors de travaux nécessitant une utilisation intensive du processeur (décompression). Il en va de même pour la fréquence de la mémoire et de l'interconnexion.
Initialiser la fréquence du processeur des grands processeurs dans le bootloader
Avant le chargement du pilote CPUfreq, le noyau n'a pas connaissance des fréquences du processeur et ne met pas à l'échelle la capacité de planification du processeur pour leur fréquence actuelle. Le noyau peut migrer des threads vers le grand processeur si la charge est suffisamment élevée sur le petit processeur.
Assurez-vous que les grands processeurs sont au moins aussi performants que les petits pour la fréquence à laquelle le bootloader les laisse. Par exemple, si le grand CPU est deux fois plus performant que le petit CPU pour la même fréquence, mais que le bootloader définit la fréquence du petit CPU sur 1,5 GHz et celle du grand CPU sur 300 MHz, les performances de démarrage vont diminuer si le noyau déplace un thread vers le grand CPU. Dans cet exemple, s'il est possible de démarrer le grand CPU à 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 l'initialisation de la première étape
Dans certains cas inévitables, le micrologiciel doit être chargé lors de l'initialisation de la première étape. Toutefois, en règle générale, les pilotes ne doivent charger aucun micrologiciel lors de l'initialisation de la première étape, en particulier dans le contexte de l'analyse de l'appareil. Le chargement du micrologiciel lors de l'initialisation de la première étape provoque l'arrêt de l'ensemble du processus de démarrage si le micrologiciel n'est pas disponible dans le disque RAM de la première étape. Même si le micrologiciel est présent dans le ramdisk de la première étape, il provoque toujours un retard inutile.