Alle modernen CPUs haben die Fähigkeit, den gerade ausgeführten Maschinenbefehl zu unterbrechen . Sie speichern genügend Status (normalerweise, aber nicht immer, auf dem Stapel), um die Ausführung später wieder aufzunehmen , als wäre nichts geschehen (normalerweise wird der unterbrochene Befehl von Grund auf neu gestartet). Dann starten sie die Ausführung eines Interrupt-Handlers , der nur aus Maschinencode besteht, aber an einer bestimmten Stelle platziert ist, damit die CPU weiß, wo er sich im Voraus befindet. Interrupt-Handler sind immer Teil des Kernels des Betriebssystems: Die Komponente, die mit den größten Berechtigungen ausgeführt wird und für die Überwachung der Ausführung aller anderen Komponenten verantwortlich ist. 1,2
Interrupts können synchron sein , was bedeutet, dass sie von der CPU selbst als direkte Antwort auf etwas ausgelöst werden, das der gerade ausgeführte Befehl ausgeführt hat, oder asynchron , was bedeutet, dass sie zu einem unvorhersehbaren Zeitpunkt aufgrund eines externen Ereignisses auftreten, wie z. B. Daten, die im Netzwerk ankommen Hafen. Einige Leute reservieren den Begriff "Interrupt" für asynchrone Interrupts und nennen synchrone Interrupts stattdessen "Traps", "Fehler" oder "Ausnahmen", aber diese Wörter haben alle andere Bedeutungen, also bleibe ich bei "synchronem Interrupt".
Heutzutage kennen die meisten modernen Betriebssysteme Prozesse . Im Grunde ist dies ein Mechanismus, mit dem der Computer mehr als ein Programm gleichzeitig ausführen kann, aber es ist auch ein wesentlicher Aspekt der Konfiguration des Speicherschutzes durch Betriebssysteme , der ein Merkmal der meisten (aber leider noch nicht alle ) modernen CPUs. Es geht zusammen mit dem virtuellen SpeicherDies ist die Möglichkeit, die Zuordnung zwischen Speicheradressen und tatsächlichen Speicherorten im RAM zu ändern. Der Speicherschutz ermöglicht es dem Betriebssystem, jedem Prozess einen eigenen privaten RAM-Block zuzuweisen, auf den nur er zugreifen kann. Außerdem kann das Betriebssystem (das im Auftrag eines bestimmten Prozesses handelt) RAM-Bereiche als schreibgeschützt, ausführbar, für eine Gruppe kooperierender Prozesse freigegeben usw. kennzeichnen. Außerdem wird ein Teil des Arbeitsspeichers vorhanden sein, auf den nur der Zugriff möglich ist Kernel. 3
Solange jeder Prozess nur so auf den Speicher zugreift, wie es die CPU zulässt, ist der Speicherschutz unsichtbar. Wenn ein Prozess gegen die Regeln verstößt, generiert die CPU einen synchronen Interrupt und fordert den Kernel auf, die Dinge zu klären. Es kommt regelmäßig vor, dass der Prozess nicht wirklich gegen die Regeln verstößt. Nur der Kernel muss etwas arbeiten, bevor der Prozess fortgesetzt werden kann. Wenn beispielsweise eine Seite des Arbeitsspeichers eines Prozesses in die Auslagerungsdatei "entfernt" werden muss, um Speicherplatz im RAM für etwas anderes freizugeben, markiert der Kernel diese Seite als unzugänglich. Wenn der Prozess das nächste Mal versucht, ihn zu verwenden, generiert die CPU einen Speicherschutz-Interrupt. Der Kernel ruft die Seite aus dem Auslagerungsmodus ab, legt sie wieder an ihrem ursprünglichen Ort ab, markiert sie als wieder zugänglich und setzt die Ausführung fort.
Angenommen, der Prozess hat wirklich gegen die Regeln verstoßen. Es wurde versucht, auf eine Seite zuzugreifen, der noch kein RAM zugeordnet war, oder es wurde versucht, eine Seite auszuführen, die als nicht mit Maschinencode gekennzeichnet ist, oder was auch immer. Die Betriebssystemfamilie, die allgemein als "Unix" bekannt ist, verwendet alle Signale , um mit dieser Situation umzugehen. 4 Signale ähneln Interrupts, werden jedoch vom Kernel generiert und von Prozessen abgefangen, anstatt von der Hardware generiert und vom Kernel abgefangen zu werden. Prozesse können Signalhandler definierenin ihrem eigenen Code, und teilen Sie dem Kernel mit, wo sie sich befinden. Diese Signalhandler werden dann ausgeführt und unterbrechen bei Bedarf den normalen Steuerungsfluss. Alle Signale haben eine Nummer und zwei Namen, von denen einer ein kryptisches Akronym und der andere eine etwas weniger kryptische Phrase ist. Das Signal, das generiert wird, wenn ein Prozess die Speicherschutzregeln verletzt, ist (gemäß Konvention) Nummer 11, und seine Namen sind SIGSEGV
und "Segmentierungsfehler". 5,6
Ein wichtiger Unterschied zwischen Signalen und Interrupts besteht darin, dass es für jedes Signal ein Standardverhalten gibt . Wenn das Betriebssystem keine Handler für alle Interrupts definiert, ist dies ein Fehler im Betriebssystem, und der gesamte Computer stürzt ab, wenn die CPU versucht, einen fehlenden Handler aufzurufen. Prozesse sind jedoch nicht verpflichtet, Signalhandler für alle Signale zu definieren. Wenn der Kernel ein Signal für einen Prozess generiert und dieses Signal auf seinem Standardverhalten belassen wurde, wird der Kernel einfach weitermachen und alles tun, was der Standard ist, und den Prozess nicht stören. Das Standardverhalten der meisten Signale ist entweder "nichts tun" oder "diesen Prozess beenden und möglicherweise auch einen Core-Dump erzeugen". SIGSEGV
ist einer der letzteren.
Um es noch einmal zusammenzufassen, wir haben einen Prozess, der die Speicherschutzregeln gebrochen hat. Die CPU hat den Prozess angehalten und einen synchronen Interrupt generiert. Der Kernel hat das unterbrochen und ein SIGSEGV
Signal für den Prozess generiert . Angenommen, der Prozess hat keinen Signal-Handler für eingerichtet SIGSEGV
, sodass der Kernel das Standardverhalten ausführt, das darin besteht, den Prozess zu beenden. Dies hat die gleichen Auswirkungen wie der _exit
Systemaufruf: Geöffnete Dateien werden geschlossen, Speicher wird freigegeben usw.
Bis zu diesem Zeitpunkt wurden keine Nachrichten ausgedruckt, die ein Mensch sehen kann, und die Shell (oder allgemein der übergeordnete Prozess des gerade abgebrochenen Prozesses) war überhaupt nicht beteiligt. SIGSEGV
Geht zu dem Prozess, der die Regeln verletzt hat, nicht zu seinem übergeordneten Element. Der nächste Schritt in der Sequenz besteht jedoch darin, dem übergeordneten Prozess mitzuteilen, dass sein untergeordnetes Element beendet wurde. Dies kann auf verschiedene Weise geschehen, von denen die einfachste ist , wenn die Eltern bereits für diese Meldung warten, eines der Verwendung von wait
Systemaufrufen ( wait
, waitpid
, wait4
, usw.). In diesem Fall veranlasst der Kernel lediglich die Rückgabe dieses Systemaufrufs und versieht den übergeordneten Prozess mit einer Codenummer, die als Exit-Status bezeichnet wird. 7 Der Beendigungsstatus informiert den Elternteil darüber, warum der Kindprozess beendet wurde. In diesem Fall wird festgestellt, dass das Kind aufgrund des Standardverhaltens eines SIGSEGV
Signals beendet wurde.
Der übergeordnete Prozess kann dann das Ereignis einem Menschen melden, indem er eine Nachricht druckt; Shell-Programme tun dies fast immer. Sie enthalten crsh
keinen Code, um das zu tun, aber es passiert trotzdem, weil die C-Bibliotheksroutine system
eine voll funktionsfähige Shell /bin/sh
"unter der Haube" ausführt. crsh
ist der Großelternteil in diesem Szenario; Die Benachrichtigung über den übergeordneten Prozess wird durch gekennzeichnet /bin/sh
, wodurch die übliche Nachricht gedruckt wird. Dann wird es /bin/sh
selbst beendet, da es nichts mehr zu tun hat, und die Implementierung der C-Bibliothek von system
empfängt diese Beendigungsbenachrichtigung. Sie können diese Beendigungsbenachrichtigung in Ihrem Code sehen, indem Sie den Rückgabewert von überprüfensystem
; Es wird Ihnen jedoch nicht mitgeteilt, dass der Enkelprozess aufgrund eines Segfault-Vorgangs gestorben ist, da dieser durch den Zwischen-Shell-Prozess verbraucht wurde.
Fußnoten
Einige Betriebssysteme implementieren keine Gerätetreiber als Teil des Kernels. Alle Interrupt-Handler müssen jedoch weiterhin Teil des Kernels sein, ebenso wie der Code, der den Speicherschutz konfiguriert, da die Hardware nur dem Kernel gestattet, diese Aufgaben auszuführen .
Es kann ein Programm geben, das als "Hypervisor" oder "Virtual Machine Manager" bezeichnet wird und noch privilegierter ist als der Kernel. Für diese Antwort kann es jedoch als Teil der Hardware betrachtet werden .
Der Kernel ist ein Programm , aber kein Prozess. es ist eher wie eine Bibliothek. Alle Prozesse führen von Zeit zu Zeit zusätzlich zu ihrem eigenen Code Teile des Kernel-Codes aus. Es kann eine Reihe von "Kernel-Threads" geben, die nur Kernel-Code ausführen, die uns hier jedoch nicht betreffen.
Das einzige Betriebssystem, mit dem Sie wahrscheinlich mehr zu tun haben, das nicht als Implementierung von Unix angesehen werden kann, ist natürlich Windows. In dieser Situation werden keine Signale verwendet. ( In der Tat ist es nicht haben Signale, unter Windows die <signal.h>
Schnittstelle vollständig durch die C - Bibliothek gefälscht ist.) Es nutzt etwas „genannt strukturierte Ausnahmebehandlung “ statt.
Einige Speicherschutzverletzungen erzeugen SIGBUS
("Busfehler") statt SIGSEGV
. Die Linie zwischen den beiden ist unterbestimmt und variiert von System zu System. Wenn Sie ein Programm geschrieben haben, das einen Handler für definiert SIGSEGV
, ist es wahrscheinlich eine gute Idee, denselben Handler für zu definieren SIGBUS
.
"Segmentierungsfehler" war der Name des Interrupts, der bei Verstößen gegen den Speicherschutz von einem der Computer generiert wurde, auf denen das ursprüngliche Unix ausgeführt wurde , wahrscheinlich der PDP-11 . „ Segmentierung “ ist ein Typ von Speicherschutz, aber heutzutage der Begriff „Segmentierung Fehler “ bezieht sich allgemein auf jede Art von Speicherschutzverletzung.
Alle anderen Möglichkeiten, wie der übergeordnete Prozess benachrichtigt werden kann, wenn ein Kind beendet wurde, führen dazu, dass der übergeordnete Prozess anruft wait
und einen Beendigungsstatus erhält. Es ist nur so, dass zuerst etwas anderes passiert.
crsh
ist eine großartige Idee für diese Art des Experimentierens. Vielen Dank, dass Sie uns alle darüber und über die dahinter stehende Idee informiert haben.