Prozess (Informatik)

aus Wikipedia, der freien Enzyklopädie
Wechseln zu: Navigation, Suche

Ein Prozess ist in der Informatik ein zentrales Konzept moderner Betriebssysteme. Vereinfachend handelt es sich um ein Computerprogramm in Ausführung. Das Prozessmodell vermittelt dem Benutzer die Illusion, dass verschiedene Programme gleichzeitig ausgeführt werden: In der Multiprogrammierung werden verschiedene Prozesse in so kurzen Abständen immer abwechselnd aktiviert, dass der Eindruck der Gleichzeitigkeit entsteht, obwohl ein Prozessor immer nur einen Prozess gleichzeitig verarbeiten kann.

Die Prozesse werden vom Prozess-Scheduler des Betriebssystems verwaltet. Er führt entsprechende Listen und versucht, wartenden Prozessen möglichst schnell die benötigten Ressourcen zuzuteilen, teilt ausführungsbereiten Prozessen Zeitschlitze zu und startet sie. In einem Prozesskontrollblock (PCP) der Prozesstabelle werden alle wichtigen Informationen eines Prozesses gespeichert, wenn er vom Zustand rechnend in die Zustände rechenbereit oder blockiert übergeht. Dadurch kann er zu einem späteren Zeitpunkt wieder aufgenommen werden, als wäre er nie unterbrochen worden.

Prozessmodell[Bearbeiten | Quelltext bearbeiten]

Ein Programm ist eine den Regeln einer bestimmten Programmiersprache genügende Folge von Anweisungen (bestehend aus Deklarationen und Instruktionen), um bestimmte Funktionen bzw. Aufgaben oder Probleme mithilfe eines Computers zu bearbeiten oder zu lösen.[1] Ein Prozess (in manchen Betriebssystemen auch Task genannt) ist ein Programm in Ausführung. Er ist die dynamische Abfolge von Aktionen mit entsprechenden Zustandsänderungen, oder anders ausgedrückt, die Instanzierung eines (statischen) Programms.[2]

Andrew S. Tanenbaum veranschaulicht den Unterschied am Prozess des Kuchenbackens: Das Rezept für den Kuchen ist das Programm (d.h. ein in einer passenden Notation geschriebener Algorithmus), der Bäcker ist die CPU und die Zutaten für den Kuchen sind die Eingabedaten. Der Prozess ist die Aktivität, die daraus besteht, dass der Bäcker das Rezept liest, die Zutaten herbeiholt und den Kuchen bäckt. Es kann nun beispielsweise vorkommen, dass der Sohn des Bäckers wie am Spiess schreiend in die Backstube gelaufen kommt. In diesem Fall notiert sich der Bäcker, an welcher Stelle des Rezeptes er sich befindet (der Zustand des aktuellen Prozesses wird gespeichert) und wechselt in einen Prozess mit höherer Priorität, nämlich „Erste Hilfe leisten“. Dazu holt er ein Erste-Hilfe-Handbuch hervor und folgt den dortigen Anweisungen. Den beiden Prozessen „Backen“ und „Erste Hilfe leisten“ liegt also ein unterschiedliches Programm zu Grunde: Kochbuch und Erste-Hilfe-Handbuch. Nachdem der Bäckerssohn medizinisch versorgt wurde, kann der Bäcker zum „Backen“ zurückkehren und an dem Punkt fortfahren, an dem er unterbrochen wurde. Das Beispiel veranschaulicht, dass sich mehrere Prozesse einen Prozessor teilen können. Eine Scheduling-Strategie entscheidet, wann die Arbeit an einem Prozess unterbrochen und ein anderer Prozess bedient wird.[3]

