2010-04-01 9 views
12
zurück

Ich weiß, dass fork() für die untergeordneten und übergeordneten Prozesse unterschiedlich zurückgibt, aber ich bin nicht in der Lage, Informationen darüber zu finden, wie dies geschieht. Wie erhält der untergeordnete Prozess den Rückgabewert 0 von fork? Und was ist der Unterschied in Bezug auf den Call-Stack? Wie ich es verstehe, für die Eltern geht es in etwa so:Wie gibt fork() für Kindprozess

Parent-Prozess - ruft Fork -> System_Call - ruft Fork -> Fork führt - kehrt zu -> System_Call - kehrt zurück zu -> Elternprozess.

Was passiert im Child-Prozess?

+7

Es ist häufiger „Eltern“ und „Kind“ Prozess bezeichnet. Leute könnten dich des Sexismus beschuldigen. – Thomas

Antwort

21

% Mann Gabel

RETURN WERTE

Upon successful completion, fork() returns a value of 0 to the child 
process and returns the process ID of the child process to the parent 
process. Otherwise, a value of -1 is returned to the parent process, no 
child process is created, and the global variable [errno][1] is set to indi- 
cate the error. 

Was das dupliziert innerhalb der Systemaufruf fork ist geschieht, wird der gesamte Prozess. Dann kehrt der Gabelruf in jedem zurück. Dies sind jedoch unterschiedliche Kontexte, sodass sie unterschiedliche Rückgabecodes zurückgeben können.

Wenn Sie wirklich wissen wollen, wie es auf einem niedrigen Niveau funktioniert, können Sie immer check the source! Der Code ist ein wenig verwirrend, wenn Sie nicht gewohnt sind, Kernel-Code zu lesen, aber die Inline-Kommentare geben einen guten Hinweis darauf, was gerade passiert.

Der interessanteste Teil der Quelle mit einer expliziten Antwort auf Ihre Frage ist ganz am Ende der fork() Definition selbst -

if (error == 0) { 
    td->td_retval[0] = p2->p_pid; 
    td->td_retval[1] = 0; 
} 

„td“ offenbar eine Liste des Rückgabewertes gilt für verschiedene Themen. Ich bin mir nicht sicher, wie genau dieser Mechanismus funktioniert (warum gibt es nicht zwei separate "Thread" -Strukturen). Wenn der Fehler (von fork1 zurückgegeben, die "echte" Verzweigungsfunktion) 0 ist (kein Fehler), dann nimm den "ersten" (übergeordneten) Thread und setze seinen Rückgabewert auf p2 (der neue Prozess). Wenn es sich um den "zweiten" Thread (in p2) handelt, setzen Sie den Rückgabewert auf 0.

+0

Ist das sys/kern/kern_fork.c - Linux-Quelle? – osgx

+0

@osgx: Nein, die angegebene Quelle stammt von FreeBSD. – jxh

0

Der Prozess erscheint von beiden Seiten identisch, bis auf den abweichenden Rückgabewert (deshalb ist der Rückgabewert da, so dass die beiden Prozesse den Unterschied überhaupt erkennen können!). Was den Sohn-Prozess betrifft, so wurde er genauso von system_call zurückgegeben, wie der Elternprozess zurückgegeben wurde.

+0

Ja, aber wie funktionieren die verschiedenen Rückgabewerte? Wie kann eine Funktion zwei verschiedene Werte zurückgeben? – EpsilonVector

+0

Da der Kern-Kernel-Code, der 'fork' unterstützt, sich darum kümmert, gibt es zwei getrennte Returns, und daher kann jeder einen anderen Wert haben. – Amber

7

Der Systemaufruf fork() wird zweimal zurückgegeben (sofern nicht fehlgeschlagen).

  • Einer der kehrt in den untergeordneten Prozess, und es ist der Rückgabewert 0.

  • Die andere Rückkehr in den Prozess Elternteil ist, und es ist der Rückgabewert nicht Null ist (entweder negativ, wenn die Gabelung fehlgeschlagen ist, oder ein Wert ungleich Null, der die PID des Kindes angibt).

Die wichtigsten Unterschiede zwischen dem Elternteil und das Kind sind:

  • Sie sind separate Prozesse
  • Der Wert der PID unterscheidet
  • Der Wert von PPID (parent PID) ist anders

Weitere obskure Unterschiede sind im POSIX Standard aufgeführt.

In einem Sinne, die Wie ist wirklich nicht dein Problem. Das Betriebssystem wird benötigt, um das Ergebnis zu erzielen. Die o/s klont jedoch den übergeordneten Prozess und erstellt einen zweiten untergeordneten Prozess, bei dem es sich um ein nahezu exaktes Replikat des übergeordneten Prozesses handelt. Dabei werden die Attribute festgelegt, die sich von den korrekten neuen Werten unterscheiden müssen schreiben) oder gleichwertig, so dass, wenn ein Prozess einen Wert ändert, er eine separate Kopie der Seite erhält, um die andere nicht zu stören. Dies ist nicht wie der veraltete (von mir mindestens - nicht-Standard für POSIX) vfork() Systemaufruf, die Sie klugerweise vermeiden sollten, auch wenn es auf Ihrem System verfügbar ist. Jeder Prozess fährt nach dem fork() fort, als ob die Funktion zurückkehrt - also (wie ich oben sagte), der Systemaufruf fork() kehrt zweimal zurück, einmal in jedem von zwei Prozessen, die nahezu identische Klone voneinander sind.

