Wie erweitert sich eine Sprache? [geschlossen]


208

Ich lerne C ++ und habe gerade angefangen, einige der Funktionen von Qt zum Codieren von GUI-Programmen kennenzulernen. Ich habe mir folgende Frage gestellt:

Wie erhält C ++, das zuvor keine Syntax hatte, die das Betriebssystem nach einem Fenster oder einer Möglichkeit zur Kommunikation über Netzwerke fragen konnte (mit APIs, die ich auch nicht vollständig verstehe, gebe ich zu), plötzlich solche Funktionen durch in C ++ selbst geschriebene Bibliotheken? Es scheint mir alles schrecklich kreisförmig. Welche C ++ - Anweisungen könnten Sie möglicherweise in diesen Bibliotheken finden?

Mir ist klar, dass diese Frage für einen erfahrenen Softwareentwickler trivial erscheint, aber ich habe stundenlang recherchiert, ohne eine direkte Antwort zu finden. Es ist an einem Punkt angelangt, an dem ich dem Tutorial über Qt nicht mehr folgen kann, weil die Existenz von Bibliotheken für mich unverständlich ist.


27
Wie zeichnet std :: cout überhaupt etwas auf den Monitor? Oder sitzt es auf einem Compiler, der Ihre Hardware versteht?
Doctorlove

14
Gute Frage. Letztendlich ist es schwer zu beantworten, bis Sie Hardware studieren.
user541686

17
Qt ist keine Erweiterung der Sprache (für die ein Qt-fähiger Compiler erforderlich wäre). Es ist lediglich eine Bibliothek , die Ihrem Arsenal hinzugefügt wird. Auf der untersten Ebene kommunizieren schließlich alle Bibliotheken über Systemaufrufe mit dem Betriebssystem, die unabhängig von der Sprache sind, jedoch stark vom Betriebssystem und der CPU-Architektur abhängen.
DevSolar

8
afaik, C ++ hat Inline-Assembly, die so ziemlich alles kann
Anzeigename

9
@ DevSolar: Tatsächlich erweitert Qt die Sprache mit einem eigenen Signalschlitzmechanismus, Reflexion und vielen anderen dynamischen Funktionen. Und diese Dinge erfordern einen Compiler (den Metaobjekt-Compiler), um bis zu C ++ - Code zu kompilieren.
Siyuan Ren

Antworten:


194

Ein Computer ist wie eine Zwiebel, es viele hat viele Schichten, aus dem inneren Kern der reinen Hardware auf die äußerste Anwendungsschicht. Jede Schicht setzt Teile von sich der nächsten äußeren Schicht aus, so dass die äußere Schicht einige der Funktionen der inneren Schichten nutzen kann.

Im Fall von z. B. Windows stellt das Betriebssystem die sogenannte WIN32-API für Anwendungen zur Verfügung, die unter Windows ausgeführt werden. Die Qt-Bibliothek verwendet diese API, um Anwendungen, die Qt verwenden, für ihre eigene API bereitzustellen. Sie verwenden Qt, Qt verwendet WIN32, WIN32 verwendet niedrigere Ebenen des Windows-Betriebssystems usw., bis es elektrische Signale in der Hardware gibt.


56
Hinweis: QtHier finden Sie eine Abstraktion der darunter liegenden Ebene, da unter Linux Qtdie Linux-API und nicht die WIN32-API aufgerufen wird.
Matthieu M.

5
Ich würde wahrscheinlich am Beispiel von Qt etwas mehr elaboarte, dass es nur so aussieht, als würde es mühelos die C ++ - Funktionen erweitern. Wenn die Realität so ist, geben sie sich viel Mühe, um eine gemeinsame API für (fraglich) viele verschiedene "Zwiebelkerne" zu erstellen. Sie bieten Portabilität über nicht tragbare, nicht standardmäßige Backends.
luk32

81
Ein Computer ist wie eine Zwiebel: Wenn man ihn durchschneidet, weint man, aber danach schmeckt er etwas.
Alecov

3
@ChristopherPfohl Ja, musste es benutzen, da ich nicht herausfinden konnte, wie ein Computer wie eine Schachtel Pralinen aussehen würde. :)
Einige Programmierer Typ

1
@Celeritas sagte der Lehrer wahrscheinlich user32.dlloder möglicherweise gdi32.dll.
user253751

59

Sie haben Recht, dass Bibliotheken im Allgemeinen nichts möglich machen können, was noch nicht möglich ist.

Die Bibliotheken müssen jedoch nicht in C ++ geschrieben sein, um von einem C ++ - Programm verwendet werden zu können. Selbst wenn sie in C ++ geschrieben sind, können sie intern andere Bibliotheken verwenden, die nicht in C ++ geschrieben sind. Die Tatsache, dass C ++ keine Möglichkeit dazu bietet, verhindert nicht, dass es hinzugefügt wird, solange es eine Möglichkeit gibt, dies außerhalb von C ++ zu tun.

