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/13
rozpoczęcie pts/14
)
źródło
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).bash -c 'grep foo bar'
i wywoływanie exec jest formą optymalizacji, którą bash wykonuje dla ciebie automatycznieZgodnie z
pts
, sprawdź to sam: w powłoce, uruchomaby poznać twój identyfikator procesu (PID), mam na przykład
Następnie uruchom na przykład,
sleep 60
a następnie w innym terminaluWięc nie, ogólnie rzecz biorąc, masz ten sam tty powiązany z procesem. (Zauważ, że to twoja,
sleep
ponieważ ma twoją powłokę jako rodzic).źródło
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 foo
zadzwonićexec()
bezpośrednio przez rodzica, rodzic wykorzystałby istnienie, a jego PID ze wszystkimi zasobami zostałby przejętygrep blabla foo
.Porozmawiajmy jednak ogólnie o
exec()
ifork()
. Głównym powodem takiego zachowania jest to, żefork()/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 zbash
).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ą zexec()
funkcji rodziny, na przykładexecve()
, 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 przezfork()
i pozwolenie na przejęcie tego procesuexecve()
. Tak więc interaktywna powłoka PID 1156 odradza dziecko zafork()
pomocą PID 1157, a następnie wywołujeexecve("/bin/df",["df","-h"],&environment)
, co/bin/df -h
uruchamia 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 zpipe()
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 poprzezdup2()
wywołanie na jegostdout
aka fd 1 (więc jeśli koniec zapisu to fd 4, to robimydup2(4,1)
). Kiedyexec()
się odrodzidf
, proces potomny nic o nim nie pomyślistdout
i 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 tegofork()
, że odczytujemy koniec potoku z fd 3 idup(3,0)
przed spawnem zagrep
pomocą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ątkiemsource
polecenia. Wymagane są podpowłokifork()
.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>'
. Pomimofork()/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 :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: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: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ż
źródło
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.
źródło