Ein Prozessor(kern) kann immer nur einen Prozess gleichzeitig verarbeiten. Bei den ersten Computern wurden daher die Programme immer nacheinander als Ganzes verarbeitet, es konnte immer nur ein Programm zur gleichen Zeit (exklusiv) ablaufen. Daher wurde die Möglichkeit geschaffen, Prozesse nur teilweise auszuführen, zu unterbrechen und später wieder aufzusetzen und fortzuführen: Im Mehrprozessbetrieb wechselt die CPU schnell zwischen mehreren Programmen, wobei jedes im Bereich von zehn bis hundert Millisekunden rechnet. Genau genommen läuft zwar zu jedem Zeitpunkt immer nur ein Programm auf der CPU, in einem Zeitraum von einer Sekunde können jedoch mehrere Programme bearbeitet werden. Dies erzeugt beim Benutzer die Illusion von Parallelität. Man spricht deshalb in diesem Zusammenhang von Quasiparallelität, um den Gegensatz zur echten Hardware-Parallelität von Mehrprozessorsystemen hervorzuheben.[4]

Das Prozessmodell ist ein konzeptionelles Modell, das den Umgang mit Parallelität vereinfacht. Dabei besitzt jeder Prozess seine eigene virtuelle CPU, auch wenn in der Realität die CPU zwischen Prozessen hin- und herschaltet. Es ist jedoch einfacher, sich eine Menge von (quasi-)parallel laufenden Prozessen vorzustellen, als zu versuchen, die Übersicht darüber zu behalten, wie die CPU zwischen Programmen wechselt. [5]

Prozessverwaltung[Bearbeiten | Quelltext bearbeiten]

Prozesserzeugung[Bearbeiten | Quelltext bearbeiten]

Ausgabe einer Unix-Shell beim Aufruf des Befehls ps -f

Einige Prozesse laufen im Vordergrund und interagieren mit (menschlichen) Benutzern. Hintergrundprozesse dagegen erfüllen spezielle Funktionen und lassen sich nicht bestimmten Benutzern zuordnen. Prozesse, die im Hintergrund bleiben, heissen Daemons. Wenn der Computer hochgefahren wird, werden üblicherweise viele Prozesse gestartet, von denen der Benutzer nichts bemerkt. Beispielsweise könnte ein Hintergrundprozess zum Empfangen von E-Mails gestartet werden, der die meiste Zeit „schläft“, jedoch plötzlich „lebendig“ wird, wenn eine E-Mail eintrifft. In Unix werden die laufenden Prozesse mit dem Kommando ps angezeigt[6], unter Windows wird dazu der Taskmanager benutzt.[7]

Nach dem Systemstart können weitere Prozesse durch einen Systemaufruf erzeugt werden. Häufig führen laufende Prozesse Systemaufrufe aus, um einen oder mehrere neue Prozesse zu erzeugen. Ein Beispiel dafür ist die Funktion fork() in unixoiden Systemen: Diese erzeugt aus einem existierenden Prozess (Elternprozess) einen neuen Prozess (Kindprozess). Der Kindprozess wird als Kopie des Elternprozesses erzeugt, erhält jedoch einen eigenen PID und wird in der Folge als eigenständige Instanz eines Programms und unabhängig vom Elternprozess ausgeführt.[8] In Windows kann ein Prozess durch die Funktion CreateProcess() gestartet werden.[9]

Unter Umständen besteht zwischen Eltern- und Kindprozessen weiterhin eine gewisse Beziehung. Wenn der Kindprozess weitere Prozesse erzeugt, entsteht eine Prozesshierarchie. In Unix formiert ein Prozess zusammen mit seinen Nachkommen eine Prozessfamilie.[10] Versendet beispielsweise ein Benutzer ein Signal mittels Tastatureingabe, so wird dieses an all diejenigen Prozesse der Prozessfamilie weitergeleitet, die momentan mit der Tastatur in Verbindung stehen. Jeder Prozess kann nun selber entscheiden, wie er mit dem Signal umgeht.

In Windows existiert dagegen kein Konzept der Prozesshierarchie. Alle Prozesse sind gleichwertig. Es ist lediglich ein spezielles Token (Handle genannt) vorhanden, das einem Elternprozess erlaubt, seinen Kindprozess zu steuern.[11]

Prozessbeendigung[Bearbeiten | Quelltext bearbeiten]

Ein Zombie ist auch ein Prozess.

