Der Unterschied zwischen fork (), vfork (), exec () und clone ()


198

Ich habe nach dem Unterschied zwischen diesen vier bei Google gesucht und erwartet, dass es eine große Menge an Informationen dazu gibt, aber es gab wirklich keinen soliden Vergleich zwischen den vier Anrufen.

Ich habe versucht, eine Art grundlegenden Überblick über die Unterschiede zwischen diesen Systemaufrufen zu erhalten, und hier ist, was ich bekommen habe. Sind alle diese Informationen korrekt / fehlt mir etwas Wichtiges?

Fork : Der Fork-Aufruf erstellt im Grunde genommen ein Duplikat des aktuellen Prozesses, das in fast jeder Hinsicht identisch ist (in einigen Implementierungen wird nicht alles kopiert, z. B. Ressourcenbeschränkungen, aber die Idee ist, eine möglichst enge Kopie zu erstellen).

Der neue Prozess (untergeordnet) erhält eine andere Prozess-ID (PID) und hat die PID des alten Prozesses (übergeordnet) als übergeordnete PID (PPID). Da die beiden Prozesse jetzt genau denselben Code ausführen, können sie anhand des Rückkehrcodes von fork erkennen, welcher Code welcher ist - das Kind erhält 0, das Elternteil erhält die PID des Kindes. Dies alles ist natürlich vorausgesetzt, dass der Fork-Aufruf funktioniert. Wenn nicht, wird kein untergeordnetes Element erstellt und das übergeordnete Element erhält einen Fehlercode.

Vfork: Der grundlegende Unterschied zwischen vfork und fork besteht darin, dass beim Erstellen eines neuen Prozesses mit vfork () der übergeordnete Prozess vorübergehend angehalten wird und der untergeordnete Prozess möglicherweise den Adressraum des übergeordneten Prozesses ausleiht. Dieser seltsame Zustand setzt sich fort, bis der untergeordnete Prozess entweder beendet wird oder execve () aufruft. An diesem Punkt wird der übergeordnete Prozess fortgesetzt.

Dies bedeutet, dass der untergeordnete Prozess einer vfork () vorsichtig sein muss, um zu vermeiden, dass Variablen des übergeordneten Prozesses unerwartet geändert werden. Insbesondere darf der untergeordnete Prozess nicht von der Funktion zurückkehren, die den Aufruf von vfork () enthält, und er darf nicht exit () aufrufen (wenn er beendet werden muss, sollte er _exit () verwenden; dies gilt tatsächlich auch für das untergeordnete Element einer normalen Gabel ()).

Exec :Der Exec-Aufruf ist eine Möglichkeit, den gesamten aktuellen Prozess durch ein neues Programm zu ersetzen. Es lädt das Programm in den aktuellen Prozessbereich und führt es vom Einstiegspunkt aus. exec () ersetzt den aktuellen Prozess durch die ausführbare Datei, auf die die Funktion zeigt. Die Steuerung kehrt niemals zum ursprünglichen Programm zurück, es sei denn, es liegt ein exec () - Fehler vor.

Clone :Klonen als Gabel erzeugt einen neuen Prozess. Im Gegensatz zu Fork ermöglichen diese Aufrufe dem untergeordneten Prozess, Teile seines Ausführungskontexts mit dem aufrufenden Prozess zu teilen, z. B. den Speicherplatz, die Tabelle der Dateideskriptoren und die Tabelle der Signalhandler.

Wenn der untergeordnete Prozess mit dem Klon erstellt wird, führt er die Funktionsanwendung fn (arg) aus. (Dies unterscheidet sich von Fork, bei dem die Ausführung im untergeordneten Element ab dem Zeitpunkt des ursprünglichen Fork-Aufrufs fortgesetzt wird.) Das Argument fn ist ein Zeiger auf eine Funktion, die vom untergeordneten Prozess zu Beginn seiner Ausführung aufgerufen wird. Das Argument arg wird an die Funktion fn übergeben.