Auf einer relativ niedrigen Ebene werden einige von C ++ (oder von C) aufgerufene Funktionen in Assembly geschrieben, und die Assembly enthält die erforderlichen Anweisungen, um alles zu tun, was in C ++ nicht möglich (oder nicht einfach) ist, beispielsweise zum Aufrufen eine Systemfunktion. Zu diesem Zeitpunkt kann dieser Systemaufruf alles tun, was Ihr Computer kann, einfach weil nichts ihn aufhält.


Meinen Sie damit, dass die in den anderen Sprachen geschriebenen Bibliotheken bereits mit anderen Compilern kompiliert wurden? Und dann müsste es eine Art Schnittstellendatei geben, die jeden von der Bibliothek für C ++ bereitgestellten Funktionsaufruf mit einer vorkompilierten Version der Bibliothek verknüpft? So kann der C ++ - Compiler wissen, in was diese Aufrufe übersetzt werden sollen?
Med Larbi Sentissi

2
@MedLarbiSentissi 1) Nicht unbedingt andere Compiler. Es ist möglich (und dies ist häufig der Fall), dass ein einzelner Compiler mehrere Sprachen einschließlich Assembly zusammenstellen kann und sogar C ++ mit Inline-Assembly kompilieren kann. 2) Abhängig vom spezifischen System und Compiler kann das Aufrufen dieser Funktionen von C ++ aus zwar mit einer Art Schnittstellendatei erfolgen, diese Art von Schnittstellendatei kann jedoch bereits ein C- (oder sogar C ++) Header sein, der direkt von C ++ aus verwendet werden kann.

1
@MedLarbiSentissi: Viele Windows-Bibliotheken werden in DLL-Dateien kompiliert, die ihre eigene Schnittstelle sowie den Code enthalten. Sie können in eine DLL schauen und eine Liste der Funktionen anzeigen, die Sie verwenden können. Sie werden häufig auch mit einer C-Header-Datei geliefert. Wenn Sie Ihre Exe erstellen, enthält sie eine Liste der DLLs, die sie ausführen muss. Wenn das Betriebssystem versucht, Ihre Exe zu laden, werden diese DLLs automatisch auch geladen, bevor die Ausführung gestartet wird.
Mooing Duck

8
Diese Antwort scheint darauf hinzudeuten, dass die "Magie" vollständig in anderen Sprachen liegt, die aufgerufen werden, aber tatsächlich ist der größte Teil des Codes , der die meisten modernen Betriebssysteme ausmacht, C (mit nur sehr hardwaregebundenen oder leistungskritischen Teilen, die in Assembly geschrieben wurden) - und Es ist definitiv möglich , stattdessen C ++ zu verwenden. Der Punkt ist, es gibt keine "Magie", Sprachen werden erstellt, um so mächtige Abstraktionen zu erstellen, und sobald Sie mit Hardware interagieren können, sind die Möglichkeiten nahezu unbegrenzt.
Matteo Italia

1
@hvd Ich denke, der ganze Konflikt in dieser Diskussion besteht darin, dass Sie (und andere) C als die dafür definierten Funktionen definieren. Tatsächlich fügen Compiler viel mehr hinzu als angegeben, sodass die Frage, was C ist, nicht trivial zu beantworten ist. Für mich ist das Besondere an einer Sprache (also das, was sie ist) die Meta-Methode, um den Programmfluss auszudrücken, und die Möglichkeiten zur Strukturierung. Die Elemente, die strukturiert sind, sind dafür nicht wichtig, da es nur ein schöner ASM-Code ist, der von Compilern nach Belieben hinzugefügt werden kann
LionC

43

C und C ++ haben zwei Eigenschaften, die all diese Erweiterbarkeit ermöglichen, von der das OP spricht.

  1. C und C ++ können auf den Speicher zugreifen
  2. C und C ++ können Assembler-Code für Anweisungen aufrufen, die nicht in der Sprache C oder C ++ vorliegen.

Im Kernel oder in einer Basisplattform im nicht geschützten Modus werden Peripheriegeräte wie die serielle Schnittstelle oder das Festplattenlaufwerk auf die gleiche Weise wie der Arbeitsspeicher der Speicherzuordnung zugeordnet. Der Speicher besteht aus einer Reihe von Schaltern. Durch Umlegen der Schalter des Peripheriegeräts (z. B. eines seriellen Anschlusses oder eines Festplattentreibers) kann Ihr Peripheriegerät nützliche Aufgaben ausführen.

Wenn in einem Betriebssystem im geschützten Modus vom Benutzerbereich aus auf den Kernel zugegriffen werden soll (z. B. beim Schreiben in das Dateisystem oder beim Zeichnen eines Pixels auf dem Bildschirm), muss ein Systemaufruf ausgeführt werden. C hat keine Anweisung zum Ausführen von Systemaufrufen, aber C kann Assembler-Code aufrufen, der den richtigen Systemaufruf auslösen kann. Auf diese Weise kann der C-Code mit dem Kernel kommunizieren.

