Über Linux-Treiberoptionen oder wie ich das Wochenende verbracht habe

"Wir sind faul und neugierig"




Diesmal war der Grund für den Beitrag ein Artikel in einem guten Magazin, das dem Linux-Betriebssystem gewidmet war (im Folgenden als L bezeichnet), in dem der angezogene "Experte" den Treiber lobte, der das LCD mit dem Raspbery-Board verband. Da solche Dinge (Verbindung, nicht das Betriebssystem) in den Bereich meiner beruflichen Interessen fallen, habe ich den Artikel aufmerksam durchgesehen, dann den eigentlichen Text des „Treibers“ gefunden und war leicht überrascht, dass die IT gelobt werden konnte. Nun, im Allgemeinen kann das Expertenlevel bestimmt werden, schon allein deshalb, weil er das Programm hartnäckig als Fahrer bezeichnet hat, obwohl es keineswegs so ist. Es scheint, und Feigen mit ihm, Sie wissen nie, was jemand für sich selbst schreibt, aber dies öffentlich zu veröffentlichen - "Ich wusste nicht, dass es möglich ist."

Besonders erfreulich war die Tatsache, dass die Geräteadresse auf dem I2C-Bus direkt im Programmtext festgelegt wurde und für deren Änderung eine Neukompilierung erforderlich war (es ist gut, dass nicht der gesamte Kernel). Übrigens ist mir aufgefallen, dass in den Foren zu L die beliebteste Antwort auf alle Fragen zu Softwareproblemen darin besteht, "die neueste Kernel-Version neu zu erstellen". Dieser Ansatz erscheint mir etwas seltsam, wahrscheinlich weiß ich nichts. Dennoch stellte sich die Frage, wie die Parametrisierung des Treibers tatsächlich in A implementiert wird (innen, nicht außen - alles ist einfach und klar), der Antwort, der dieser Beitrag gewidmet ist.

Es ist nicht so, dass ich ständig Treiber für L geschrieben habe, aber mit dem gesamten Prozess bin ich vertraut und Google hat vage Erinnerungen bestätigt, dass es eine Reihe von Makros gibt, die beim Erstellen des Modulquellcodes verwendet werden sollten, um Betriebsparameter an ihn übergeben zu können, z. B. die Geräteadresse an zum Bus. Trotzdem wurde die Mechanik des Prozesses selbst nirgendwo beschrieben. Ich habe den gleichen Text in zahlreichen Links gesehen (übrigens eine interessante Frage - warum dies tun, dh den Text eines anderen auf meine Ressource setzen - ich verstehe die Bedeutung dieser Operation nicht wirklich), in der die obigen Makros beschrieben wurden. Ich fand keine einzige Erwähnung des Mechanismus zur Durchführung der Operation, für ein anderes bekanntes Betriebssystem (Windows) müsste ich eine Tatsache angeben und mich darauf beschränken, aber einer der Vorteile von A ist die Verfügbarkeit von Quelltexten und die Fähigkeit, eine Antwort auf jede Frage zu seiner internen Struktur zu finden. was wir tun werden. Ich stelle sofort fest, dass ich versuchen werde, die Informationen, die Sie aus anderen Quellen erhalten können, nicht zu duplizieren, und mich nur auf das beschränken werde, was zum Verständnis des Textes erforderlich ist.

Bevor Sie sich jedoch die Quelle ansehen, werden wir zunächst ein wenig darüber nachdenken, aber wie würden wir es tun, wenn wir eine ähnliche Aufgabe bekommen würden (und plötzlich, nach diesem Beitrag, werden sie mich zu den L-Bergleuten einladen, und Sie werden es nicht ablehnen). Es besteht also die Möglichkeit, ein Modul zu erstellen - eine bestimmte speziell entwickelte Programmeinheit, die zur Ausführung mit einem Systemdienstprogramm (Insmode - im Folgenden I) in den Speicher geladen werden kann, während eine Zeichenfolge als Startparameter übergeben wird. Diese Zeile kann streng definierte lexikalische Einheiten enthalten, deren Formatbeschreibung beim Erstellen des Quelltextes des Moduls angegeben wird. Diese Einheiten enthalten Informationen, mit denen Sie den Wert der internen Variablen dieses Moduls ändern können.

