Sur les options du pilote Linux ou comment j'ai passé le week-end

"Nous sommes paresseux et curieux"




Cette fois, la raison de la publication était un article dans un bon magazine dédié au système d'exploitation Linux (ci-après dénommé L), dans lequel "l'expert" attiré a félicité le pilote connectant l'écran LCD à la carte Raspbery. Étant donné que de telles choses (connexion, pas le système d'exploitation) entrent dans le champ de mes intérêts professionnels, j'ai parcouru l'article avec attention, puis j'ai trouvé le texte réel du «pilote» et j'ai été légèrement surpris que l'informatique puisse être louée. Eh bien, en général, le niveau d'expert peut être déterminé, ne serait-ce que parce qu'il a obstinément appelé le programme un pilote, malgré le fait que ce ne soit en aucun cas. Il semblerait, et les figues avec lui, vous ne savez jamais ce que quelqu'un écrit pour lui-même, mais pour le publier dans le domaine public - "Je ne savais pas que c'était possible."

Particulièrement satisfait du fait que l'adresse du périphérique sur le bus I2C a été directement définie dans le texte du programme et son changement a nécessité une recompilation (enfin, ce n'est pas tout le noyau). Soit dit en passant, j'ai remarqué que sur les forums consacrés à L, la réponse la plus populaire à toute question concernant les problèmes logiciels est «reconstruire la dernière version du noyau». Cette approche me semble un peu étrange, probablement, je ne sais pas quelque chose. Mais, néanmoins, la question s'est posée de savoir comment le paramétrage du pilote est réellement implémenté (à l'intérieur, pas à l'extérieur - tout est simple et clair) dans A, la réponse à laquelle ce billet est consacré.

Ce n'est pas que j'écrivais constamment des pilotes pour L, mais avec le processus dans son ensemble, je suis familier et Google a confirmé de vagues souvenirs qu'il existe un ensemble de macros qui devraient être utilisées lors de la création du code source du module afin de pouvoir lui transmettre des paramètres de fonctionnement, par exemple, l'adresse de l'appareil à au bus. Néanmoins, la mécanique du processus lui-même n'a été décrite nulle part. J'ai vu le même texte dans de nombreux liens (en passant, une question intéressante - pourquoi faire cela, c'est-à-dire placer le fragment du texte de quelqu'un d'autre sur ma ressource - je ne comprends pas vraiment la signification de cette opération), qui décrivait les macros ci-dessus. Je n'ai pas trouvé une seule mention du mécanisme pour effectuer l'opération, pour un autre système d'exploitation bien connu (Windows), je devrais énoncer un fait et me limiter à cela, mais l'un des avantages de A est la disponibilité des textes sources et la possibilité de trouver une réponse à toute question sur sa structure interne, ce que nous ferons. Je constate tout de suite que j'essaierai de ne pas dupliquer les informations que vous pouvez obtenir auprès d'autres sources, et je me limiterai seulement à ce qui est nécessaire à la compréhension du texte.

Mais avant de regarder la source, nous allons d'abord réfléchir un peu, mais comment ferions-nous si nous obtenions une tâche similaire (et soudain, après ce post, ils m'inviteront chez les mineurs L, et vous ne refuserez pas). Il y a donc la possibilité de créer un module - une certaine unité de programme spécialement conçue qui peut être chargée en mémoire pour être exécutée à l'aide d'un utilitaire système (insmode - ci-après I), tandis qu'une chaîne de caractères est transmise en tant que paramètres de lancement. Cette ligne peut inclure des unités lexicales strictement définies, dont la description du format est spécifiée lors de la création du texte source du module, et ces unités contiennent des informations qui vous permettent de modifier la valeur des variables internes de ce module.

Examinons plus attentivement la manière de décrire les unités lexicales ci-dessus, nous en avons besoin pour envisager différentes solutions. L'unité d'analyse est déterminée en appelant la macro, qui est informée des informations nécessaires - le nom de la variable qui doit être modifiée pendant le processus de configuration, son nom externe (généralement le même que le précédent), le type de la variable de l'ensemble limité et les droits d'accès à la variable dans le style de rw-rw-rw. De plus, une chaîne de texte (facultative) décrivant la variable peut être spécifiée. De toute évidence, ces informations sont nécessaires et suffisantes (en conjonction avec les règles de conception des unités syntaxiques - séparateurs et jetons) pour construire l'analyseur de la liste de paramètres spécifiée sous la forme d'une chaîne de texte, mais laissent de la place pour la mise en œuvre de la distribution des fonctions entre le participant au processus.

