Ich habe immer gedacht, dass Funktionen wie printf()
im letzten Schritt mithilfe der Inline-Assembly definiert werden. So tief im Darm von stdio.h steckt ein asm-Code, der der CPU tatsächlich sagt, was zu tun ist. Ich erinnere mich zum Beispiel, dass es in dos implementiert wurde, indem zuerst mov
der Anfang des Strings an eine Speicherstelle oder ein Register gesetzt und dann ein int
Terupt aufgerufen wurde.
Da die x64-Version von Visual Studio den Inline-Assembler überhaupt nicht unterstützt, habe ich mich gefragt, wie es in C / C ++ überhaupt keine vom Assembler definierten Funktionen geben kann. Wie wird eine Bibliotheksfunktion wie printf()
in C / C ++ implementiert, ohne Assembler-Code zu verwenden? Was führt eigentlich den richtigen Software-Interrupt aus? Vielen Dank.
quelle
Antworten:
Zunächst muss man das Konzept der Ringe verstehen.
Ein Kernel läuft in Ring 0, was bedeutet, dass er vollen Zugriff auf Speicher und Opcodes hat.
Ein Programm wird normalerweise in Ring 3 ausgeführt. Es hat einen eingeschränkten Zugriff auf den Speicher und kann nicht alle Opcodes verwenden.
Wenn eine Software mehr Berechtigungen benötigt (zum Öffnen einer Datei, Schreiben in eine Datei, Zuweisen von Speicher usw.), muss sie den Kernel fragen.
Dies kann auf viele Arten erfolgen. Software-Interrupts, SYSENTER usw.
Nehmen wir das Beispiel von Software-Interrupts mit der Funktion printf ():
1 - Ihre Software ruft printf () auf.
2 - printf () verarbeitet Ihre Zeichenfolge und Argumente und muss dann eine Kernelfunktion ausführen, da in Ring 3 nicht in eine Datei geschrieben werden kann.
3 - printf () erzeugt einen Software-Interrupt und legt die Nummer einer Kernelfunktion (in diesem Fall die Funktion write ()) in ein Register.
4 - Die Softwareausführung wird unterbrochen und der Befehlszeiger bewegt sich zum Kernelcode. Wir befinden uns jetzt in Ring 0 in einer Kernelfunktion.
5 - Der Kernel verarbeitet die Anforderung und schreibt in die Datei (stdout ist ein Dateideskriptor).
6 - Wenn Sie fertig sind, kehrt der Kernel mithilfe der iret-Anweisung zum Code der Software zurück.
7 - Der Code der Software wird fortgesetzt.
So können Funktionen der C-Standardbibliothek in C implementiert werden. Sie müssen lediglich wissen, wie der Kernel aufgerufen wird, wenn weitere Berechtigungen erforderlich sind.
quelle
printf
ist nur eine Funktion innerhalb des Kernels. (Wie der Linux-Kernelprintk
.)Unter Linux
strace
können Sie mit dem Dienstprogramm sehen, welche Systemaufrufe von einem Programm ausgeführt werden. Nehmen Sie also ein Programm wie diesesSagen Sie, Sie kompilieren es als
printx
, dannstrace printx
gibtDer Gummi trifft beim vorletzten Aufruf der Spur auf die Straße (sortieren, siehe unten) :
write(1,"x",1x)
. Zu diesem Zeitpunkt geht das Steuerelement vom Benutzerlandprintx
zum Linux-Kernel über, der den Rest erledigt.write()
ist eine Wrapper-Funktion, die in deklariert istunistd.h
Die meisten Systemaufrufe werden auf diese Weise verpackt. Die Wrapper-Funktion ist, wie der Name schon sagt, kaum mehr als eine dünne Codeschicht, die die Argumente in die richtigen Register legt und dann einen Software-Interrupt 0x80 ausführt. Der Kernel fängt den Interrupt ab und der Rest ist Geschichte. Zumindest funktionierte das früher so. Anscheinend war der Overhead des Interrupt-Trapping ziemlich hoch, und wie bereits in einem früheren Beitrag erwähnt, führten moderne CPU-Architekturen
sysenter
Assembler-Anweisungen ein, die mit hoher Geschwindigkeit das gleiche Ergebnis erzielen. Diese Seite Systemaufrufe enthält eine schöne Zusammenfassung der Funktionsweise von Systemaufrufen.Ich habe das Gefühl, dass Sie von dieser Antwort wahrscheinlich ein bisschen enttäuscht sein werden, genau wie ich. In gewissem Sinne ist dies eindeutig ein falscher Grund, da zwischen dem Anruf
write()
und dem Punkt, an dem der Anruf getätigt werden muss, noch einige Dinge zu tun sind Der Grafikkarten-Frame-Puffer wurde tatsächlich so geändert, dass der Buchstabe "x" auf Ihrem Bildschirm angezeigt wird. Das Heranzoomen des Kontaktpunkts (um bei der Analogie "Gummi gegen die Straße" zu bleiben) durch Eintauchen in den Kernel ist sicher lehrreich, wenn dies zeitaufwändig ist. Ich vermute, Sie müssten durch mehrere Abstraktionsebenen wie gepufferte Ausgabestreams, Zeichengeräte usw. reisen. Stellen Sie sicher, dass Sie die Ergebnisse veröffentlichen, wenn Sie sich dazu entschließen, dies weiterzuverfolgen :)quelle
Die Standardbibliotheksfunktionen werden auf einer zugrunde liegenden Plattformbibliothek (z. B. UNIX-API) und / oder durch direkte Systemaufrufe (die noch C-Funktionen sind) implementiert. Die Systemaufrufe werden (auf mir bekannten Plattformen) intern durch einen Aufruf einer Funktion mit Inline-ASM implementiert, die eine Systemaufrufnummer und Parameter in CPU-Register schreibt und einen Interrupt auslöst, den der Kernel dann verarbeitet.
Neben Syscalls gibt es auch andere Möglichkeiten der Kommunikation mit Hardware. Diese sind jedoch normalerweise nicht verfügbar oder eher eingeschränkt, wenn sie unter einem modernen Betriebssystem ausgeführt werden oder zumindest für deren Aktivierung einige Syscalls erforderlich sind. Ein Gerät kann speicherabgebildet sein, so dass Schreibvorgänge an bestimmte Speicheradressen (über reguläre Zeiger) das Gerät steuern. Oft werden auch E / A-Ports verwendet, auf die je nach Architektur über spezielle CPU-Opcodes zugegriffen werden kann, oder sie können auch Speicher sein, der bestimmten Adressen zugeordnet ist.
quelle
Nun, alle C ++ - Anweisungen mit Ausnahme des Semikolons und der Kommentare werden zu Maschinencode, der der CPU mitteilt, was zu tun ist. Sie können Ihre eigene printf-Funktion schreiben, ohne auf die Assembly zurückgreifen zu müssen. Die einzigen Operationen, die in Assembly geschrieben werden müssen, sind die Eingabe und Ausgabe von Ports sowie Dinge, die Interrupts aktivieren und deaktivieren.
Die Baugruppe wird jedoch aus Leistungsgründen weiterhin in der Programmierung auf Systemebene verwendet. Auch wenn Inline-Assembly nicht unterstützt wird, hindert Sie nichts daran, ein separates Modul in Assembly zu schreiben und es mit Ihrer Anwendung zu verknüpfen.
quelle
syscall
/sysenter
oder. Daherint
erfolgt dies mit handgeschriebenem asm.Im Allgemeinen werden Bibliotheksfunktionen vorkompiliert und Anzeigenobjekte verteilt. Inline-Assembler wird aus Leistungsgründen nur in bestimmten Situationen verwendet, dies ist jedoch die Ausnahme und nicht die Regel. Eigentlich scheint mir printf kein guter Kandidat zu sein, um inline zusammengestellt zu werden. Insetad, Funktionen wie memcpy oder memcmp. Funktionen auf sehr niedriger Ebene können von einem nativen Assembler (masm? Gnu asm?) Kompiliert und als Objekt in einer Bibliothek verteilt werden.
quelle
Der Compiler generiert die Assembly aus dem C / C ++ - Quellcode.
quelle
syscall
,sysenter
oderint
Anweisungen. Natürlich ist dies nicht instdio.h
, es ist in der bereits kompilierten Bibliothek