Betrachten wir die Art und Weise der Beschreibung der obigen lexikalischen Einheiten genauer. Wir benötigen diese, um verschiedene Lösungen zu betrachten. Die Analyseeinheit wird durch Aufrufen des Makros bestimmt, das über die erforderlichen Informationen informiert wird - den Namen der Variablen, die während des Einrichtungsprozesses geändert werden soll, ihren externen Namen (normalerweise derselbe wie der vorherige), den Typ der Variablen aus der begrenzten Menge und Zugriffsrechte auf die Variable im Stil von rw-rw-rw. Zusätzlich kann eine (optionale) Textzeichenfolge angegeben werden, die die Variable beschreibt. Offensichtlich sind diese Informationen notwendig und ausreichend (in Verbindung mit den Regeln für den Entwurf syntaktischer Einheiten - Trennzeichen und Token), um den Parser der in Form einer Textzeichenfolge angegebenen Parameterliste zu erstellen, lassen jedoch Raum für die Implementierung der Funktionsverteilung zwischen den Prozessteilnehmern.

Um das Modul zu konfigurieren, benötigen wir:

  1. Form (nun, dies befindet sich in der Kompilierungsphase, Sie können es tun, wie Sie möchten, obwohl es immer noch interessant ist, wie) und eine Tabelle mit den oben genannten Einstellungen speichern.
  2. Analyse der Eingabeparameter gemäß dieser Tabelle und
  3. Nehmen Sie Änderungen an bestimmten Speicherbereichen vor, die dem Ergebnis des Parsens einer syntaktischen Einheit entsprechen.

Wir werden ein bisschen im Stil von "Wenn ich der Regisseur wäre" denken und mögliche Implementierungen finden. Wie wir das ähnliche Verhalten des Systemdienstprogramms und des Moduls implementieren könnten - wir werden mit der Analyse der Optionen in zunehmender Komplexität beginnen.

Die erste Lösung ist, dass das Dienstprogramm And fast nichts tut, nur das angegebene Modul aufruft und die verbleibenden Parameter im Befehlszeilenstil an das Modul überträgt. Das Modul analysiert sie bereits, wobei es sich auf die darin verfügbaren Informationen stützt und die erforderlichen Änderungen vornimmt. Diese Lösung ist einfach, verständlich und durchaus machbar, aber der folgende Umstand sollte berücksichtigt werden: Die Analyse der Parameter sollte in keiner Weise dem Willen des Autors des Moduls überlassen werden, da dies ihm inakzeptablen Platz bietet und schließlich zwei Programmierer immer drei Parseroptionen schreiben. Und so gingen wir ihm entgegen und ließen ihm Parameter eines unbestimmten Typs zu, die eine Textzeichenfolge als Wert haben.

Daher sollte ein bestimmter Standardparser automatisch in den Text des Moduls aufgenommen werden. Dies ist auf der Ebene der Makrosubstitution einfach zu implementieren.

Diese Lösung hat zwei Nachteile:

  1. Es ist nicht klar, warum wir es überhaupt brauchen. Und Sie können das Modul sofort mit Parametern über die Befehlszeile aufrufen.
  2. Der Modulcode (Initialisierungsteil) muss alle drei Abschnitte der erforderlichen Informationen enthalten. Diese Informationen sind nur erforderlich, wenn das Modul gestartet wird und in Zukunft nicht mehr verwendet wird. Es nimmt immer Platz ein. Machen Sie sofort eine Reservierung, dass diese Informationen notwendigerweise Speicherplatz in der Datei beanspruchen, aber beim Laden des Moduls möglicherweise nicht in den Speicher gelangen, wenn alles sorgfältig ausgeführt wird. Um genau das zu tun, erinnern wir uns an die Direktiven _init und _initdata (übrigens, aber wie sie funktionieren, müssten wir es herausfinden - das ist das Thema des nächsten Beitrags - werden Sie sich darauf freuen?). Im letzteren Fall sind die Abschnitte 2 und 3 der Informationen in der Datei eindeutig redundant, da in vielen Modulen derselbe Code vorhanden ist, was böswillig gegen das DRY-Prinzip verstößt.

