W wielu programach i stronach podręcznika systemu Linux widziałem kod używający fork()
. Dlaczego musimy używać fork()
i jaki jest jego cel?
Odpowiedzi:
fork()
to sposób tworzenia nowych procesów w systemie Unix. Kiedy dzwonisz fork
, tworzysz kopię własnego procesu, który ma własną przestrzeń adresową . Dzięki temu wiele zadań może być wykonywanych niezależnie od siebie, tak jakby każde z nich miało pełną pamięć maszyny.
Oto kilka przykładowych zastosowań fork
:
fork
do uruchamiania programów wywoływanych z wiersza poleceń.fork
do tworzenia wielu procesów serwerowych, z których każdy obsługuje żądania we własnej przestrzeni adresowej. Jeśli ktoś umiera lub wycieka pamięć, inne pozostają nienaruszone, więc działa jako mechanizm odporności na błędy.fork
do obsługi każdej strony w oddzielnym procesie. Dzięki temu kod po stronie klienta na jednej stronie nie spowoduje wyłączenia całej przeglądarki.fork
służy do tworzenia procesów w niektórych równoległych programach (takich jak te napisane przy użyciu MPI ). Zauważ, że różni się to od używania wątków , które nie mają własnej przestrzeni adresowej i istnieją w procesie.fork
pośrednio do uruchamiania procesów potomnych. Na przykład za każdym razem, gdy używasz polecenia takiego jak subprocess.Popen
w Pythonie, jesteś fork
procesem potomnym i czytasz jego wyjście. Umożliwia to współpracę programów.Typowe użycie fork
w powłoce może wyglądać mniej więcej tak:
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);
}
Powłoka tworzy proces potomny przy użyciu exec
i czeka na jego zakończenie, a następnie kontynuuje swoje własne wykonanie. Zauważ, że nie musisz w ten sposób używać forka. Zawsze możesz odrodzić wiele procesów potomnych, tak jak może to zrobić program równoległy, i każdy może uruchamiać program jednocześnie. Zasadniczo za każdym razem, gdy tworzysz nowe procesy w systemie Unix, używasz fork()
. W przypadku odpowiednika systemu Windows spójrz naCreateProcess
.
Jeśli chcesz więcej przykładów i dłuższego wyjaśnienia, Wikipedia ma przyzwoite podsumowanie. A oto kilka slajdów pokazujących , jak procesy, wątki i współbieżność działają w nowoczesnych systemach operacyjnych.
fork()
jest to sposób, aby utworzyć nowy proces w systemie UNIX, ale być pedantyczny, istnieje co najmniej jeden inny: posix_spawn()
.
fork () to sposób, w jaki Unix tworzy nowe procesy. W miejscu, w którym wywołałeś fork (), twój proces jest klonowany i dwa różne procesy kontynuują wykonywanie z tego miejsca. Jeden z nich, dziecko, będzie miał fork () zwraca 0. Drugi, rodzic, będzie miał fork () zwróci PID (identyfikator procesu) dziecka.
Na przykład, jeśli wpiszesz następujące polecenie w powłoce, program powłoki wywoła fork (), a następnie wykona polecenie, które przekazałeś (telnetd, w tym przypadku) w dziecku, podczas gdy rodzic ponownie wyświetli monit jako komunikat wskazujący PID procesu w tle.
$ telnetd &
Jeśli chodzi o powód, dla którego tworzysz nowe procesy, w ten sposób Twój system operacyjny może robić wiele rzeczy w tym samym czasie. Dlatego możesz uruchomić program, a gdy jest uruchomiony, przełączyć się do innego okna i zrobić coś innego.
fork () służy do tworzenia procesu potomnego. Gdy zostanie wywołana funkcja fork (), zostanie utworzony nowy proces, a wywołanie funkcji fork () zwróci inną wartość dla dziecka i rodzica.
Jeśli wartość zwracana wynosi 0, wiesz, że jesteś procesem podrzędnym, a jeśli wartość zwracana jest liczbą (która jest identyfikatorem procesu potomnego), wiesz, że jesteś rodzicem. (a jeśli jest to liczba ujemna, rozwidlenie nie powiodło się i nie utworzono żadnego procesu potomnego)
fork () jest zasadniczo używana do tworzenia procesu potomnego dla procesu, w którym wywołujesz tę funkcję. Za każdym razem, gdy wywołujesz fork (), zwraca zero dla id dziecka.
pid=fork()
if pid==0
//this is the child process
else if pid!=0
//this is the parent process
w ten sposób możesz zapewnić różne działania dla rodzica i dziecka oraz skorzystać z funkcji wielowątkowości.
fork () utworzy nowy proces potomny, identyczny z rodzicem. Więc wszystko, co potem uruchomisz w kodzie, zostanie uruchomione przez oba procesy - bardzo przydatne, jeśli masz na przykład serwer i chcesz obsłużyć wiele żądań.
Jeśli piszesz aplikacje, prawdopodobnie nie musisz używać forka w codziennym programowaniu.
Nawet jeśli chcesz, aby Twój program uruchamiał inny program do wykonania jakiegoś zadania, istnieją inne prostsze interfejsy, które używają widelca za kulisami, takie jak „system” w C i perl.
Na przykład, jeśli chcesz, aby Twoja aplikacja uruchamiała inny program, taki jak bc, w celu wykonania pewnych obliczeń, możesz użyć słowa „system”, aby ją uruchomić. System wykonuje „fork”, aby utworzyć nowy proces, a następnie „exec”, aby przekształcić ten proces w bc. Gdy bc zakończy działanie, system przywróci kontrolę do twojego programu.
Możesz także uruchamiać inne programy asynchronicznie, ale nie pamiętam jak.
Jeśli piszesz serwery, powłoki, wirusy lub systemy operacyjne, prawdopodobnie będziesz chciał użyć rozwidlenia.
system()
. Czytałem o tym, fork()
ponieważ chcę, aby mój kod C uruchamiał skrypt w języku Python.
Wywołanie systemowe fork () służy do tworzenia procesów. Nie pobiera argumentów i zwraca identyfikator procesu. Celem fork () jest utworzenie nowego procesu, który staje się procesem potomnym obiektu wywołującego. Po utworzeniu nowego procesu potomnego oba procesy wykonają następną instrukcję po wywołaniu systemowym fork (). Dlatego musimy odróżnić rodzica od dziecka. Można to zrobić, testując zwróconą wartość fork ():
Jeśli fork () zwraca wartość ujemną, tworzenie procesu potomnego nie powiodło się. fork () zwraca zero do nowo utworzonego procesu potomnego. fork () zwraca wartość dodatnią, identyfikator procesu potomnego, do rodzica. Zwrócony identyfikator procesu ma typ pid_t zdefiniowany w sys / types.h. Zwykle identyfikator procesu jest liczbą całkowitą. Ponadto proces może użyć funkcji getpid () do pobrania identyfikatora procesu przypisanego do tego procesu. Dlatego po wywołaniu systemowym fork () prosty test może stwierdzić, który proces jest dzieckiem. Należy pamiętać, że Unix zrobi dokładną kopię przestrzeni adresowej rodzica i przekaże ją dziecku. Dlatego procesy nadrzędne i potomne mają oddzielne przestrzenie adresowe.
Zrozummy to na przykładzie, aby wyjaśnić powyższe kwestie. Ten przykład nie rozróżnia procesów nadrzędnych i podrzędnych.
#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));
}
}
Załóżmy, że powyższy program jest wykonywany do momentu wywołania funkcji fork ().
Jeśli wywołanie fork () zostanie wykonane pomyślnie, Unix utworzy dwie identyczne kopie przestrzeni adresowych, jedną dla rodzica, a drugą dla dziecka. Oba procesy rozpoczną wykonywanie od następnej instrukcji następującej po wywołaniu fork (). W takim przypadku oba procesy rozpoczną wykonywanie w momencie przypisania
pid = .....;
Oba procesy rozpoczynają wykonywanie zaraz po wywołaniu systemowym fork (). Ponieważ oba procesy mają identyczne, ale oddzielne przestrzenie adresowe, te zmienne zainicjowane przed wywołaniem fork () mają te same wartości w obu przestrzeniach adresowych. Ponieważ każdy proces ma własną przestrzeń adresową, wszelkie modyfikacje będą niezależne od innych. Innymi słowy, jeśli rodzic zmieni wartość swojej zmiennej, modyfikacja wpłynie tylko na zmienną w przestrzeni adresowej procesu nadrzędnego. Inne przestrzenie adresowe utworzone przez wywołania fork () nie zostaną zmienione, nawet jeśli mają identyczne nazwy zmiennych.
Jaki jest powód używania write zamiast printf? Dzieje się tak, ponieważ printf () jest „buforowana”, co oznacza, że printf () zgrupuje razem wyjście procesu. Podczas buforowania wyjścia dla procesu nadrzędnego, dziecko może również użyć printf do wydrukowania pewnych informacji, które również zostaną zbuforowane. W rezultacie, ponieważ dane wyjściowe nie zostaną natychmiast wysłane na ekran, możesz nie uzyskać odpowiedniej kolejności oczekiwanego wyniku. Co gorsza, wyniki obu procesów mogą być mieszane w dziwny sposób. Aby rozwiązać ten problem, możesz rozważyć użycie zapisu „niebuforowanego”.
Jeśli uruchomisz ten program, na ekranie może pojawić się następujący komunikat:
................
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
................
Identyfikator procesu 3456 może być tym przypisanym do rodzica lub dziecka. Ze względu na fakt, że te procesy są uruchamiane jednocześnie, ich linie wyjściowe są mieszane w dość nieprzewidywalny sposób. Ponadto kolejność tych linii jest określana przez program planujący CPU. Dlatego jeśli ponownie uruchomisz ten program, możesz otrzymać zupełnie inny wynik.
Wieloprocesorowość ma kluczowe znaczenie dla komputerów. Na przykład przeglądarka IE lub Firefox może utworzyć proces pobierania pliku, gdy nadal przeglądasz internet. Lub, gdy drukujesz dokument w edytorze tekstu, możesz nadal przeglądać różne strony i nadal je edytować.
Fork () służy do tworzenia nowych procesów, tak jak napisało to każde ciało.
Oto mój kod, który tworzy procesy w postaci drzewa binarnego ....... Poprosi o przeskanowanie liczby poziomów do których chcesz utworzyć procesy w drzewie binarnym
#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;
}
WYNIK
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
Najpierw należy zrozumieć, co to jest wywołanie systemowe fork (). Pozwól mi wyjaśnić
Funkcja systemowa fork () tworzy dokładny duplikat procesu nadrzędnego, tworzy duplikat stosu nadrzędnego, sterty, zainicjowanych danych, niezainicjowanych danych i współdzieli kod w trybie tylko do odczytu z procesem nadrzędnym.
Wywołanie systemowe Fork kopiuje pamięć na zasadzie kopiowania przy zapisie, co oznacza, że dziecko tworzy stronę pamięci wirtualnej, gdy istnieje potrzeba kopiowania.
Teraz Cel fork ():
fork()
służy do odrodzenia procesu potomnego. Zwykle jest używany w podobnych sytuacjach jak wątkowanie, ale istnieją różnice. W przeciwieństwie do wątków fork()
tworzy całe oddzielne procesy, co oznacza, że dziecko i rodzic, będąc bezpośrednimi kopiami siebie nawzajem w fork()
wywołanym punkcie , są całkowicie oddzielne, żadne z nich nie ma dostępu do przestrzeni pamięci drugiego (bez przechodzenia do normalnych problemów idziesz, aby uzyskać dostęp do pamięci innego programu).
fork()
jest nadal używany przez niektóre aplikacje serwerowe, głównie te, które działają jako root na maszynie * NIX, która zrzuca uprawnienia przed przetwarzaniem żądań użytkowników. Nadal istnieją inne zastosowania, ale większość ludzi przeszła teraz na wielowątkowość.
Racjonalne uzasadnienie fork () w porównaniu z posiadaniem funkcji exec () do zainicjowania nowego procesu jest wyjaśnione w odpowiedzi na podobne pytanie dotyczące wymiany stosów unixowych .
Zasadniczo, ponieważ fork kopiuje bieżący proces, wszystkie różne możliwe opcje procesu są ustalane domyślnie, więc programista ich nie dostarcza.
Natomiast w systemie operacyjnym Windows programiści muszą używać funkcji CreateProcess, która jest DUŻO bardziej skomplikowana i wymaga wypełnienia różnorodnej struktury w celu zdefiniowania parametrów nowego procesu.
Podsumowując, powodem rozwidlenia (w przeciwieństwie do wykonywania) jest prostota w tworzeniu nowych procesów.
Wykorzystanie wywołania systemowego Fork () do utworzenia procesu potomnego. Jest to dokładny duplikat procesu macierzystego. Rozwidlenie kopiuje sekcję stosu, sekcję sterty, sekcję danych, zmienną środowiskową, argumenty wiersza poleceń z rodzica.
Funkcja fork () służy do tworzenia nowego procesu przez powielenie istniejącego procesu, z którego została wywołana. Istniejący proces, z którego wywoływana jest ta funkcja, staje się procesem nadrzędnym, a nowo utworzony proces staje się procesem potomnym. Jak już wspomniano, dziecko jest duplikatem rodzica, ale są od tego wyjątki.
Dziecko ma unikalny PID, jak każdy inny proces działający w systemie operacyjnym.
Dziecko ma identyfikator procesu nadrzędnego, który jest taki sam, jak PID
procesu, który je utworzył.
Wykorzystanie zasobów i liczniki czasu procesora są resetowane do zera w procesie potomnym.
Zestaw oczekujących sygnałów w dziecku jest pusty.
Dziecko nie dziedziczy żadnych liczników czasu po swoim rodzicu
Przykład:
#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;
}
Teraz, gdy powyższy kod zostanie skompilowany i uruchomiony:
$ ./fork
Parent process :: var_lcl = [10], var_glb[20]
Child Process :: var_lcl = [1], var_glb[1]