+4

Ich wünschte, der Vfork() - Fanjunge würde erklären, warum sie eine Abstimmung abgegeben haben. –

+0

Schöne Erklärung und nette Verbindung zum neuesten POSIX-Standard (Ausgabe 7)! –

+1

+1 für den Vfork Fanboy zu kompensieren. –

3

Antwort Steven Schlansker das ist ganz gut, aber nur etwas mehr Details hinzuzufügen:

Jede Ausführung Prozess hat einen zugeordneten Kontext (daher „Kontextwechsel“) - In diesem Zusammenhang umfasst unter anderem das Codesegment des Prozesses (enthält die Maschinenanweisungen), seinen Heap-Speicher, seinen Stack und seinen Registerinhalt. Wenn ein Kontextwechsel stattfindet, wird der Kontext aus dem alten Prozess gespeichert und der Kontext aus dem neuen Prozess wird geladen.

Der Speicherort für einen Rückgabewert wird vom ABI definiert, um die Code-Interoperabilität zu ermöglichen. Wenn ich ASM-Code für meinen x86-64-Prozessor schreibe und in die C-Laufzeit rufe, weiß ich, dass der Rückgabewert im RAX-Register angezeigt wird.

Setzt man diese beiden Dinge zusammen, ist die logische Schlussfolgerung, dass der Anruf zu int pid = fork() Ergebnisse in zwei Kontexten, wo der nächste Befehl in jedem auszuführen ist, die den Wert von RAX (der Rückgabewert von dem fork call) bewegt sich in die lokale Variable pid. Natürlich kann nur ein Prozess gleichzeitig auf einer einzelnen CPU ausgeführt werden, so dass die Reihenfolge, in der diese "Rückgaben" stattfinden, vom Scheduler bestimmt wird.

1

Ich werde versuchen, aus dem Blickwinkel des Prozessspeichers Layout zu beantworten. Leute, bitte korrigiert mich, wenn etwas falsch oder ungenau ist. Schaffung im Kernel

fork() ist der einzige System Aufruf zur Prozesserstellung (mit Ausnahme des Anfang Prozess 0), so dass die Frage ist eigentlich, was mit Prozess geschieht. Es gibt zwei Kernel-Datenstrukturen, die mit dem Prozess, dem Struct-Proc-Array (auch als Prozesstabelle bezeichnet) und dem Struct-User (auch als Area bezeichnet) in Beziehung stehen.

Um einen neuen Prozess zu erstellen, müssen diese beiden Datenstrukturen korrekt erstellt oder parametrisiert werden. Der unkomplizierte Weg ist es, mit dem Bereich (&) des Creaters (oder Elternteils) übereinzustimmen. Die meisten Daten werden zwischen dem Elternteil & Kind (z.B., das Code-Segment), mit Ausnahme der Werte im Rückgabe-Register (z. B. EAX in 80x86), für die Eltern mit Kind pid und Kind 0 ist. Seitdem haben Sie zwei Prozesse (bestehende eine & neue) vom Scheduler ausgeführt und bei der Planung gibt jeder seine Werte zurück.

7

Sowohl Eltern als auch Kind gibt verschiedene Werte aufgrund der Manipulation der CPU-Register im Kontext des Kindes zurück.

Jeder Prozess im Linux-Kernel wird durch task_struct repräsentiert. task_struct ist in der thread_info-Struktur eingeschlossen (Zeiger), die am Ende des Kernel-Modus-Stacks liegt. Whole CPU-Kontext (Register) sind in dieser thread_info-Struktur gespeichert.

struct thread_info { 
    struct task_struct *task;  /* main task structure */ 
    struct cpu_context_save cpu_context; /* cpu context */ 
} 

Alle fork/clone() - Systemaufrufe ruft Kernel-gleichwertige Funktion do_fork().

long do_fork(unsigned long clone_flags, 
      unsigned long stack_start, 
      struct pt_regs *regs, 
      unsigned long stack_size, 
      int __user *parent_tidptr, 
      int __user *child_tidptr) 

Hier ist die Reihenfolge der Ausführung

do_fork() -> copy_process-> copy_thread() (copy_thread ist arch spezifische Funktionsaufruf)

copy_thread() kopiert die Registerwerte von dem Elternteil und ändert den Rückgabewert zu 0 (im Fall des Arms)

struct pt_regs *childregs = task_pt_regs(p); 
*childregs = *regs; /* Copy register value from parent process*/ 
childregs->ARM_r0 = 0; /*Change the return value*/ 
thread->cpu_context.sp = (unsigned long)childregs;/*Write back the value to thread info*/ 
thread->cpu_context.pc = (unsigned long)ret_from_fork; 

Wenn das untergeordnete Element eingeplant wird, führt es eine Assembly-Routine ret_from_fork() aus, die Null zurückgibt. Für die Eltern wird es den Rückgabewert aus dem do_fork(), dem Prozess pid von

ist
nr = task_pid_vnr(p); 
return nr;