Um die Programmierung einer bestimmten Plattform zu vereinfachen, werden Systemaufrufe in komplexere Funktionen eingeschlossen, die einige nützliche Funktionen innerhalb des eigenen Programms ausführen können. Es steht einem frei, die Systemaufrufe direkt (mit Assembler) aufzurufen, aber es ist wahrscheinlich einfacher, nur eine der von der Plattform bereitgestellten Wrapper-Funktionen zu verwenden.

Es gibt eine andere API-Ebene, die viel nützlicher ist als ein Systemaufruf. Nehmen Sie zum Beispiel Malloc. Dies ruft nicht nur das System dazu auf, große Speicherblöcke zu erhalten, sondern verwaltet diesen Speicher auch, indem das gesamte Buch über die Vorgänge geführt wird.

Win32-APIs umschließen einige Grafikfunktionen mit einem gemeinsamen Plattform-Widget-Set. Qt geht noch einen Schritt weiter, indem es die Win32- (oder X Windows-) API plattformübergreifend umschließt.

Obwohl ein C-Compiler C-Code in Maschinencode umwandelt und der Computer für die Verwendung von Maschinencode ausgelegt ist, sollten Sie grundsätzlich erwarten, dass C in der Lage ist, die Löwenfreigabe zu erreichen oder was ein Computer tun kann. Alles, was die Wrapper-Bibliotheken tun, ist das schwere Heben für Sie, damit Sie es nicht müssen.


Vorbehalt zu # 2: C und C ++ können nur Funktionen aufrufen, die einer "Aufrufkonvention" entsprechen, die der Compiler versteht und erwartet. (Assembly-Code kann jede beliebige oder gar keine Konvention verwenden. Daher kann der Code möglicherweise nicht direkt aufgerufen werden.) Glücklicherweise bietet jeder Compiler mit Selbstachtung eine integrierte Möglichkeit, die allgemeinen Konventionen der Plattform zu verwenden. (Mit Windows C-Compilern können Sie beispielsweise Funktionen verwenden, die die Konventionen "cdecl", "stdcall" oder "fastcall" verwenden.) Der Assemblycode muss jedoch eine Konvention verwenden, die der Compiler kennt, oder C und C ++ können ' nenne es nicht direkt.
CHao

2
Außerdem: speicherabgebildete E / A sind üblich, aber nicht die ganze Geschichte. PCs adressieren beispielsweise häufig serielle Ports, Festplatten usw. über die "E / A-Ports" des x86, einen ganz anderen Mechanismus. (Der Videopuffer ist normalerweise speicherabgebildet, aber Videomodi usw. werden normalerweise über E / A-Ports gesteuert.)
CHA

@cHao: Natürlich wird der klassische Ansatz mit INP und OUTP zugunsten von DMA verdrängt. Die PCI-Generation scheint mehr mit speicherabgebildeten Sonderfunktionsregistern zu tun zu haben, da es jetzt eine Möglichkeit gibt, Geräte automatisch nicht überlappenden Regionen zuzuordnen und sie von Treibern zu erkennen, und weniger mit E / A-Ports.
Ben Voigt

Moderne Peripheriegeräte verwenden DMA für die Massendatenübertragung, aber Sie programmieren den DMA-Controller weiterhin mit adressierbarem Speicher
Doron

@doron: Ähm, Sie programmieren den DMA-Controller über den E / A-Adressraum (nicht den Speicherplatz) auf einem PC, zumindest wenn Sie gesund sind. Moderne x86-CPUs ordnen Speicherzugriffe gerne neu, um die Leistung zu verbessern. Bei MMIO kann dies katastrophal sein. Sie müssen also darauf achten, dass diese Adressen nicht zwischengespeichert werden und die Anweisungen zur Serialisierung an den richtigen Stellen platziert werden. OTOH, der x86 selbst stellt sicher, dass das Lesen und Schreiben in den E / A-Bereich in der Programmreihenfolge erfolgt. Aus diesem Grund wird ein Großteil der wichtigen Dinge immer noch über den E / A-Bereich erledigt (auf den im Allgemeinen nicht über einen Zeiger zugegriffen werden kann) und wird es wahrscheinlich immer sein.
CHao

23

Sprachen (wie C ++ 11 ) sind Spezifikationen auf Papier, die normalerweise in Englisch geschrieben sind. Schauen Sie sich den neuesten C ++ 11-Entwurf an (oder kaufen Sie die teure endgültige Spezifikation bei Ihrem ISO-Anbieter).

Sie verwenden im Allgemeinen einen Computer mit einer Sprachimplementierung (Sie könnten ein C ++ - Programm im Prinzip ohne Computer ausführen, z. B. mit einer Gruppe menschlicher Sklaven, die es interpretieren; das wäre unethisch und ineffizient).

