Proszę wyjaśnić funkcję exec () i jej rodzinę


98

Jaka jest exec()funkcja i jej rodzina? Dlaczego ta funkcja jest używana i jak działa?

Proszę, aby ktoś wyjaśnił te funkcje.


4
Spróbuj ponownie przeczytać Stevensa i wyjaśnij, czego nie rozumiesz.
vlabrecque

Odpowiedzi:


245

Mówiąc prościej, w systemie UNIX masz pojęcie procesów i programów. Proces to środowisko, w którym program jest wykonywany.

Prosta idea stojąca za „modelem wykonywania” systemu UNIX polega na tym, że można wykonać dwie operacje.

Pierwszym jest to fork(), który tworzy zupełnie nowy proces zawierający duplikat (w większości) bieżącego programu, w tym jego stan. Istnieje kilka różnic między tymi dwoma procesami, które pozwalają im dowiedzieć się, kto jest rodzicem, a który dzieckiem.

Drugi to program exec(), który zastępuje program w obecnym procesie zupełnie nowym programem.

Z tych dwóch prostych operacji można zbudować cały model wykonywania systemu UNIX.


Aby dodać więcej szczegółów do powyższego:

Użycie fork()i exec()przykład ducha systemu UNIX, ponieważ zapewnia bardzo prosty sposób uruchamiania nowych procesów.

fork()Rozmowa sprawia pobliżu duplikat bieżącego procesu, identyczną w niemal każdym względem (nie wszystko jest kopiowane, na przykład, limity zasobów w niektórych implementacjach, ale idea jest stworzenie tak blisko jak to możliwe, kopia). Tylko jeden proces wywołuje, fork() ale dwa procesy wracają z tego wywołania - brzmi dziwnie, ale jest naprawdę całkiem eleganckie

Nowy proces (nazywany dzieckiem) otrzymuje inny identyfikator procesu (PID) i ma PID starego procesu (macierzystego) jako jego nadrzędny PID (PPID).

Ponieważ oba procesy działają teraz dokładnie ten sam kod, muszą być w stanie stwierdzić, który jest który - kod zwrotny fork()dostarcza tych informacji - dziecko otrzymuje 0, rodzic otrzymuje PID dziecka (jeśli się fork()nie powiedzie, nie dziecko jest tworzone, a rodzic otrzymuje kod błędu).

W ten sposób rodzic zna PID dziecka i może się z nim komunikować, zabijać, czekać na to itd. (Dziecko zawsze może znaleźć proces nadrzędny za pomocą wywołania getppid()).

exec()Wezwanie zastępuje cały bieżącą zawartość procesu z nowym programem. Ładuje program do bieżącej przestrzeni procesu i uruchamia go od punktu wejścia.

Tak więc, fork()i exec()są często używane po kolei, aby nowy program działał jako element podrzędny bieżącego procesu. Powłoki zazwyczaj robią to za każdym razem, gdy próbujesz uruchomić program taki jak find- powłoka forksuje, a następnie dziecko ładuje findprogram do pamięci, ustawiając wszystkie argumenty wiersza poleceń, standardowe we / wy i tak dalej.

Ale nie trzeba ich używać razem. Jest całkowicie dopuszczalne, aby program wywoływał fork()bez następującego exec()po nim następowania, jeśli na przykład program zawiera zarówno kod nadrzędny, jak i potomny (musisz uważać, co robisz, każda implementacja może mieć ograniczenia).

Było to używane dość często (i nadal jest) dla demonów, które po prostu nasłuchują na porcie TCP i rozwidlają swoją kopię, aby przetworzyć określone żądanie, podczas gdy rodzic wraca do nasłuchiwania. W tej sytuacji program zawiera zarówno kod nadrzędny, jak i podrzędny.

Podobnie programy, które wiedzą, że są skończone i chcą po prostu uruchomić inny program, nie muszą tego robić fork(), exec()a potem wait()/waitpid()dla dziecka. Mogą po prostu załadować dziecko bezpośrednio do ich bieżącej przestrzeni procesu za pomocą exec().

