Sous Linux, il existe de nombreuses idées de programmation qui valent la peine d'être apprises. De nombreux experts techniques ont appliqué ces idées et mécanismes à la programmation de microcontrôleurs, notamment en simulant le processus d'initialisation automatique du noyau Linux dans STM32.
De manière générale, nous suivons certaines routines lors de l'écriture de programmes. Nous exécuterons les fonctions les unes après les autres selon une logique séquentielle.
Si la logique est très complexe et implique de nombreux modules, alors le code exécuté séquentiellement sera gonflé et les modules seront très étroitement couplés. Il existe différents pilotes de périphériques dans le noyau Linux et il est presque impossible de les exécuter logiquement dans une séquence.
Et le code du kenrel peut contenir une si grande quantité de code, il est volumineux mais pas compliqué, il sépare efficacement chaque niveau et module, et une grande quantité de code est logiquement organisée ensemble, et cet appel initial joue un rôle essentiel.
En imitant cette méthode, nous effaçons enfin le code de la fonction principale dans l'image, séparons cette logique et obtenons la même fonction.
Comment implémenter une telle fonction nécessite quelques connaissances de base :
1. Organisation du code du programme
2. Connaissances liées aux scripts de liens.
3. Application des pointeurs de fonction.
L'organisation du code, comme l'image, vous devez connaître les variables a, b et le pointeur de fonction f, f2 sont stockés dans quelles sections du programme, vous pouvez lire cette implémentation du code de démarrage stm32 | au-dessus de a, f sont stockés dans le segment bss, b, f2 sont stockés dans le segment de données, car la valeur initiale a été donnée, et l'implémentation de cet appel int placera les données qui doivent être automatiquement initialisées dans un segment personnalisé, tel que .initcall.
Comment le mettre dans une section spécifique, vous devez utiliser le mot-clé attribut((section)) pour modifier la section de stockage des données.
Le programme actuel est compilé à l'aide de ces segments. À l'exception de .isr_vector, qui est également ajouté, les autres sont par défaut par le compilateur.
Ajoutez d'abord un morceau de code :
Bien sûr, cela ne suffit pas, vous devez également dire à l'éditeur de liens (LD) de lier la section .initcall au programme, cette section doit donc également être modifiée.
Cette section est alignée sur 8 octets, définit deux variables globales et relie ces données dans l'ordre 0-5. Avec ces deux modifications, jetons un œil à chaque section du programme.
Comme image :
Il y a une case rouge supplémentaire pour la section .initcalls. Cette section fait 8 octets au total, à partir de 0x80005a8.
Regardons la situation spécifique de ce paragraphe, en utilisant l'outil readelf.
Il correspond à l'outil de taille ci-dessus et l'adresse de la boîte verte est SystemInit (0x08000231, mode petit endian.)
Ainsi, grâce à l'attribut et à la modification du script de lien, la variable de pointeur de fonction est placée dans la section .initcall.
Alors, comment appeler cette fonction ? Elle est similaire aux données de segment de données d'initialisation précédentes. Elle traverse ce segment, puis extrait l'adresse de la fonction, puis convertit de force l'adresse du segment en pointeur de fonction, puis l'appelle directement. .
L'image implémentée est de prendre l'adresse de la fonction dans la section .initcall puis de l'appeler directement. Il est très facile de confondre l'adresse de la fonction avec l'adresse de la variable du pointeur de fonction.
Avec le code modifié ainsi, la fonction d'initialisation automatique peut en effet être ajustée, mais à chaque fois je dois écrire une si longue section de static initcall_t __attribut__(( __ used__,__ section__(“.initcall.0.init”) )), c'est-à-dire inconfortable. Modifié via des macros dans le noyau Linux.
Il en va de même pour celui-ci.
Ajoutez quelques macros qui sont exécutées dans l'ordre logique du programme
0, low_level_init Par exemple, initialisez l'horloge de base du système
1, arch_init Par exemple, mettez l'architecture CPU d comme l'initialisation d'une initialisation de NVIC.
2. dev_init initialise les modules périphériques, tels que i2c, flash, spi, etc.
3. board_init effectue certains réglages pour des cartes matérielles spécifiques.
4. os_init Certains paramètres du système d'exploitation, tels que le système de fichiers, la pile de protocoles réseau, etc.
5, app_init exécute enfin le programme utilisateur.
Modifiez votre propre programme et utilisez plutôt des macros. De cette façon, l'appel à do_initcalls sera exécuté dans l'ordre de 0, 1 à 5.
Enfin, jetons un œil à la section initcall :
De cette façon, ajoutez simplement quelque chose comme dev_init(), app_init() à la fonction d'initialisation automatique, et elle sera appelée automatiquement, sans qu'il soit nécessaire de les exécuter un par un dans la fonction principale.
Par exemple, l'initialisation du contrôle i2c est placée dans dev_init. Il y a de nombreux périphériques esclaves i2c suspendus ci-dessous. Initialisez simplement chaque périphérique esclave avec app_init, même si un nouveau arrive, utilisez simplement ce app_init pour l'initialiser. pour changer celui d'origine, un haut degré de couplage entre modules séparés.
Cela simule l'initialisation et la vérification de Linux kenerl avec succès, et finalement téléchargé dans la bibliothèque.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!