Pour configurer le module, nous avons besoin de:

  1. formulaire (enfin, c'est au stade de la compilation, vous pouvez le faire comme vous le souhaitez, bien que ce soit toujours intéressant de voir comment) et stocker un tableau des paramètres ci-dessus
  2. analyser les paramètres d'entrée selon ce tableau et
  3. apporter des modifications à certaines zones de la mémoire en fonction du résultat de l'analyse d'une unité syntaxique.

Nous réfléchirons un peu dans le style de «si j'étais le réalisateur» et proposerons des implémentations possibles. Comment nous pourrions implémenter le comportement similaire de l'utilitaire système et du module - nous commencerons l'analyse des options dans une complexité croissante.

La première solution est que l'utilitaire And ne fait presque rien, appelle simplement le module qui lui est indiqué et lui transfère les paramètres restants dans le style de ligne de commande, et le module les analyse déjà, en s'appuyant sur les informations disponibles et en effectuant les modifications nécessaires. Cette solution est simple, compréhensible et tout à fait réalisable, mais la circonstance suivante doit être prise en compte: en aucun cas l'analyse des paramètres ne doit être laissée à la volonté de l'auteur du module, car cela lui donnera un espace inacceptable, et après tout, deux programmeurs écriront toujours trois options d'analyseur. Et donc nous sommes allés à sa rencontre, en lui permettant des paramètres de type indéfini, qui ont une chaîne de texte comme valeur.

Par conséquent, un certain analyseur standard devrait être automatiquement inclus dans le texte du module, c'est facile à implémenter au niveau de la substitution de macro.

Cette solution présente deux inconvénients:

  1. on ne sait pas pourquoi nous en avons besoin. Et vous pouvez immédiatement appeler le module avec des paramètres à partir de la ligne de commande,
  2. le code du module (partie initialisation) doit contenir les trois sections des informations nécessaires, et ces informations sont nécessaires uniquement lorsque le module est démarré et n'est pas utilisé à l'avenir, et il prend toujours de la place. Faites immédiatement une réservation que ces informations prennent nécessairement de la place dans le fichier, mais elles peuvent ne pas aller en mémoire lors du chargement du module, si tout est fait avec soin. Pour ce faire, nous rappelons les directives _init et _initdata (au fait, mais comment elles fonctionnent, nous devrons le comprendre - c'est le sujet du prochain billet - vous attendez-vous avec impatience?). Mais dans ce dernier cas, les sections 2 et 3 des informations du fichier sont clairement redondantes, car le même code sera présent dans de nombreux modules, violant malicieusement le principe DRY.

En raison des lacunes constatées, la mise en œuvre de cette option est hautement improbable. De plus, on ne sait pas pourquoi dans la macro définir des informations sur le type de paramètre, car le module lui-même sait très bien ce qu'il modifie (bien que cela puisse être nécessaire pour l'analyseur lors de la vérification des paramètres). L'évaluation globale de la probabilité d'une telle décision est de 2 à 3%.

La digression nécessaire au sujet de l'inconvénient noté 2 - J'ai été formé en tant que spécialiste à l'époque où 256 kilo-octets de RAM étaient suffisants pour organiser 4 postes de travail, 56 kilo-octets avaient un système d'exploitation à double tâche et un système d'exploitation à tâche unique a commencé à fonctionner à 16 kilo-octets. Eh bien, 650 ko, ce qui devrait être suffisant pour n'importe quel programme, étaient généralement quelque chose du domaine de la fiction non scientifique. Par conséquent, j'ai l'habitude de penser que la RAM est une ressource rare et je désapprouve extrêmement son utilisation inutile, à moins qu'elle ne soit absolument nécessaire (en règle générale, les exigences de performance), et dans ce cas, je n'observe pas une telle situation. Puisque la plupart de mes lecteurs se sont formés dans des réalités différentes, vous pouvez avoir vos propres évaluations de la préférence de telle ou telle option.

La deuxième solution - l'analyseur lui-même est transféré vers AND, qui transfère les données extraites au module (sa partie d'initialisation) - le numéro et la valeur du paramètre. Ensuite, nous préservons l'uniformité des paramètres et réduisons les exigences de taille du module. La question demeure, comment fournir une liste ET de paramètres possibles, mais cela est fourni par des macros en créant une structure prédéterminée du module et l'emplacement du bloc à un endroit spécifique (fichier ou mémoire). La solution est meilleure que la précédente, mais la mémoire excédentaire du module demeure. En général, j'aime la solution, car mon analyseur (qui est pire que tous les autres programmeurs, j'ai mon propre analyseur, non sans défauts, mais certainement pas fatal) fonctionne selon ce schéma, renvoyant le numéro de la règle et la valeur identifiées au programme principal paramètre. Néanmoins, la probabilité de mettre en œuvre cette option particulière n'est pas très élevée - 5%.