Niektóre implementacje UNIX mają zoptymalizowaną fork()wersję, która używa tego, co nazywają kopiowaniem przy zapisie. Jest to sztuczka polegająca na opóźnieniu kopiowania przestrzeni procesu fork()do czasu, gdy program spróbuje coś 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. W Linuksie fork()tylko kopia tabel stron i nowa struktura zadań exec()wykona podstawową pracę polegającą na „oddzieleniu” pamięci dwóch procesów.

Jeśli nazywane exec jest śledzeniem fork(i tak się dzieje najczęściej), powoduje zapis do przestrzeni procesu, a następnie jest kopiowany dla procesu potomnego, zanim dozwolone będą modyfikacje.

Linux ma również vfork(), jeszcze bardziej zoptymalizowany, który dzieli prawie wszystko między dwoma procesami. Z tego powodu istnieją pewne ograniczenia dotyczące tego, co dziecko może zrobić, a rodzic zatrzymuje się, dopóki dziecko nie zadzwoni exec()lub _exit().

Rodzic musi zostać zatrzymany (a dziecko nie może powrócić z bieżącej funkcji), ponieważ oba procesy mają nawet ten sam stos. Jest to nieco bardziej wydajne w klasycznym przypadku użycia, fork()po którym następuje natychmiast exec().

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 jest używana do wyświetlania katalogu za pomocą lspolecenia:

+--------+
| 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

12
Dziękuję za tak rozbudowane wyjaśnienie :)
Faizan,

2
Dzięki za odniesienie do powłoki z programem wyszukiwania. Dokładnie to, co chciałem zrozumieć.
Użytkownik

Dlaczego execnarzędzie jest używane do przekierowywania operacji we / wy bieżącego procesu? W jaki sposób przypadek „null”, uruchamiający exec bez argumentu, został użyty w tej konwencji?
Ray

@Ray, zawsze myślałem o tym jako o naturalnym przedłużeniu. Jeśli uważasz, że execjako sposobu na zastąpienie obecnego programu (shell) w tym procesie z innym, wtedy nie określając, że inny program, aby zastąpić go można po prostu znaczy, że nie chcą go zastąpić.
paxdiablo

Rozumiem, co masz na myśli, jeśli przez „naturalne rozszerzenie” masz na myśli coś w rodzaju „organicznego wzrostu”. Wydaje się, że przekierowanie zostałoby dodane w celu obsługi funkcji zamiany programu i widzę, że to zachowanie pozostaje w zdegenerowanym przypadku execwywołania bez programu. Ale w tym scenariuszu jest to trochę dziwne, ponieważ pierwotna użyteczność przekierowania dla nowego programu - programu, który faktycznie execzostałby wykorzystany - znika i masz użyteczny artefakt, przekierowujący bieżący program - który nie jest używany execani uruchamiany w jakikolwiek sposób - zamiast tego.
Ray

36

Funkcje w rodzinie exec () mają różne zachowania:

  • l: argumenty są przekazywane jako lista ciągów do funkcji main ()
  • v: argumenty są przekazywane jako tablica ciągów do funkcji main ()
  • p: ścieżka / s do wyszukania nowego uruchomionego programu
  • e: środowisko może być określone przez dzwoniącego

Możesz je mieszać, dzięki czemu masz:

  • int execl (const char * path, const char * arg, ...);
  • int execlp (const char * plik, const char * arg, ...);
  • int execle (const char * path, const char * arg, ..., char * const envp []);
  • int execv (const char * path, char * const argv []);
  • int execvp (const char * plik, char * const argv []);
  • int execvpe (const char * plik, char * const argv [], char * const envp []);

Dla nich wszystkich argumentem początkowym jest nazwa pliku, który ma zostać wykonany.

Aby uzyskać więcej informacji, przeczytaj stronę podręcznika exec (3) :

man 3 exec  # if you are running a UNIX system

