In vielen Programmen und Manpages von Linux habe ich Code mit gesehen fork()
. Warum müssen wir verwenden fork()
und was ist ihr Zweck?
Antworten:
fork()
So erstellen Sie neue Prozesse in Unix. Wenn Sie anrufen fork
, erstellen Sie eine Kopie Ihres eigenen Prozesses mit einem eigenen Adressraum . Auf diese Weise können mehrere Aufgaben unabhängig voneinander ausgeführt werden, als hätten sie jeweils den vollen Speicher der Maschine für sich.
Hier sind einige Verwendungsbeispiele für fork
:
fork
die Programme, die Sie über die Befehlszeile aufrufen.fork
, um mehrere Serverprozesse zu erstellen, von denen jeder Anforderungen in seinem eigenen Adressraum verarbeitet. Wenn einer stirbt oder Speicher verliert, sind andere davon nicht betroffen, sodass er als Mechanismus für die Fehlertoleranz fungiert.fork
, um jede Seite in einem separaten Prozess zu behandeln. Dadurch wird verhindert, dass clientseitiger Code auf einer Seite Ihren gesamten Browser herunterfährt.fork
wird verwendet, um Prozesse in einigen parallelen Programmen (wie denen, die mit MPI geschrieben wurden) zu erzeugen . Beachten Sie, dass sich dies von der Verwendung von Threads unterscheidet , die keinen eigenen Adressraum haben und in einem Prozess vorhanden sind.fork
indirekt verwendet, um untergeordnete Prozesse zu starten. Beispielsweise verarbeiten Sie jedes Mal, wenn Sie einen Befehl wie subprocess.Popen
in Python verwenden, fork
ein untergeordnetes Element und lesen dessen Ausgabe. Dadurch können Programme zusammenarbeiten.Die typische Verwendung fork
in einer Shell sieht ungefähr so aus:
int child_process_id = fork();
if (child_process_id) {
// Fork returns a valid pid in the parent process. Parent executes this.
// wait for the child process to complete
waitpid(child_process_id, ...); // omitted extra args for brevity
// child process finished!
} else {
// Fork returns 0 in the child process. Child executes this.
// new argv array for the child process
const char *argv[] = {"arg1", "arg2", "arg3", NULL};
// now start executing some other program
exec("/path/to/a/program", argv);
}
Die Shell erzeugt einen untergeordneten Prozess mit exec
und wartet auf dessen Abschluss. Anschließend wird die eigene Ausführung fortgesetzt. Beachten Sie, dass Sie die Gabel nicht auf diese Weise verwenden müssen. Sie können immer viele untergeordnete Prozesse erzeugen, wie dies bei einem parallelen Programm der Fall ist, und jeder kann gleichzeitig ein Programm ausführen. Grundsätzlich verwenden Sie jedes Mal, wenn Sie neue Prozesse in einem Unix-System erstellen fork()
. Schauen Sie sich das Windows-Äquivalent an CreateProcess
.
Wenn Sie mehr Beispiele und eine längere Erklärung wünschen, hat Wikipedia eine anständige Zusammenfassung. Im Folgenden finden Sie einige Folien zur Funktionsweise von Prozessen, Threads und Parallelität in modernen Betriebssystemen.
fork()
der Weg ist, um einen neuen Prozess unter UNIX zu erstellen, aber um pedantisch zu sein, gibt es mindestens einen anderen : posix_spawn()
.
Mit fork () erstellt Unix neue Prozesse. An dem Punkt, an dem Sie fork () aufgerufen haben, wird Ihr Prozess geklont, und zwei verschiedene Prozesse setzen die Ausführung von dort aus fort. Bei einem von ihnen, dem Kind, gibt fork () 0 zurück. Bei dem anderen, dem Elternteil, gibt fork () die PID (Prozess-ID) des Kindes zurück.
Wenn Sie beispielsweise Folgendes in eine Shell eingeben, ruft das Shell-Programm fork () auf und führt dann den Befehl aus, den Sie (in diesem Fall telnetd) im untergeordneten Element übergeben haben, während das übergeordnete Element die Eingabeaufforderung ebenfalls erneut anzeigt als Nachricht, die die PID des Hintergrundprozesses angibt.
$ telnetd &
Aus dem Grund, warum Sie neue Prozesse erstellen, kann Ihr Betriebssystem viele Dinge gleichzeitig erledigen. Aus diesem Grund können Sie ein Programm ausführen und während der Ausführung zu einem anderen Fenster wechseln und etwas anderes tun.
fork () wird verwendet, um einen untergeordneten Prozess zu erstellen. Wenn eine fork () - Funktion aufgerufen wird, wird ein neuer Prozess erzeugt und der fork () - Funktionsaufruf gibt einen anderen Wert für das Kind und das Elternteil zurück.
Wenn der Rückgabewert 0 ist, wissen Sie, dass Sie der untergeordnete Prozess sind, und wenn der Rückgabewert eine Zahl ist (die zufällig die untergeordnete Prozess-ID ist), wissen Sie, dass Sie der übergeordnete Prozess sind. (und wenn es sich um eine negative Zahl handelt, ist die Verzweigung fehlgeschlagen und es wurde kein untergeordneter Prozess erstellt.)
Mit fork () wird grundsätzlich ein untergeordneter Prozess für den Prozess erstellt, in dem Sie diese Funktion aufrufen. Immer wenn Sie ein fork () aufrufen, wird eine Null für die untergeordnete ID zurückgegeben.
pid=fork()
if pid==0
//this is the child process
else if pid!=0
//this is the parent process
Auf diese Weise können Sie verschiedene Aktionen für Eltern und Kind bereitstellen und die Multithreading-Funktion verwenden.
fork () erstellt einen neuen untergeordneten Prozess, der mit dem übergeordneten identisch ist. Alles, was Sie danach im Code ausführen, wird von beiden Prozessen ausgeführt - sehr nützlich, wenn Sie beispielsweise einen Server haben und mehrere Anforderungen bearbeiten möchten.
Sie müssen Fork wahrscheinlich nicht für die tägliche Programmierung verwenden, wenn Sie Anwendungen schreiben.
Selbst wenn Sie möchten, dass Ihr Programm ein anderes Programm startet, um eine Aufgabe zu erledigen, gibt es andere einfachere Schnittstellen, die Gabel hinter den Kulissen verwenden, wie "System" in C und Perl.
Wenn Sie beispielsweise möchten, dass Ihre Anwendung ein anderes Programm wie bc startet, um eine Berechnung für Sie durchzuführen, können Sie es mit 'system' ausführen. Das System führt einen 'Fork' aus, um einen neuen Prozess zu erstellen, und einen 'Exec', um diesen Prozess in bc umzuwandeln. Sobald bc abgeschlossen ist, gibt das System die Kontrolle an Ihr Programm zurück.
Sie können auch andere Programme asynchron ausführen, aber ich kann mich nicht erinnern, wie.
Wenn Sie Server, Shells, Viren oder Betriebssysteme schreiben, möchten Sie mit größerer Wahrscheinlichkeit Fork verwenden.
system()
. Ich habe darüber gelesen, fork()
weil ich möchte, dass mein C-Code ein Python-Skript ausführt.
Der Systemaufruf fork () wird zum Erstellen von Prozessen verwendet. Es akzeptiert keine Argumente und gibt eine Prozess-ID zurück. Der Zweck von fork () besteht darin, einen neuen Prozess zu erstellen, der zum untergeordneten Prozess des Aufrufers wird. Nachdem ein neuer untergeordneter Prozess erstellt wurde, führen beide Prozesse die nächste Anweisung nach dem Systemaufruf fork () aus. Deshalb müssen wir den Elternteil vom Kind unterscheiden. Dies kann durch Testen des zurückgegebenen Werts von fork () erfolgen:
Wenn fork () einen negativen Wert zurückgibt, war die Erstellung eines untergeordneten Prozesses nicht erfolgreich. fork () gibt eine Null an den neu erstellten untergeordneten Prozess zurück. fork () gibt einen positiven Wert, die Prozess-ID des untergeordneten Prozesses, an das übergeordnete Element zurück. Die zurückgegebene Prozess-ID ist vom Typ pid_t, der in sys / types.h definiert ist. Normalerweise ist die Prozess-ID eine Ganzzahl. Darüber hinaus kann ein Prozess die Funktion getpid () verwenden, um die diesem Prozess zugewiesene Prozess-ID abzurufen. Daher kann nach dem Systemaufruf von fork () ein einfacher Test feststellen, welcher Prozess das untergeordnete Element ist. Bitte beachten Sie, dass Unix eine exakte Kopie des Adressraums der Eltern erstellt und diese dem Kind gibt. Daher haben der übergeordnete und der untergeordnete Prozess separate Adressräume.
Lassen Sie es uns anhand eines Beispiels verstehen, um die obigen Punkte klar zu machen. In diesem Beispiel werden übergeordnete und untergeordnete Prozesse nicht unterschieden.
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#define MAX_COUNT 200
#define BUF_SIZE 100
void main(void)
{
pid_t pid;
int i;
char buf[BUF_SIZE];
fork();
pid = getpid();
for (i = 1; i <= MAX_COUNT; i++) {
sprintf(buf, "This line is from pid %d, value = %d\n", pid, i);
write(1, buf, strlen(buf));
}
}
Angenommen, das obige Programm wird bis zum Aufruf von fork () ausgeführt.
Wenn der Aufruf von fork () erfolgreich ausgeführt wird, erstellt Unix zwei identische Kopien von Adressräumen, eine für das übergeordnete und das andere für das untergeordnete Element. Beide Prozesse beginnen ihre Ausführung bei der nächsten Anweisung nach dem Aufruf von fork (). In diesem Fall beginnen beide Prozesse ihre Ausführung bei der Zuweisung
pid = .....;
Beide Prozesse starten ihre Ausführung direkt nach dem Systemaufruf fork (). Da beide Prozesse identische, aber separate Adressräume haben, haben diese vor dem Aufruf von fork () initialisierten Variablen in beiden Adressräumen dieselben Werte. Da jeder Prozess seinen eigenen Adressraum hat, sind alle Änderungen unabhängig von den anderen. Mit anderen Worten, wenn das übergeordnete Element den Wert seiner Variablen ändert, wirkt sich die Änderung nur auf die Variable im Adressraum des übergeordneten Prozesses aus. Andere durch fork () -Aufrufe erstellte Adressräume sind nicht betroffen, obwohl sie identische Variablennamen haben.
Was ist der Grund für die Verwendung von write anstelle von printf? Dies liegt daran, dass printf () "gepuffert" ist, was bedeutet, dass printf () die Ausgabe eines Prozesses zusammenfasst. Während die Ausgabe für den übergeordneten Prozess gepuffert wird, kann das Kind auch printf verwenden, um einige Informationen auszudrucken, die ebenfalls gepuffert werden. Da die Ausgabe nicht sofort an den Bildschirm gesendet wird, erhalten Sie möglicherweise nicht die richtige Reihenfolge des erwarteten Ergebnisses. Schlimmer noch, die Ausgabe der beiden Prozesse kann auf seltsame Weise gemischt werden. Um dieses Problem zu beheben, können Sie das "ungepufferte" Schreiben verwenden.
Wenn Sie dieses Programm ausführen, wird möglicherweise Folgendes auf dem Bildschirm angezeigt:
................
This line is from pid 3456, value 13
This line is from pid 3456, value 14
................
This line is from pid 3456, value 20
This line is from pid 4617, value 100
This line is from pid 4617, value 101
................
This line is from pid 3456, value 21
This line is from pid 3456, value 22
................
Die Prozess-ID 3456 kann diejenige sein, die dem Elternteil oder dem Kind zugewiesen ist. Aufgrund der Tatsache, dass diese Prozesse gleichzeitig ausgeführt werden, werden ihre Ausgangsleitungen auf ziemlich unvorhersehbare Weise vermischt. Darüber hinaus wird die Reihenfolge dieser Zeilen vom CPU-Scheduler festgelegt. Wenn Sie dieses Programm erneut ausführen, erhalten Sie möglicherweise ein völlig anderes Ergebnis.
Multiprocessing ist für die Datenverarbeitung von zentraler Bedeutung. Beispielsweise kann Ihr IE oder Firefox einen Prozess zum Herunterladen einer Datei für Sie erstellen, während Sie noch im Internet surfen. Oder während Sie ein Dokument in einem Textverarbeitungsprogramm ausdrucken, können Sie immer noch verschiedene Seiten betrachten und damit noch etwas bearbeiten.
Fork () wird verwendet, um neue Prozesse zu erstellen, wie jeder Körper geschrieben hat.
Hier ist mein Code, der Prozesse in Form eines Binärbaums erstellt. Er fordert Sie auf, die Anzahl der Ebenen zu scannen, bis zu denen Sie Prozesse im Binärbaum erstellen möchten
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
int t1,t2,p,i,n,ab;
p=getpid();
printf("enter the number of levels\n");fflush(stdout);
scanf("%d",&n);
printf("root %d\n",p);fflush(stdout);
for(i=1;i<n;i++)
{
t1=fork();
if(t1!=0)
t2=fork();
if(t1!=0 && t2!=0)
break;
printf("child pid %d parent pid %d\n",getpid(),getppid());fflush(stdout);
}
waitpid(t1,&ab,0);
waitpid(t2,&ab,0);
return 0;
}
AUSGABE
enter the number of levels
3
root 20665
child pid 20670 parent pid 20665
child pid 20669 parent pid 20665
child pid 20672 parent pid 20670
child pid 20671 parent pid 20670
child pid 20674 parent pid 20669
child pid 20673 parent pid 20669
Zuerst muss man verstehen, was ein Systemaufruf von fork () ist. Lassen Sie mich erklären
Der Systemaufruf fork () erstellt das genaue Duplikat des übergeordneten Prozesses. Er erstellt das Duplikat des übergeordneten Stapels, des Heaps, der initialisierten Daten und der nicht initialisierten Daten und teilt den Code im schreibgeschützten Modus mit dem übergeordneten Prozess.
Der Fork-Systemaufruf kopiert den Speicher auf der Basis von Copy-on-Write. Dies bedeutet, dass das Kind eine virtuelle Speicherseite erstellt, wenn ein Kopieren erforderlich ist.
Jetzt Zweck der Gabel ():
fork()
wird verwendet, um einen untergeordneten Prozess zu erzeugen. Normalerweise wird es in ähnlichen Situationen wie beim Threading verwendet, es gibt jedoch Unterschiede. Im Gegensatz zu Threads fork()
werden ganze separate Prozesse erstellt. Dies bedeutet, dass das Kind und das Elternteil, während sie an dem fork()
aufgerufenen Punkt direkte Kopien voneinander sind, vollständig getrennt sind und weder auf den Speicherplatz des anderen zugreifen können (ohne zu den normalen Problemen zu wechseln) Sie gehen, um auf den Speicher eines anderen Programms zuzugreifen.
fork()
wird immer noch von einigen Serveranwendungen verwendet, hauptsächlich von solchen, die als Root auf einem * NIX-Computer ausgeführt werden und Berechtigungen löschen, bevor Benutzeranforderungen verarbeitet werden. Es gibt noch einige andere Anwendungsfälle, aber die meisten Menschen sind jetzt zum Multithreading übergegangen.
Die Gründe für fork () im Vergleich zu einer exec () - Funktion zum Initiieren eines neuen Prozesses werden in einer Antwort auf eine ähnliche Frage zum Unix-Stack-Austausch erläutert .
Da Fork den aktuellen Prozess kopiert, sind im Wesentlichen alle verschiedenen möglichen Optionen für einen Prozess standardmäßig festgelegt, sodass der Programmierer sie nicht bereitstellt.
Im Windows-Betriebssystem hingegen müssen Programmierer die Funktion CreateProcess verwenden, die VIEL komplizierter ist und das Auffüllen einer vielfältigen Struktur zum Definieren der Parameter des neuen Prozesses erfordert.
Zusammenfassend lässt sich sagen, dass der Grund für das Gabeln (im Vergleich zum Ausführen) die Einfachheit bei der Erstellung neuer Prozesse ist.
Fork () -Systemaufruf zum Erstellen eines untergeordneten Prozesses. Es ist ein genaues Duplikat des übergeordneten Prozesses. Fork kopiert Stapelabschnitte, Heapabschnitte, Datenabschnitte, Umgebungsvariablen und Befehlszeilenargumente vom übergeordneten Element.
Mit der Funktion fork () wird ein neuer Prozess erstellt, indem der vorhandene Prozess dupliziert wird, von dem aus er aufgerufen wird. Der vorhandene Prozess, von dem aus diese Funktion aufgerufen wird, wird zum übergeordneten Prozess und der neu erstellte Prozess zum untergeordneten Prozess. Wie bereits erwähnt, handelt es sich bei dem Kind um eine Kopie des Elternteils, es gibt jedoch einige Ausnahmen.
Das Kind hat eine eindeutige PID wie jeder andere Prozess, der im Betriebssystem ausgeführt wird.
Das Kind hat eine übergeordnete Prozess-ID, die mit der PID des
Prozesses übereinstimmt , der es erstellt hat.
Ressourcenauslastung und CPU-Zeitzähler werden im untergeordneten Prozess auf Null zurückgesetzt.
Der Satz anstehender Signale im Kind ist leer.
Das Kind erbt keine Timer von seinem Elternteil
Beispiel:
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int var_glb; /* A global variable*/
int main(void)
{
pid_t childPID;
int var_lcl = 0;
childPID = fork();
if(childPID >= 0) // fork was successful
{
if(childPID == 0) // child process
{
var_lcl++;
var_glb++;
printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
else //Parent process
{
var_lcl = 10;
var_glb = 20;
printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
}
else // fork failed
{
printf("\n Fork failed, quitting!!!!!!\n");
return 1;
}
return 0;
}
Wenn nun der obige Code kompiliert und ausgeführt wird:
$ ./fork
Parent process :: var_lcl = [10], var_glb[20]
Child Process :: var_lcl = [1], var_glb[1]