Jaka jest exec()
funkcja i jej rodzina? Dlaczego ta funkcja jest używana i jak działa?
Proszę, aby ktoś wyjaśnił te funkcje.
Jaka jest exec()
funkcja i jej rodzina? Dlaczego ta funkcja jest używana i jak działa?
Proszę, aby ktoś wyjaśnił te funkcje.
Odpowiedzi:
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 find
program 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 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 jest używana do wyświetlania katalogu za pomocą ls
polecenia:
+--------+
| 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
exec
narzę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?
exec
jako 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ć.
exec
wywołania bez programu. Ale w tym scenariuszu jest to trochę dziwne, ponieważ pierwotna użyteczność przekierowania dla nowego programu - programu, który faktycznie exec
zostałby wykorzystany - znika i masz użyteczny artefakt, przekierowujący bieżący program - który nie jest używany exec
ani uruchamiany w jakikolwiek sposób - zamiast tego.
Funkcje w rodzinie exec () mają różne zachowania:
Możesz je mieszać, dzięki czemu masz:
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
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.
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()
.
exec
Rodzina 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 ls
program 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, fork
używane jest wywołanie systemowe. Aby wykonać program bez zamieniania oryginału, trzeba fork
, po czym exec
.
jaka jest funkcja exec i jej rodzina.
exec
Rodzina 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 .
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.
exec
zamienia 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 exec
polega na tym, że wiele zasobów, które jądro systemu operacyjnego sprawdza, aby zobaczyć, czy plik, do którego przekazujesz exec
jako argument programu (pierwszy argument), jest wykonywalny przez bieżącego użytkownika (identyfikator użytkownika procesu wykonanie exec
połączenia), a jeśli tak, zastępuje mapowanie pamięci wirtualnej bieżącego procesu pamięcią wirtualną nowego procesu i kopiuje dane argv
i envp
przekazane w exec
wywoł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ą int
wartoś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ń.
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.
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ą:
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?
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 ().
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.