1
Co ciekawe, przegapiłeś execve()listę, która jest definiowana przez POSIX, a dodałeś execvpe(), która nie jest zdefiniowana przez POSIX (głównie z powodów historycznych; uzupełnia zestaw funkcji). W przeciwnym razie pomocne wyjaśnienie konwencji nazewnictwa rodziny - przydatne uzupełnienie paxdiablo ”- odpowiedź, która wyjaśnia więcej o działaniu funkcji.
Jonathan Leffler

I na twoją obronę, widzę, że strona podręcznika Linux dla execvpe()(i innych) nie zawiera listy execve(); ma własną, oddzielną stronę podręcznika (przynajmniej na Ubuntu 16.04 LTS) - z tą różnicą, że inne exec()funkcje rodziny są wymienione w sekcji 3 (funkcje), a execve()w sekcji 2 (wywołania systemowe). Zasadniczo wszystkie inne funkcje w rodzinie są realizowane w formie wywołania execve().
Jonathan Leffler

18

execRodzina funkcji uczynić proces wykonać inny program, który zastąpi stary program został uruchomiony. To znaczy, jeśli zadzwonisz

execl("/bin/ls", "ls", NULL);

następnie lsprogram jest wykonywany z identyfikatorem procesu, bieżącym katalogiem roboczym i użytkownikiem / grupą (prawami dostępu) procesu, który wywołał execl. Potem oryginalny program już nie działa.

Aby rozpocząć nowy proces, forkużywane jest wywołanie systemowe. Aby wykonać program bez zamieniania oryginału, trzeba fork, po czym exec.


Dziękuję, że to było naprawdę pomocne. Obecnie realizuję projekt wymagający od nas użycia funkcji exec (), a Twój opis umocnił moje zrozumienie.
TwilightSparkleTheGeek

7

jaka jest funkcja exec i jej rodzina.

execRodzina funkcja wszystkie funkcje wykorzystywane do wykonywania pliku, takie jak execl, execlp, execle, execv, i execvp.They są wszystkie nakładki dlaexecve i zapewniają różne metody nazywając ją.

dlaczego jest używana ta funkcja

Funkcje Exec są używane, gdy chcesz wykonać (uruchomić) plik (program).

i jak to działa.

Działają poprzez nadpisanie bieżącego obrazu procesu tym, który uruchomiłeś. Zastępują (kończąc) aktualnie działający proces (ten, który wywołał polecenie exec) nowym uruchomionym procesem.

Aby uzyskać więcej informacji: zobacz ten link .


7

exec jest często używany w połączeniu z fork , o które, jak widziałem, również pytałeś, więc omówię to pod tym kątem.

execzamienia bieżący proces w inny program. Jeśli kiedykolwiek oglądałeś Doctor Who, to tak jakby się regenerował - jego stare ciało zostaje zastąpione nowym.

Sposób, w jaki dzieje się to z twoim programem i execpolega na tym, że wiele zasobów, które jądro systemu operacyjnego sprawdza, aby zobaczyć, czy plik, do którego przekazujesz execjako argument programu (pierwszy argument), jest wykonywalny przez bieżącego użytkownika (identyfikator użytkownika procesu wykonanie execpołączenia), a jeśli tak, zastępuje mapowanie pamięci wirtualnej bieżącego procesu pamięcią wirtualną nowego procesu i kopiuje dane argvi envpprzekazane w execwywołaniu do obszaru tej nowej mapy pamięci wirtualnej. Może się tu również zdarzyć kilka innych rzeczy, ale pliki, które były otwarte dla programu, który wywołał exec, nadal będą otwarte dla nowego programu i będą miały ten sam identyfikator procesu, ale program, który wywołał exec, przestanie działać (chyba że wykonanie zakończy się niepowodzeniem).

Dlatego, że odbywa się to w ten sposób, że przez oddzielenie uruchomiony do nowego programu w dwóch etapach, jak to można zrobić kilka rzeczy między dwoma etapami. Najczęstszą czynnością jest upewnienie się, że w nowym programie są otwarte określone pliki jako określone deskryptory plików. (pamiętaj, że deskryptory plików nie są tym samym, co FILE *, ale są intwartościami, o których jądro wie). W ten sposób możesz:

