Dlaczego powłoki wywołują fork ()?

32

Kiedy proces jest uruchamiany z powłoki, dlaczego sama powłoka rozwidla się przed wykonaniem procesu?

Na przykład, kiedy użytkownik wprowadza dane grep blabla foo, dlaczego powłoka nie może po prostu wywołać exec()grep bez powłoki potomnej?

Ponadto, gdy powłoka rozwidla się w emulatorze terminala GUI, czy uruchamia inny emulator terminala? (takie jak pts/13rozpoczęcie pts/14)

użytkownik3122885
źródło

Odpowiedzi:

34

Wywołanie execmetody rodziny nie powoduje utworzenia nowego procesu, zamiast tego execzastępuje bieżącą pamięć procesu i zestaw instrukcji itp. Procesem, który chcesz uruchomić.

Na przykład chcesz uruchomić grepprzy użyciu exec. bashjest procesem (który ma osobną pamięć, przestrzeń adresową). Teraz kiedy zadzwonisz exec(grep), exec zastąpi pamięć bieżącego procesu, przestrzeń adresową, zestaw instrukcji itp grep's. Danymi. Oznacza to, że bashproces już nie będzie istniał. W rezultacie nie możesz wrócić do terminala po wykonaniu greppolecenia. Dlatego metody z rodziny exec nigdy nie wracają. Nie można wykonać żadnego kodu po exec; jest nieosiągalny.

shantanu
źródło
Prawie ok --- podstawiłem Terminal bash. ;-)
Rmano
2
BTW, to można powiedzieć, aby wykonać bash grep bez rozwidlone pierwsze, za pomocą polecenia exec grep blabla foo. Oczywiście w tym konkretnym przypadku nie będzie to bardzo przydatne (ponieważ okno terminala zamknie się zaraz po zakończeniu grep), ale może być czasami przydatne (np. Jeśli uruchamiasz inną powłokę, być może za pośrednictwem ssh / sudo / screen, i nie zamierzaj wracać do pierwotnego, lub jeśli proces powłoki, na którym go uruchamiasz, jest podpowłoką, która i tak nigdy nie powinna wykonywać więcej niż jednego polecenia).
Ilmari Karonen
7
Zestaw instrukcji ma bardzo konkretne znaczenie. I to nie jest znaczenie, w jakim go używasz.
Andrew Savinykh
@IlmariKaronen Przydałoby się to w skryptach opakowujących, w których chcesz przygotować argumenty i środowisko dla polecenia. I wspomniany przypadek, w którym bash nigdy nie ma na celu uruchomienia więcej niż jednego polecenia, to tak naprawdę bash -c 'grep foo bar'i wywoływanie exec jest formą optymalizacji, którą bash wykonuje dla ciebie automatycznie
Sergiy Kolodyazhnyy
3

Zgodnie z pts, sprawdź to sam: w powłoce, uruchom

echo $$ 

aby poznać twój identyfikator procesu (PID), mam na przykład

echo $$
29296

Następnie uruchom na przykład, sleep 60a następnie w innym terminalu

(0)samsung-romano:~% ps -edao pid,ppid,tty,command | grep 29296 | grep -v grep
29296  2343 pts/11   zsh
29499 29296 pts/11   sleep 60

Więc nie, ogólnie rzecz biorąc, masz ten sam tty powiązany z procesem. (Zauważ, że to twoja, sleepponieważ ma twoją powłokę jako rodzic).

Rmano
źródło
2

TL; DR : Ponieważ jest to optymalna metoda tworzenia nowych procesów i utrzymywania kontroli w interaktywnej powłoce

fork () jest niezbędny dla procesów i rur

Aby odpowiedzieć na konkretną część tego pytania, gdyby grep blabla foozadzwonić exec()bezpośrednio przez rodzica, rodzic wykorzystałby istnienie, a jego PID ze wszystkimi zasobami zostałby przejęty grep blabla foo.

Porozmawiajmy jednak ogólnie o exec()i fork(). Głównym powodem takiego zachowania jest to, że fork()/exec()jest to standardowa metoda tworzenia nowego procesu w systemach Unix / Linux, a nie jest to rzecz specyficzna dla basha; metoda ta istnieje od samego początku i jest pod wpływem tej samej metody z już istniejących systemów operacyjnych tamtych czasów. Aby nieco sparafrazować odpowiedź goldilocks na pokrewne pytanie, fork()tworzenie nowego procesu jest łatwiejsze, ponieważ jądro ma mniej pracy, jeśli chodzi o przydzielanie zasobów, i wiele właściwości (takich jak deskryptory plików, środowisko itp.) - wszystko może być dziedziczone z procesu nadrzędnego (w tym przypadku z bash).

