Różnice między fork i exec

199

Jakie są różnice między forki exec?

Sashi
źródło
3
Dobre, szczegółowe podsumowanie funkcji fork, exec i innych funkcji kontroli procesu znajduje się na stronie yolinux.com/TUTORIALS/ForkExecProcesses.html
Jonathan Fingland,
9
@Justin, ponieważ chcemy, aby SO stało się miejscem, w którym można znaleźć pytania dotyczące programowania.
paxdiablo
4
@ Polaris878: och, teraz! : D
Janusz Lenar
podobnie forkjest w zasadzie klonowanie: O
Sebastian Hojas

Odpowiedzi:

364

Wykorzystanie forki execprzykład ducha systemu UNIX polega na tym, że zapewnia on bardzo prosty sposób na rozpoczęcie nowych procesów.

forkWezwanie zasadzie sprawia duplikat bieżącego procesu, identyczny w prawie każdym względem. Nie wszystko jest kopiowane (na przykład limity zasobów w niektórych implementacjach), ale pomysł polega na stworzeniu jak najbliższej kopii.

Nowy proces (podrzędny) otrzymuje inny identyfikator procesu (PID) i ma PID starego procesu (nadrzędnego) jako nadrzędny PID (PPID). Ponieważ oba procesy działają teraz dokładnie w tym samym kodzie, mogą stwierdzić, który kod powrotu fork- dziecko otrzymuje 0, rodzic otrzymuje PID dziecka. To wszystko oczywiście przy założeniu, że forkpołączenie działa - jeśli nie, nie zostanie utworzone żadne dziecko, a rodzic otrzyma kod błędu.

execRozmowa jest sposobem na zasadzie wymienić cały proces bieżący z nowym programem. Ładuje program do bieżącej przestrzeni procesu i uruchamia go od punktu wejścia.

Tak, forki execsą często stosowane w kolejności, aby otrzymać nowy program działający jako dziecko aktualnego procesu. Powłoki zwykle robią to za każdym razem, gdy próbujesz uruchomić program taki jak find- powłoka się rozwidla, a następnie dziecko ładuje findprogram do pamięci, ustawiając wszystkie argumenty wiersza poleceń, standardowe operacje we / wy i tak dalej.

Ale nie muszą być używane razem. Jest całkowicie akceptowalny dla forksamego programu bez execing, jeśli na przykład program zawiera zarówno kod nadrzędny, jak i podrzędny (musisz uważać, co robisz, każda implementacja może mieć ograniczenia). Było to dość często używane (i nadal jest) dla demonów, które po prostu nasłuchują na porcie TCP i forkna swoich kopiach, aby przetworzyć określone żądanie, podczas gdy rodzic wraca do nasłuchiwania.

Podobnie programy, które wiedzą, że są gotowe i po prostu chcą uruchomić inny program, nie muszą tego robić fork, execa potem waitdla dziecka. Mogą po prostu załadować dziecko bezpośrednio do swojej przestrzeni procesowej.

Niektóre implementacje UNIX mają zoptymalizowane, forkktóre wykorzystują to, co nazywają kopiowaniem przy zapisie. Jest to sztuczka polegająca na opóźnieniu kopiowania przestrzeni procesu, forkdopóki program nie spróbuje czegoś zmienić w tej przestrzeni. Jest to przydatne dla tych programów, które używają tylko, forka nie execdlatego, że nie muszą kopiować całej przestrzeni procesu.

Jeśli wywoływany exec jest następujący fork(i tak się najczęściej dzieje), powoduje to zapis w przestrzeni procesu, a następnie jest kopiowany do procesu potomnego.

Należy pamiętać, że istnieje cała rodzina execpołączeń ( execl, execle, execvei tak dalej), ale execw kontekście oznacza tutaj żadnej z nich.

Poniższy diagram ilustruje typową fork/execoperację, w której bashpowłoka służy do wyświetlenia katalogu z lspoleceniem:

+--------+
| pid=7  |
| ppid=4 |
| bash   |
+--------+
    |
    | calls fork
    V