Ihre allgemeine C ++ - Implementierung funktioniert über einem bestimmten Betriebssystem und kommuniziert mit diesem (unter Verwendung eines implementierungsspezifischen Codes, häufig in einer Systembibliothek). Im Allgemeinen erfolgt diese Kommunikation über Systemaufrufe . Suchen Sie beispielsweise in syscalls (2) nach einer Liste der auf dem Linux-Kernel verfügbaren Systemaufrufe .

Aus Sicht der Anwendung ist ein Syscall eine elementare Maschinenanweisung wie SYSENTERauf x86-64 mit einigen Konventionen ( ABI ).

Auf meinem Linux-Desktop befinden sich die Qt-Bibliotheken über den X11- Client-Bibliotheken, die über X Windows-Protokolle mit dem X11-Server Xorg kommunizieren .

Verwenden Sie unter Linux lddIhre ausführbare Datei, um die (lange) Liste der Abhängigkeiten von Bibliotheken anzuzeigen. Verwenden pmapSie diese Option, um zu sehen, welche zur Laufzeit "geladen" werden. Übrigens, unter Linux verwendet Ihre Anwendung wahrscheinlich nur freie Software. Sie können den Quellcode (von Qt über Xlib, libc, ... den Kernel) studieren, um mehr darüber zu erfahren, was passiert


2
Als Referenz verkauft ANSI die C ++ 11-Spezifikation zum etwas weniger unverschämten Preis von 60 US-Dollar. (Früher war es die Hälfte, aber Inflation .: P) Es ist als INCITS / ISO / IEC 14882 gekennzeichnet, aber es ist mindestens die gleiche Basisspezifikation, die ISO bietet. Ich bin mir nicht sicher über Errata / TRs.
CHao

19

Ich denke, das Konzept, das Sie vermissen, sind Systemaufrufe . Jedes Betriebssystem bietet eine enorme Menge an Ressourcen und Funktionen, auf die Sie zugreifen können, um Betriebssysteme auf niedriger Ebene auszuführen. Selbst wenn Sie eine reguläre Bibliotheksfunktion aufrufen, wird wahrscheinlich hinter den Kulissen ein Systemaufruf ausgeführt.

Systemaufrufe sind eine einfache Möglichkeit, die Leistung des Betriebssystems zu nutzen. Sie können jedoch komplex und umständlich zu verwenden sein. Sie werden daher häufig in APIs "eingeschlossen", damit Sie sich nicht direkt mit ihnen befassen müssen. Aber fast alles, was Sie tun, was O / S-bezogene Ressourcen betrifft, verwendet Systemaufrufe, einschließlich Drucken, Netzwerk und Sockets usw.

Bei Windows ist die GUI von Microsoft Windows tatsächlich in den Kernel geschrieben. Daher gibt es Systemaufrufe zum Erstellen von Fenstern, Zeichnen von Grafiken usw. In anderen Betriebssystemen ist die GUI möglicherweise nicht Teil des Kernels. In diesem Fall Soweit ich weiß, würde es keine Systemaufrufe für GUI-bezogene Dinge geben, und Sie könnten nur auf einer noch niedrigeren Ebene mit den verfügbaren Grafiken und eingabebezogenen Aufrufen auf niedriger Ebene arbeiten.


3
Wichtig ist, dass diese Systemaufrufe keineswegs magisch sind. Sie werden vom Kernel bedient, der normalerweise in C (++) geschrieben ist. Darüber hinaus sind Systemaufrufe nicht einmal erforderlich. In einem rudimentären Betriebssystem ohne Speicherschutz können Fenster gezeichnet werden, indem Pixel direkt in den Hardware-Framebuffer eingefügt werden.
el.pescado

15

Gute Frage. Jeder neue C- oder C ++ - Entwickler hat dies im Sinn. Ich gehe für den Rest dieses Beitrags von einer Standard-x86-Maschine aus. Wenn Sie den Microsoft C ++ - Compiler verwenden, öffnen Sie Ihren Editor und geben Sie diesen ein (nennen Sie die Datei Test.c).

int main(int argc, char **argv)
{
   return 0
}

Und jetzt kompilieren Sie diese Datei (mit der Entwickler-Eingabeaufforderung) cl Test.c /FaTest.asm

Öffnen Sie nun Test.asm in Ihrem Notizblock. Was Sie sehen, ist der übersetzte Code - C / C ++ wird in Assembler übersetzt. Verstehst du den Hinweis?

_main   PROC
    push    ebp
    mov ebp, esp
    xor eax, eax
    pop ebp
    ret 0
_main   ENDP

C / C ++ - Programme können auf dem Metall ausgeführt werden. Dies bedeutet, dass sie Zugriff auf Hardware niedrigerer Ebene haben, wodurch es einfacher wird, die Funktionen der Hardware zu nutzen. Angenommen, ich werde eine C-Bibliothek getch () auf einem x86-Computer schreiben.