Aufgrund der festgestellten Mängel ist die Umsetzung dieser Option höchst unwahrscheinlich. Darüber hinaus ist unklar, warum dann im Makro Informationen über den Parametertyp festgelegt werden sollen, da das Modul selbst sehr gut weiß, was es ändert (obwohl es für den Parser bei der Überprüfung der Parameter erforderlich sein kann). Die Gesamtbewertung der Wahrscheinlichkeit einer solchen Entscheidung beträgt 2-3 Prozent.

Der notwendige Exkurs über den bekannten Nachteil Nummer 2 - Ich wurde als Spezialist in jenen Tagen gegründet, als 256 KByte RAM ausreichten, um 4 Workstations zu organisieren, 56 KByte ein Dual-Tasking-Betriebssystem hatten und ein Single-Tasking-Betriebssystem mit 16 KByte zu arbeiten begann. Nun, 650 kb, was für jedes Programm ausreichen sollte, waren im Allgemeinen etwas aus dem Bereich der unwissenschaftlichen Fiktion. Daher bin ich es gewohnt, RAM als knappe Ressource zu betrachten, und ich lehne seine verschwenderische Verwendung äußerst ab, wenn es nicht durch einen Notfall verursacht wird (in der Regel Leistungsanforderungen), und in diesem Fall beobachte ich eine solche Situation nicht. Da sich die meisten meiner Leser in unterschiedlichen Realitäten gebildet haben, können Sie Ihre eigenen Einschätzungen über die Präferenz dieser oder jener Option haben.

Die zweite Lösung - der Parser selbst wird an AND übertragen, das die extrahierten Daten an das Modul (seinen Initialisierungsteil) überträgt - Parameternummer und -wert. Dann behalten wir die Einheitlichkeit der Parameter bei und reduzieren die Anforderungen an die Größe des Moduls. Es bleibt die Frage, wie eine UND-Liste möglicher Parameter bereitgestellt werden kann. Dies wird jedoch durch Makros bereitgestellt, indem eine vorbestimmte Struktur des Moduls und die Position des Blocks an einer bestimmten Stelle (Datei oder Speicher) erstellt werden. Die Lösung ist besser als die vorherige, aber der überschüssige Speicher im Modul bleibt bestehen. Im Allgemeinen gefällt mir die Lösung, weil mein Parser (der schlechter ist als alle anderen Programmierer, ich habe meinen eigenen Parser, nicht ohne Fehler, aber definitiv nicht schwerwiegend) nach diesem Schema arbeitet und die Nummer der identifizierten Regel und den Wert an das Hauptprogramm zurückgibt Parameter. Dennoch ist die Wahrscheinlichkeit, diese spezielle Option umzusetzen, nicht sehr hoch - 5 Prozent.

Eine Unteroption der zweiten Lösung besteht darin, die extrahierten Parameter nicht in den Startteil des Moduls zu übertragen, sondern direkt in den geladenen Arbeitsteil, beispielsweise über ioctl - die Speicheranforderungen sind gleich. Wir haben die einmalige Möglichkeit, Parameter "on the fly" zu ändern, was in anderen Versionen nicht implementiert ist. Es ist nicht ganz klar, warum wir eine solche Funktion benötigen, aber sie sieht wunderschön aus. Der Nachteil ist, dass 1) Sie einen Teil des Funktionsbereichs im Voraus für eine möglicherweise nicht verwendete Anforderung reservieren müssen und 2) der Modifikatorcode ständig im Speicher vorhanden sein muss. Einschätzung der Umsetzungswahrscheinlichkeit - Prozent 5.

Die dritte Lösung besteht darin, auf Und auch die Änderung der Parameter zu übertragen. Beim Laden des Binärcodes des Moduls And kann es dann die Daten im Zwischenspeicher ändern und den Treibercode mit den geänderten Parametern an den Ort der permanenten Bereitstellung laden oder diese Änderungen direkt in dem Speicherbereich vornehmen, in den die Binärdatei geladen wurde, und die in der Datei vorhandene Parametertabelle befindet sich im Speicher kann es sowohl laden als auch nicht belegen (denken Sie an die Anweisungen). Die Entscheidung ist verantwortlich, sie erfordert wie die vorherige das Vorhandensein eines vordefinierten Kommunikationsbereichs zwischen dem Modul und AND, um die Beschreibung der Parameter zu speichern, reduziert jedoch die Anforderungen für übermäßigen Speicher im Modul weiter. Wir stellen sofort den Hauptnachteil einer solchen Lösung fest - die Unfähigkeit, die Parameterwerte und ihre Konsistenz zu steuern, aber es gibt nichts zu tun. Es ist eine ganz normale Lösung, höchstwahrscheinlich 75 Prozent.

