Dieses Handbuch wird mit freundlicher Genehmigung von Sebastian Hetze auf den Servern der Linux Information Systems AG gehosted.

next up previous contents index
Next: Betrachtungen über die Zeit Up: Systemverwaltung Previous: Laufzeitmodule für den Kernel

Subsections


Prozeßordnung

Ein lauffähiges Programm, das sich im Arbeitsspeicher unter der Kontrolle des Betriebssystems befindet, wird als Prozeß bezeichnet. Zu einem Prozeß gehören mehr als nur die binären Daten aus der Programmdatei, die vom Prozessor abgearbeitet werden. Zu einem Prozeß gehören unter anderem auch

Entstehung der Prozesse: fork und exec

Prozesse entstehen nicht spontan, sondern sie werden von bereits existierenden Prozessen erzeugt. Dazu muß ein laufender Prozeß die beiden Systemaufrufe fork und exec benutzen. Nach einem fork erzeugt der Kernel eine genaue Kopie des laufenden Prozesses. Die Kopie erhält einen neuen Eintrag in der Prozeßtabelle  mit einer neuen Prozeßnummer und einen eigenen virtuellen Adressraum. Der erzeugende Prozeß wird als Elternprozeß bezeichnet, der neue Zweig als das Kind. Als Kopie seines Erzeugers ``erbt'' ein Kindprozeß  die Prozeßumgebung und die offenen Dateien von seinem Elternprozeß.

Eltern und Kind arbeiten unabhängig voneinander weiter. Zunächst ist der Programmtext der beiden Zweige identisch, der Programmablauf wird aber in der Regel nach dem fork unterschiedlich weitergeführt. Durch den Systemcall exec kann das Kind nun den Kernel veranlassen, eine neue Programmdatei von der Festplatte zu lesen und damit den virtuellen Speicherbereich des Kindes zu überschreiben. Auf diese Weise kommt das neue Programm zur Ausführung.

Bei der Verdrängung des Programmtextes im virtuellen Speicherbereich des Kindes bleibt die Prozeßnummer und die Prozeßumgebung erhalten. Auch das Verwandschaftsverhältnis zum Elternprozeß mit allen daraus resultierenden Rechten bleibt bestehen.

Copy on Write und Demand Loading

  

Wenn der Kernel nach dem Systemcall fork einen Prozeß kopiert, wird der Speicherinhalt nicht wirklich dupliziert. Die virtuelle Speicherverwaltung des Kernels benutzt die gleichen physikalischen Speicherbereiche für Eltern und Kind, solange keiner der beiden Prozesse deren Inhalt verändert hat. Erst wenn in eine Speicherseite geschrieben wird, erzeugt der Kernel die Kopie und tauscht die veränderte Seite in dem virtuellen Arbeitsspeicher des schreibenden Prozesses aus. Diese sehr schnelle und sparsame Art der Speicherverwaltung wird als copy on write bezeichnet.

Beim Laden neuer Programmdateien von der Festplatte geht das Betriebssystem ähnlich sparsam vor, indem es nur die Teile des Programmtexts in den Arbeitsspeicher liest, die unmittelbar für den Programmablauf gebraucht werden. Dieses ``demand loading'' verkürzt die Zeit von der Eingabe eines Kommandos bis zu seiner Ausführung erheblich.

Prozeßgruppen, Sessions und kontrollierende Terminals

Nicht jedes Kommando, das Sie der Shell zur Bearbeitung übergeben, besteht aus einem einfachen Programmaufruf. Bei einer Pipeline müssen mehrere Programme geladen und bei der Ausführung miteinander verbunden werden. Die Shell erzeugt für jedes Programm mit der oben beschriebenen Methode durch fork und exec einen eigenen Prozeß. Um die Prozesse der Pipeline gemeinsam kontrollieren zu können, faßt die Shell die Prozesse in einer Prozeßgruppe zusammen. Die ID der Prozeßgruppe ist mit der Prozeß-ID des ersten Prozesses der Pipeline identisch, dieser Prozeß ist der Führer der Gruppe.

  Eine Session umfasst eine oder mehr Prozeßgruppen. Für jede Loginshell wird vom Programm login automatisch eine neue Session eröffnet. Die Session trägt die gleiche ID wie der Prozeß der Loginshell. Die Loginshell ist damit Führer einer Session.