Wenn die Funktionsanwendung fn (arg) zurückkehrt, wird der untergeordnete Prozess beendet. Die von fn zurückgegebene Ganzzahl ist der Exit-Code für den untergeordneten Prozess. Der untergeordnete Prozess kann auch explizit durch Aufrufen von exit (2) oder nach Empfang eines schwerwiegenden Signals beendet werden.

Informationen erhalten Form:

Vielen Dank, dass Sie sich die Zeit genommen haben, dies zu lesen! :) :)


2
Warum darf vfork exit () nicht aufrufen? Oder nicht zurückkehren? Verwendet exit () nicht einfach _exit ()? Ich versuche auch zu verstehen :)
LazerSharks

2
@Gnuey: weil es möglicherweise (wenn es anders implementiert fork()ist als unter Linux und wahrscheinlich allen BSDs) den Adressraum seiner Eltern ausleiht. Alles, was es tut, außer anzurufen execve()oder _exit(), hat ein großes Potenzial, die Eltern durcheinander zu bringen. Insbesondere exit()ruft atexit()Handler und andere „Finalizers“, zum Beispiel: es stdio Streams spült. Die Rückkehr von einem vfork()Kind würde möglicherweise (dieselbe Einschränkung wie zuvor) den Stapel der Eltern durcheinander bringen.
Ninjalj

Ich habe mich gefragt, was mit den Threads des übergeordneten Prozesses passiert. Sind alle geklont oder nur der Thread, der den forkSyscall aufruft ?
Mohammad Jafar Mashhadi

@LazerSharks vfork erzeugt einen threadähnlichen Prozess, bei dem der Speicher ohne Kopier- / Schreibschutz gemeinsam genutzt wird, sodass das Ausführen von Stapeln den übergeordneten Prozess in den Papierkorb werfen kann.
Jasen

Antworten:


160
  • vfork()ist eine veraltete Optimierung. Vor einer guten Speicherverwaltung wurde fork()eine vollständige Kopie des Speichers der Eltern erstellt, sodass dies ziemlich teuer war. Da in vielen Fällen a fork()folgte exec(), wodurch die aktuelle Speicherzuordnung verworfen und eine neue erstellt wird, war dies ein unnötiger Aufwand. Heutzutage wird fork()der Speicher nicht kopiert. Es wird einfach als "Kopieren beim Schreiben" festgelegt, sodass fork()+ exec()genauso effizient ist wie vfork()+ exec().

  • clone()ist der von fork(). Mit einigen Parametern wird ein neuer Prozess erstellt, mit anderen wird ein Thread erstellt. Der Unterschied zwischen ihnen besteht darin, welche Datenstrukturen (Speicherplatz, Prozessorstatus, Stapel, PID, geöffnete Dateien usw.) gemeinsam genutzt werden oder nicht.



22
vforkvermeidet die Notwendigkeit, vorübergehend viel mehr Speicher zu belegen, nur damit ausgeführt werden kann exec, und es ist immer noch effizienter als fork, wenn auch nicht annähernd so hoch. Auf diese Weise kann vermieden werden, dass der Speicher überlastet werden muss, damit ein großes Programm einen untergeordneten Prozess auslösen kann. Dies ist also nicht nur eine Leistungssteigerung, sondern könnte es überhaupt machbar machen.
Deduplikator

5
Eigentlich habe ich aus erster Hand gesehen, wie billig fork () ist, wenn Ihr RSS groß ist. Ich nehme an, das liegt daran, dass der Kernel noch alle Seitentabellen kopieren muss.
Martina Ferrari

4
Es muss alle Seitentabellen kopieren, den gesamten beschreibbaren Speicher in beiden Prozessen kopieren , den TLB leeren und dann alle Änderungen am übergeordneten Speicher zurücksetzen (und den TLB erneut leeren) exec.
zwol