Abhängig vom Assembler würde ich etwas folgendermaßen eingeben:

_getch proc 
   xor AH, AH
   int 16h
   ;AL contains the keycode (AX is already there - so just return)
ret

Ich führe es mit einem Assembler aus und generiere eine .OBJ - Nenne es getch.obj.

Ich schreibe dann ein C-Programm (ich schließe nichts ein)

extern char getch();

void main(int, char **)
{
  getch();
}

Nennen Sie diese Datei nun GetChTest.c. Kompilieren Sie diese Datei, indem Sie getch.obj weitergeben. (Oder kompilieren Sie einzeln zu .obj und LINK GetChTest.Obj und getch.Obj zusammen, um GetChTest.exe zu erstellen.)

Führen Sie GetChTest.exe aus und Sie werden feststellen, dass es auf die Tastatureingabe wartet.

Bei der C / C ++ - Programmierung geht es nicht nur um Sprache. Um ein guter C / C ++ - Programmierer zu sein, müssen Sie ein gutes Verständnis für den Typ des Computers haben, auf dem er ausgeführt wird. Sie müssen wissen, wie die Speicherverwaltung gehandhabt wird, wie die Register aufgebaut sind usw. Sie benötigen möglicherweise nicht alle diese Informationen für die reguläre Programmierung - aber sie würden Ihnen immens helfen. Abgesehen von den grundlegenden Hardware-Kenntnissen ist es sicherlich hilfreich, wenn Sie verstehen, wie der Compiler funktioniert (dh wie er übersetzt wird) - was es Ihnen ermöglichen könnte, Ihren Code nach Bedarf zu optimieren. Es ist ein interessantes Paket!

Beide Sprachen unterstützen das Schlüsselwort __asm, dh Sie können auch Ihren Assembler-Code mischen. Wenn Sie C und C ++ lernen, werden Sie insgesamt zu einem runderen Programmierer.

Es ist nicht notwendig, immer eine Verbindung mit Assembler herzustellen. Ich hatte es erwähnt, weil ich dachte, das würde dir helfen, besser zu verstehen. Meistens verwenden die meisten dieser Bibliotheksaufrufe Systemaufrufe / APIs, die vom Betriebssystem bereitgestellt werden (das Betriebssystem übernimmt wiederum die Hardware-Interaktion).


10

Wie erhält C ++ ... plötzlich solche Funktionen durch Bibliotheken, die in C ++ selbst geschrieben wurden?

Die Verwendung anderer Bibliotheken ist nicht magisch. Bibliotheken sind einfache große Taschen mit Funktionen, die Sie aufrufen können.

Stellen Sie sich vor, Sie schreiben eine solche Funktion

void addExclamation(std::string &str)
{
    str.push_back('!');
}

Wenn Sie diese Datei jetzt einschließen, können Sie schreiben addExclamation(myVeryOwnString);. Nun könnten Sie sich fragen: "Wie hat C ++ plötzlich die Möglichkeit erhalten, einer Zeichenfolge Ausrufezeichen hinzuzufügen?" Die Antwort ist einfach: Sie haben eine Funktion dafür geschrieben und sie dann aufgerufen.

Um Ihre Frage zu beantworten, wie C ++ Funktionen zum Zeichnen von Fenstern durch in C ++ geschriebene Bibliotheken erhalten kann, ist die Antwort dieselbe. Jemand anderes hat dazu Funktionen geschrieben, diese dann kompiliert und Ihnen in Form einer Bibliothek übergeben.

Die anderen Fragen beantworten, wie die Fensterzeichnung tatsächlich funktioniert, aber Sie klangen verwirrt darüber, wie Bibliotheken funktionieren, und ich wollte den grundlegendsten Teil Ihrer Frage ansprechen.


8

Der Schlüssel ist die Möglichkeit des Betriebssystems, eine API verfügbar zu machen, und eine detaillierte Beschreibung, wie diese API verwendet werden soll.

Das Betriebssystem bietet eine Reihe von APIs mit Aufrufkonventionen. Die Aufrufkonvention definiert, wie ein Parameter in die API eingegeben wird, wie Ergebnisse zurückgegeben werden und wie der eigentliche Aufruf ausgeführt wird.

Betriebssysteme und die Compiler, die Code für sie erstellen, spielen gut zusammen, sodass Sie normalerweise nicht darüber nachdenken müssen, sondern sie nur verwenden müssen.


7

Zum Erstellen von Fenstern ist keine spezielle Syntax erforderlich. Erforderlich ist lediglich, dass das Betriebssystem eine API zum Erstellen von Fenstern bereitstellt. Eine solche API besteht aus einfachen Funktionsaufrufen, für die C ++ Syntax bereitstellt.