int X = open("./output_file.txt", O_WRONLY);

pid_t fk = fork();
if (!fk) { /* in child */
    dup2(X, 1); /* fd 1 is standard output,
                   so this makes standard out refer to the same file as X  */
    close(X);

    /* I'm using execl here rather than exec because
       it's easier to type the arguments. */
    execl("/bin/echo", "/bin/echo", "hello world");
    _exit(127); /* should not get here */
} else if (fk == -1) {
    /* An error happened and you should do something about it. */
    perror("fork"); /* print an error message */
}
close(X); /* The parent doesn't need this anymore */

Osiąga to bieganie:

/bin/echo "hello world" > ./output_file.txt

z powłoki poleceń.


5

Kiedy proces używa fork (), tworzy swoją zduplikowaną kopię, a ten duplikat staje się elementem podrzędnym procesu. Fork () jest zaimplementowany przy użyciu funkcji systemowej clone () w Linuksie, która dwukrotnie zwraca z jądra.

  • Wartość niezerowa (identyfikator procesu dziecka) jest zwracana do elementu nadrzędnego.
  • Do dziecka zwracana jest wartość zero.
  • W przypadku, gdy dziecko nie zostało pomyślnie utworzone z powodu problemów, takich jak mała ilość pamięci, -1 jest zwracane do fork ().

Zrozummy to na przykładzie:

pid = fork(); 
// Both child and parent will now start execution from here.
if(pid < 0) {
    //child was not created successfully
    return 1;
}
else if(pid == 0) {
    // This is the child process
    // Child process code goes here
}
else {
    // Parent process code goes here
}
printf("This is code common to parent and child");

W tym przykładzie założyliśmy, że exec () nie jest używana wewnątrz procesu potomnego.

Jednak rodzic i dziecko różnią się niektórymi atrybutami PCB (blok kontroli procesu). To są:

  1. PID - zarówno dziecko, jak i rodzic mają inny identyfikator procesu.
  2. Oczekujące sygnały - dziecko nie dziedziczy oczekujących sygnałów rodzica. Po utworzeniu będzie pusty dla procesu potomnego.
  3. Blokady pamięci - dziecko nie dziedziczy blokad pamięci swoich rodziców. Blokady pamięci to blokady, których można użyć do zablokowania obszaru pamięci, a następnie tego obszaru nie można zamienić na dysk.
  4. Blokady rekordów - dziecko nie dziedziczy blokad rekordów swoich rodziców. Blokady rekordów są powiązane z blokiem pliku lub całym plikiem.
  5. Wykorzystanie zasobów procesu i czas pracy procesora są ustawione na zero dla dziecka.
  6. Dziecko również nie dziedziczy timerów od rodzica.

A co z pamięcią dziecka? Czy utworzono nową przestrzeń adresową dla dziecka?

Odpowiedzi nie. Po fork (), rodzic i dziecko współużytkują przestrzeń adresową pamięci rodzica. W systemie Linux ta przestrzeń adresowa jest podzielona na wiele stron. Tylko wtedy, gdy dziecko zapisuje na jednej ze stron pamięci nadrzędnej, tworzony jest duplikat tej strony dla dziecka. Nazywa się to również kopiowaniem przy zapisie (kopiuj strony nadrzędne tylko wtedy, gdy pisze do nich dziecko).

Rozumiemy kopiowanie na pisanie na przykładzie.

int x = 2;
pid = fork();
if(pid == 0) {
    x = 10;
    // child is changing the value of x or writing to a page
    // One of the parent stack page will contain this local               variable. That page will be duplicated for child and it will store the value 10 in x in duplicated page.  
}
else {
    x = 4;
}

Ale dlaczego kopiowanie na piśmie jest konieczne?

Typowe tworzenie procesu odbywa się poprzez kombinację fork () - exec (). Najpierw zrozumiemy, co robi exec ().