3
vfork ist in cygwin (einer Kernel-emulierenden DLL, die auf Microsoft Windows ausgeführt wird) weiterhin nützlich. cygwin kann keine effiziente Verzweigung implementieren, da das zugrunde liegende Betriebssystem keine hat.
Strg-Alt-Delor

81
  • execve() Ersetzt das aktuelle ausführbare Image durch ein anderes, das aus einer ausführbaren Datei geladen wurde.
  • fork() erstellt einen untergeordneten Prozess.
  • vfork()ist eine historisch optimierte Version von fork(), die verwendet werden soll, wenn execve()sie direkt danach aufgerufen wird fork(). Es stellte sich heraus, dass es in Nicht-MMU-Systemen (in denen fork()nicht effizient gearbeitet werden kann) und in fork()Prozessen mit großem Speicherbedarf gut funktioniert , um ein kleines Programm auszuführen (denken Sie an Java Runtime.exec()). POSIX hat das standardisiert posix_spawn(), um diese beiden letztgenannten moderneren Anwendungen zu ersetzen vfork().
  • posix_spawn()macht das Äquivalent von a fork()/execve()und erlaubt auch etwas fd Jonglieren dazwischen. Es soll ersetzen fork()/execve(), hauptsächlich für Nicht-MMU-Plattformen.
  • pthread_create() erstellt einen neuen Thread.
  • clone()ist ein Linux-spezifischer Aufruf, mit dem alles von fork()bis implementiert werden kann pthread_create(). Es gibt viel Kontrolle. Inspiriert am rfork().
  • rfork()ist ein Plan-9-spezifischer Anruf. Es soll ein generischer Aufruf sein, der mehrere Grade der gemeinsamen Nutzung zwischen vollständigen Prozessen und Threads ermöglicht.

2
Vielen Dank, dass Sie mehr Informationen hinzugefügt haben, als tatsächlich angefordert wurden. Dies hat mir geholfen, meine Zeit zu sparen
Neeraj,

5
Plan 9 ist so ein Scherz.
JJ

1
Für diejenigen, die sich nicht erinnern können, was MMU bedeutet: "Memory Management Unit" - weiterführende Literatur auf Wikipedia
mgarey

43
  1. fork()- Erstellt einen neuen untergeordneten Prozess, der eine vollständige Kopie des übergeordneten Prozesses ist. Untergeordnete und übergeordnete Prozesse verwenden unterschiedliche virtuelle Adressräume, die anfänglich von denselben Speicherseiten gefüllt werden. Wenn dann beide Prozesse ausgeführt werden, unterscheiden sich die virtuellen Adressräume immer mehr, da das Betriebssystem ein verzögertes Kopieren von Speicherseiten durchführt, die von einem dieser beiden Prozesse geschrieben werden, und eine unabhängige Kopie der geänderten Seiten von zuweist Speicher für jeden Prozess. Diese Technik wird als Copy-On-Write (COW) bezeichnet.
  2. vfork()- Erstellt einen neuen untergeordneten Prozess, bei dem es sich um eine "schnelle" Kopie des übergeordneten Prozesses handelt. Im Gegensatz zum Systemaufruf fork()teilen sich untergeordnete und übergeordnete Prozesse denselben virtuellen Adressraum. HINWEIS! Bei Verwendung des gleichen virtuellen Adressraums verwenden sowohl das übergeordnete als auch das untergeordnete Element denselben Stapel, den Stapelzeiger und den Anweisungszeiger, wie im Fall des Klassikers fork()! Um unerwünschte Interferenzen zwischen übergeordneten und untergeordneten Elementen zu vermeiden, die denselben Stapel verwenden, wird die Ausführung des übergeordneten Prozesses eingefroren, bis das untergeordnete Element entweder exec()(Erstellen eines neuen virtuellen Adressraums und Übergang zu einem anderen Stapel) oder _exit()(Beenden der Prozessausführung) aufruft ). vfork()ist die Optimierung der fork()für „fork-and-exec“ -Modell. Es kann 4-5 mal schneller als das durchgeführt werden fork(), weil im Gegensatz zumfork()(Selbst wenn COW im Auge behalten wird) beinhaltet die Implementierung eines vfork()Systemaufrufs nicht die Erstellung eines neuen Adressraums (die Zuweisung und Einrichtung neuer Seitenverzeichnisse).
  3. clone()- Erstellt einen neuen untergeordneten Prozess. Verschiedene Parameter dieses Systemaufrufs geben an, welche Teile des übergeordneten Prozesses in den untergeordneten Prozess kopiert werden müssen und welche Teile zwischen ihnen geteilt werden. Infolgedessen kann dieser Systemaufruf verwendet werden, um alle Arten von Ausführungsentitäten zu erstellen, beginnend mit Threads und endend durch völlig unabhängige Prozesse. Tatsächlich ist der clone()Systemaufruf die Basis, die für die Implementierung pthread_create()und die gesamte Familie der fork()Systemaufrufe verwendet wird.
  4. exec()- setzt den gesamten Speicher des Prozesses zurück, lädt und analysiert die angegebene ausführbare Binärdatei, richtet einen neuen Stapel ein und übergibt die Steuerung an den Einstiegspunkt der geladenen ausführbaren Datei. Dieser Systemaufruf gibt niemals die Kontrolle an den Aufrufer zurück und dient zum Laden eines neuen Programms in den bereits vorhandenen Prozess. Dieser Systemaufruf mitfork() Systemaufruf bildet zusammen ein klassisches UNIX-Prozessmanagementmodell namens "fork-and-exec".