Darüber hinaus sind C und C ++ sogenannte Systemprogrammiersprachen und können auf beliebige Zeiger zugreifen (die möglicherweise von der Hardware einem bestimmten Gerät zugeordnet werden). Darüber hinaus ist es auch recht einfach, in Assembly definierte Funktionen aufzurufen, die den gesamten Funktionsumfang des Prozessors ermöglichen. Daher ist es möglich, ein Betriebssystem selbst mit C oder C ++ und einer kleinen Menge an Assembly zu schreiben.

Es sollte auch erwähnt werden, dass Qt ein schlechtes Beispiel ist, da es einen sogenannten Meta-Compiler verwendet um die C ++ - Syntax zu erweitern. Dies hängt jedoch nicht mit der Fähigkeit zusammen, die vom Betriebssystem bereitgestellten APIs aufzurufen, um tatsächlich Fenster zu zeichnen oder zu erstellen.


7

Erstens gibt es ein kleines Missverständnis, denke ich

Wie funktioniert C ++, das zuvor keine Syntax hatte, die das Betriebssystem nach einem Fenster oder einer Möglichkeit zur Kommunikation über Netzwerke fragen konnte?

Es gibt keine Syntax für Betriebssystemoperationen. Es ist die Frage der Semantik .

Erhalten Sie solche Funktionen plötzlich durch Bibliotheken, die in C ++ selbst geschrieben wurden

Nun, das Betriebssystem ist meistens in C geschrieben. Sie können gemeinsam genutzte Bibliotheken (also DLL) verwenden, um den externen Code aufzurufen. Darüber hinaus kann der Betriebssystemcode Systemroutinen auf Systemaufrufen * oder Interrupts registrieren, die Sie mithilfe von Assembly aufrufen können . Da gemeinsam genutzte Bibliotheken häufig nur Systemaufrufe für Sie ausführen, werden Sie von der Inline-Assembly verschont.

Hier ist das nette Tutorial dazu: http://www.win.tue.nl/~aeb/linux/lk/lk-4.html
Es ist für Linux, aber die Prinzipien sind dieselben.

Wie arbeitet das Betriebssystem mit Grafikkarten, Netzwerkkarten usw.? Es ist ein sehr breites Thema, aber meistens müssen Sie auf Interrupts, Ports zugreifen oder Daten in einen speziellen Speicherbereich schreiben. Da diese Vorgänge geschützt sind, müssen Sie sie trotzdem über das Betriebssystem aufrufen.


7

In dem Versuch, andere Antworten etwas anders zu betrachten, werde ich so antworten.

(Haftungsausschluss: Ich vereinfache die Dinge leicht, die Situation, die ich gebe, ist rein hypothetisch und dient dazu, Konzepte zu demonstrieren, anstatt zu 100% dem Leben treu zu bleiben.)

Stellen Sie sich die Dinge aus einer anderen Perspektive vor. Stellen Sie sich vor, Sie haben gerade ein einfaches Betriebssystem mit grundlegenden Funktionen für Threading, Fensterung und Speicherverwaltung geschrieben. Sie möchten eine C ++ - Bibliothek implementieren, mit der Benutzer in C ++ programmieren und beispielsweise Fenster erstellen, auf Fenster zeichnen usw. Die Frage ist, wie dies zu tun ist.

Da C ++ zu Maschinencode kompiliert wird, müssen Sie zunächst eine Möglichkeit definieren, Maschinencode für die Schnittstelle mit C ++ zu verwenden. Hier kommen Funktionen ins Spiel, Funktionen akzeptieren Argumente und geben Rückgabewerte an. Sie bieten somit eine Standardmethode für die Übertragung von Daten zwischen verschiedenen Codeabschnitten. Sie tun dies, indem sie eine sogenannte Rufkonvention etablieren .

Eine aufrufende Konvention gibt an, wo und wie Argumente im Speicher abgelegt werden sollen, damit eine Funktion sie finden kann, wenn sie ausgeführt wird. Wenn eine Funktion aufgerufen wird, legt die aufrufende Funktion die Argumente im Speicher ab und fordert die CPU auf, zur anderen Funktion zu springen, wo sie das tut, was sie tut, bevor sie dorthin zurückspringt, von wo sie aufgerufen wurde. Dies bedeutet, dass der aufgerufene Code absolut alles sein kann und sich nicht ändert, wie die Funktion aufgerufen wird. In diesem Fall wäre der Code hinter der Funktion jedoch für das Betriebssystem relevant und würde mit dem internen Status des Betriebssystems arbeiten.

Viele Monate später haben Sie alle Funktionen Ihres Betriebssystems erledigt. Ihr Benutzer kann Funktionen aufrufen, um Fenster zu erstellen und darauf zu zeichnen. Er kann Threads und alle möglichen wunderbaren Dinge erstellen. Hier ist das Problem: Die Funktionen Ihres Betriebssystems unterscheiden sich von den Funktionen von Linux oder Windows. Sie müssen dem Benutzer also eine Standardschnittstelle geben, damit er tragbaren Code schreiben kann. Hier kommt QT ins Spiel.