Die Eröffnung von Sessions und Prozeßgruppen ist nicht auf die angeführten Beispiele mit den Shells beschränkt. Jeder neue Prozeß kann eine neue Prozeßgruppe oder Session erzeugen.

  Solange ein Prozeß keine neue Session eröffnet, ist er in der gleichen Session wie sein Elternprozeß. Der führende Prozeß einer Session kann mit einem Terminal verbunden sein, das dann als das kontrollierende Terminal bezeichnet wird. Jedes Terminalgerät kann nur eine Session kontrollieren. Von den Prozeßgruppen einer Session kann nur eine mit dem kontrollierenden Terminal verbunden sein. Diese Prozeßgruppe arbeitet im Vordergrund, alle andern Prozeßgruppen arbeiten im Hintergrund.  Die Prozesse können nur auf das kontrollierende Terminal ihrer Session zugreifen.

Prozeßtabelle und Programmumgebung

 

Für jeden Prozeß verwaltet Linux das umfangreiche Task-Struct, eine Datenstruktur, in der alle wichtigen Informationen über den Prozeß verzeichnet sind. Ein großer Teil dieser Daten wird von dem Programm ps aufbereitet und übersichtlich angezeigt.

Unter anderem sind dort die Prozeßnummer des Prozesses selbst und seiner Eltern, die realen und effektiven IDs von Eigentümer und Benutzergruppe des Prozesses, die Ressourcen-Limits und die verbrauchten Ressourcen verzeichnet.

Abstürzende Programme und hängende Prozesse

 

Es gibt kein fehlerfreies Programm und überall Benutzer, die einem sonst sehr zuverlässigen Programm ganz erstaunliches und unerklärliches Verhalten entlocken, und es gibt das Naturgesetz von Murphy ...

Deshalb kommt es mit Sicherheit bei jedem Rechner irgendwann einmal zu einem Programmabsturz. Weil aber jedes Programm in einem eigenen, vom übrigen System hermetisch abgeschirmten virtuellen Adressraum arbeitet, hat ein Programmabsturz keine unkontrollierbaren Auswirkungen auf das gesamte System.[*] Beim Versuch, auf den Speicherbereich eines anderen Programms zuzugreifen, wird jeder Porzeß sofort mit dem SIGSEGV Signal abgebrochen. Der Prozeß wird dadurch aus dem Arbeitsspeicher gelöscht und kann keinen Schaden mehr anrichten. 

Die einzige wirklich kritische Situation kann entstehen, wenn ein Prozeß absichtlich oder aus Versehen den gesamten Arbeitsspeicher verbraucht. Dieser Gefahr kann durch Beschränkung der Ressourcen für User und Prozesse durch ulimit begegnet werden. 

Es kann auch passieren, daß sich ein Programm ``aufhängt''. So ein Prozeß stürzt nicht wirklich ab und bleibt deshalb lauffähig im Arbeitsspeicher. Wegen eines internen Fehlers, beispielsweise einer Endlosschleife, hat sich das Programm jedoch in einem sinnlosen Zweig des Programmablaufs verfangen und kommt nicht zu seinem vorgesehenen Abschluß.

  In solch einem Fall kann ein Prozeß meistens durch ein Unterbrechungssignal von der Tastatur angehalten werden. Durch die Tastenkombination CONTROL-C (^C) wird der mit dem kontrollierenden Terminal verbundenen Vordergrundprozeßgruppe das Signal SIGINT geschickt. Durch die Tastenkombination CONTROL-Z (^Z) erhält die gleiche Gruppe das Signal SIGSTP.