Une sous-option de la deuxième solution consiste à transférer les paramètres extraits non pas vers la partie de départ du module, mais directement vers sa partie active chargée, par exemple via ioctl - les besoins en mémoire sont les mêmes. Nous avons une opportunité unique de changer les paramètres "à la volée", ce qui n'est pas implémenté dans les autres versions. Il n'est pas très clair pourquoi nous pourrions avoir besoin d'une telle fonctionnalité, mais elle est magnifique. L'inconvénient est 1) vous devez réserver une partie de la zone de fonction à l'avance pour une demande éventuellement inutilisée, et 2) le code de modification doit être présent en mémoire en permanence. Estimation de la probabilité de mise en œuvre - pourcentage 5.

La troisième solution est de transférer vers Et aussi la modification des paramètres. Ensuite, dans le processus de chargement du code binaire du module Et, il peut modifier les données dans la mémoire intermédiaire et charger le code du pilote avec les paramètres modifiés à l'emplacement de déploiement permanent, ou apporter ces modifications directement dans la zone de mémoire dans laquelle le binaire a été chargé, et la table des paramètres présente dans le fichier est en mémoire peut à la fois charger et ne pas l'occuper (rappelez-vous des directives). La décision est responsable, elle nécessitera, comme la précédente, la présence d'une zone de communication prédéfinie entre le module et ET pour stocker la description des paramètres, mais elle réduit encore les exigences de mémoire excessive dans le module. Immédiatement, on note le principal inconvénient d'une telle solution - l'incapacité à contrôler les valeurs des paramètres et leur cohérence, mais il n'y a rien à faire. C'est une solution tout à fait normale, très probablement - 75%.

Une variante de la troisième solution - les informations sur les paramètres ne sont pas stockées dans le module lui-même, mais dans un fichier auxiliaire, il n'y a tout simplement pas de mémoire excédentaire dans le module. En principe, la même chose peut être faite dans la version précédente, lorsque le module contient la partie de configuration, qui est utilisée ET pendant le processus de démarrage, mais n'est pas chargée dans la RAM contenant la partie exécutable réelle du module. Par rapport à la version précédente, un fichier supplémentaire a été ajouté et il n'est pas clair pour quoi nous payons, mais peut-être qu'ils ont fait des directives d'initialisation avant l'invention - 5%.

Les 7% restants seront laissés à d'autres options que je n'ai pas pu trouver. Eh bien, maintenant que notre fantaisie s'est épuisée (la mienne à coup sûr, s'il y a plus d'idées, veuillez demander dans le commentaire), nous allons commencer à étudier la source de L.

Pour commencer, je note que, apparemment, l'art de distribuer des textes sources dans des fichiers a été perdu avec le système d'exploitation, qui tient dans 16 ko, car la structure des répertoires, leurs noms et noms de fichiers sont liés au contenu un peu plus que rien. Étant donné la présence d'inclusions intégrées, l'étude classique des sources téléchargées avec l'aide d'un éditeur se transforme en une quête étrange et sera improductive. Heureusement, il existe un charmant utilitaire Elixir, disponible en ligne, qui vous permet d'effectuer des recherches contextuelles, et avec lui, le processus devient beaucoup plus intéressant et fructueux. J'ai effectué mes recherches complémentaires sur le site elixir.bootlin.com. Oui, ce site n'est pas une collection officielle de fromages à noyau, contrairement à kernel.org, mais espérons que leur code source soit identique.

