Woher weiß fork(), wann 0 zurückgegeben werden soll?

Lesezeit: 10 Minuten

Benutzeravatar von Machina333
Machina333

Nehmen Sie das folgende Beispiel:

int main(void)
{
     pid_t  pid;

     pid = fork();
     if (pid == 0) 
          ChildProcess();
     else 
          ParentProcess();
}

Korrigieren Sie mich also, wenn ich falsch liege, sobald fork() ausgeführt wird, wird ein untergeordneter Prozess erstellt. Gehen Sie nun nach dieser Antwort, gibt fork() zweimal zurück. Das ist einmal für den Elternprozess und einmal für den Kindprozess.

Das bedeutet, dass WÄHREND des Fork-Aufrufs und nicht nach dessen Beendigung zwei separate Prozesse entstehen.

Jetzt verstehe ich nicht, wie es versteht, 0 für den untergeordneten Prozess und die richtige PID für den übergeordneten Prozess zurückzugeben.

Hier wird es wirklich verwirrend. Diese Antwort besagt, dass fork() funktioniert, indem die Kontextinformationen des Prozesses kopiert und der Rückgabewert manuell auf 0 gesetzt wird.

Erstens habe ich recht, wenn ich sage, dass die Rückkehr zu jeder Funktion in einem einzigen Register platziert wird? Da in einer Einzelprozessorumgebung ein Prozess nur eine Unterroutine aufrufen kann, die nur einen Wert zurückgibt (korrigieren Sie mich, wenn ich hier falsch liege).

Nehmen wir an, ich rufe eine Funktion foo() innerhalb einer Routine auf und diese Funktion gibt einen Wert zurück, dieser Wert wird in einem Register gespeichert, sagen wir BAR. Jedes Mal, wenn eine Funktion einen Wert zurückgeben möchte, verwendet sie ein bestimmtes Prozessorregister. Wenn ich also den Rückgabewert im Prozessblock manuell ändern kann, kann ich den an die Funktion zurückgegebenen Wert ändern, richtig?

Gehe ich also richtig in der Annahme, dass fork() so funktioniert?

  • Es gibt kein “es”, zwei Werte gleichzeitig zurückzugeben. Wann fork aufgerufen wird, übernimmt das Betriebssystem. Es erstellt zwei Kopien des Prozesses, die in jeder Hinsicht identisch sind, mit Ausnahme des Inhalts einer Speicherstelle. Dieser Speicherort ist zufällig einer, von dem schließlich zurückgegeben wird fork.

    – n. 1.8e9-wo-ist-meine-Aktie m.

    15. April 2016 um 7:13 Uhr

  • ‘WÄHREND des Fork-Aufrufs entstehen zwei getrennte Prozesse’ – nein, es existiert bereits eine. Während des Fork-Aufrufs entsteht ein zweiter Prozess.

    – Licht

    15. April 2016 um 17:47 Uhr

  • @nm Obwohl das nicht einmal notwendig ist. Anwendungs- oder Bibliothekscode könnte das Ergebnis von speichern getpid in einer Variablen. Wenn das Betriebssystem dann zurückkehrt, könnte es anrufen getpid wieder. Im übergeordneten Element stimmen die beiden überein. Beim Kind werden sie es nicht. Alles, was Sie brauchen, ist etwas, das Sie testen können, das sich zwischen den beiden unterscheidet, und die PID qualifiziert sich.

    – David Schwartz

    15. April 2016 um 22:39 Uhr


  • @DavidSchwartz Aber wie bekommt der Elternteil dann die PID des Kindes?

    – Sebastian Redl

    16. April 2016 um 9:37 Uhr

  • @SebastianRedl Wie es will. Sobald jeder Prozess weiß, was was ist, gibt es überhaupt keine weiteren Probleme. Beispielsweise könnte der Systemaufruf die PID des Kindes an beide Prozesse zurückgeben, und das Kind könnte sie ignorieren, während der Elternteil sie zurückgibt. Das einzige Problem, das Sie lösen müssen, ist, wie jeder Prozess weiß, um welchen es sich handelt. Danach kann alles andere gelöst werden, indem Sie beiden Prozessen dieselben Informationen zur Verfügung stellen und den “falschen” Prozess ignorieren lassen.

    – David Schwartz

    16. April 2016 um 19:22 Uhr