Ein Prozess wird normalerweise beim Ende des Programmablaufs oder von einem anderen Prozess beendet (terminiert). Dazu existiert unter Unix der Systemaufruf exit und unter Windows exitProcess. Bildschirmorientierte Programme wie Textverarbeitungsprogramme und Webbrowser unterstützen auch die freiwillige Beendigung, indem sie ein Symbol oder einen Menüpunkt bereitstellen, den der Benutzer anklicken kann, um den Prozess anzuweisen, sämtliche geöffneten Dateien zu löschen und sich zu beenden. Ein Systemaufruf, der durch einen Prozess das Betriebssystem anweist, einen anderen Prozess zu beenden, heisst unter Unix kill und unter Windows TerminateProcess.

Ein weiterer Grund für eine Terminierung liegt vor, wenn ein Prozess einen schwerwiegenden Fehler feststellt. Dies kann beispielsweise an einer falschen Benutzereingabe liegen. Auch Prozesse selbst können Fehler verursachen. Dies geschieht häufig aufgrund von Programmierfehlern.[12]

Wenn ein Prozess in unixoiden Betriebssystemen beendet wurde, kann er trotzdem noch in der Prozesstabelle auftauchen und geringfügig Systemressourcen belegen. Ein solcher Prozess wird Zombie-Prozess genannt. Wenn ein Kindprozess beendet wird, kann der Elternprozess dadurch vom Betriebssystem erfragen, auf welche Art dieser beendet wurde: erfolgreich, mit Fehler, abgestürzt, abgebrochen, etc. Um diese Abfrage zu ermöglichen, bleibt ein Prozess, selbst nachdem er beendet wurde, in der Prozesstabelle stehen, bis der Elternprozess diese Abfrage durchführt – egal ob diese Information gebraucht wird oder nicht. Bis dahin hat der Kindprozess den Zustand Zombie.

Prozesszustände[Bearbeiten | Quelltext bearbeiten]

Ein Prozess durchläuft während seiner Lebenszeit verschiedene Zustände. Wenn das Betriebssystem entscheidet, den Prozessor für eine gewisse Zeit einem anderen Prozess zuzuteilen, wird zunächst der aktuell rechnende Prozess gestoppt. Es kann auch sein, dass ein Prozess blockiert wird, weil er nicht weiterarbeiten kann. Typischerweise geschieht dies durch das Warten auf Eingabedaten, die noch nicht zur Verfügung stehen. Vereinfacht lassen sich vier Zustände unterscheiden[13]:

  • rechnend (running): die Befehle werden in diesem Moment auf der CPU ausgeführt.
  • rechenbereit (ready): kurzzeitig gestoppt, um einen anderen Prozess rechnen zu lassen.
  • blockiert (blocked): nicht lauffähig bis ein bestimmtes Ereignis eintritt.
  • beendet (terminated): der Prozess ist terminiert.

Dies lässt sich als Zustandsautomat eines Prozesses mit vier Zuständen modellieren:

Zustandsdiagramm-prozesse.jpg

Es ergeben sich dabei u.a. die folgenden Zustandsübergänge:

(a) Das Betriebssystem wählt den Prozess aus
(b) Das Betriebssystem wählt einen anderen Prozess aus (Deaktivieren, Preemption, Vorrangunterbrechung)
(c) Der Prozess wird blockiert (z.B. wegen Warten auf Input, Betriebsmittel wird angefordert)
(d) Der Blockierungsgrund wird aufgehoben (Betriebsmittel verfügbar)
(e) Prozessbeendigung oder schwerwiegender Fehler (Terminieren des Prozesses)

Der Übergang (a) erfolgt, wenn die anderen Prozesse ihren gerechten Anteil an der Rechenzeit verbraucht haben und nun wieder der zu betrachtende Prozess an der Reihe ist. Wenn der Scheduler entscheidet, dass dieser lange genug gelaufen ist, tritt Übergang (b) ein. Übergang (d) erfolgt, wenn das externe Ereignis eintritt, auf das der blockierte Prozess gewartet hat. Falls in diesem Moment kein anderer Prozess läuft, wird Übergang (a) ausgelöst und der Prozess startet. Ansonsten wartet er im rechenbereiten Zustand, bis er an der Reihe ist.