+--------+             +--------+
| pid=7  |    forks    | pid=22 |
| ppid=4 | ----------> | ppid=7 |
| bash   |             | bash   |
+--------+             +--------+
    |                      |
    | waits for pid 22     | calls exec to run ls
    |                      V
    |                  +--------+
    |                  | pid=22 |
    |                  | ppid=7 |
    |                  | ls     |
    V                  +--------+
+--------+                 |
| pid=7  |                 | exits
| ppid=4 | <---------------+
| bash   |
+--------+
    |
    | continues
    V
paxdiablo
źródło
52

fork()dzieli bieżący proces na dwa procesy. Innymi słowy, twój przyjemny liniowy, łatwy do pomyślenia program nagle staje się dwoma oddzielnymi programami uruchamiającymi jeden fragment kodu:

 int pid = fork();

 if (pid == 0)
 {
     printf("I'm the child");
 }
 else
 {
     printf("I'm the parent, my child is %i", pid);
     // here we can kill the child, but that's not very parently of us
 }

To może trochę zaskoczyć twój umysł. Teraz masz jeden fragment kodu o prawie identycznym stanie wykonywanym przez dwa procesy. Proces potomny dziedziczy cały kod i pamięć procesu, który go właśnie utworzył, w tym począwszy od miejsca, w którym fork()wywołanie zostało przerwane. Jedyną różnicą jest fork()kod powrotu informujący, czy jesteś rodzicem czy dzieckiem. Jeśli jesteś rodzicem, zwracaną wartością jest identyfikator dziecka.

execjest nieco łatwiejszy do uchwycenia, po prostu każesz execwykonać proces przy użyciu docelowego pliku wykonywalnego i nie masz dwóch procesów uruchamiających ten sam kod lub dziedziczących ten sam stan. Jak mówi @Steve Hawkins, execmożna go użyć po forkwykonaniu w bieżącym procesie docelowego pliku wykonywalnego.

Doug T.
źródło
6
istnieje również stan, kiedy pid < 0i fork()połączenia nie powiodła się
Jonathan Fingland
3
To wcale mnie nie denerwuje :-) Jeden kawałek kodu wykonywany przez dwa procesy dzieje się za każdym razem, gdy używana jest biblioteka współdzielona lub DLL.
paxdiablo
31

Myślę, że niektóre koncepcje z „Zaawansowanego programowania unixowego” Marc'a Rochkinda były pomocne w zrozumieniu różnych ról fork()/ exec(), szczególnie dla kogoś przyzwyczajonego do CreateProcess()modelu Windows :

Program jest zbiorem instrukcji i danych, które są trzymane w zwykłym pliku na dysku. (od 1.1.2 Programy, procesy i wątki)

.

Aby uruchomić program, jądro jest najpierw proszone o utworzenie nowego procesu , który jest środowiskiem, w którym program się wykonuje. (także z 1.1.2 Programy, procesy i wątki)

.

Nie można zrozumieć wywołań systemowych exec lub fork bez pełnego zrozumienia różnicy między procesem a programem. Jeśli te warunki są dla Ciebie nowe, możesz wrócić i przejrzeć sekcję 1.1.2. Jeśli jesteś gotowy, aby kontynuować, podsumujemy rozróżnienie w jednym zdaniu: Proces to środowisko wykonawcze, które składa się z instrukcji, danych użytkownika i segmentów danych systemowych, a także wielu innych zasobów pozyskanych w czasie wykonywania , podczas gdy program to plik zawierający instrukcje i dane, które są używane do inicjalizacji segmentu instrukcji i danych użytkownika. (z 5.3 execwywołań systemowych)

Po zrozumieniu różnicy między programem a procesem zachowanie fork()i exec()działanie można podsumować jako:

  • fork() tworzy duplikat bieżącego procesu
  • exec() zastępuje program w bieżącym procesie innym programem

(jest to w zasadzie uproszczona wersja paxdiablo „dla manekinów” )

Michael Burr
źródło
29

Fork tworzy kopię procesu wywoływania. ogólnie podąża za strukturą wprowadź opis zdjęcia tutaj

int cpid = fork( );