Eine Variante der dritten Lösung - Informationen über die Parameter werden nicht im Modul selbst gespeichert, sondern in einer Zusatzdatei, dann gibt es einfach keinen überschüssigen Speicher im Modul. Im Prinzip kann das gleiche in der vorherigen Version gemacht werden, wenn das Modul den Konfigurationsteil enthält, der UND während des Startvorgangs verwendet wird, aber nicht in den RAM geladen wird, der den tatsächlich ausführbaren Teil des Moduls enthält. Im Vergleich zur vorherigen Version wurde eine zusätzliche Datei hinzugefügt, und es ist unklar, wofür wir bezahlen, aber möglicherweise haben sie vor der Erfindung Initialisierungsanweisungen ausgeführt - 5 Prozent.

Die restlichen 7 Prozent bleiben für andere Optionen übrig, die ich mir nicht ausdenken konnte. Nun, da sich unsere Fantasie erschöpft hat (meine sicher, wenn es weitere Ideen gibt, fragen Sie bitte im Kommentar), werden wir beginnen, die Quelle von L. zu untersuchen.

Zunächst stelle ich fest, dass anscheinend die Kunst, Quelltexte in Dateien zu verteilen, zusammen mit dem Betriebssystem, das in 16 KB passt, verloren gegangen ist, da die Verzeichnisstruktur, ihre Namen und Dateinamen ein wenig mehr als nichts mit dem Inhalt verbunden sind. Angesichts des Vorhandenseins eingebetteter Einschlüsse wird das klassische Studium heruntergeladener Quellen mit Hilfe eines Editors zu einer seltsamen Aufgabe und wird unproduktiv. Glücklicherweise gibt es ein charmantes Dienstprogramm Elixir, das online verfügbar ist und das es Ihnen ermöglicht, kontextbezogene Suchen durchzuführen, und hier wird der Prozess viel interessanter und fruchtbarer. Ich habe meine weiteren Forschungen auf der Website elixir.bootlin.com durchgeführt. Ja, diese Seite ist im Gegensatz zu kernel.org keine offizielle Sammlung von Kernel-Käse, aber hoffen wir, dass der Quellcode für sie identisch ist.

Schauen wir uns zunächst ein Makro zur Bestimmung von Parametern an - erstens kennen wir seinen Namen und zweitens sollte es einfacher sein (ja, jetzt). Es befindet sich in der Datei moduleparam.h - es ist durchaus vernünftig, aber dies ist eine angenehme Überraschung, wenn man bedenkt, was wir später sehen werden. Makro

{0}module_param(name,type,perm) 

ist ein Wrapper vorbei

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

- syntaktischer Zucker für den häufigsten Fall. Gleichzeitig wird aus irgendeinem Grund die Aufzählung der zulässigen Werte eines der Parameter, nämlich des Typs der Variablen, in den Kommentaren vor dem Wrapper-Text angegeben und nicht im zweiten Makro, das die Aufgabe wirklich erledigt und direkt verwendet werden kann.

Makro {0a} enthält einen Aufruf von drei Makros

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

(Es gibt eine Reihe von Makros für alle gültigen Typen)

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

und

 {3}__MODULE_PARM_TYPE(n,t) 

(Achten Sie jedoch auf die Namen, Charme), und der erste von ihnen wird an anderer Stelle verwendet, dh die Empfehlungen von Occam und das KISS-Prinzip werden von den Machern von A ebenfalls kühn vernachlässigt - anscheinend eine Art Grundlage für die Zukunft. Natürlich sind dies nur Makros, aber sie kosten nichts, aber trotzdem ...

Das erste der drei Makros {1} überprüft, wie der Name schon sagt, die Übereinstimmung von Parametertypen und Wraps

 __param_check(n,p,t) 