Natürlich sind die Zustandsautomaten moderner Betriebssysteme etwas komplexer, aber im Prinzip sind sie ähnlich konzipiert.

Prozesstabelle[Bearbeiten | Quelltext bearbeiten]

Bei der Prozesserzeugung werden einem Prozess die notwendigen Betriebsmittel (CPU, Hauptspeicher, usw.) zugeordnet, der Programmcode wird in den Speicher geladen und der Prozess wird aktiviert. Innerhalb des Betriebssystems wird einem Prozess ein Process identifier (kurz PID, auch Prozessnummer, Prozesskennung oder Prozess-ID) zugeordnet, ein einzigartiger Schlüssel, welcher der eindeutigen Identifikation von Prozessen dient. Der PID ändert sich während der Laufzeit des Prozesses nicht. Das Betriebssystem stellt sicher, dass systemweit keine Nummer zweimal vorkommt.[14]

Das Betriebssystem führt eine Tabelle mit aktuellen Prozessen in einer kerneleigenen Datenstruktur, der sog. Prozesstabelle. Bei der Erzeugung eines neuen Prozesses wird darin ein neuer Eintrag angelegt, ein sog. Prozesskontrollblock (Process Control Block, kurz PCB). Der Prozesskontrollblock beinhaltet Informationen über den Zustand des Prozesses, seinen Befehlszähler, CPU-Register, seinen Stackpointer sowie seine Speicherbelegung und den Zustand seiner geöffneten Dateien. Es werden also alle wichtigen Informationen über einen Prozess gespeichert, wenn er vom Zustand rechnend in die Zustände rechenbereit oder blockiert übergeht. Dadurch kann er zu einem späteren Zeitpunkt wieder aufgenommen werden, so als wäre er nie unterbrochen worden.[15]

Viele Zugriffe auf einen Prozess basieren darauf, dass über den PID der zugehörige PCB in der Prozesstabelle gesucht wird. Da PCBs Informationen über die belegten Ressourcen enthalten, müssen sie im Speicher direkt zugreifbar sein. Eine mögliche Implementierung wäre, an fester Stelle im Speicher ein Array zu reservieren, dessen Elemente die PCBs sind und die Indices zum Zugriff benutzt werden. So belegten frühere Systeme beispielsweise ein Array von Strukturen mit 256 Einträgen als Prozesstabelle. Es konnten dadurch immer nur maximal 256 Prozesse existieren. Der Versuch, einen 257. Prozess zu starten, schlug fehl, da kein Tabellenplatz mehr vorhanden war.[16] Ein zu grosses Array dagegen führt dazu, dass ein grosser Teil des Speichers ungenutzt bleibt, denn meistens laufen dann weit weniger Prozesse als PIDs zur Verfügung stehen.

Statt die PCBs in Arrays zu verwalten, wird deshalb häufig eine Hashfunktion verwendet. Das ist eine Abbildung, die eine große Eingabemenge (die Schlüssel) auf eine kleinere Zielmenge (die Hashwerte) abbildet. Es wird also ein grosser, wenig genutzter Adressraum auf einen kleinen Raum abgebildet. Der PID wird als Hash-Schlüssel benutzt, an der berechneten Hash-Adresse ist ein Zeiger auf den PCB eingetragen. Eine sog. Kollision tritt dann auf, wenn unterschiedlichen Eingabedaten derselbe Hashwert zugeordnet wird. Linux geht mit möglichen Hash-Kollisionen bei der Prozess-Identifizierung folgendermassen um: Die Einträge für Prozesse mit gleicher Hash-Adresse befinden sich in einer verketteten Liste. Die Listeneinträge enthalten neben den notwendigen Listenzeigern den jeweiligen PID und einen Zeiger zum jeweiligen PCB. Wird ein bestimmter Prozess gesucht, wird über den PID die Hash-Adresse berechnet und anschliessend die verkettete Liste nach dem gewünschten PID durchsucht.[17]

