Le code source du noyau Linux est stocké dans le répertoire /usr/src/linux. La composition du code source du noyau : 1. Le répertoire arch, qui contient le code du noyau lié à l'architecture matérielle prise en charge par le code source du noyau ; 2. Le répertoire include, qui contient la plupart des fichiers d'inclusion du noyau ; répertoire, qui contient le code de démarrage principal ; 4. répertoire mm, contient tout le code de gestion de la mémoire ; 5. répertoire des pilotes, contient tous les pilotes de périphériques du système ; 6. répertoire Ipc, contient le code de communication inter-processus principal.
Où se trouve le code source du noyau Linux ?
Le code source du noyau Linux peut être obtenu à partir de nombreuses sources. Normalement, une fois le système Linux installé, le répertoire /usr/src/linux contient le code source du noyau.
Afin de lire le code source en douceur, il est préférable d'avoir au préalable une certaine compréhension des connaissances de base du code source.
La composition du code source du noyau Linux est la suivante (supposée par rapport au répertoire Linux) :
arch# 🎜🎜##🎜 🎜#Ce sous-répertoire contient le code principal lié à l'architecture matérielle prise en charge par ce code source principal. Par exemple, pour la plateforme X86, il s'agit du i386.
includeCe répertoire contient la plupart des fichiers d'inclusion de base. Il existe également un sous-répertoire pour chaque architecture prise en charge.
initCe répertoire contient le code de démarrage principal.
mmCe répertoire contient tout le code de gestion de la mémoire. Le code de gestion de la mémoire lié à l'architecture matérielle spécifique se trouve dans le répertoire arch/*/mm. Par exemple, celui correspondant à X86 est arch/i386/mm/fault.c.
driversTous les pilotes de périphériques du système se trouvent dans ce répertoire. Les pilotes de périphérique sont ensuite subdivisés en catégories, avec des sous-répertoires correspondants pour chaque catégorie. Par exemple, le pilote de la carte son correspond à « drivers/sound ».
IpcCe répertoire contient le code de base de la communication inter-processus.
modulesCe répertoire contient des modules qui ont été construits et peuvent être chargés dynamiquement.
fs LinuxCodes du système de fichiers pris en charge. Différents systèmes de fichiers ont différents sous-répertoires correspondant les uns aux autres. Par exemple, le système de fichiers ext2 correspond au sous-répertoire ext2.
KernelCode principal. Parallèlement, le code lié à la structure du processeur est placé dans le répertoire arch/*/kernel.
NetLe code de la pièce du réseau central. Chaque sous-répertoire à l'intérieur correspond à un aspect du réseau.
LibCe répertoire contient le code de la bibliothèque principale. Le code de la bibliothèque lié à l'architecture du processeur est placé dans le répertoire arch/*/lib/.
ScriptsCe répertoire contient les fichiers de script utilisés pour configurer le noyau.
DocumentationCe répertoire contient quelques documents pour référence.
Méthode d'analyse du code source du noyau Linux3. Compétences en programmation étonnantes. Dans le domaine de la conception générale de logiciels d'application, le statut du codage ne peut pas être surestimé, car les développeurs accordent plus d'attention à la bonne conception des logiciels, et le codage n'est qu'une question de moyens de mise en œuvre, tout comme utiliser une hache pour couper du bois, sans trop de réflexion. Mais cela n’est pas vrai dans le noyau. Une bonne conception de codage améliore non seulement la maintenabilité, mais améliore également les performances du code.
La compréhension du noyau par chacun sera différente. À mesure que notre compréhension du noyau continue de s'approfondir, nous aurons davantage de réflexions et d'expériences sur sa conception et sa mise en œuvre. Par conséquent, cet article espère guider davantage de personnes qui s'éloignent des portes du noyau Linux pour entrer dans le monde de Linux et découvrir par elles-mêmes la magie et la grandeur du noyau. Et je ne suis pas un expert en code source du noyau, j'espère juste partager ma propre expérience et mon expérience dans l'analyse du code source et fournir des références et de l'aide à ceux qui en ont besoin. Pour le dire "haut de gamme", cela peut être considéré comme. pour l'industrie informatique, notamment en ce qui concerne le noyau du système d'exploitation, apportez vos propres efforts. Sans plus tarder (c'est déjà trop long, désolé~), permettez-moi de partager ma propre méthode d'analyse du code source du noyau Linux.
3 Méthode d'analyse du code source du noyau
Il en va de même pour l'analyse du code du noyau. Nous devons d'abord localiser le contenu impliqué dans le code à analyser. Est-ce le code pour la synchronisation et la planification des processus, le code pour la gestion de la mémoire, le code pour la gestion des appareils, le code pour le démarrage du système, etc. La taille énorme du noyau fait que nous ne pouvons pas analyser tout le code du noyau en même temps, nous devons donc nous donner une division raisonnable du travail. Comme nous le dit la conception des algorithmes, pour résoudre un gros problème, nous devons d’abord résoudre les sous-problèmes qu’il implique.
Après avoir localisé la plage de code à analyser, nous pouvons utiliser toutes les ressources disponibles pour comprendre la structure globale et les fonctions générales de cette partie du code de la manière la plus complète possible.
Toutes les ressources mentionnées ici font référence à Baidu, aux moteurs de recherche en ligne à grande échelle de Google, aux manuels de principes du système d'exploitation et aux livres professionnels, ou à l'expérience et aux informations fournies par d'autres, ou même au code source Linux fourni. Documentation, commentaires , et les noms des identifiants du code source (ne sous-estimez pas la dénomination des identifiants dans le code, ils peuvent parfois fournir des informations clés). En bref, toutes les ressources ici font référence à toutes les ressources disponibles auxquelles vous pouvez penser. Bien entendu, il nous est impossible d’obtenir toutes les informations souhaitées grâce à cette forme de collecte d’informations, nous voulons simplement être aussi complets que possible. Parce que plus les informations sont collectées de manière complète, plus d'informations peuvent être utilisées dans le processus ultérieur d'analyse du code et moins le processus d'analyse sera difficile.
Voici un exemple simple, en supposant que l'on souhaite analyser le code implémenté par le mécanisme de conversion de fréquence de Linux. Jusqu'à présent, nous ne connaissons ce terme que par son sens littéral, nous pouvons à peu près deviner qu'il devrait être lié au réglage de la fréquence du CPU. Grâce à la collecte d'informations, nous devrions être en mesure d'obtenir les informations pertinentes suivantes :
1. Mécanisme CPUFreq.
2. performances, économies d'énergie, espace utilisateur, à la demande, stratégies conservatrices de régulation de fréquence.
3. /pilote/cpufreq/.
4. /documentation/cpufreq.
5. État P et état C.
Si vous parvenez à collecter ces informations lors de l'analyse du code du noyau Linux, vous devriez être très "chanceux". Après tout, les informations sur le noyau Linux ne sont en effet pas aussi riches que .NET et JQuery. Cependant, par rapport à il y a plus de dix ans, lorsqu'il n'existait pas de moteurs de recherche puissants ni de documents de recherche pertinents, il devrait être qualifié de « Grand ». L'ère des récoltes ! Grâce à une simple "recherche" (cela peut prendre un ou deux jours), nous avons même trouvé le répertoire du fichier de code source où se trouve cette partie du code. Je dois dire que ce genre d'information est tout simplement "inestimable" !
Dès la collecte de données, nous avons eu la "chance" de trouver le répertoire de code source lié au code source. Mais cela ne signifie pas que nous analysons effectivement le code source de ce répertoire. Parfois, les répertoires que nous trouvons peuvent être dispersés, et parfois les répertoires que nous trouvons contiennent beaucoup de code lié à des machines spécifiques, et nous nous préoccupons davantage du mécanisme principal du code à analyser plutôt que du code spécialisé lié à la machine ( Cela nous aidera à mieux comprendre la nature du noyau). Par conséquent, nous devons sélectionner soigneusement les informations impliquant des fichiers de code dans les informations. Bien entendu, il est peu probable que cette étape soit terminée en une seule fois, et personne ne peut garantir que tous les fichiers de code source à analyser pourront être sélectionnés en même temps et qu'aucun d'entre eux ne sera oublié. Mais nous n'avons pas à nous inquiéter tant que nous pouvons capturer les fichiers sources principaux liés à la plupart des modules, nous pouvons naturellement tous les retrouver grâce à une analyse détaillée du code ultérieurement.
Retour à l'exemple ci-dessus, nous lisons attentivement la documentation sous /documention/cpufreq. Le code source Linux actuel enregistrera la documentation relative au module dans le dossier de documentation du répertoire du code source. Si le module à analyser n'a pas de documentation, cela augmentera quelque peu la difficulté de localiser les fichiers de code source clés, mais cela n'en causera pas. nous pour les trouver. Le code source que nous voulons analyser. En lisant la documentation, on peut au moins faire attention au fichier source /driver/cpufreq/cpufreq.c. Grâce à cette documentation des fichiers sources, combinée aux stratégies de modulation de fréquence collectées précédemment, nous pouvons facilement prêter attention aux cinq fichiers sources cpufreq_performance.c, cpufreq_powersave.c, cpufreq_userspace.c, cpufreq_ondemand et cpufreq_conservative.c. Tous les documents concernés ont-ils été retrouvés ? Ne vous inquiétez pas, commencez à les analyser et tôt ou tard vous trouverez d'autres fichiers sources. Si vous utilisez sourceinsight pour lire le code source du noyau sous Windows, nous pouvons facilement trouver d'autres fichiers freq_table.c, cpufreq_stats.c et /include/linux/cpufreq grâce à des fonctions telles que l'appel de fonctions et la recherche de références de symboles, combinées à l'analyse de code. h.
Selon le sens du flux d'informations recherché, nous pouvons localiser complètement les fichiers de code source qui doivent être analysés. L'étape de localisation du code source n'est pas très critique, car nous n'avons pas besoin de trouver tous les fichiers du code source et nous pouvons reporter une partie du travail au processus d'analyse du code. Le positionnement du code source est également essentiel. La recherche d'une partie des fichiers de code source constitue la base de l'analyse du code source.
Commentaires simples
Dans les fichiers de code source localisés, analysez la signification générale et la fonction de chaque variable, macro, fonction, structure et autres éléments de code. La raison pour laquelle on appelle cela une annotation simple ne signifie pas que le travail d'annotation dans cette partie est très simple, mais cela signifie que l'annotation dans cette partie n'a pas besoin d'être trop détaillée, à condition qu'elle décrive grossièrement la signification de l'annotation. éléments de code pertinents. Au contraire, le travail ici constitue en réalité l’étape la plus difficile de tout le processus d’analyse. Parce que c'est la première fois qu'on approfondit le code du noyau, en particulier pour ceux qui analysent le code source du noyau pour la première fois, le grand nombre de syntaxes GNU C inconnues et les définitions de macros écrasantes seront très décevantes. À ce stade, tant que vous vous calmez et comprenez chaque difficulté clé, vous pouvez vous assurer que vous ne serez pas piégé lorsque vous rencontrerez des difficultés similaires à l'avenir. De plus, nos autres connaissances liées au noyau continueront à s’étendre comme un arbre.
Par exemple, l'utilisation de la macro "DEFINE_PER_CPU" apparaîtra au début du fichier cpufreq.c On peut essentiellement comprendre la signification et la fonction de cette macro en consultant les informations. La méthode utilisée ici est fondamentalement la même que la méthode utilisée pour collecter des données auparavant. De plus, nous pouvons également utiliser la fonction d'accès à la définition fournie par sourceinsight pour afficher sa définition, ou utiliser LKML (Linux Kernel Mail List) pour l'afficher. Bref, en utilisant tous les moyens possibles, nous pouvons toujours comprendre la signification de cette macro : définir une variable utilisée indépendamment pour chaque CPU.
Nous n'insistons pas pour décrire avec précision les commentaires en une seule fois (nous n'avons même pas besoin de comprendre le processus de mise en œuvre spécifique de chaque fonction, il suffit de comprendre la signification fonctionnelle générale), nous combinons les informations collectées et les éléments suivants code L'analyse améliore continuellement la signification des commentaires (les commentaires originaux et la dénomination des identifiants dans le code source sont ici d'une grande utilité). Grâce à une annotation constante, une référence constante à l'information et une modification constante du sens des annotations.
Après avoir simplement annoté tous les fichiers de code source impliqués, nous pouvons obtenir les résultats suivants :
1. Comprenez essentiellement la signification des éléments de code dans le code source.
2. Fondamentalement, tous les fichiers de code source clés impliqués dans ce module ont été trouvés.
Combiné à la description globale ou architecturale du code à analyser sur la base des informations et données précédemment collectées, nous pouvons comparer les résultats de l'analyse avec les données pour déterminer et réviser notre compréhension du code. De cette façon, à travers un simple commentaire, nous pouvons saisir la structure principale du module de code source dans son ensemble. Cela atteint également l’objectif fondamental de notre simple annotation.
Après avoir terminé les commentaires simples du code, on peut considérer que l'analyse du module est à moitié terminée et que le contenu restant est une analyse approfondie et une compréhension approfondie du code. Les commentaires simples ne peuvent pas toujours décrire de manière très précise la signification spécifique des éléments de code, des commentaires détaillés sont donc très nécessaires. Dans cette étape, nous devons clarifier les points suivants :
1. Lorsque la définition de variable est utilisée.
2. Lorsque le code défini par la macro est utilisé.
3. La signification des paramètres de fonction et des valeurs de retour.
4. Le flux d'exécution et la relation d'appel de la fonction.
5. La signification spécifique et les conditions d'utilisation des champs de structure.
Nous pouvons même appeler cette étape une annotation de fonction détaillée, car la signification des éléments de code en dehors de la fonction est fondamentalement claire dans de simples commentaires. Le flux d'exécution et l'algorithme de la fonction elle-même sont les tâches principales de cette partie d'annotation et d'analyse.
Par exemple, comment l'algorithme d'implémentation de la politique cpufreq_ondemand (dans la fonction dbs_check_cpu) est implémenté. Il faut analyser progressivement les variables utilisées par la fonction et les fonctions appelées pour comprendre les tenants et les aboutissants de l'algorithme. Pour de meilleurs résultats, nous avons besoin de l'organigramme d'exécution et du diagramme d'appel de fonction de ces fonctions complexes, ce qui constitue le moyen d'expression le plus intuitif.
Grâce aux commentaires de cette étape, nous pouvons fondamentalement comprendre pleinement le mécanisme global de mise en œuvre du code à analyser. Tous les travaux d’analyse peuvent être considérés comme terminés à 80 %. Cette étape est particulièrement critique. Il faut essayer de rendre les informations d'annotation suffisamment précises pour mieux comprendre la division des modules internes du code à analyser. Bien que le noyau Linux utilise la syntaxe de macro "module_init" et "module_exit" pour déclarer les fichiers du module, la division des sous-fonctions au sein du module est basée sur une compréhension complète des fonctions du module. Ce n'est qu'en divisant correctement le module que nous pouvons déterminer les fonctions et variables externes fournies par le module (en utilisant les symboles exportés par EXPORT_SYMBOL_GPL ou EXPORT_SYMBOL). Ce n'est qu'alors que nous pourrons passer à l'étape suivante d'analyse des dépendances des identifiants au sein du module.
En divisant les modules de code dans la quatrième étape, nous pouvons "facilement" analyser les modules un par un. Généralement, on peut partir des fonctions d'entrée et de sortie du module en bas du fichier (les fonctions déclarées par "module_init" et "module_exit" sont généralement en fin de fichier), en fonction des fonctions qu'elles appellent (fonctions définies par nous-mêmes ou d'autres modules) et les fonctions utilisées. Les variables clés (variables globales de ce fichier ou variables externes d'autres modules) dessinent un diagramme de dépendances "fonction-variable-fonction" - nous l'appelons diagramme de dépendance d'identifiant.
Bien sûr, la relation de dépendance des identifiants au sein du module n'est pas une simple structure arborescente, mais dans de nombreux cas, il s'agit d'une relation de réseau complexe. À ce stade, le rôle de nos commentaires détaillés sur le code est reflété. Nous divisons le module en sous-fonctions en fonction de la signification de la fonction elle-même et extrayons l'arbre de dépendance des identifiants de chaque sous-fonction.
Grâce à l'analyse des dépendances des identifiants, il peut être clairement affiché quelles fonctions sont appelées par les fonctions définies par le module, quelles variables sont utilisées et les dépendances entre les sous-fonctions du module - quelles fonctions sont partagées et variables, etc.
Interdépendance entre les modules
Une fois tous les diagrammes de dépendances des identifiants internes du module triés, il peut être facilement obtenu en fonction des variables ou des fonctions d'autres modules utilisés par les dépendances du module entre.
La relation de dépendance du module du code cpufreq peut être exprimée comme la relation suivante.
Grâce au diagramme de dépendance entre les modules, l'état et la fonction du module dans l'ensemble du code à analyser peuvent être clairement exprimés. Sur cette base, nous pouvons classer les modules et trier la relation architecturale du code.
Comme le montre le diagramme de dépendance des modules de cpufreq, nous pouvons clairement voir que tous les modules de stratégie de modulation de fréquence dépendent des modules de base cpufreq, cpufreq_stats et freq_table. Si nous faisons abstraction des trois modules dépendants dans le cadre de base du code, ces modules de stratégie de modulation de fréquence sont construits sur ce cadre et sont responsables de l'interaction avec la couche utilisateur. Le module principal cpufreq fournit des pilotes et d'autres interfaces associées chargées d'interagir avec le système sous-jacent. Par conséquent, nous pouvons obtenir le diagramme d’architecture de module suivant.
Bien entendu, le diagramme d'architecture n'est pas un épissage inorganique de modules. Nous devons également enrichir le diagramme d'architecture. sur la base des informations que nous avons consultées. Par conséquent, les détails du diagramme d'architecture ici varieront en fonction de la compréhension de différentes personnes. Mais la signification du corps principal du diagramme d’architecture est fondamentalement la même. À ce stade, nous avons terminé toutes les analyses du code du noyau à analyser.
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!