Jaki jest cel funkcji fork ()?


87

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?


150
Aby wszyscy ci filozofowie kulinarni nie głodowali.
kenj0418

Odpowiedzi:


106

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:

  1. Twoja powłoka używa forkdo uruchamiania programów wywoływanych z wiersza poleceń.
  2. Serwery internetowe, takie jak apache, używają forkdo 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.
  3. Google Chrome używafork 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.
  4. forksł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.
  5. Języki skryptowe używają forkpośrednio do uruchamiania procesów potomnych. Na przykład za każdym razem, gdy używasz polecenia takiego jak subprocess.Popenw Pythonie, jesteś forkprocesem potomnym i czytasz jego wyjście. Umożliwia to współpracę programów.

Typowe użycie forkw 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 execi 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.


Punkt 5: „często”? Tylko „często”? Które z nich go nie używają lub w jakich okolicznościach funkcja fork () nie jest używana - to znaczy w systemach obsługujących fork ().
Jonathan Leffler

19
O dziwo, nazywa się CreateProcess () - ci szaleni faceci z Windows :-)
paxdiablo

2
do tej pory nie zdawałem sobie sprawy, że „powłoka używa forka do uruchamiania programów wywoływanych z linii poleceń”!
Lazer,

1
Link do slajdów jest uszkodzony
piertoni

1
Wszystkie odpowiedzi powiedzieć, że fork()jest to sposób, aby utworzyć nowy proces w systemie UNIX, ale być pedantyczny, istnieje co najmniej jeden inny: posix_spawn().
Davislor,

15

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.


@varDumper Dobry chwyt!
Daniel C. Sobral

9

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)

http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html


1
Chyba że zwracana wartość to -1, w którym to przypadku fork () nie powiodło się.
Jonathan Leffler

8

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.


6

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ń.


dlaczego tworzysz dziecko, które jest identyczne z rodzicem, jaki jest pożytek?

1
To jak budowanie armii przeciwko pojedynczemu żołnierzowi. Rozwidlasz, aby Twój program mógł obsługiwać więcej żądań w tym samym czasie, zamiast jednego po drugim.
Cloudhead

fork () zwraca 0 w przypadku dziecka i pid dziecka w przypadku rodzica. Dziecko może następnie użyć wywołania takiego jak exec (), aby zamienić swój stan na nowy program. W ten sposób uruchamiane są programy.
Todd Gamblin

Procesy są bardzo zbliżone do identycznych, ale istnieje wiele subtelnych różnic. Rażące różnice to aktualny PID i macierzysty PID. Istnieją problemy związane z posiadanymi blokadami i przechowywanymi semaforami. Strona podręcznika fork () dla POSIX wymienia 25 różnic między rodzicem a dzieckiem.
Jonathan Leffler

2
@kar: Gdy masz już dwa procesy, można je kontynuować oddzielnie, a jeden z nich może całkowicie zastąpić siebie (exex ()) innym programem.
Vatine

4

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.


Dziękuję za system(). Czytałem o tym, fork()ponieważ chcę, aby mój kod C uruchamiał skrypt w języku Python.
Bean Taxi

4

Fork tworzy nowe procesy. Bez forka miałbyś system uniksowy, który mógłby uruchamiać tylko init.


4

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.


3
Zamiast kopiować i wklejać tekst, mógłbyś skomentować link: csl.mtu.edu/cs4411.ck/www/NOTES/process/fork/create.html
chaitanya lakkundi

3

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ć.


3

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

2

Najpierw należy zrozumieć, co to jest wywołanie systemowe fork (). Pozwól mi wyjaśnić

  1. 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.

  2. 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 ():

  1. Fork () może być używany w miejscu, w którym występuje podział pracy, tak jak serwer musi obsługiwać wielu klientów, więc rodzic musi regularnie akceptować połączenie, więc serwer robi fork dla każdego klienta, aby wykonać odczyt i zapis.

1

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ść.


2
Nie rozumiem poglądu, że „większość ludzi” przeszła na wielowątkowość. Procesy pozostają tutaj, podobnie jak wątki. Nikt z żadnej z nich nie „odszedł”. W programowaniu równoległym największymi i najczęściej współbieżnymi kodami są programy wieloprocesowe z pamięcią rozproszoną (np. MapReduce i MPI). Mimo to większość ludzi zdecydowałaby się na OpenMP lub jakiś paradygmat pamięci współdzielonej dla maszyny wielordzeniowej, a procesory graficzne używają obecnie wątków, ale jest jeszcze wiele innych rzeczy. Założę się jednak, że więcej programistów w tej witrynie napotyka równoległość procesów po stronie serwera niż czegokolwiek wielowątkowego.
Todd Gamblin

1

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.


0

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.

refer: http://man7.org/linux/man-pages/man2/fork.2.html


0

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]
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.