Prozesse durch Signale beenden

   Wenn ein Prozeß die Verbindung zu einem kontrollierenden Terminal aufgegeben oder verloren hat, kann er durch die Tastatursignale nicht mehr beeinflußt werden. Wenn nicht durch die Begrenzung einer Ressource, beispielsweise der CPU-Zeit, der Prozeß vom Kernel suspendiert wird, läßt sich das Programm nur noch durch ein extern generiertes Signal beenden.

Das Programm kill generiert solch ein Signal und sendet es an den gewünschten Prozeß. Der Kernel leitet das Signal nur weiter, wenn es von einem berechtigten User abgeschickt wurde. Zum Senden von Signalen sind außer der Systemverwalterin mit Rootprivilegien der reale und der effektive Eigentümer des Prozesses befugt.

Eine Liste aller Signale, die mit kill generiert werden können, erhalten Sie durch das Kommando ``kill -l'' (Option list). Die Signale, die normalerweise zur Beendigung eines Prozesses führen, sind SIGTERM (15), SIGHUP (1) und SIGINT (2). Diese Signale können von dem Prozeß, der sie empfängt, abgefangen und in einer Fehlerroutine behandelt werden. Auf diese Weise kann beispielsweise ein Editor die geöffneten Dateien noch ordnungsgemäß schließen, bevor er als Reaktion auf das Signal terminiert. Das Signal SIGKILL kann nicht abgefangen werden und führt zur sofortigen Beendigung eines Prozesses.