Scheduling[Bearbeiten | Quelltext bearbeiten]

Hauptartikel: Prozess-Scheduler

Ein Prozess-Scheduler (Scheduler = Steuerprogramm; von Englisch schedule für Zeitplan) ist eine Arbitrationslogik, die die zeitliche Ausführung mehrerer Prozesse in Betriebssystemen regelt. Prozess-Scheduler kann man grob in unterbrechende (präemptiv) und nicht unterbrechende (non preemptive, auch kooperativ genannt) aufteilen. Nicht unterbrechende Scheduler lassen einen Prozess, nachdem ihm die CPU einmal zugeteilt wurde, solange laufen, bis dieser diese von sich aus wieder freigibt oder bis er blockiert. Unterbrechende Scheduler teilen die CPU von vornherein nur für eine bestimmte Zeitspanne zu und entziehen dem Prozess diese daraufhin wieder. Weiter ist eine Unterscheidung in „work-conserving“ und „non work-conserving“ Strategien möglich. Eine Scheduler-Strategie arbeitet „work-conserving“, wenn das Umschalten zwischen Prozessen nur eine vernachlässigbar geringe Zeit in Anspruch nimmt. Man kann verschiedene Systeme unterscheiden, in welchen jeweils verschiedene Anforderungen an den Scheduler gestellt werden:

In Stapelverarbeitungssystemen sieht der Scheduler denkbar einfach aus: Ankommende Aufträge werden in eine Warteschlange eingereiht und jedes Mal, wenn ein Job abgearbeitet ist, kommt der nächste aus der Schlange dran (Queue-Manager). Interaktive Systeme stellen andere Anforderungen: Der Benutzer legt Wert auf kurze Antwortzeit. Wenn er beispielsweise in einem Texteditor eine Tastatureingabe tätigt, sollte der Text sofort erscheinen. Ein Echtzeitsystem muss garantieren, dass ein Prozess eine Aufgabe innerhalb einer vorgegebenen Zeitspanne abgearbeitet haben muss. Bei harten Echtzeitanforderungen wird das in 100 % aller Fälle garantiert, während bei weichen Anforderungen das Zeitlimit in einem kleinen Prozentsatz der Fälle überschritten werden darf. Typische Desktop-PCs sind interaktive Systeme, auf denen gelegentlich auch Prozesse als so genannte Hintergrundprozesse mit niedrigerer Priorität ablaufen können.

Threads[Bearbeiten | Quelltext bearbeiten]

Hauptartikel: Thread (Informatik)

Da die Verwaltung von Prozessen relativ aufwändig ist, unterstützen moderne Betriebssysteme auch ein Ressourcen-schonenderes Konzept, die sogenannten Threads (deutsch: Fäden, Ausführungsstränge). Ein Thread verkörpert eine nebenläufige Ausführungseinheit innerhalb eines Prozesses. Im Gegensatz zu den schwergewichtigen (heavy-weighet) Prozessen werden Threads als leichtgewichtige (light-weight) Prozesse (kurz LWP) charakterisiert. Diese lassen sich leichter erzeugen und wieder zerstören: In vielen Systemen läuft die Erstellung eines Threads 10-100-mal schneller ab als die Erstellung eines Prozesses. Insbesondere wenn sich die Anzahl an benötigten Threads dynamisch und schnell verändert, ist diese Eigenschaft von Vorteil.