Benutzeravatar von paxdiablo
paxdiablo

Wie ob es funktioniert, ist weitgehend irrelevant – als Entwickler, der auf einem bestimmten Level arbeitet (zB Codierung für die UNIX-APIs), müssen Sie es wirklich nur wissen das Es klappt.

Abgesehen davon und der Erkenntnis, dass Neugier oder das Bedürfnis, etwas tiefgründig zu verstehen, im Allgemeinen eine gute Eigenschaft ist, gibt es eine Reihe von Möglichkeiten, dies zu tun könnte getan werden.

Zunächst einmal ist Ihre Behauptung, dass eine Funktion nur einen Wert zurückgeben kann, soweit richtig, aber Sie müssen bedenken, dass es nach der Prozessaufteilung tatsächlich gibt zwei Instanzen der laufenden Funktion, eine in jedem Prozess. Sie sind größtenteils unabhängig voneinander und können unterschiedlichen Codepfaden folgen. Das folgende Diagramm kann helfen, dies zu verstehen:

Process 314159 | Process 271828
-------------- | --------------
runs for a bit |
calls fork     |
               | comes into existence
returns 271828 | returns 0

Sie können dort hoffentlich sehen, dass a Single Instanz von fork kann nur einen Wert zurückgeben (wie bei jeder anderen C-Funktion), aber es werden tatsächlich mehrere Instanzen ausgeführt, weshalb es in der Dokumentation heißt, dass es mehrere Werte zurückgibt.


Hier ist eine Möglichkeit, wie es geht könnte Arbeit.

Wenn der fork() Wenn die Funktion ausgeführt wird, speichert sie die aktuelle Prozess-ID (PID).

Dann, wenn es an der Zeit ist, zurückzukehren, wenn die PID die gleiche wie die gespeicherte ist, ist es der Elternteil. Sonst ist es das Kind. Pseudocode folgt:

def fork():
    saved_pid = getpid()

    # Magic here, returns PID of other process or -1 on failure.

    other_pid = split_proc_into_two();

    if other_pid == -1:        # fork failed -> return -1
        return -1

    if saved_pid == getpid():  # pid same, parent -> return child PID
        return other_pid

    return 0                   # pid changed, child, return zero

Beachten Sie, dass viel Magie darin steckt split_proc_into_two() rufen Sie an und unter der Decke wird es mit ziemlicher Sicherheit nicht funktionieren(a). Es dient nur zur Veranschaulichung der damit verbundenen Konzepte, die im Wesentlichen sind:

  • Holen Sie sich die ursprüngliche PID vor der Aufteilung, die nach der Aufteilung für beide Prozesse identisch bleibt.
  • mach die Trennung.
  • Holen Sie sich die aktuelle PID nach der Aufteilung, die sein wird anders in den beiden Prozessen.

Vielleicht möchten Sie sich auch diese Antwort ansehen, sie erklärt die fork/exec Philosophie.


(a) Es ist mit ziemlicher Sicherheit komplexer, als ich erklärt habe. Zum Beispiel in MINIX der Aufruf an fork endet im Kernel, der Zugriff auf den gesamten Prozessbaum hat.

Es kopiert einfach die übergeordnete Prozessstruktur in einen freien Slot für das Kind, nach dem Vorbild von:

sptr = (char *) proc_addr (k1); // parent pointer
chld = (char *) proc_addr (k2); // child pointer
dptr = chld;
bytes = sizeof (struct proc);   // bytes to copy
while (bytes--)                 // copy the structure
    *dptr++ = *sptr++;

Dann werden geringfügige Änderungen an der untergeordneten Struktur vorgenommen, um sicherzustellen, dass sie geeignet ist, einschließlich der Zeile:

chld->p_reg[RET_REG] = 0;       // make sure child receives zero

Also im Grunde identisch mit dem von mir postulierten Schema, aber mit Datenänderungen anstelle der Codepfadauswahl, um zu entscheiden, was an den Aufrufer zurückgegeben werden soll – mit anderen Worten, Sie würden so etwas sehen wie:

return rpc->p_reg[RET_REG];