Po drugie, jeśli chodzi o interaktywne powłoki, nie można uruchomić zewnętrznego polecenia bez rozwidlenia. Aby uruchomić plik wykonywalny znajdujący się na dysku (na przykład /bin/df -h), musisz wywołać jedną z exec()funkcji rodziny, na przykład execve(), która zastąpi element nadrzędny nowym procesem, przejmie jego identyfikator PID i istniejące deskryptory plików itp. W przypadku powłoki interaktywnej chcesz, aby formant zwrócił się do użytkownika i pozwolił na kontynuację nadrzędnej powłoki interaktywnej. Zatem najlepszym sposobem jest utworzenie podprocesu przez fork()i pozwolenie na przejęcie tego procesu execve(). Tak więc interaktywna powłoka PID 1156 odradza dziecko za fork()pomocą PID 1157, a następnie wywołuje execve("/bin/df",["df","-h"],&environment), co /bin/df -huruchamia się z PID 1157. Teraz powłoka musi tylko czekać na zakończenie procesu i przywrócenie mu kontroli.

W przypadku, gdy musisz utworzyć potok między dwoma lub więcej poleceniami, powiedzmy df | grep, że potrzebujesz sposobu na utworzenie dwóch deskryptorów plików (czyli odczyt i zapis końca potoku, który pochodzi z pipe()syscall), a następnie pozwól, aby odziedziczyły je dwa nowe procesy. Odbywa się to przy rozwiązywaniu nowego procesu, a następnie poprzez kopiowanie końca zapisu potoku poprzez dup2()wywołanie na jego stdoutaka fd 1 (więc jeśli koniec zapisu to fd 4, to robimy dup2(4,1)). Kiedy exec()się odrodzi df, proces potomny nic o nim nie pomyśli stdouti napisze do niego, nie będąc świadomym (chyba że aktywnie sprawdza), że jego dane wyjściowe idą potokiem. Ten sam proces zdarza się grep, z wyjątkiem tego fork(), że odczytujemy koniec potoku z fd 3 i dup(3,0)przed spawnem za greppomocąexec(). Cały czas trwa proces nadrzędny, który czeka na odzyskanie kontroli po ukończeniu potoku.

W przypadku wbudowanych poleceń, ogólnie powłoka nie fork(), z wyjątkiem sourcepolecenia. Wymagane są podpowłoki fork().

Krótko mówiąc, jest to niezbędny i użyteczny mechanizm.

Wady rozwidlania i optymalizacji

Teraz jest inaczej w przypadku nieinteraktywnych powłok , takich jak bash -c '<simple command>'. Pomimo fork()/exec()tego, że jest to optymalna metoda, w której musisz przetwarzać wiele poleceń, marnowanie zasobów jest możliwe, gdy masz tylko jedno polecenie. Cytując Stéphane'a Chazelasa z tego postu :

Rozwidlenie jest drogie, czas procesora, pamięć, przydzielone deskryptory plików ... Posiadanie procesu powłoki polegającego na czekaniu na inny proces przed wyjściem jest marnowaniem zasobów. Utrudnia to także prawidłowe zgłaszanie statusu wyjścia z osobnego procesu, który wykonałby polecenie (na przykład, gdy proces został zabity).

Dlatego wiele powłok (nie tylko bash) używa, exec()aby umożliwić bash -c ''przejęcie tego pojedynczego prostego polecenia. I właśnie z wyżej wymienionych powodów, minimalizacja potoków w skryptach powłoki jest lepsza. Często widać, że początkujący robią coś takiego:

cat /etc/passwd | cut -d ':' -f 6 | grep '/home'

Oczywiście będą to fork()3 procesy. Jest to prosty przykład, ale rozważ duży plik z zakresu gigabajtów. Byłby o wiele bardziej wydajny z jednym procesem:

awk -F':' '$6~"/home"{print $6}' /etc/passwd

Marnowanie zasobów może w rzeczywistości być formą ataku Denial of Service, a w szczególności bomby rozwidlone są tworzone za pomocą funkcji powłoki, które wywołują się w rurociągu, który wykopuje wiele kopii. Obecnie ogranicza się to poprzez ograniczenie maksymalnej liczby procesów w grupach systemd na systemied , z których korzysta także Ubuntu od wersji 15.04.

Oczywiście nie oznacza to, że rozwidlenie jest po prostu złe. Jest to nadal przydatny mechanizm, jak omówiono wcześniej, ale w przypadku, gdy można uniknąć mniejszych procesów, a co za tym idzie mniej zasobów, a tym samym lepszej wydajności, należy unikać, fork()jeśli to możliwe.

Zobacz też

Sergiy Kolodyazhnyy
źródło
1

Dla każdego polecenia (na przykład: grep), które wydajesz w wierszu polecenia bash, faktycznie zamierzasz rozpocząć nowy proces, a następnie powrócić do wiersza polecenia bash po wykonaniu.

Jeśli proces powłoki (bash) wywoła exec () w celu uruchomienia grep, proces powłoki zostanie zastąpiony grep. Grep będzie działał dobrze, ale po wykonaniu formant nie może powrócić do powłoki, ponieważ proces bash jest już zastąpiony.

Z tego powodu bash wywołuje fork (), który nie zastępuje bieżącego procesu.

FlowRaja
źródło