Das kill-Kommando benötigt auf der Kommandozeile eine Identifikation des Prozesses, an den das Signal geschickt werden soll. Dazu kann jedes kill-Kommando die Prozeßnummer dieses Prozesses verarbeiten. Um diese Prozeßnummer (PID) herauszubekommen, kann das ps-Kommando benutzt werden. Mit der Option `-ax' zeigt es alle Prozesse mit ihren Prozeßnummern, den Terminals (falls sie noch kontrollierende Terminals haben), dem Status und dem Namen an.

Zombies und blockierte Prozesse

 

Gelegentlich werden in Ausgabe von ps Prozesse angezeigt, die mit dem Status Z als Zombie gekennzeichnet sind. Diese ``lebendigen Toten'' existieren normalerweise nur für einige Augenblicke. Sie entstehen, wenn ein Prozeß beendet ist, und sie verschwinden, sobald der Elternprozeß das Signal von der Beendigung seines Kindes erhalten und bestätigt hat.

Wenn ein Zombie nicht aus der Prozeßtabelle verschwindet, bedeutet das, daß der Elternprozeß des Zombies eigentlich auf das Signal von der Beendigung seines Kindes warten wollte, jedoch aus irgendwelchen Gründen nicht mehr existiert.

Ein Zombie kann auch durch ein SIGKILL nicht aus der Prozeßtabelle entfernt werden. Weil der eigentliche Prozeß nicht mehr existiert und weder Arbeitsspeicher noch Rechenzeit verbraucht, hat ein Zombie außer dem unschönen Eintrag in der Anzeige von ps keine nachteilige Auswirkung auf das laufende System.

Es gibt noch weitere Fälle, in denen ein Prozeß auch durch das Signal SIGKILL nicht sofort beendet werden kann. Die Ursache hierfür liegt meistens in einem blockierten Systemaufruf. Diese Situation entsteht beispielsweise, wenn ein Prozeß auf die Beendigung einer Schreib- oder Leseoperation eines langsamen Gerätes wartet.

Systemabsturz

   Was für die Anwenderprogramme gilt, ist auch für den Kernel selbst nicht verkehrt. Auch wenn der Linux-Kernel eine beachtliche Stabilität erreicht hat und auf vielen Systemen unter hoher Last wochenlang ohne Unterbrechung läuft, kann das Betriebssystem nicht auf alle möglichen Ausnahmen vorbereitet sein, und es enthält auch Fehler. Ein Fehler des Betriebssystems ist nicht so leicht abzufangen wie der eines Anwenderprogramms. In einem solchen Fall kommt es häufiger zu einem Systemabsturz; manchmal in Form eines ,,Kernel Panic`` Systemhalts, manchmal zu einem Reset, manchmal zu einem kompletten Systemstillstand.

Wenn sich ein Systemabsturz irgendwie ankündigt, indem beispielsweise das System immer langsamer wird, obwohl kein rechenzeitintensives Programm läuft, können Sie versuchen, den Rechner mit einem halt-Kommando oder der Tastenkombination ALT-CONTROL-DELETE anzuhalten und so den Schaden zu begrenzen. In jedem Fall sollten Sie alle normalen Benutzeraktivitäten am Rechner beenden und häufig das Systemprogramm sync ausführen.

Wenn der unerfreuliche Fall eines echten Systemabsturzes eingetreten ist, erscheint manchmal eine Meldung der folgenden Form auf dem Bildschirm:

unable to handle kernel paging request at address C0000010
Oops: 0002
EIP:   0010:00118348
EFLAGS: 00000246
eax: fffffffd   ebx: 00000000   ecx: 00000216   edx: 000003d5
esi: 00105816   edi: 0016709f   ebp: 001756f8
ds: 002b  es: 002b  fs: 002b  gs: 002b
Pid: 00, process nr: 00
89 50 04 c7 03 00 00 00 00 c7
Aus der Fehlermeldung in der ersten Zeile kann geschlossen werden, daß eine Kernelfunktion versucht hat, auf die Speicheradresse 0x010 zuzugreifen. Weil der Kernel in den logischen Speicherbereich über ein Gigabyte (0xC0000000) verschoben ist, erscheint die Adresse mit diesem Offset.

In der zweiten Zeile erscheinen eine weitere Fehlermeldung und ein Fehlercode. Die Oops Meldung aus diesem Beispiel ist typisch für Fehler des Memory-Management. Die Fehlernummer kann weiteren Aufschluß über die Ursache des Absturzes geben; um sie zu entschlüsseln, müssen Sie die Kernelsourcen heranziehen.

Der Extended Instruction Pointer EIP zum Zeitpunkt des Fehlers läßt Rückschlüsse auf die Kernelfunktion zu, die für den Absturz verantwortlich ist. Sie können den Namen der Funktion herausfinden, indem Sie die Symboltabelle des Kernels nach der diese Adresse umfassenden Funktion durchsuchen. Das folgende Kommando erledigt diese Aufgabe:

# nm /usr/src/linux/tools/zSystem | sort | grep 00118...
00118294 t _try_to_free_page
00118324 T _free_page
0011847c T ___get_free_page
00118638 t _try_to_unuse
0011878c T _sys_swapoff
00118934 T _sys_swapon
00118c64 T _si_swapinfo
00118cf4 T _do_mmap
00118cf4 t ___gnu_compiled_c
00118cf4 t gcc2_compiled.
00118cf4 t mmap.o
00118ec4 T _sys_mmap
00118f50 T _unmap_fixup
# _
Aus dieser Liste läßt sich die verantwortliche Funktion, in diesem Beispiel free_page,[*] leicht herausfinden.

Die EFLAGS und die in den darauffolgenden Zeilen aufgelisteten Inhalte der Prozessorregister können im Einzelfall zur genauen Bestimmung der Fehlerursache ebenso herangezogen werden wie die Maschinencode-Sequenz in der letzten Zeile.


next up previous contents index
Next: Betrachtungen über die Zeit Up: Systemverwaltung Previous: Laufzeitmodule für den Kernel

Das Linux Anwenderhandbuch
(C) 1997 LunetIX