Wie Sie mit ziemlicher Sicherheit wissen, verfügt QT über eine Vielzahl nützlicher Klassen und Funktionen, mit denen Sie die Aufgaben von Betriebssystemen ausführen können, die jedoch unabhängig vom zugrunde liegenden Betriebssystem erscheinen. Dies funktioniert so, dass QT Klassen und Funktionen bereitstellt, die in ihrer Darstellung für den Benutzer einheitlich sind, der Code hinter den Funktionen jedoch für jedes Betriebssystem unterschiedlich ist. Zum Beispiel würde QTs QApplication :: closeAllWindows () tatsächlich die spezielle Fensterschließfunktion jedes Betriebssystems aufrufen, abhängig von der verwendeten Version. In Windows würde es höchstwahrscheinlich CloseWindow (hwnd) aufrufen, während es auf einem Betriebssystem, das das X Window System verwendet, möglicherweise XDestroyWindow (Anzeige, Fenster) aufrufen würde.

Es ist offensichtlich, dass ein Betriebssystem viele Schichten hat, die alle über Schnittstellen vieler Arten interagieren müssen. Es gibt viele Aspekte, die ich noch nicht einmal angesprochen habe, aber es würde sehr lange dauern, sie alle zu erklären. Wenn Sie sich weiter für das Innenleben von Betriebssystemen interessieren, empfehle ich Ihnen, das OS-Entwickler-Wiki zu lesen .

Bedenken Sie jedoch, dass viele Betriebssysteme Schnittstellen für C / C ++ verfügbar machen, weil sie zu Maschinencode kompiliert werden, Assembleranweisungen mit ihrem eigenen Code gemischt werden können und dem Programmierer ein hohes Maß an Freiheit bieten.

Auch hier ist viel los. Ich möchte weiter erklären, wie Bibliotheken wie .so und .dll-Dateien nicht in C / C ++ geschrieben werden müssen und in Assembler oder anderen Sprachen geschrieben werden können, aber ich habe das Gefühl, dass ich es auch tun könnte, wenn ich weitere hinzufüge schreibe einen ganzen Artikel, und so gerne ich das tun würde, ich habe keine Seite, auf der ich ihn hosten könnte.


6

Wenn Sie versuchen, etwas auf dem Bildschirm zu zeichnen, ruft Ihr Code einen anderen Code auf, der einen anderen Code (usw.) aufruft, bis schließlich ein "Systemaufruf" erfolgt, eine spezielle Anweisung, die die CPU ausführen kann. Diese Anweisungen können entweder in Assembly oder in C ++ geschrieben werden, wenn der Compiler ihre "Intrinsics" unterstützt (Funktionen, die der Compiler "speziell" verarbeitet, indem er sie in speziellen Code konvertiert, den die CPU verstehen kann). Ihre Aufgabe ist es, das Betriebssystem anzuweisen, etwas zu tun.

Wenn ein Systemaufruf erfolgt, wird eine Funktion aufgerufen, die eine andere Funktion (usw.) aufruft, bis der Anzeigetreiber schließlich aufgefordert wird, etwas auf dem Bildschirm zu zeichnen. Zu diesem Zeitpunkt betrachtet der Anzeigetreiber einen bestimmten Bereich im physischen Speicher, der eigentlich kein Speicher ist, sondern einen Adressbereich, in den geschrieben werden kann, als wäre er Speicher. Statt jedoch zu diesem Adressbereich Schreiben bewirkt , dass die Grafik - Hardware zum Abfangen des Speicher - Schreib und etwas auf dem Bildschirm zeichnen.
Das Schreiben in diese Region des Gedächtnisses ist etwas, das es könnte. in C codiert werden ++, da auf der Software - Seite ist es nur ein ganz normaler Speicherzugriff. Es ist nur so, dass die Hardware anders damit umgeht.
Das ist also eine wirklich grundlegende Erklärung, wie es funktionieren kann.


4
Afaik ein Systemaufruf ist nicht wirklich eine CPU-Anweisung und hat nichts mit Intrinsics zu tun. Es ist eher eine Funktion des Betriebssystemkerns, der mit den Geräten kommuniziert.
MatthiasB

3
@MatthiasB: Nun, Sie liegen falsch, da syscall(und sein Cousin sysenter) in der Tat eine CPU-Anweisung ist.
user541686

2
Dies war nur ein Hinweis, um Ihre Antwort zu verbessern, da es für mich nicht klar war. Sehen Sie es nicht als persönlichen Angriff oder so.
MatthiasB

