Jakie są różnice między fork
i exec
?
fork
jest w zasadzie klonowanie: O
Jakie są różnice między fork
i exec
?
fork
jest w zasadzie klonowanie: O
Odpowiedzi:
Wykorzystanie fork
i exec
przykład ducha systemu UNIX polega na tym, że zapewnia on bardzo prosty sposób na rozpoczęcie nowych procesów.
fork
Wezwanie 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 fork
połączenie działa - jeśli nie, nie zostanie utworzone żadne dziecko, a rodzic otrzyma kod błędu.
exec
Rozmowa 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, fork
i exec
są 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 find
program 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 fork
samego programu bez exec
ing, 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 fork
na 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
, exec
a potem wait
dla dziecka. Mogą po prostu załadować dziecko bezpośrednio do swojej przestrzeni procesowej.
Niektóre implementacje UNIX mają zoptymalizowane, fork
które wykorzystują to, co nazywają kopiowaniem przy zapisie. Jest to sztuczka polegająca na opóźnieniu kopiowania przestrzeni procesu, fork
dopóki program nie spróbuje czegoś zmienić w tej przestrzeni. Jest to przydatne dla tych programów, które używają tylko, fork
a nie exec
dlatego, ż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 exec
połączeń ( execl
, execle
, execve
i tak dalej), ale exec
w kontekście oznacza tutaj żadnej z nich.
Poniższy diagram ilustruje typową fork/exec
operację, w której bash
powłoka służy do wyświetlenia katalogu z ls
poleceniem:
+--------+
| 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
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.
exec
jest nieco łatwiejszy do uchwycenia, po prostu każesz exec
wykonać 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, exec
można go użyć po fork
wykonaniu w bieżącym procesie docelowego pliku wykonywalnego.
pid < 0
i fork()
połączenia nie powiodła się
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
exec
wywoł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 procesuexec()
zastępuje program w bieżącym procesie innym programem(jest to w zasadzie uproszczona wersja paxdiablo „dla manekinów” )
Fork tworzy kopię procesu wywoływania. ogólnie podąża za strukturą
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ą
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})
Są one używane razem, aby utworzyć nowy proces potomny. Po pierwsze, wywołanie fork
tworzy kopię bieżącego procesu (proces potomny). Następnie exec
jest 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
}
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.
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.‖
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ść:
fork()
zwraca identyfikator procesu (pid) procesu potomnego w rodzicu
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;
}
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.)