2
Beachten Sie, dass die BSD- und POSIX-Anforderungen für vforkso schwach sind, dass es legal wäre, vforkein Synonym für zu erstellen fork(und POSIX.1-2008 wird vforkvollständig aus der Spezifikation entfernt). Wenn Sie Ihren Code auf einem System testen, das sie synonymisiert (z. B. die meisten Post-4.4-BSDs außer NetBSD, Linux-Kernel vor 2.2.0-pre6 usw.), funktioniert dies möglicherweise auch dann, wenn Sie gegen den vforkVertrag verstoßen und dann explodieren wenn Sie es woanders ausführen. Einige von denen, die es mit simulieren fork(z. B. OpenBSD), garantieren immer noch, dass der Elternteil erst dann wieder läuft, wenn das Kind execoder die Kinder _exit. Es ist lächerlich nicht tragbar.
ShadowRanger

2
Zum letzten Satz Ihres dritten Punktes: Ich habe unter Linux unter Verwendung von strace festgestellt, dass der glibc-Wrapper für fork () zwar den Klonsyscall aufruft, der Wrapper für vfork () jedoch den vfork-Syscall
aufruft

7

Fork (), vfork () und clone () rufen alle do_fork () auf, um die eigentliche Arbeit zu erledigen, jedoch mit unterschiedlichen Parametern.

asmlinkage int sys_fork(struct pt_regs regs)
{
    return do_fork(SIGCHLD, regs.esp, &regs, 0);
}

asmlinkage int sys_clone(struct pt_regs regs)
{
    unsigned long clone_flags;
    unsigned long newsp;

    clone_flags = regs.ebx;
    newsp = regs.ecx;
    if (!newsp)
        newsp = regs.esp;
    return do_fork(clone_flags, newsp, &regs, 0);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0);
}
#define CLONE_VFORK 0x00004000  /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_VM    0x00000100  /* set if VM shared between processes */

SIGCHLD means the child should send this signal to its father when exit.

Für Fork haben das Kind und der Vater die unabhängige VM-Seitentabelle, aber da Fork aus Effizienzgründen keine Seiten wirklich kopiert, werden nur alle beschreibbaren Seiten so eingestellt, dass sie für den untergeordneten Prozess schreibgeschützt sind. Wenn der untergeordnete Prozess etwas auf diese Seite schreiben möchte, tritt eine Seitenausnahme auf und der Kernel weist eine neue Seite zu, die mit Schreibberechtigung von der alten Seite geklont wurde. Das nennt man "copy on write".

