Der fork()
und vfork()
Wrapper in glibc werden über clone()
implementiert Systemaufruf. Um die Beziehung zwischen fork()
besser zu verstehen und clone()
, müssen wir die Beziehung zwischen Prozessen und Threads in Linux berücksichtigen.
Traditionell fork()
würde alle Ressourcen duplizieren, die dem übergeordneten Prozess gehören, und die Kopie dem untergeordneten Prozess zuweisen. Dieser Ansatz verursacht einen beträchtlichen Overhead, der alles umsonst sein könnte, wenn das Kind sofort exec()
ruft . Unter Linux fork()
verwendet Copy-on-Write Seiten, um das Kopieren der Daten zu verzögern oder ganz zu vermeiden, die zwischen den Eltern- und Kindprozessen geteilt werden können. Somit ist der einzige Overhead, der während eines normalen fork()
anfällt ist das Kopieren der Seitentabellen der Eltern und die Zuweisung einer eindeutigen Prozessdeskriptorstruktur, task_struct
, für das Kind.
Linux verfolgt auch einen außergewöhnlichen Ansatz für Threads. Unter Linux sind Threads lediglich gewöhnliche Prozesse, die zufällig einige Ressourcen mit anderen Prozessen teilen. Dies ist ein radikal anderer Ansatz für Threads im Vergleich zu anderen Betriebssystemen wie Windows oder Solaris, wo Prozesse und Threads völlig unterschiedliche Arten von Bestien sind. Unter Linux hat jeder Thread einen gewöhnlichen task_struct
ein eigener Prozess, der zufällig so eingerichtet ist, dass er bestimmte Ressourcen, wie z. B. einen Adressraum, mit dem übergeordneten Prozess teilt.
Die flags
Parameter des clone()
Der Systemaufruf enthält eine Reihe von Flags, die anzeigen, welche Ressourcen, falls vorhanden, die Eltern- und Kindprozesse gemeinsam nutzen sollten. Prozesse und Threads werden beide über clone()
erstellt , der einzige Unterschied ist der Satz von Flags, der an clone()
übergeben wird .
Ein normaler fork()
könnte implementiert werden als:
clone(SIGCHLD, 0);
Dadurch wird eine Aufgabe erstellt, die keine Ressourcen mit ihrer übergeordneten Aufgabe teilt und so eingestellt ist, dass sie SIGCHLD
sendet Beendigungssignal an den Elternteil, wenn er beendet wird.
Im Gegensatz dazu ein Task, der Adressraum, Dateisystemressourcen, Dateideskriptoren und Signalhandler mit dem Elternteil teilt, also einen Thread , könnte erstellt werden mit:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
vfork()
wiederum wird über einen separaten CLONE_VFORK
implementiert -Flag, wodurch der übergeordnete Prozess in den Ruhezustand versetzt wird, bis der untergeordnete Prozess ihn über ein Signal aufweckt. Das untergeordnete Element ist der einzige Ausführungsthread im Namespace des übergeordneten Elements, bis es exec()
aufruft oder Ausgänge. Das Kind darf nicht in den Speicher schreiben. Der entsprechende clone()
Der Aufruf könnte wie folgt lauten:
clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0)
Die Implementierung von sys_clone()
ist architekturspezifisch, aber der Großteil der Arbeit findet in do_fork()
statt definiert in kernel/fork.c
. Diese Funktion ruft den statischen clone_process()
auf , der einen neuen Prozess als Kopie des übergeordneten Prozesses erstellt, ihn aber noch nicht startet. clone_process()
kopiert die Register, weist der neuen Aufgabe eine PID zu und dupliziert oder teilt entsprechende Teile der Prozessumgebung, wie durch den Klon flags
spezifiziert . Wenn clone_process()
zurück, do_clone()
wird den neu erstellten Prozess aufwecken und seine Ausführung planen.
Die Komponente, die für die Übersetzung von Userland-Systemaufruffunktionen in Kernel-Systemaufrufe unter Linux verantwortlich ist, ist die libc. In GLibC leitet die NPTL-Bibliothek dies an clone(2)
um Systemaufruf.