Einem Prozess sind ein Adressraum und weitere Betriebssystemmittel zugeordnet - insbesondere sind Prozesse gegeneinander abgeschirmt: Versucht ein Prozess, auf Adressen oder Betriebsmittel zuzugreifen, die ihm nicht zugeteilt wurden (und ggf. einem anderen Prozess gehören), so schlägt dies fehl und er wird vom Betriebssystem abgebrochen. Ein Prozess kann mehrere Threads oder – wenn bei dem Programmablauf keine Parallelverarbeitung vorgesehen ist – auch nur einen einzigen Thread beinhalten. Threads teilen sich innerhalb eines Prozesses Prozessoren, den Speicher und andere betriebssystemabhängige Ressourcen wie Dateien und Netzwerkverbindungen. Deswegen ist der Verwaltungsaufwand für Threads üblicherweise geringer als der für Prozesse. Ein wesentlicher Effizienzvorteil von Threads besteht zum einen darin, dass im Gegensatz zu Prozessen beim Threadwechsel kein vollständiger Wechsel des Prozesskontextes notwendig ist, da alle Threads einen gemeinsamen Teil des Prozesskontextes verwenden, zum anderen in der einfachen Kommunikation und schnellem Datenaustausch zwischen Threads. Threads eines Prozesses sind aber nicht gegeneinander geschützt und müssen sich daher beim Zugriff auf die gemeinsamen Prozess-Ressourcen abstimmen (synchronisieren).[18]

Die Implementierung von Threads hängt vom jeweiligen Betriebssystem ab. Sie kann auf Kernel- oder auf Benutzerebene erfolgen. So sind Threads in Windows-Betriebssystemen auf Kernelebene realisiert, in Unix sind Thread-Implementierungen sowohl auf der Kernel- als auch auf der Benutzerebene möglich. Bei Threads auf der Benutzerebene führt die entsprechende Threadbibliothek das Scheduling und Umschalten zwischen den Threads durch. Das Betriebssystem hat davon keine Kenntnis. Bei Kernel-Threads werden die Threads im Kernelmodus verwaltet. Eine spezielle Threadbibliothek ist dabei für den Anwendungsprogrammierer nicht erforderlich.[19]

Process-thread.jpg

Drei Prozesse mit einem Thread und ein Prozess mit drei Threads.

Verbesserung der CPU-Ausnutzung[Bearbeiten | Quelltext bearbeiten]

Durch den Einsatz von Multiprogrammierung kann die CPU-Ausnutzung verbessert werden. Wenn ein Prozess einen Anteil p seiner Laufzeit auf die Beendigung von Ein-/Ausgaben wartet, so ist die Wahrscheinlichkeit, dass n solche Prozesse auf die Ein-/Ausgabe warten p^n. Dies entspricht der Wahrscheinlichkeit, dass die CPU unbeschäftigt wäre. Die CPU-Ausnutzung kann dadurch als Funktion von n ausgedrückt werden, die Grad der Multiprogrammierung genannt wird:

CPU-Ausnutzung = 1-p^n

Es ist durchaus üblich, dass ein interaktiver Prozess 80 % oder mehr im Ein-/Ausgabe-Wartezustand verbringt. Auch auf Servern, die viel Plattenein-/ausgabe durchführen, ist dieser Wert realistisch. Unter dieser Annahme, dass Prozesse 80 % ihrer Zeit im blockierten Zustand verbringen, müssen mindestens 10 Prozesse laufen, damit die CPU weniger als 10 % der Zeit verschwendet wird.

Natürlich stellt dieses probabilistische Modell nur eine Annäherung dar. So setzt es voraus, dass alle Prozesse unabhängig sind. In einer einzigen CPU können jedoch nicht mehrere Prozesse gleichzeitig laufen. Es müsste also noch berücksichtigt werden, dass ein rechenbereiter Prozess warten muss, während die CPU läuft. Ein exaktes Modell kann mit Hilfe der Warteschlangentheorie konstruiert werden. Dennoch veranschaulicht das Modell die Verbesserung der CPU-Ausnutzung: Mit Multiprogrammierung können Prozesse die CPU benutzen, die ansonsten untätig wäre. Es lassen sich so zumindest überschlägige Vorhersagen über die CPU-Performanz machen.[20]

Programmbeispiele[Bearbeiten | Quelltext bearbeiten]

Erzeugen eines Kindprozesses mit dem fork()-Aufruf[Bearbeiten | Quelltext bearbeiten]

Mit Hilfe der fork-Funktion erstellt ein Prozess eine nahezu identische Kopie von sich selber. Der Name bedeutet im Englischen in etwa „sich gabeln, verzweigen oder spalten“: Der aufrufende Prozess gelangt an eine Weggabelung, an der sich Eltern- und Kindprozess trennen.

Das folgende C-Programm deklariert eine Zählervariable counter und weist ihr zunächst den Wert 0 zu. Durch fork() wird danach ein Kindprozess erzeugt, der eine identische Kopie des Elternprozesses ist. Der Systemaufruf fork() liefert bei Erfolg dem Elternprozess den PID des eben geschaffenen Kindes zurück. Im Kind liefert die Funktion dagegen den Rückgabewert 0. Mit Hilfe dieses Rückgabewerts kann man nun Auskunft darüber erlangen, ob es sich jeweils um Eltern- oder Kindprozess handelt und entsprechend in einer if-else-Verzweigung fortfahren. Um die eigene PID zu finden, ist getpid() nötig.

Nach dem Aufruf von fork() laufen also zwei Prozesse quasi-parallel, die beide ihre eigene Version der Zählervariable counter von 0 bis 1000 erhöhen. Man hat nun keinen Einfluss darauf, welcher Prozess zu welchem Zeitpunkt bearbeitet wird. Dementsprechend kann die Ausgabe auf der Konsole von einem Durchgang zum nächsten variieren. [21]

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    printf("PROGRAMMSTART\n");

    int counter = 0;
    pid_t pid = fork();

    if (pid == 0)
    {
        // Hier befinden wir uns im Kindprozess
        int i = 0;
        for (; i < 1000; ++i)
        {
            printf("            PID: %d; ", getpid());
            printf("Kindprozess: counter=%d\n", ++counter);
        }
    }
    else if (pid > 0)
    {
        // Hier befinden wir uns im Elternprozess
        int j = 0;
        for (; j < 1000; ++j)
        {
            printf("PID: %d; ", getpid());
            printf("Elternprozess: counter=%d\n", ++counter);
        }
    }
    else
    {
        // Fehler bei fork()
        printf("fork() fehlgeschlagen!\n");
        return 1;
    }

    printf("PROGRAMMENDE\n");

    return 0;
}

Forkbomb[Bearbeiten | Quelltext bearbeiten]

Rekursive Prozesserzeugung

Bei der Forkbomb wird ein Prozess gestartet, der mittels fork() rekursiv immer wieder Kindprozesse erzeugt, die sich gleich verhalten wie der Elternprozess. Dadurch werden die verfügbaren Systemressourcen (Prozesstabellen, CPU, usw.) aufgebraucht. Eine Forkbomb realisiert somit eine Denial-of-Service-Attacke, kann aber auch bei unbedachter Anwendung des fork-Aufrufs „hochgehen“. Der konkrete Effekt einer Forkbomb hängt in erster Linie von der Konfiguration des Betriebssystems ab. Beispielsweise erlaubt PAM auf Unix, die Zahl der Prozesse und den maximal zu verbrauchenden Speicher pro Benutzer zu beschränken. „Explodiert“ eine Forkbomb auf einem System, welches diese Möglichkeiten der Beschränkung nutzt, scheitert irgendwann der Versuch, neue Kopien der Forkbomb zu starten und das Wachstum ist eingedämmt.

#include <unistd.h>

int main(void)
{
    while(1)
    {
        fork();
    }

    return 0;
}

Siehe auch[Bearbeiten | Quelltext bearbeiten]