if (cpid = = 0) 
{

  //child code

  exit(0);

}

//parent code

wait(cpid);

// end

(dla tekstu procesu potomnego (kodu), danych, stos jest taki sam jak proces wywołujący) proces potomny wykonuje kod w bloku if.

EXEC zastępuje bieżący proces kodem, danymi i stosem nowego procesu. ogólnie podąża za strukturą wprowadź opis zdjęcia tutaj

int cpid = fork( );

if (cpid = = 0) 
{   
  //child code

  exec(foo);

  exit(0);    
}

//parent code

wait(cpid);

// end

(po wykonaniu przez jądro unix wywołania kasuje tekst procesu potomnego, dane, stos i wypełnia tekstem / danymi związanymi z procesem foo), dlatego proces potomny ma inny kod (kod foo {inny niż nadrzędny})

Sandesh Kobal
źródło
1
Jest to trochę niezwiązane z pytaniem, ale czy powyższy kod nie powoduje warunków wyścigu, jeśli proces potomny zakończy swój kod jako pierwszy? W takim przypadku proces nadrzędny zostałby zawieszony na zawsze i czekałby, aż dziecko się zakończy, prawda?
stdout
7

Są one używane razem, aby utworzyć nowy proces potomny. Po pierwsze, wywołanie forktworzy kopię bieżącego procesu (proces potomny). Następnie execjest wywoływany z poziomu procesu potomnego w celu „zastąpienia” kopii procesu nadrzędnego nowym procesem.

Proces przebiega mniej więcej tak:

child = fork();  //Fork returns a PID for the parent process, or 0 for the child, or -1 for Fail

if (child < 0) {
    std::cout << "Failed to fork GUI process...Exiting" << std::endl;
    exit (-1);
} else if (child == 0) {       // This is the Child Process
    // Call one of the "exec" functions to create the child process
    execvp (argv[0], const_cast<char**>(argv));
} else {                       // This is the Parent Process
    //Continue executing parent process
}
Steve Hawkins
źródło
2
W siódmym wierszu jest wspomniane, że funkcja exec () tworzy proces potomny. Czy to prawda, że ​​fork () już utworzył proces potomny, a wywołanie exec () po prostu zastępuje program właśnie utworzonego nowego procesu
cbinder
4

fork () tworzy kopię bieżącego procesu, a wykonywanie w nowym potomku rozpoczyna się zaraz po wywołaniu fork (). Po fork () są identyczne, z wyjątkiem wartości zwracanej przez funkcję fork (). (RTFM, aby uzyskać więcej informacji.) Dwa procesy mogą się dalej rozchodzić, przy czym jeden nie jest w stanie zakłócać drugiego, chyba że za pośrednictwem współdzielonych uchwytów plików.

exec () zastępuje bieżący proces nowym. Nie ma to nic wspólnego z fork (), z wyjątkiem tego, że exec () często podąża za fork (), gdy potrzebne jest uruchomienie innego procesu potomnego, zamiast zastąpienia bieżącego.

Warren Young
źródło
3

Główna różnica między fork()i exec()polega na tym, że

fork()Wezwanie system tworzy klona program uruchomiony. Oryginalny program kontynuuje wykonywanie od następnego wiersza kodu po wywołaniu funkcji fork (). Klon rozpoczyna także wykonywanie od następnego wiersza kodu. Spójrz na następujący kod, który otrzymałem z http://timmurphy.org/2014/04/26/using-fork-in-cc-a-minimum-working-example/

#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
    printf("--beginning of program\n");
    int counter = 0;
    pid_t pid = fork();
    if (pid == 0)
    {
        // child process
        int i = 0;
        for (; i < 5; ++i)
        {
            printf("child process: counter=%d\n", ++counter);
        }
    }
    else if (pid > 0)
    {
        // parent process
        int j = 0;
        for (; j < 5; ++j)
        {
            printf("parent process: counter=%d\n", ++counter);
        }
    }
    else
    {
        // fork failed
        printf("fork() failed!\n");
        return 1;
    }
    printf("--end of program--\n");
    return 0;
}