am Ende von fork() damit der richtige Wert zurückgegeben wird, je nachdem, ob es sich um den übergeordneten oder den untergeordneten Prozess handelt.

  • fork() gibt die zurück PID des Kindes im Elternteil und kehrt zurück 0 im Kind, so kehrt es zurück zwei Werte, einer im Elternteil (PID) und einer im Kind (0) (bei Erfolg …)

    – Ralf Htp

    15. April 2016 um 7:15 Uhr

  • @ralfhtp, das hängt ganz von deiner Definition von “es” ab. Eine einzelne Instanz von fork nur Rücksendungen eines Wert, es ist nur da, jetzt zwei Kopien, jeweils eine im untergeordneten und im übergeordneten Prozess.

    – paxdiablo

    15. April 2016 um 7:18 Uhr

  • @Machina333, ja, in Bezug auf die C-Umgebung “virtuelles Gerät”, die der Standard beschreibt, kann nur ein Wert von einer Funktion zurückgegeben werden. Forking funktioniert außerhalb des Geltungsbereichs des Standards, sodass der Standard nur für eine einzige Instanz wirklich gilt.

    – paxdiablo

    15. April 2016 um 8:59 Uhr


  • Nicht genug JQuery. “Wie es funktioniert, ist für Sie vielleicht weitgehend irrelevant”. Aber die Frage ist klar, OP fragt genau nach dem “Wie”.

    – vz0

    15. April 2016 um 16:06 Uhr

  • @paxdiablo: Nein, Fork gibt nur einen einzigen Wert zurück. Es ist nur so, dass nach dem Aufruf von Fork zwei separate Prozesse laufen und Fork in jedem einen anderen Wert zurückgibt. Es ist wirklich nicht anders, als wenn zwei Instanzen eines Programms ausgeführt werden und einige Funktionsaufrufe innerhalb des Programms je nach Zustand jeder Instanz unterschiedliche Werte zurückgeben.

    – jamesqf

    15. April 2016 um 17:31 Uhr

Antti Haapala -- Benutzeravatar von Слава Україні
Antti Haapala – Слава Україні

Unter Linux fork() passiert im Kernel; der eigentliche ort ist der _do_fork hier. Vereinfacht, die fork() Systemaufruf könnte so etwas wie sein

pid_t sys_fork() {
    pid_t child = create_child_copy();
    wait_for_child_to_start();
    return child;
}

Also im Kernel, fork() kehrt wirklich zurück einmal, in den übergeordneten Prozess. Der Kernel erstellt jedoch auch den Kindprozess als Kopie des Elternprozesses; aber anstatt von einer gewöhnlichen Funktion zurückzukehren, würde es synthetisch eine neue erzeugen Kernel-Stack für den neu erstellten Thread des untergeordneten Prozesses; und dann Kontextwechsel zu diesem Thread (und Prozess); Wenn der neu erstellte Prozess von der Kontextwechselfunktion zurückkehrt, würde der Thread des untergeordneten Prozesses dazu führen, dass er mit 0 als Rückgabewert von in den Benutzermodus zurückkehrt fork().


Grundsätzlich fork() Im Userland gibt nur ein dünner Wrapper den Wert zurück, den der Kernel auf seinen Stapel / in das Rückgaberegister gelegt hat. Der Kernel richtet den neuen untergeordneten Prozess so ein, dass er über diesen Mechanismus von seinem einzigen Thread 0 zurückgibt; und die untergeordnete PID wird im übergeordneten Systemaufruf wie jeder andere Rückgabewert von jedem Systemaufruf zurückgegeben, z read(2) wäre.

  • “synthetisch einen neuen Stapel für den untergeordneten Prozess erstellen” fällt unter die Kategorie “kopiert den gesamten Adressraum des übergeordneten Prozesses” – an diesem Teil gibt es nichts anderes.

    – ClickRick

    15. April 2016 um 12:37 Uhr

  • @ClickRick nein, absolut nicht. Hier geht es um den Kernel-Stack.

    – Antti Haapala – Слава Україні

    15. April 2016 um 12:38 Uhr


  • @ClickRick Der Kernel-Stack des Threads des untergeordneten Prozesses würde sich an einer anderen Adresse befinden, er kann nicht kopiert werden.

    – Antti Haapala – Слава Україні

    15. April 2016 um 12:42 Uhr