Literatur[Bearbeiten | Quelltext bearbeiten]

  • Albert Achilles: Betriebssysteme. Eine kompakte Einführung mit Linux. Springer: Berlin, Heidelberg, 2006.
  • Uwe Baumgarten, Hans-Jürgen Siegert: Betriebssysteme. Eine Einführung. 6., überarbeitete, aktualisierte und erweiterte Auflage, Oldenbourg Verlag: München, Wien, 2007.
  • Erich Ehses, Lutz Köhler, Petra Riemer, Horst Stenzel, Frank Victor: Systemprogrammierung in UNIX / Linux. Grundlegende Betriebssystemkonzepte und praxisorientierte Anwendungen. Vieweg+Teubner: Wiesbaden, 2012.
  • Peter Mandl: Grundkurs Betriebssysteme. Architekturen, Betriebsmittelverwaltung, Synchronisation, Prozesskommunikation. Vieweg: Wiesbaden, 2008.
  • Andrew S. Tanenbaum: Moderne Betriebssysteme. 3., aktualisierte Auflage. Pearson Studium, München u.a., 2009, ISBN 978-3-8273-7342-7.
  • Jürgen Wolf: Linux-UNIX-Programmierung. Das umfassende Handbuch. 3., aktualisierte und erweiterte Auflage, Rheinwerk: Bonn, 2009.

Einzelnachweise und Anmerkungen[Bearbeiten | Quelltext bearbeiten]

  1. ISO/IEC 2382-1:1993 definiert „computer program“: „A syntactic unit that conforms to the rules of a particular programming language and that is composed of declarations and statements or instructions needed to solve a certain function, task, or problem.“ Bis 2001 definierte die DIN 44300 „Informationsverarbeitung Begriffe“ identisch.
  2. Mandl: Grundkurs Betriebssysteme. 2009, S. 74.
  3. Tanenbaum: Moderne Betriebssysteme. 3. Aufl., 2009, S. 126-127.
  4. Tanenbaum: Moderne Betriebssysteme. 3. Aufl., 2009, S. 124-125.
  5. Tanenbaum: Moderne Betriebssysteme. 3. Aufl., 2009, S. 126-127.
  6. Anmerkung: ps ohne Optionen zeigt nur solche Prozesse an, die aus Textkonsolen bzw. Shell-Fenstern gestartet wurden. Durch die Option x werden auch Prozesse angezeigt, denen kein Terminal zugeordnet ist. Ausserdem gibt es das Kommando top: Dieses ordnet die Prozesse danach, wie sehr sie die CPU belasten und zeigt die gerade aktiven Prozesse zuerst an.
  7. Tanenbaum: Moderne Betriebssysteme. 3. Aufl., 2009, S. 127.
  8. UNIXguide.net by Hermelito Go: What does fork() do? (abgerufen am 20. April 2016)
  9. Windows Dev Center: Creating Processes
  10. Anmerkung: Die Darstellung der Prozesshierarchie gelingt am einfachsten mit dem Shell-Kommando pstree.
  11. Tanenbaum: Moderne Betriebssysteme. 3. Aufl., 2009, S. 130-131.
  12. Tanenbaum: Moderne Betriebssysteme. 3. Aufl., 2009, S. 129-130.
  13. Mandl: Grundkurs Betriebssysteme. 2008, S. 78; Tanenbaum: Moderne Betriebssysteme. 3. Aufl., 2009, S. 131-132.
  14. Anmerkung: Unter Umständen kann explizit von einem Prozess gefordert, dass er den gleichen PID wie ein anderer hat, z.B. durch !CLONE_PID.
  15. Tanenbaum: Moderne Betriebssysteme. 3. Aufl., 2009, S. 133-135.
  16. Tanenbaum: Moderne Betriebssysteme. 3. Aufl., 2009, S. 1122.
  17. Achilles: Betriebssysteme. 2006, S. 24-25.
  18. Mandl: Grundkurs Betriebssysteme. 2008, S. 78-79; Tanenbaum: Moderne Betriebssysteme. 3. Aufl. 2009, S. 137-140.
  19. Mandl: Grundkurs Betriebssysteme. 2008, S. 79-82.
  20. Tanenbaum: Moderne Betriebssysteme. 3. Aufl., 2009, S. 135-136.
  21. Das Programmbeispiel orientiert sich an Ehses, u.a.: Systemprogrammierung in UNIX / Linux. 2012, S. 50-51; siehe auch Wolf: Linux-UNIX-Programmierung. 3. Aufl., 2009, S. 211-219 und Markus Zahn: Unix-Netzwerkprogrammierung mit Threads, Sockets und SSL. Springer: Berlin, Heidelberg, 2006, S. 79-89.