Tout d'abord, regardons une macro pour déterminer les paramètres - premièrement, nous connaissons son nom, et deuxièmement, cela devrait être plus facile (ouais, maintenant). Il se trouve dans le fichier moduleparam.h - c'est tout à fait raisonnable, mais c'est une agréable surprise, compte tenu de ce que nous verrons plus tard. Macro

{0}module_param(name,type,perm) 

est un wrapper sur

  {0a}module_param_named(n,n,t,p) 

- sucre syntaxique pour le cas le plus courant. Dans le même temps, pour une raison quelconque, l'énumération des valeurs autorisées de l'un des paramètres, à savoir le type de la variable, est donnée dans les commentaires avant le texte de l'encapsuleur, et non dans la deuxième macro, qui fait vraiment le travail et peut être utilisée directement.

La macro {0a} contient un appel à trois macros

 {1}param_check_##t(n,&v) 

(il existe un ensemble de macros pour tous les types valides)

 {2}module_param_cb(n,&op##t,&v,p) 

et

 {3}__MODULE_PARM_TYPE(n,t) 

(faites attention aux noms, cependant, charme), et le premier d'entre eux est utilisé ailleurs, c'est-à-dire que les recommandations d'Occam et le principe KISS sont également hardiment négligés par les créateurs de A - apparemment, une sorte de travail préparatoire pour l'avenir. Bien sûr, ce ne sont que des macros, mais elles ne coûtent rien, mais quand même ...

La première des trois macros {1}, comme son nom l'indique, vérifie la correspondance des types de paramètres et encapsule

 __param_check(n,p,t) 

Notez qu'à la première étape de l'habillage, le niveau d'abstraction des macros diminue, et à la seconde, il augmente probablement d'une manière différente, et il me semble seulement que cela pourrait être plus simple et plus logique, d'autant plus que la macro moyenne n'est utilisée nulle part ailleurs. D'accord, mettons une autre façon de vérifier les paramètres macro dans la tirelire et de continuer.

Mais les deux macros suivantes génèrent en fait un élément de la table des paramètres. Pourquoi ne demandez-vous pas deux, et pas un, j'ai depuis longtemps cessé de comprendre la logique des créateurs de L. Très probablement, en fonction de la différence de style de ces deux macros, à commencer par les noms, le second a été ajouté plus tard pour étendre les fonctionnalités et modifier la structure existante C'était impossible, car au départ, ils regrettaient d'attribuer une place pour indiquer l'option des paramètres. La macro {2}, comme toujours, nous masque la macro

 {2a}_module_param_call(MODULE_PARAM_PREFIX,n,ops,arg,p,-1,0) 

(c'est drôle que cette macro ne soit appelée directement nulle part sauf 8250_core.c, où elle est appelée avec les mêmes paramètres supplémentaires), mais cette dernière produit déjà le code source.

Une petite remarque - pendant la recherche, nous nous assurons que la navigation de texte fonctionne bien, mais il y a deux circonstances désagréables: la recherche par le fragment de nom ne fonctionne pas (check_param_ n'a pas été trouvé, bien que check_param_byte a été trouvé) et la recherche ne fonctionne que sur les déclarations d'objets (la variable n'est pas trouvée, puis est trouvé dans ce fichier par ctrF, mais la recherche intégrée par source n'est pas détectée). Pas très encourageant, car nous aurons peut-être besoin de rechercher un objet en dehors du fichier courant, mais "au final, nous n'en avons pas d'autre".

À la suite du travail de {1} dans le texte du module compilé en présence des deux lignes suivantes

 module_param_named(name, c, byte, 0x444); module_param_named(name1, i, int, 0x444); 

un fragment du type ci-dessous apparaît

 static const char __param_str_name[] = "MODULE" "." "name"; static struct kernel_param const __param_name \ __attribute__((__used__)) \ __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \ = { __param_str_name, ((struct module *)0), &param_ops_byte, (0x444), -1, 0, { &c } }; static const char __UNIQUE_ID_nametype72[] \ __attribute__((__used__)) __attribute__((section(".modinfo"), unused, aligned(1))) \ = "parmtype" "=" "name" ":" "byte"; static const char __param_str_name1[] = "MODULE" "." "name1"; static struct kernel_param const __param_name1 \ __attribute__((__used__)) \ __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \ = { __param_str_name1, ((struct module *)0), &param_ops_int, (0x444), -1, 0, { &i } }; static const char __UNIQUE_ID_name1type73[] __attribute__((__used__)) \ __attribute__((section(".modinfo"), unused, aligned(1))) \ = "parmtype" "=" "name1" ":" "int"; 

(en fait, des fichiers sur une seule ligne y sont générés, je les ai divisés en lignes pour en faciliter l'examen) et nous pouvons immédiatement dire qu'il n'y a aucune allusion à l'inclusion d'une section de programme d'analyse ou d'un module pour attribuer des valeurs aux paramètres dans le texte source, donc les options 1 et 2 peuvent considéré comme exclu de tout examen ultérieur. La présence d'attributs spéciaux pour l'éditeur de liens, pour ainsi dire, suggère l'existence d'une région de communication située à un endroit prédéterminé à travers lequel la description des paramètres est transmise. Dans le même temps, nous constatons avec étonnement l'absence totale de toute description du bloc généré de paramètres possibles sous forme de texte qui pourrait être utilisé par le module analyseur. Il est clair qu'un code bien écrit est auto-documenté, mais pas dans la même mesure que cela n'augmente pas la probabilité de l'option 1 ou 2, l'analyseur étant écrit par le développeur du module.

La combinaison des attributs __used__ et inutilisés en même temps semble drôle dans la dernière ligne générée, surtout si vous regardez le prochain fragment de code de macro

 #if GCC_VERSION < 30300 # define __used __attribute__((__unused__)) #else # define __used __attribute__((__used__)) #endif 

Quel est le genre d'agilité que les développeurs de A fument, la manière douloureusement tortueuse de leurs pensées incarnée dans le code. Je sais que vous pouvez utiliser les deux formes d'écriture d'attributs, mais pourquoi le faire sur la même ligne - je ne comprends pas.

On peut noter une autre caractéristique intéressante du code résultant - la duplication d'informations sur le nom de la variable et son type. On ne sait pas encore pourquoi cela a été fait, mais le fait lui-même ne fait aucun doute. Bien sûr, ces informations sont cohérentes, car elles sont construites en mode automatique, et cette cohérence sera préservée lorsque le texte source change (et c'est bien), mais il est dupliqué (et c'est mauvais), peut-être plus tard nous comprendrons la nécessité d'une telle solution. De plus, la nécessité de former un nom unique en utilisant le numéro de ligne du code source reste incertaine, car la première ligne générée s'en est sortie.

Une autre note - comprendre exactement ce que la définition du paramètre se transforme n'était pas une tâche complètement triviale, mais grâce à MinGW, elle était toujours terminée. Sous le capot, il y avait une stringification et un double collage des paramètres, la formation de noms uniques, ainsi que d'autres astuces délicates de travail avec les macros, mais je ne présente que les résultats. Résumant le résultat intermédiaire, je peux dire que l'étude des macros A n'est pas ce dont je voudrais gagner ma vie, c'est seulement possible comme divertissement, mais nous continuons.

Nous ne ferons pas avancer la compréhension des macros pour comprendre la tâche, nous nous tournons donc vers le code source de l'utilitaire And et essayons de comprendre ce qu'il fait.

Tout d'abord, nous sommes étonnés de voir que les fromages requis ne sont pas inclus dans les sources du noyau. Oui, je suis prêt à admettre que le I est un utilitaire et interagit avec le noyau via le point d'entrée pour charger le module, mais tout livre sur les pilotes L nous parle de cet utilitaire, donc l'absence d'une version «officielle» de sa source quelque part près de la source du noyau provoque me mal compris. Bon, d'accord, Google ne nous a pas laissé tomber, et nous avons quand même pris le fromage.

La deuxième chose étonnante est que cet utilitaire est formé à partir d'un package dont le nom n'est en aucun cas associé à son nom, il existe plus d'un de ces packages, et chacun est nommé à sa manière à différents endroits - pour le moins drôle. Si vous avez installé L, alors avec la commande - vous pouvez découvrir à partir de quel package l'utilitaire I est construit, puis le rechercher, mais si nous effectuons des recherches théoriques (personnellement, je ne garde pas L sur mon ordinateur personnel pour plusieurs raisons, dont certaines que je J'ai énoncé mes posts, un tel boxeur théorique), alors cette méthode ne nous est pas accessible et il ne reste qu'une recherche sur Internet, heureusement, elle donne des résultats.

Eh bien, la troisième chose étonnante est que le nom de l'utilitaire lui-même n'apparaît nulle part dans le code source, il n'est pas utilisé dans les noms de fichiers et se trouve uniquement dans le fichier make, je sais qu'en C, nous sommes obligés de nommer la fonction principale principale, et cela n'est pas discuté (je ne suis pas personnellement Je m'en réjouis, puisque Pascal est gâté, mais ils ne m'ont pas demandé mon avis lors de la conception du langage), mais au moins il serait possible d'écrire le nom externe de l'utilitaire dans les commentaires. Remarque nécessaire - beaucoup de choses dans le langage C ont été faites sur le principe "c'est tellement habituel avec nous", il était probablement difficile de rendre les choses différentes parfois, voire impossibles, mais que pouvez-vous faire maintenant, en faisant glisser une valise sans poignée plus loin.

Nous trouvons deux paquets contenant le texte source. Et, nous trouvons également les fromages sur github, nous voyons qu'ils sont identiques et nous croyons que c'est à cela que ressemble le code source de l'utilitaire. Ensuite, nous étudions uniquement le fichier sur git, d'autant plus qu'il s'appelle ici insmod.c, et que pour commencer, il convertit la liste des paramètres en une longue chaîne terminée par un caractère nul, dans laquelle les éléments individuels sont séparés par des espaces. Ensuite, il appelle deux fonctions, dont la première s'appelle grub_file et ouvre évidemment le binaire, tandis que la seconde a le nom init_module et prend un pointeur vers le fichier ouvert avec le module binaire et une chaîne de paramètres et s'appelle load_module, ce qui suggère le but de cette fonction comme chargement avec modification des paramètres.

Nous passons au texte de la deuxième fonction, qui se trouve dans le fichier ... et voici une déception - pas dans aucun des fichiers du dépôt étudié sur le Geet (eh bien, c'est juste logique, cela fait partie du noyau et sa place n'est pas ici) ce n'est pas le cas. Google à nouveau pressé d'aider et nous renvoie aux fromages en grains sous Elixir et au fichier module.c. Il convient de noter que, de manière surprenante, le nom du fichier contenant les fonctions de travail avec les modules semble logique, je ne comprends même pas comment l'expliquer, cela est probablement arrivé par accident.

Maintenant, il est devenu clair pour nous le manque de texte Et à côté du noyau - il ne fait pratiquement rien, il transfère uniquement les paramètres d'une forme à une autre et transfère le contrôle au noyau lui-même, il est donc même indigne de se trouver à côté de lui. À partir de ce moment, il devient clair qu'il n'y a pas d'informations externes claires sur la structure des paramètres, car le noyau les a ignorés dans ses propres macros et sait tout à leur sujet parfaitement, et les autres n'ont besoin de rien savoir de la structure interne (à la lumière du fait que la source sont disponibles pour consultation, quelques commentaires ne feraient pas de mal, mais en principe c'est vraiment de plus en plus clair même sans eux), mais jusqu'à présent, il n'a presque jamais mis en lumière la mise en œuvre du mécanisme d'exécution lui-même.

Remarque - concernant le transfert de contrôle vers le noyau, je suis un peu excité, car pour l'instant nous voyons l'utilisation de la fonction dans la source du noyau, si la partie binaire sera liée au module, ou si elle se trouve dans l'image du noyau elle-même, est encore inconnue. Le fait que le point d'entrée vers le traitement de cette fonction soit encadré de manière particulière, via SYSCALL_DEFINE3, témoigne indirectement en faveur de la deuxième option, mais j'ai longtemps compris que mes idées sur le logique et l'illogique, l'acceptable et l'inacceptable, ainsi que le admissible et l'inacceptable, sont très significatives s'écarter de ceux des développeurs de L.

Remarque - un caillou de plus dans le jardin de recherche intégré - lors de la recherche d'une définition pour cette macro, j'ai vu de nombreux endroits pour l'utiliser en tant que fonction, parmi lesquels la définition même de celle-ci en tant que macro s'est cachée très modestement.

Par exemple, je ne comprends pas pourquoi un utilitaire externe est nécessaire pour traduire les paramètres du formulaire standard pour le système d'exploitation (agrc, argv) sous la forme d'une chaîne terminée par un zéro avec des espaces comme séparateurs, qui est ensuite traitée par le module système - cette approche dépasse quelque peu la mienne capacités cognitives. Surtout, étant donné que l'utilisateur entre une chaîne de paramètres sous la forme d'une chaîne terminée par un zéro avec des espaces comme séparateurs, et l'utilitaire dans le noyau la convertit en une forme (argc, argv). Rappelle fortement la vieille plaisanterie "Nous retirons la bouilloire du poêle, en versons de l'eau et obtenons un problème dont la solution est déjà connue." Et puisque j'essaie d'adhérer au principe «Ne considérez pas votre interlocuteur plus stupide que vous, jusqu'à ce qu'il prouve le contraire. Et même après cela, vous pouvez vous tromper », et en ce qui concerne les développeurs de A, la première phrase est définitivement valable, cela signifie que je me méprends sur quelque chose, mais je n'y suis pas habitué. Si quelqu'un peut offrir une explication raisonnable du fait déclaré de la double conversion, alors je demande dans le commentaire. Mais nous continuons l'enquête.

Les perspectives de mise en œuvre des options 1 et 2 deviennent «très faiblement visibles» (un charmant libellé d'un article récent sur les perspectives de développement d'ADC domestiques à haut débit), car il serait très étrange de charger un module en mémoire à l'aide de la fonction noyau, puis de lui passer le contrôle pour implémenter le noyau fonction intégrée dans son corps. Et pour sûr, dans le texte de la fonction load_module, nous trouvons assez rapidement l'appel parse_args - il semble que nous soyons sur la bonne voie. Ensuite, nous passons rapidement en revue la chaîne d'appels (comme toujours, nous verrons les fonctions wrapper et les macros wrapper, mais nous sommes déjà habitués à fermer les yeux sur ces farces si mignonnes des développeurs) et nous trouvons la fonction parse_one, qui place le paramètre requis au bon endroit.

Notez qu'il n'y a pas de contrôle sur la validité des paramètres, comme on pourrait s'y attendre, car le noyau, contrairement au module lui-même, ne sait rien de leur fonction. Il y a des vérifications de syntaxe et le nombre d'éléments dans le tableau (oui, il peut y avoir un tableau d'entiers comme paramètre) et lorsque des erreurs de ce type sont détectées, le chargement du module s'arrête, mais rien de plus. Cependant, tout n'est pas perdu, car après le chargement, le contrôle est transféré à la fonction init_module, qui peut effectuer la validation nécessaire des paramètres définis et, si la sauvegarde est nécessaire, mettre fin au processus de démarrage.

Cependant, nous avons complètement ignoré la question de savoir comment les fonctions d'analyse accèdent à un tableau d'échantillons de paramètres, car sans cela, l'analyse est quelque peu difficile. Un rapide coup d'œil au code montre qu'un hack sale a été appliqué, une astuce évidente - dans le fichier binaire, la fonction find_module_sections recherche la section nommée __param, divise sa taille par la taille de l'enregistrement (fait beaucoup plus) et renvoie les données nécessaires via la structure. Je mettrais toujours les lettres p devant les noms des paramètres de cette fonction, mais c'est une question de goût.

Tout semble être clair et compréhensible, la seule chose qui inquiète est le manque de l'attribut __initdata sur les données générées, peut-il vraiment rester en mémoire après l'initialisation, cet attribut est probablement décrit quelque part dans la partie générale, par exemple, dans les données de l'éditeur de liens, pour être honnête , paresseux à regarder, voir l'épigraphe.

En résumé - le week-end a été utile, il était intéressant de comprendre le code source de L, de se souvenir de quelque chose et d'apprendre quelque chose, mais la connaissance n'est jamais superflue.
Eh bien, dans mes hypothèses, je n'ai pas deviné, dans L, une option a été mise en œuvre qui s'est avérée être dans les 7% restants, mais ce n'était douloureusement pas évident.

Eh bien, en conclusion, le cri de Yaroslavna (comment peut-on s'en passer) pourquoi dois-je chercher les informations nécessaires (je ne parle pas de la cuisine interne, mais de la présentation externe) de diverses sources qui n'ont pas de statut officiel, où un document similaire au livre
«Logiciel d'un ordinateur. Système d'exploitation fonctionnel.
RAFOS. Guide du programmeur système. ", Ou ne le font-ils plus?

Source: https://habr.com/ru/post/fr431860/


All Articles