Ten program deklaruje zmienną licznika, ustawioną na zero, przed fork()ing. Po wywołaniu rozwidlenia mamy równolegle dwa procesy, oba zwiększające własną wersję licznika. Każdy proces zostanie uruchomiony do końca i zakończony. Ponieważ procesy przebiegają równolegle, nie mamy możliwości dowiedzieć się, który zakończy się jako pierwszy. Uruchomienie tego programu spowoduje wydrukowanie czegoś podobnego do pokazanego poniżej, chociaż wyniki mogą się różnić w zależności od serii.

--beginning of program
parent process: counter=1
parent process: counter=2
parent process: counter=3
child process: counter=1
parent process: counter=4
child process: counter=2
parent process: counter=5
child process: counter=3
--end of program--
child process: counter=4
child process: counter=5
--end of program--

exec()Rodzina wywołań systemowych zastąpi aktualnie wykonywanie kodu procesu z innego kawałka kodu. Proces zachowuje swój PID, ale staje się nowym programem. Weźmy na przykład następujący kod:

#include <stdio.h> 
#include <unistd.h> 
main() {
 char program[80],*args[3];
 int i; 
printf("Ready to exec()...\n"); 
strcpy(program,"date"); 
args[0]="date"; 
args[1]="-u"; 
args[2]=NULL; 
i=execvp(program,args); 
printf("i=%d ... did it work?\n",i); 
} 

Ten program wywołuje execvp()funkcję zastępowania kodu programem daty. Jeśli kod jest zapisany w pliku o nazwie exec1.c, wówczas jego wykonanie daje następujące dane wyjściowe:

Ready to exec()... 
Tue Jul 15 20:17:53 UTC 2008 

Program wypisuje wiersz eReady to exec (). . . ‖ I po wywołaniu funkcji execvp (), zastępuje swój kod programem daty. Zauważ, że linia -. . . to zadziałało‖ nie wyświetla się, ponieważ w tym momencie kod został zastąpiony. Zamiast tego widzimy wynik działania funkcji -data -u.‖

Abdulhakim Zeinu
źródło
1

wprowadź opis zdjęcia tutajfork():

Tworzy kopię uruchomionego procesu. Działający proces nazywa się procesem nadrzędnym, a nowo utworzony proces nazywa się procesem potomnym . Sposobem na rozróżnienie tych dwóch jest spojrzenie na zwróconą wartość:

  1. fork() zwraca identyfikator procesu (pid) procesu potomnego w rodzicu

  2. fork() zwraca 0 w potomku.

exec():

Inicjuje nowy proces w ramach procesu. Ładuje nowy program do bieżącego procesu, zastępując istniejący.

fork()+ exec():

Podczas uruchamiania nowego programu należy najpierw fork()utworzyć nowy proces, a następnie exec()(tj. Załadować do pamięci i wykonać) program binarny, który ma uruchomić.

int main( void ) 
{
    int pid = fork();
    if ( pid == 0 ) 
    {
        execvp( "find", argv );
    }

    //Put the parent to sleep for 2 sec,let the child finished executing 
    wait( 2 );

    return 0;
}
Yogeesh HT
źródło
0

Doskonałym przykładem do zrozumienia pojęcia fork()i exec()jest powłoka , program interpretujący polecenia, który użytkownicy zwykle wykonują po zalogowaniu się do systemu. Powłoka interpretuje pierwsze słowo wiersza poleceń jako nazwę polecenia

W przypadku wielu poleceń powłoka rozwidla się, a proces potomny wykonuje polecenie powiązane z nazwą, traktując pozostałe słowa w wierszu poleceń jako parametry polecenia.

Powłoki pozwala trzech rodzajów poleceń. Po pierwsze, polecenie może być plikiem wykonywalnym, który zawiera kod obiektowy wytworzony przez kompilację kodu źródłowego (na przykład program w języku C). Po drugie, polecenie może być plikiem wykonywalnym, który zawiera sekwencję wierszy poleceń powłoki. Wreszcie, polecenie może być poleceniem powłoki wewnętrznej (zamiast pliku wykonywalnego ex-> cd , ls itp.)

krpra
źródło