Grupa funkcji Exec () zastępuje przestrzeń adresową dziecka nowym programem. Gdy exec () zostanie wywołana w obrębie dziecka, zostanie utworzona osobna przestrzeń adresowa dla dziecka, która będzie zupełnie inna niż przestrzeń nadrzędna.

Gdyby nie było mechanizmu zapisu związanego z fork (), zduplikowane strony zostałyby utworzone dla dziecka, a wszystkie dane zostałyby skopiowane na strony dziecka. Przydzielanie nowej pamięci i kopiowanie danych jest bardzo kosztownym procesem (zajmuje czas procesora i inne zasoby systemowe). Wiemy również, że w większości przypadków dziecko będzie wywoływać exec (), co zastąpi pamięć dziecka nowym programem. Tak więc pierwsza kopia, którą zrobiliśmy, byłaby marnotrawstwem, gdyby nie było kopii zapisywanej.

pid = fork();
if(pid == 0) {
    execlp("/bin/ls","ls",NULL);
    printf("will this line be printed"); // Think about it
    // A new memory space will be created for the child and that   memory will contain the "/bin/ls" program(text section), it's stack, data section and heap section
else {
    wait(NULL);
    // parent is waiting for the child. Once child terminates, parent will get its exit status and can then continue
}
return 1; // Both child and parent will exit with status code 1.

Dlaczego rodzic czeka na proces dziecka?

  1. Rodzic może przypisać zadanie swojemu dziecku i poczekać, aż zakończy swoje zadanie. Wtedy może wykonać inną pracę.
  2. Po zakończeniu działania dziecka wszystkie powiązane z nim zasoby są zwalniane z wyjątkiem bloku sterowania procesem. Teraz dziecko jest w stanie zombie. Za pomocą funkcji wait () rodzic może zapytać o status dziecka, a następnie poprosić jądro o zwolnienie PCB. Jeśli rodzic nie użyje czekania, dziecko pozostanie w stanie zombie.

Dlaczego konieczne jest wywołanie systemowe exec ()?

Nie jest konieczne używanie exec () z fork (). Jeśli kod, który wykona dziecko, znajduje się w programie powiązanym z rodzicem, funkcja exec () nie jest potrzebna.

Ale pomyśl o przypadkach, gdy dziecko musi uruchamiać wiele programów. Weźmy przykład programu powłoki. Obsługuje wiele poleceń, takich jak find, mv, cp, date itp. Czy będzie słuszne włączenie kodu programu związanego z tymi poleceniami w jednym programie, czy też dziecko załaduje te programy do pamięci, gdy będzie to wymagane?

Wszystko zależy od twojego przypadku użycia. Masz serwer WWW, który podał wejście x, które zwraca klientom 2 ^ x. Dla każdego żądania serwer sieci Web tworzy nowe dziecko i prosi o wykonanie obliczeń. Czy napiszesz osobny program do obliczenia tego i użyjesz funkcji exec ()? A może po prostu napiszesz kod obliczeniowy w programie nadrzędnym?

Zwykle tworzenie procesu obejmuje kombinację wywołań fork (), exec (), wait () i exit ().


4

Te exec(3,3p)funkcje zastąpić bieżący proces z innym. Oznacza to, że bieżący proces zatrzymuje się , a zamiast tego uruchamia się inny, przejmując część zasobów, które posiadał oryginalny program.


6
Nie do końca. Zastępuje bieżący obraz procesu nowym obrazem procesu. Proces jest tym samym procesem z tym samym numerem pid, tym samym środowiskiem i tą samą tabelą deskryptorów plików. To, co się zmieniło, to cała pamięć wirtualna i stan procesora.
JeremyP

@JeremyP „Ten sam deskryptor pliku” był tutaj ważny, wyjaśnia, jak działa przekierowanie w powłokach! Byłem zdziwiony, jak może działać przekierowanie, jeśli exec nadpisuje wszystko! Dzięki
FUD
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.