Beachten Sie, dass in der ersten Phase des Umbruchs die Ebene der Makroabstraktion abnimmt und in der zweiten Phase wahrscheinlich auf andere Weise zunimmt, und es scheint mir nur, dass dies einfacher und logischer sein könnte, insbesondere wenn man bedenkt, dass das durchschnittliche Makro nirgendwo anders verwendet wird. Okay, lassen Sie uns einen anderen Weg einschlagen, um die Makroparameter im Sparschwein zu überprüfen und fortzufahren.

Die nächsten beiden Makros generieren jedoch tatsächlich ein Element der Parametertabelle. Warum fragst du mich nicht zwei und nicht einen? Ich habe die Logik der Schöpfer von L. schon lange nicht mehr verstanden. Aufgrund des unterschiedlichen Stils dieser beiden Makros, beginnend mit den Namen, wurde der zweite wahrscheinlich später hinzugefügt, um die Funktionalität zu erweitern und die vorhandene Struktur zu ändern Es war unmöglich, weil sie es anfangs bereuten, einen Platz zur Angabe der Optionsparameter zugewiesen zu haben. Das Makro {2} maskiert wie immer das Makro vor uns

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

(Es ist lustig, dass dieses Makro nirgendwo direkt aufgerufen wird, außer in 8250_core.c, wo es mit denselben zusätzlichen Parametern aufgerufen wird.) Letzteres erzeugt jedoch bereits den Quellcode.