1
@MatthiasB: Ich nehme es nicht persönlich. Ich sage, ich weiß bereits, dass die Antwort nicht 100% ig korrekt ist, aber ich denke, es ist eine gute Vereinfachung, um das OP zu beantworten. Wenn Sie also tatsächlich wissen, wie Sie eine bessere Antwort schreiben können, schreiben Sie bitte entweder Ihre eigene antworte oder nimm dir die Zeit, meine zu bearbeiten. Ich habe wirklich nichts hinzuzufügen, was ich für wert halte. Wenn Sie also etwas Besseres auf dieser Seite sehen möchten, müssen Sie sich selbst anstrengen.
user541686

3
Systemaufrufe erfolgen über Software-Interrupts. Anweisungen wie sysentersind optimierte Aufrufpfade, da die von Interrupt-Handlern verwendete Kontextumschaltung nicht so schnell war, wie alle es wünschten, aber im Grunde ist es immer noch ein durch Software generierter Interrupt, während er durch Vektorisierung an einen vom OS-Kernel installierten Handler behandelt wird. Ein Teil des Kontextumschaltprozesses, den IS durchführt, sysenterbesteht darin, die Modusbits im Prozessor so zu ändern, dass Ring 0 gesetzt wird - vollständiger Zugriff auf alle privilegierten Befehle, Register sowie Speicher- und E / A-Bereiche.
Ben Voigt

4

Ihr C ++ - Programm verwendet die Qt-Bibliothek (ebenfalls in C ++ codiert). Die Qt-Bibliothek verwendet die Windows- Funktion CreateWindowEx (die in C in kernel32.dll codiert wurde). Oder unter Linux wird möglicherweise Xlib verwendet (ebenfalls in C codiert), aber es können auch die Rohbytes gesendet werden, die im X-Protokoll " Bitte erstellen Sie ein Fenster für mich " bedeuten " .

Bezogen auf Ihren Catch-22 Frage steht der historische Hinweis, dass „der erste C ++ - Compiler in C ++ geschrieben wurde“, obwohl es sich tatsächlich um einen C-Compiler mit einigen C ++ - Begriffen handelte, der ausreichte, um die erste Version zu kompilieren, die sich dann selbst kompilieren konnte .

In ähnlicher Weise verwendet der GCC-Compiler GCC-Erweiterungen: Er wird zuerst zu einer Version kompiliert und dann zum erneuten Kompilieren verwendet. (GCC Build-Anweisungen)


2

Wie ich die Frage sehe, ist dies eigentlich eine Compiler-Frage.

Betrachten Sie es so, Sie schreiben einen Code in Assembly (Sie können ihn in jeder Sprache ausführen), der Ihre neu geschriebene Sprache, die Sie Z ++ aufrufen möchten, in Assembly übersetzt. Der Einfachheit halber können Sie sie als Compiler bezeichnen (es ist ein Compiler). .

Jetzt geben Sie diesem Compiler einige grundlegende Funktionen, damit Sie int, string, arrays usw. schreiben können. Tatsächlich geben Sie ihm genügend Fähigkeiten, damit Sie den Compiler selbst in Z ++ schreiben können. und jetzt haben Sie einen Compiler für Z ++ in Z ++ geschrieben, ziemlich ordentlich richtig.

Was noch cooler ist, ist, dass Sie diesem Compiler jetzt Fähigkeiten hinzufügen können, indem Sie die bereits vorhandenen Fähigkeiten verwenden, wodurch die Z ++ - Sprache durch die Verwendung der vorherigen Funktionen um neue Funktionen erweitert wird

Wenn Sie beispielsweise genügend Code schreiben, um ein Pixel in einer beliebigen Farbe zu zeichnen, können Sie es mit Z ++ erweitern, um alles zu zeichnen, was Sie möchten.


0

Die Hardware ermöglicht dies. Sie können sich den Grafikspeicher als großes Array vorstellen (bestehend aus jedem Pixel auf dem Bildschirm). Um auf den Bildschirm zu zeichnen, können Sie mit C ++ oder einer beliebigen Sprache, die den direkten Zugriff auf diesen Speicher ermöglicht, in diesen Speicher schreiben. Dieser Speicher ist zufällig für die Grafikkarte zugänglich oder befindet sich auf dieser.

Auf modernen Systemen, die direkt auf den Grafikspeicher zugreifen, muss aufgrund verschiedener Einschränkungen ein Treiber geschrieben werden, sodass Sie indirekte Mittel verwenden. Bibliotheken, die ein Fenster erstellen (eigentlich nur ein Bild wie jedes andere Bild) und dieses Bild dann in den Grafikspeicher schreiben, den die GPU dann auf dem Bildschirm anzeigt. Der Sprache muss nichts hinzugefügt werden, außer der Fähigkeit, in bestimmte Speicherorte zu schreiben, wofür Zeiger gedacht sind.


Der Punkt, den ich ansprechen wollte, ist, dass eine Sprache sich nicht in dem Sinne "erweitern" muss, dass eine neue Version der Sprache neu geschrieben werden muss, und dass sie nicht wirklich kreisförmig ist, um irgendetwas Interessantes an einem Programm zu tun muss mit der Hardware verbunden sein.
John
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.