Ein Prozess ist die laufende Instanz eines Programms — und alles, was unter Linux Rechenzeit verbraucht, ist ein Prozess. Wer den Lebenszyklus, die PID-Hierarchie und das fork/exec-Modell versteht, durchschaut nicht nur ps und top, sondern auch, warum Linux so robust und vorhersagbar funktioniert. Dieses Kapitel legt die Grundlage für alles Weitere rund um Jobs, Signale und Prozesssteuerung.
Was ein Prozess ist
Ein Prozess ist mehr als nur eine ausgeführte Datei. Wenn der Kernel ein Programm startet, legt er im Speicher eine vollständige Ausführungsumgebung an: den Programmcode (Text-Segment), die statischen Daten, einen Heap für dynamisch alloziierten Speicher, einen Stack für lokale Variablen und Aufrufrahmen sowie eine Tabelle der offenen Datei-Deskriptoren.
Zusätzlich verwaltet der Kernel zu jedem Prozess Metadaten: die PID, die Eltern-PID, den User und die Gruppe, das aktuelle Arbeitsverzeichnis, die geltende umask, die zugewiesenen Signale, die CPU-Affinität und vieles mehr. Diese Informationen liegen in einer Kernel-Datenstruktur namens task_struct — der Linux-Kernel kennt streng genommen keinen Unterschied zwischen Prozess und Thread, beide sind task_struct-Einträge mit unterschiedlich geteilten Ressourcen.
Aus Sicht des Anwenders zählt vor allem: Ein Prozess hat einen klar definierten Anfang (Start), einen Lebenszyklus mit verschiedenen Zuständen und ein klar definiertes Ende (Exit-Code). Der Kernel ist allein zuständig für Erzeugung, Scheduling und Beendigung — Programme können das nur per Systemaufruf anfordern.
PID, PPID und die Hierarchie
Jeder Prozess hat eine eindeutige PID (Process ID), eine ganzzahlige Nummer, die ihn systemweit identifiziert. Zusätzlich kennt jeder Prozess seine PPID (Parent Process ID) — die PID des Prozesses, der ihn erzeugt hat. Daraus ergibt sich automatisch eine Baumstruktur mit einem einzigen Wurzelknoten: PID 1.
Typische PIDs auf einem modernen Linux-System:
| PID | Prozess | Rolle |
|---|---|---|
| 0 | swapper / idle | Kernel-Idle-Task, vom User aus nicht sichtbar |
| 1 | systemd (oder init) | Wurzel des Prozessbaums, startet alle Dienste |
| 2 | kthreadd | Eltern aller Kernel-Threads |
| 3+ | diverse kernel-Threads | mit Klammern in ps, z. B. [kworker/...] |
| >100 | User- und System-Prozesse | alles, was nach dem Boot startet |
Den Prozessbaum visualisierst du mit pstree:
pstree -psystemd(1)─┬─NetworkManager(812)
├─sshd(945)───sshd(2031)───bash(2034)───pstree(2099)
├─systemd(1547)───(user.slice)
└─cron(901)Die eigene PID liefert die Shell-Variable $$, die der Eltern-Shell $PPID. Mit ps -ef siehst du PID, PPID und die Befehlszeile zu jedem Prozess.
fork und exec
Linux erbt von Unix ein ungewöhnliches, aber elegantes Modell zur Prozess-Erzeugung: fork und exec sind zwei getrennte Schritte. Andere Betriebssysteme (Windows, klassische Embedded-OS) haben dafür meist einen einzigen Aufruf wie CreateProcess. Linux trennt das bewusst.
fork() dupliziert den aktuellen Prozess. Nach dem Aufruf existieren zwei nahezu identische Prozesse: der Eltern- und der Kindprozess. Beide laufen ab dem Punkt nach fork() weiter, mit denselben offenen Dateien, demselben Speicherinhalt und demselben Code. Der einzige Unterschied: Der Rückgabewert von fork(). Im Eltern-Prozess ist es die PID des Kindes, im Kind 0.
exec() ersetzt den laufenden Programmcode des Prozesses durch ein anderes Programm. Die PID bleibt erhalten, aber alles, was bisher im Speicher lag, wird durch das neue Programm überschrieben. exec() kehrt im Erfolgsfall nicht zurück.
Der typische Ablauf, wenn die Shell ein Programm startet:
| Schritt | Was passiert |
|---|---|
| 1. | Shell ruft fork() auf — es entsteht ein Kind mit der gleichen Shell als Programm |
| 2. | Im Kind ruft die Shell exec("/bin/ls", ...) auf — der Kindprozess wird zu ls |
| 3. | Eltern-Shell ruft wait() und blockiert, bis das Kind endet |
| 4. | ls läuft, schreibt nach stdout, terminiert mit Exit-Code |
| 5. | wait() kehrt zurück, Shell zeigt wieder den Prompt |
Das fork/exec-Modell ist deshalb mächtig, weil zwischen den beiden Aufrufen Zeit ist, im Kindprozess Vorbereitungen zu treffen: Datei-Deskriptoren umlenken (für Pipes und Redirections), Umgebungsvariablen ändern, in ein anderes Verzeichnis wechseln, Berechtigungen droppen. Die ganze Unix-Pipeline-Mechanik basiert darauf.
Prozess-Zustände
Ein Prozess befindet sich zu jedem Zeitpunkt in genau einem Kernel-internen Zustand. Die wichtigsten Zustände, wie sie in ps (Spalte STAT) und top (Spalte S) erscheinen:
| Code | Zustand | Bedeutung |
|---|---|---|
| R | Running / Runnable | läuft gerade auf einer CPU oder wartet auf einen freien CPU-Slot |
| S | Sleeping (interruptible) | wartet auf ein Ereignis (Tastatur, Netzwerk, Timer), kann durch Signale geweckt werden |
| D | Uninterruptible Sleep | wartet meist auf Disk- oder NFS-I/O, kann NICHT mit Signalen geweckt werden |
| T | Stopped | per Signal angehalten (SIGSTOP, SIGTSTP), wartet auf SIGCONT |
| Z | Zombie | beendet, aber Eltern hat noch nicht wait() gerufen |
| I | Idle | leerlaufender Kernel-Thread (seit Kernel 4.2) |
Zusätzlich erscheinen in ps häufig Zusatzflags wie + (im Vordergrund einer Terminalsitzung), s (Session-Leader), l (multithreaded), < (höhere Priorität) oder N (niedrigere Priorität, niced).
Der Großteil aller Prozesse liegt zu jedem Zeitpunkt in S und wartet auf irgendwas — Tastatureingaben, Netzwerkpakete, Timer-Ablauf. R-Prozesse sind die, die gerade tatsächlich CPU verbrauchen. D ist der unangenehmste Zustand, weil ein hängender D-Prozess weder mit kill -9 noch mit kill -KILL beendet werden kann.
Zombies und Orphans
Zwei Sonderfälle entstehen aus der Eltern-Kind-Beziehung von Prozessen — und beide haben mit dem Ende eines Prozesses zu tun.
Zombie (Zustand Z): Ein Kindprozess hat seine Arbeit beendet und einen Exit-Code hinterlassen. Bevor der Kernel den Eintrag aus seiner Prozess-Tabelle entfernen kann, muss der Eltern-Prozess den Exit-Code per wait() oder waitpid() abholen. Tut er das nicht — etwa weil er schlampig programmiert ist — bleibt der tote Kindprozess als Zombie liegen. Er belegt keinen Speicher mehr (kein Code, kein Heap, kein Stack), aber einen Slot in der Prozess-Tabelle und seine PID. Wenige Zombies sind harmlos; viele Zombies deuten auf einen Bug im Eltern-Prozess hin.
Orphan (Waise): Wenn der Eltern-Prozess stirbt, bevor das Kind beendet ist, wird das Kind zum Orphan. Linux übergibt es automatisch an PID 1 (systemd) — der adoptiert den Waisen und ruft später wait(), sobald das Kind endet. Dadurch bleibt kein Orphan dauerhaft existieren. Genau aus diesem Grund werden Hintergrunddienste oft bewusst „verwaist” gestartet (Daemonisierung): Der ursprüngliche Eltern-Prozess beendet sich sofort, und systemd übernimmt die Aufsicht.
/proc — die Prozess-Lupe
Linux exportiert sämtliche Prozess-Informationen über das virtuelle Dateisystem /proc. Für jede laufende PID existiert dort ein Verzeichnis mit demselben Namen, in dem Pseudo-Dateien Auskunft über jeden Aspekt des Prozesses geben. Klassische Werkzeuge wie ps, top und htop lesen ihre Daten ausschließlich aus /proc — es gibt keinen anderen offiziellen Weg.
Die wichtigsten Einträge unter /proc/PID/:
| Pfad | Inhalt |
|---|---|
status | menschenlesbare Übersicht (Name, PID, PPID, UID, RAM, Threads, Status) |
cmdline | originale Befehlszeile, mit \0 getrennt |
cwd | Symlink auf das Arbeitsverzeichnis |
exe | Symlink auf die ausgeführte Binärdatei |
environ | Umgebungsvariablen, mit \0 getrennt |
fd/ | Verzeichnis mit allen offenen Datei-Deskriptoren als Symlinks |
maps | Speicher-Mappings (Bibliotheken, Heap, Stack) |
stat | maschinenlesbare Variante von status, eine Zeile, vom Kernel-Format |
limits | aktuelle ulimit-Werte |
tr '\0' ' ' < /proc/$$/cmdlinebashDas Lesen aus /proc ist günstig, weil keine Daten von Festplatte kommen — der Kernel generiert die Inhalte zur Laufzeit. Schreibzugriffe sind in einigen Pfaden ebenfalls möglich (z. B. /proc/sys/...), aber unter /proc/PID/ größtenteils nur Lesen.
PID 1 — init und systemd
PID 1 ist der erste Prozess, den der Kernel nach dem Booten startet. Auf modernen Linux-Distributionen ist das systemd, auf älteren oder minimalistischen Systemen klassisches SysV-init, OpenRC oder runit. Auf Alpine Linux läuft busybox init, in Containern oft ein schlanker Init wie tini oder dumb-init.
Was PID 1 besonders macht:
- Wurzel des Prozessbaums — alle anderen User-Prozesse sind direkt oder indirekt Kinder von PID 1.
- Adoptiert Orphans — verwaiste Prozesse werden automatisch zu Kindern von PID 1 und dort sauber abgeerntet.
- Kann nicht getötet werden — der Kernel ignoriert die Standard-Signale (
SIGTERM,SIGKILL) für PID 1; eine Beendigung würde eine Kernel-Panic auslösen. - Läuft bis zum Reboot — PID 1 startet nicht neu; ein Crash von PID 1 ist gleichbedeutend mit einem Systemausfall.
- Verarbeitet
SIGCHLD— sobald ein adoptierter Prozess endet, ruft PID 1wait()und räumt den Zombie ab.
Bei systemd kommen weitere Aufgaben dazu: das Starten von Services, die Verwaltung von Sockets und Timern, das Mounten von Dateisystemen, das Logging via journald. systemd ist damit weit mehr als ein klassischer init — es ist ein vollständiges Service-Management-System. Details dazu findest du im systemd-Kapitel.
Prozess-Gruppen und Sessions
Über die einfache Eltern-Kind-Beziehung hinaus gibt es zwei weitere Gruppierungs-Konzepte, die vor allem für die Job-Steuerung in der Shell relevant sind.
Eine Process Group (Prozessgruppe) ist eine Sammlung verwandter Prozesse, die gemeinsam Signale empfangen können. Wenn du eine Pipeline wie cat log | grep ERROR | wc -l startest, fasst die Shell alle drei Prozesse zu einer Gruppe zusammen — das ist der „Job”. Drückst du Strg + C, geht das SIGINT an die ganze Gruppe, nicht nur an den vordersten Prozess. Die PGID (Process Group ID) ist meist die PID des ersten Prozesses der Gruppe.
Eine Session ist eine Sammlung von Prozessgruppen, die zu einem Login oder Terminal gehören. Jede Session hat einen Session-Leader (typischerweise die Login-Shell) und ein kontrollierendes Terminal. Wenn das Terminal geschlossen wird, bekommen alle Prozesse der Session ein SIGHUP — das ist der Grund, warum Hintergrundjobs ohne nohup oder disown nach dem Schließen des Terminals sterben.
Diese beiden Konzepte sind die Grundlage der Job-Control mit jobs, fg, bg und &. Eine ausführliche Behandlung findest du im Artikel Jobs, fg und bg.
Besonderheiten
Zombies sind kein Versagen des Kindes
Ein Zombie entsteht nicht, weil das Kind abstürzt oder fehlerhaft ist — sondern weil der Eltern-Prozess kein wait() ausführt. Korrekt geschriebene Server installieren einen SIGCHLD-Handler oder rufen periodisch waitpid() mit WNOHANG auf, um den Exit-Code abzuholen. Wer einen Zombie loswerden will, ohne den Eltern-Prozess zu kennen, kann nur Geduld haben oder den Eltern-Prozess beenden — dann übernimmt PID 1 und räumt sofort auf.
D-State ist nicht killbar
Ein Prozess im Zustand Uninterruptible Sleep (D) wartet auf einen Kernel-Vorgang, der nicht unterbrochen werden darf — meist Disk- oder NFS-I/O. Selbst kill -9 erreicht ihn nicht, weil das Signal erst zugestellt wird, wenn der Prozess den D-Zustand verlässt. Hängende NFS-Mounts oder defekte USB-Sticks sind die typische Ursache. Gegenmaßnahme: Ursache beheben (NFS-Server starten, Hardware abziehen) oder im Notfall reboot.
PID-Wraparound und pid_max
PIDs werden sequenziell vergeben. Ist die maximale PID erreicht, fängt der Kernel wieder bei niedrigen PIDs an und überspringt vergebene. Das Maximum steht in /proc/sys/kernel/pid_max, Default ist 32768 (auf 64-Bit-Systemen oft auf vier Millionen erhöhbar). Auf Servern mit vielen kurzlebigen Prozessen (CI-Runner, Container-Hosts) lohnt sich das Anheben — sonst werden PIDs schnell wiederverwendet, was Logs und Monitoring verwirrt.
Copy-on-Write macht fork() bezahlbar
Auf den ersten Blick wirkt fork() teuer — der gesamte Speicher des Eltern-Prozesses muss dupliziert werden. In der Praxis nutzt Linux Copy-on-Write: Beide Prozesse teilen sich zunächst dieselben Speicherseiten, und erst beim ersten Schreibzugriff wird die betroffene Seite wirklich kopiert. Da nach fork() meistens sofort exec() folgt und das Kind den ganzen Speicher ohnehin verwirft, fällt die Kopie oft komplett aus.
cgroups gruppieren Prozesse für Limits
Klassische Unix-Konzepte (Process Group, Session) reichen für moderne Anforderungen wie Container und Quotas nicht aus. Linux ergänzt sie mit Control Groups (cgroups): hierarchische Gruppen, in denen CPU, RAM, I/O und Netzwerk-Bandbreite pro Gruppe limitiert werden können. systemd organisiert jeden Service automatisch in einer eigenen cgroup — so lässt sich ein einzelner Dienst sauber auf z. B. 1 GB RAM beschränken. Details im systemd-Kapitel.
Namespaces als Container-Grundlage
Während cgroups Ressourcen limitieren, isolieren Namespaces den Sichtbereich eines Prozesses. Es gibt Namespaces für PIDs, Mount-Points, Netzwerk, User-IDs, Hostnamen und IPC. Ein Prozess in einem eigenen PID-Namespace sieht sich selbst als PID 1 — die Grundlage von Docker, Podman und LXC. Linux liefert die Bausteine, Container-Engines kombinieren sie zu vollständig isolierten Umgebungen.
Weiterführende Ressourcen
Externe Quellen
- man 5 proc (man7.org) — vollständige Referenz zu /proc und allen Pseudo-Dateien
- GNU C Library: Process Creation — fork, exec und wait aus Sicht der glibc
- Arch Wiki: Processes — kompakte Übersicht zu Prozess-Werkzeugen unter Linux
- kernel.org Documentation: Scheduler — wie der Kernel Prozesse einplant
- Linux Programmer’s Manual: fork(2) — die Mutter aller Prozess-Systemaufrufe
Verwandte Artikel
- ps — Prozesse anzeigen — die wichtigsten Optionen und Spalten verstehen
- top und htop — Live-Monitoring mit Sortierung und Filtern
- kill und Prozesse beenden — Signale gezielt zustellen
- Signale unter Linux — von SIGINT bis SIGKILL und ihre Bedeutung
- systemd — Services und Units — wie PID 1 Dienste verwaltet