Für vfork ist der virtuelle Speicher genau von Kind und Vater - nur deshalb können Vater und Kind nicht gleichzeitig wach sein, da sie sich gegenseitig beeinflussen. Der Vater wird also am Ende von "do_fork ()" schlafen und aufwachen, wenn das Kind exit () oder execve () aufruft, da es seitdem eine neue Seitentabelle besitzt. Hier ist der Code (in do_fork ()), den der Vater schläft.

if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;

Hier ist der Code (in mm_release (), der von exit () und execve () aufgerufen wird und den Vater weckt.

up(tsk->p_opptr->vfork_sem);

Für sys_clone () ist es flexibler, da Sie beliebige clone_flags eingeben können. Also ruft pthread_create () diesen Systemaufruf mit vielen clone_flags auf:

int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM);

Zusammenfassung: Mit fork (), vfork () und clone () werden untergeordnete Prozesse mit unterschiedlichen Ressourcen für die gemeinsame Nutzung von Ressourcen mit dem Vaterprozess erstellt. Wir können auch sagen, dass vfork () und clone () Threads erstellen können (tatsächlich sind sie Prozesse, da sie unabhängige task_struct haben), da sie die VM-Seitentabelle mit dem Vaterprozess gemeinsam nutzen.


-4

In fork () wird entweder der untergeordnete oder der übergeordnete Prozess basierend auf der CPU-Auswahl ausgeführt. In vfork () wird das untergeordnete Element sicherlich zuerst ausgeführt. Nachdem das Kind beendet wurde, wird das Elternteil ausgeführt.


3
Falsch. vfork()kann einfach als implementiert werden fork().
Ninjalj

Nach AnyFork () ist nicht definiert, wer das erste Elternteil / Kind ausführt.
AjayKumarBasuthkar

5
@ Raj: Sie haben einige konzeptionelle Missverständnisse, wenn Sie denken, dass es nach dem Gabeln einen impliziten Begriff der seriellen Reihenfolge gibt. Forking erstellt einen neuen Prozess und gibt dann die Kontrolle an beide Prozesse zurück (wobei jeder einen anderen zurückgibt pid). Das Betriebssystem kann den neuen Prozess so planen, dass er parallel ausgeführt wird, wenn dies sinnvoll ist (z. B. mehrere Prozessoren). Wenn Sie diese Prozesse aus irgendeinem Grund in einer bestimmten seriellen Reihenfolge ausführen müssen, benötigen Sie eine zusätzliche Synchronisation, die das Forking nicht bietet. Ehrlich gesagt, würden Sie wahrscheinlich gar nicht erst eine Gabel wollen.
Andon M. Coleman

Eigentlich @AjayKumarBasuthkar und @ninjalj, Sie sind beide falsch. mit vfork()läuft das Kind zuerst. Es ist in den Manpages; Die Hinrichtung der Eltern wird ausgesetzt, bis das Kind entweder stirbt oder execs. Und ninjalj schlägt den Kernel-Quellcode nach. Es gibt keine Möglichkeit zu implementieren vfork(), fork()da sie unterschiedliche Argumente an do_fork()den Kernel übergeben. Sie können jedoch vforkmit dem cloneSyscall
Zac Wimer

@ZacWimer: Siehe ShadowRangers Kommentar zu einer anderen Antwort. Stackoverflow.com/questions/4856255/… Altes Linux hat sie synonimisiert, wie es anscheinend andere BSDs als NetBSD (die tendenziell auf viele Nicht-MMU-Systeme portiert werden) tun. Aus der Linux-Manpage: In 4.4BSD wurde es synonym zu fork (2) gemacht, aber NetBSD führte es erneut ein; Siehe ⟨netbsd.org/Documentation/kernel/vfork.html⟩ . Unter Linux war es bis 2.2.0-pre6 oder so gleichbedeutend mit fork (2).
Ninjalj
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.