Eine kleine Bemerkung - während der Suche stellen wir sicher, dass die Textnavigation gut funktioniert, aber es gibt zwei unangenehme Umstände: Die Suche nach dem Namensfragment funktioniert nicht (check_param_ wurde nicht gefunden, obwohl check_param_byte gefunden wurde) und die Suche funktioniert nur bei Objektdeklarationen (die Variable wird dann nicht gefunden wird in dieser Datei von ctrF gefunden, aber die integrierte Suche nach Quelle wird nicht erkannt. Nicht sehr ermutigend, da wir möglicherweise nach einem Objekt außerhalb der aktuellen Datei suchen müssen, aber "am Ende haben wir kein anderes".

Als Ergebnis der Arbeit von {1} im Text des kompilierten Moduls in Gegenwart der folgenden zwei Zeilen

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

Ein Fragment des folgenden Typs wird angezeigt

 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"; 

(Tatsächlich werden dort einzeilige Dateien generiert, ich habe sie zur leichteren Überprüfung in Zeilen unterteilt.) Wir können sofort sagen, dass es keinen Hinweis auf die Aufnahme eines Parser-Programmabschnitts oder eines Moduls zum Zuweisen von Werten zu Parametern im Quelltext gibt, sodass die Optionen 1 und 2 dies können als von weiteren Überlegungen ausgeschlossen betrachtet. Das Vorhandensein spezieller Attribute für den Linker deutet sozusagen auf die Existenz eines Kommunikationsbereichs hin, der sich an einem vorbestimmten Ort befindet, über den die Beschreibung der Parameter übertragen wird. Gleichzeitig stellen wir mit Verwirrung fest, dass keinerlei Beschreibung des generierten Blocks möglicher Parameter in Form von Text vorliegt, der vom Parser-Modul verwendet werden könnte. Es ist klar, dass gut geschriebener Code sich selbst dokumentiert, aber nicht in dem Maße, wie dies wiederum die Wahrscheinlichkeit von Option 1 oder 2 nicht erhöht, da der Parser vom Modulentwickler geschrieben wird.

Die gleichzeitige Kombination der Attribute __used__ und nicht verwendet sieht in der zuletzt generierten Zeile lustig aus, insbesondere wenn Sie sich das nächste Fragment des Makrocodes ansehen

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

Was ist die Art von Beweglichkeit, die die Entwickler von A rauchen, die schmerzlich gewundene Art ihrer Gedanken, die im Code enthalten sind. Ich weiß, dass Sie beide Formen des Attributschreibens verwenden können, aber warum dies in derselben Zeile tun - ich verstehe nicht.

Ein weiteres interessantes Merkmal des resultierenden Codes kann festgestellt werden - das Duplizieren von Informationen über den Variablennamen und seinen Typ. Es ist noch nicht klar, warum dies getan wurde, aber die Tatsache selbst ist nicht zweifelhaft. Natürlich sind diese Informationen kohärent, da sie im automatischen Modus erstellt wurden und diese Kohärenz erhalten bleibt, wenn sich der Quelltext ändert (und das ist gut), aber sie werden dupliziert (und das ist schlecht). Vielleicht werden wir später die Notwendigkeit einer solchen Lösung verstehen. Außerdem bleibt die Notwendigkeit, unter Verwendung der Zeilennummer des Quellcodes einen eindeutigen Namen zu bilden, unklar, da die erste generierte Zeile darauf verzichtete.

Ein weiterer Hinweis: Es war keine ganz triviale Aufgabe, genau herauszufinden, wie sich die Definition des Parameters entwickelt, aber dank MinGW war sie immer noch abgeschlossen. Unter der Haube gab es eine Stringifizierung und doppelte Verklebung von Parametern, die Bildung eindeutiger Namen sowie andere knifflige Tricks bei der Arbeit mit Makros, aber ich präsentiere nur die Ergebnisse. Zusammenfassend kann ich sagen, dass das Studium von Makros A nicht das ist, wovon ich meinen Lebensunterhalt verdienen möchte, es ist nur als Unterhaltung möglich, aber wir fahren fort.

Wir werden das Verständnis von Makros beim Verständnis der Aufgabe nicht weiter verbessern, daher wenden wir uns dem Quellcode des Dienstprogramms And zu und versuchen zu verstehen, was es tut.

Zunächst einmal sind wir erstaunt zu sehen, dass die erforderlichen Käsesorten nicht in den Kernelquellen enthalten sind. Ja, ich bin bereit zuzustimmen, dass das I ein Dienstprogramm ist und über den Einstiegspunkt zum Laden des Moduls mit dem Kernel interagiert. In jedem Buch über L-Treiber wird jedoch über dieses Dienstprogramm berichtet, sodass das Fehlen einer „offiziellen“ Version der Quelle in der Nähe der Kernelquelle verursacht wird mich missverstehen. Okay, Google hat uns nicht enttäuscht und wir sind trotzdem mit dem Käse rausgekommen.

Das zweite Erstaunliche ist, dass dieses Dienstprogramm aus einem Paket besteht, dessen Name in keiner Weise mit seinem Namen verknüpft ist. Es gibt mehr als ein solches Paket, und jedes wird an verschiedenen Stellen auf seine eigene Weise benannt - gelinde gesagt lustig. Wenn Sie L installiert haben, können Sie mit dem Befehl herausfinden, aus welchem ​​Paket das Dienstprogramm And zusammengestellt ist, und dann danach suchen. Wenn wir jedoch theoretische Untersuchungen durchführen (ich persönlich lasse L aus mehreren Gründen nicht auf meinem Heimcomputer, von denen einige ich habe Ich habe meine Beiträge angegeben (so ein theoretischer Boxer), dann steht uns diese Methode nicht zur Verfügung und alles, was bleibt, ist eine Suche im Internet, zum Glück gibt sie Ergebnisse.

Das dritte Erstaunliche ist, dass der Name des Dienstprogramms selbst nirgendwo im Quellcode vorkommt, nicht in Dateinamen verwendet wird und nur in der make-Datei enthalten ist. Ich weiß, dass wir in C verpflichtet sind, die Hauptfunktion main zu benennen, und dies wird nicht besprochen (ich bin nicht persönlich) Ich freue mich darüber, da Pascal verwöhnt ist, aber sie haben mich beim Entwerfen der Sprache nicht nach meiner Meinung gefragt, aber es wäre zumindest möglich, den externen Namen des Dienstprogramms in die Kommentare zu schreiben. Notwendige Anmerkung - viele Dinge in Sprache C wurden nach dem Prinzip "es ist so üblich bei uns" gemacht, dass es wahrscheinlich schwierig war, Dinge manchmal anders oder sogar unmöglich zu machen, aber was können Sie jetzt tun, indem Sie einen Koffer ohne Griff weiter ziehen.

Wir finden zwei Pakete, die den Quelltext enthalten. Und wir finden auch die Käse auf Github. Wir sehen, dass sie identisch sind, und gehen davon aus, dass der Quellcode des Dienstprogramms so aussieht. Als nächstes untersuchen wir nur die Datei auf git, zumal sie hier nur insmod.c heißt. Wir stellen fest, dass die Liste der Parameter zunächst in eine lange nullterminierte Zeichenfolge konvertiert wird, in der einzelne Elemente durch Leerzeichen getrennt sind. Anschließend ruft er zwei Funktionen auf, von denen die erste grub_file heißt und offensichtlich die Binärdatei öffnet, während die zweite den Namen init_module hat und einen Zeiger auf eine geöffnete Datei mit der Modulbinärdatei und einer Folge von Parametern nimmt und als load_module bezeichnet wird, was den Zweck dieser Funktion als Laden nahe legt mit Änderung der Parameter.

Wir wenden uns dem Text der zweiten Funktion zu, der in der Datei liegt ... und hier ist ein Mist - nicht in einer der Dateien des untersuchten Repositorys auf dem Geet (nun, das ist nur logisch, dies ist Teil des Kernels und sein Platz ist nicht hier) ist es nicht. Google hat es wieder eilig zu helfen und bringt uns zu den Kernel-Käsesorten unter Elixir und der Datei module.c zurück. Es sollte beachtet werden, dass der Name der Datei, die die Funktionen für die Arbeit mit Modulen enthält, überraschenderweise logisch aussieht. Ich verstehe nicht einmal, wie ich das erklären soll. Es ist wahrscheinlich zufällig passiert.

Jetzt wurde uns der Mangel an Text klar. Und neben dem Kernel - es macht eigentlich fast nichts, es überträgt nur Parameter von einer Form in eine andere und überträgt die Kontrolle auf den Kern selbst, so dass es sogar unwürdig ist, daneben zu liegen. Von diesem Moment an wird klar, dass es keine klaren externen Informationen über die Struktur der Parameter gibt, da der Kernel sie durch seine eigenen Makros übersprungen hat und alles über sie perfekt weiß und der Rest nichts über die interne Struktur wissen muss (angesichts der Tatsache, dass die Quelle stehen zur Ansicht zur Verfügung, ein paar Kommentare würden nicht schaden, aber im Prinzip ist es auch ohne sie wirklich immer klarer, aber bisher hat es fast nie Licht auf die Implementierung des Ausführungsmechanismus selbst geworfen.

Hinweis - In Bezug auf die Übertragung der Kontrolle auf den Kernel war ich ein wenig aufgeregt. Wir sehen mit Sicherheit, wie die Funktion im Quellcode eines bestimmten verwendet wird und ob der Binärteil mit dem Modul verknüpft wird oder ob er tatsächlich im Kernel-Image liegt. Weitere Untersuchungen sind erforderlich. Die Tatsache, dass der Einstiegspunkt für die Verarbeitung dieser Funktion in besonderer Weise durch SYSCALL_DEFINE3 festgelegt ist, spricht indirekt für die zweite Option, aber ich habe lange verstanden, dass meine Vorstellungen über das Logische und Unlogische, Akzeptable und Inakzeptable sowie über das Zulässige und Inakzeptable sehr bedeutsam sind von denen der Entwickler von L. abweichen

Hinweis - ein weiterer Kieselstein im eingebauten Suchgarten - Als ich nach einer Definition für dieses Makro suchte, sah ich viele Stellen, an denen es als Funktion verwendet werden konnte, unter denen sich die Definition als Makro sehr bescheiden versteckte.

Ich verstehe zum Beispiel nicht, warum ein externes Dienstprogramm erforderlich ist, um die Parameter aus dem Standardformular für das Betriebssystem (agrc, argv) in die Form einer nullterminierten Zeichenfolge mit Leerzeichen als Trennzeichen zu übersetzen, die vom Systemmodul weiter verarbeitet wird - dieser Ansatz übertrifft meinen etwas kognitive Fähigkeiten. Insbesondere angesichts der Tatsache, dass der Benutzer eine Parameterzeichenfolge in Form einer nullterminierten Zeichenfolge mit Leerzeichen als Trennzeichen eingibt und das Dienstprogramm im Kernel diese in eine Form (argc, argv) konvertiert. Erinnert stark an den alten Witz "Wir nehmen den Wasserkocher vom Herd, gießen Wasser heraus und bekommen ein Problem, dessen Lösung bereits bekannt ist." Und da ich versuche, mich an das Prinzip zu halten: „Betrachten Sie Ihren Gesprächspartner nicht dümmer als Sie selbst, bis er das Gegenteil beweist. Und selbst danach können Sie sich irren. “In Bezug auf die Entwickler von A ist der erste Satz definitiv gültig. Das bedeutet, dass ich etwas falsch verstehe, aber ich bin nicht daran gewöhnt. Wenn jemand eine vernünftige Erklärung für die angegebene Tatsache der doppelten Umwandlung liefern kann, dann frage ich im Kommentar. Aber wir setzen die Untersuchung fort.

Die Aussichten für die Implementierung der Optionen 1 und 2 werden „sehr schwach sichtbar“ (ein charmanter Wortlaut aus einem kürzlich erschienenen Artikel über die Aussichten für die Entwicklung von inländischen Hochgeschwindigkeits-ADCs), da es sehr seltsam wäre, ein Modul mithilfe der Kernelfunktion in den Speicher zu laden und dann die Steuerung an ihn zu übergeben, um den Kernel zu implementieren Funktion in seinen Körper eingebaut. Und sicher finden wir im Text der Funktion load_module ziemlich schnell den Aufruf parse_args - anscheinend sind wir auf dem richtigen Weg. Als nächstes gehen wir schnell die Aufrufkette durch (wie immer werden wir Wrapper-Funktionen und Wrapper-Makros sehen, aber wir sind es bereits gewohnt, die Augen vor solch niedlichen Streiche von Entwicklern zu verschließen) und finden die Funktion parse_one, die den erforderlichen Parameter an der richtigen Stelle platziert.

Beachten Sie, dass die Gültigkeit der Parameter nicht wie erwartet überprüft wird, da der Kernel im Gegensatz zum Modul selbst nichts über ihren Zweck weiß. Es gibt Syntaxprüfungen und die Anzahl der Elemente im Array (ja, es kann ein Array von Ganzzahlen als Parameter geben), und wenn Fehler dieser Art erkannt werden, stoppt das Laden des Moduls, aber nichts weiter. Es geht jedoch nicht alles verloren, da nach dem Laden die Steuerung an die Funktion init_module übertragen wird, die die erforderliche Validierung der eingestellten Parameter durchführen und, falls der Sicherungswurf erforderlich ist, den Startvorgang beenden kann.

Wir haben jedoch die Frage, wie Parsing-Funktionen auf ein Array von Parameterbeispielen zugreifen, völlig übersehen, da das Parsen ohne dies etwas schwierig ist. Ein kurzer Blick auf den Code zeigt, dass ein Dirty Hack angewendet wurde, ein offensichtlicher Trick. In der Binärdatei sucht die Funktion find_module_sections nach dem benannten Abschnitt __param, dividiert seine Größe durch die Größe des Datensatzes (macht viel mehr) und gibt die erforderlichen Daten über die Struktur zurück. Ich würde immer noch die Buchstaben p vor die Parameternamen dieser Funktion setzen, aber das ist Geschmackssache.

Alles scheint klar und verständlich zu sein. Das einzige, was sich Sorgen macht, ist das Fehlen des Attributs __initdata für die generierten Daten. Kann es nach der Initialisierung wirklich im Speicher bleiben? Wahrscheinlich wird dieses Attribut, um ehrlich zu sein, irgendwo im allgemeinen Teil beschrieben, beispielsweise in den Linkerdaten , faul zu schauen, sehen Sie die Inschrift.

Fazit: Das Wochenende war nützlich, es war interessant, den Quellcode von L zu verstehen, sich an etwas zu erinnern und etwas zu lernen, aber Wissen ist niemals überflüssig.
Nun, in meinen Annahmen habe ich nicht vermutet, dass in L eine Option implementiert wurde, die sich als die verbleibenden 7 Prozent herausstellte, aber es war schmerzlich nicht offensichtlich.

Nun, abschließend Jaroslawnas Schrei (wie könnte man darauf verzichten), warum ich nach den notwendigen Informationen (ich meine nicht die interne Küche, sondern die externe Präsentation) aus verschiedenen Quellen suchen muss, die keinen offiziellen Status haben, wo es ein Dokument gibt, das dem Buch ähnlich ist
„Software eines Computers. Funktionales Betriebssystem.
RAFOS. System Programmer's Guide. ”, Oder nicht mehr?

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


All Articles