Benutzeravatar von Jean-Baptiste Yunès
Jean-Baptiste Yunes

Zunächst müssen Sie wissen, wie Multitasking funktioniert. Es ist nicht sinnvoll, alle Details zu verstehen, aber jeder Prozess läuft in einer Art virtueller Maschine, die vom Kernel gesteuert wird: Ein Prozess hat seinen eigenen Speicher, Prozessor und Register usw. Es gibt eine Abbildung dieser virtuellen Objekte auf die realen (Die Magie liegt im Kernel), und es gibt einige Maschinen, die im Laufe der Zeit virtuelle Kontexte (Prozesse) auf physische Maschinen übertragen.

Dann, wenn der Kernel einen Prozess verzweigt (fork() ist ein Eintrag in den Kernel) und erstellt eine Kopie von fast allem in der Elternteil Prozess zum Kind Prozess, ist es in der Lage, alles Notwendige zu ändern. Eine davon ist die Modifikation der entsprechenden Strukturen, um 0 für das Kind und die PID des Kindes im Elternteil vom aktuellen Aufruf zum Fork zurückzugeben.

Hinweis: Sagen Sie nicht “Fork gibt zweimal zurück”, ein Funktionsaufruf gibt nur einmal zurück.

Denken Sie nur an eine Klonmaschine: Sie treten alleine ein, aber zwei Personen verlassen den Raum, einer sind Sie und der andere ist Ihr Klon (sehr geringfügig anders). Während des Klonens kann die Maschine dem Klon einen anderen Namen als Ihren geben.

  • Tut is an entry to the kernel bedeuten syscall?

    – Katze

    15. April 2016 um 19:21 Uhr

  • Das ist es (in der Unix-Familie).

    – Jean-Baptiste Yunes

    15. April 2016 um 19:40 Uhr

  • Ich war nur neugierig auf den Wortlaut 🙂

    – Katze

    15. April 2016 um 19:47 Uhr

Der Fork-Systemaufruf erstellt einen neuen Prozess und kopiert viele Zustände des übergeordneten Prozesses. Dinge wie die Dateideskriptortabelle werden kopiert, die Speicherzuordnungen und deren Inhalte usw. Dieser Zustand befindet sich im Kernel.

Eines der Dinge, die der Kernel für jeden Prozess verfolgt, sind die Werte von Registern, die dieser Prozess bei der Rückkehr von einem Systemaufruf, Trap, Interrupt oder Kontextwechsel wiederhergestellt haben muss (die meisten Kontextwechsel finden bei Systemaufrufen oder Interrupts statt). Diese Register werden bei einem Syscall/Trap/Interrupt gespeichert und dann bei der Rückkehr ins Userland wiederhergestellt. Das System ruft Rückgabewerte auf, indem es in diesen Zustand schreibt. Was die Gabel tut. Der Parent-Fork erhält einen Wert, der Child-Prozess einen anderen.

Da sich der gegabelte Prozess vom übergeordneten Prozess unterscheidet, könnte der Kernel alles daran ändern. Geben Sie ihm beliebige Werte in Registern, geben Sie ihm beliebige Speicherzuordnungen. Tatsächlich sicherzustellen, dass fast alles außer dem Rückgabewert gleich ist wie im Elternprozess, erfordert mehr Aufwand.

Benutzeravatar von vz0
vz0

Für jeden laufenden Prozess hat der Kernel eine Tabelle mit Registern, die zurückgeladen werden, wenn ein Kontextwechsel durchgeführt wird. fork() ist ein Systemaufruf; ein spezieller Aufruf, bei dem der Prozess, wenn er ausgeführt wird, einen Kontextwechsel erhält und der Kernelcode, der den Aufruf ausführt, in einem anderen (Kernel-)Thread ausgeführt wird.

Der von Systemaufrufen zurückgegebene Wert wird in einem speziellen Register (EAX in x86) abgelegt, das Ihre Anwendung nach dem Aufruf liest. Wenn der fork() wird, erstellt der Kernel eine Kopie des Prozesses und schreibt in jede Registertabelle jedes Prozessdeskriptors den entsprechenden Wert: 0 und die PID.

1409680cookie-checkWoher weiß fork(), wann 0 zurückgegeben werden soll?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy