Wenn ein untergeordneter Prozess von vfork()
erstellt wurde ruft exec()
auf , nicht exec()
den Adressraum des übergeordneten Prozesses ändern, indem Sie das neue Programm laden?
Nein, exec()
stellt einen neuen Adressraum für das neue Programm bereit; Der übergeordnete Adressraum wird nicht geändert. Siehe zum Beispiel die Diskussion von exec
Funktionen in POSIX und Linux execve()
Handbuchseite.
Wenn ein von vfork() erstellter untergeordneter Prozess exit() aufruft, ändert exit() nicht den Adressraum des übergeordneten Prozesses, wenn der untergeordnete Prozess beendet wird?
Einfach exit()
könnte – es führt Exit-Hooks aus, die vom laufenden Programm (einschließlich seiner Bibliotheken) installiert wurden. vfork()
ist restriktiver; daher schreibt es unter Linux die Verwendung von _exit()
vor was nicht Rufen Sie die Aufräumfunktionen der C-Bibliothek auf.
vfork()
stellte sich als recht schwierig heraus; es wurde in aktuellen Versionen des POSIX-Standards und posix_spawn()
entfernt sollte stattdessen verwendet werden.
Allerdings, es sei denn, Sie wirklich wissen, was Sie tun, sollten Sie nicht Verwenden Sie entweder vfork()
oder posix_spawn()
; Bleiben Sie beim guten alten fork()
und exec()
.
Die oben verlinkte Linux-Manpage bietet mehr Kontext:
Allerdings war in den schlechten alten Zeiten ein fork(2)
würde erfordern, dass eine vollständige Kopie des Datenraums des Aufrufers erstellt wird, oft unnötig, da normalerweise unmittelbar danach ein exec(3)
erledigt. Daher hat BSD für mehr Effizienz den vfork()
eingeführt Systemaufruf, der den Adressraum des Elternprozesses nicht vollständig kopierte, sondern den Speicher und den Steuerungsthread des Elternprozesses bis zu einem Aufruf von execve(2)
ausborgte oder ein Austritt erfolgte. Der Elternprozess wurde angehalten, während der Kindprozess seine Ressourcen verwendete. Die Verwendung von vfork()
war knifflig:Zum Beispiel hing das Nichtverändern von Daten im Elternprozess davon ab, zu wissen, welche Variablen in einem Register gehalten werden.
Wenn Sie vfork()
anrufen , wird ein neuer Prozess erstellt und dieser neue Prozess leiht sich das Prozessabbild des übergeordneten Prozesses mit Ausnahme des Stacks. Der untergeordnete Prozess erhält einen eigenen neuen Stack-Stern, lässt jedoch return
nicht zu aus der Funktion, die vfork()
aufgerufen hat .
Während der untergeordnete Prozess läuft, ist der übergeordnete Prozess blockiert, da der untergeordnete Prozess den Adressraum des übergeordneten Prozesses ausgeliehen hat.
Unabhängig davon, was Sie tun, ändert alles, was nur auf den Stack zugreift, nur den privaten Stack des untergeordneten Elements. Wenn Sie jedoch globale Daten ändern, ändert dies die gemeinsamen Daten und wirkt sich somit auch auf die übergeordneten Daten aus.
Dinge, die globale Daten modifizieren, sind z. B.:
-
Aufruf von malloc() oder free()
-
mit stdio
-
Signaleinstellungen ändern
-
Ändern von Variablen, die nicht lokal für die Funktion sind, die
vfork()
aufgerufen hat . -
...
Rufen Sie einmal _exit()
an (Wichtig, niemals exit()
anrufen ), wird das untergeordnete Element beendet und die Kontrolle an das übergeordnete Element zurückgegeben.
Wenn Sie eine Funktion aus dem exec*()
aufrufen Familie wird ein neuer Adressraum mit neuem Programmcode, neuen Daten und einem Teil des Stacks vom Elternteil erstellt (siehe unten). Sobald diese fertig ist, leiht sich das Kind den Adressraum nicht mehr vom Kind, sondern verwendet einen eigenen Adressraum.
Die Kontrolle wird an den Elternprozess zurückgegeben, da sein Adressraum nicht mehr von einem anderen Prozess verwendet wird.
Wichtig:Unter Linux gibt es kein echtes vfork()
Implementierung. Linux implementiert vielmehr vfork()
basierend auf Copy on Write fork()
Konzept, das 1988 von SunOS-4.0 eingeführt wurde. Um Benutzer glauben zu machen, dass sie vfork()
verwenden , richtet Linux nur gemeinsam genutzte Daten ein und suspendiert das übergeordnete Element, während das untergeordnete Element _exit()
nicht aufgerufen hat oder einer der exec*()
Funktionen.
Linux profitiert also nicht davon, dass ein echter vfork()
muss keine Adressraumbeschreibung für das Kind im Kernel einrichten. Dies ergibt einen vfork()
das ist nicht schneller als fork()
. Auf Systemen, die einen echten vfork()
implementieren , ist es normalerweise 3x schneller als fork()
und wirkt sich auf die Leistung von Shells aus, die vfork()
verwenden - ksh93
, der aktuelle Bourne Shell
und csh
.
Der Grund, warum Sie niemals exit()
anrufen sollten aus dem vfork()
Ed Kind ist das exit()
löscht stdio, falls es ungelöschte Daten aus der Zeit vor dem Aufruf von vfork()
gibt . Dies könnte zu seltsamen Ergebnissen führen.
Übrigens:posix_spawn()
wird auf vfork()
implementiert , also vfork()
wird nicht aus dem Betriebssystem entfernt. Es wurde erwähnt, dass Linux vfork()
nicht verwendet für posix_spawn()
.
Für den Stack gibt es nur wenige Dokumentationen, hier ist, was die Solaris-Manpage sagt:
The vfork() and vforkx() functions can normally be used the
same way as fork() and forkx(), respectively. The calling
procedure, however, should not return while running in the
child's context, since the eventual return from vfork() or
vforkx() in the parent would be to a stack frame that no
longer exists.
Die Implementierung kann also machen, was sie will. Die Solaris-Implementierung verwendet gemeinsam genutzten Speicher für den Stapelrahmen der Funktion, die vfork()
aufruft . Keine Implementierung gewährt Zugriff auf ältere Teile des Stapels vom Elternteil.