Perché le shell chiamano fork ()?


32

Quando un processo viene avviato da una shell, perché la shell esegue il fork stesso prima di eseguire il processo?

Ad esempio, quando l'utente inserisce grep blabla foo, perché la shell non può semplicemente chiamare exec()grep senza una shell figlio?

Inoltre, quando una shell si forgia all'interno di un emulatore di terminale GUI, avvia un altro emulatore di terminale? (come l' pts/13avvio pts/14)

Risposte:


34

Quando si chiama un execmetodo familiare, questo non crea un nuovo processo, execsostituisce invece la memoria di processo corrente, il set di istruzioni ecc. Con il processo che si desidera eseguire.

Ad esempio, si desidera eseguire greputilizzando exec. bashè un processo (che ha memoria separata, spazio di indirizzi). Ora quando chiami exec(grep), exec sostituirà la memoria del processo corrente, lo spazio degli indirizzi, il set di istruzioni ecc. Con i grep'sdati. Ciò significa che il bashprocesso non esisterà più. Di conseguenza non è possibile tornare al terminale dopo aver completato il grepcomando. Ecco perché i metodi della famiglia exec non ritornano mai. Non è possibile eseguire alcun codice dopo exec; è irraggiungibile.


Quasi ok --- Ho sostituito Terminal con bash. ;-)
Rmano

2
A proposito, puoi dire a bash di eseguire grep senza prima biforcarti, usando il comando exec grep blabla foo. Naturalmente, in questo caso particolare, non sarà molto utile (poiché la finestra del tuo terminale si chiuderà appena termina il grep), ma può essere occasionalmente utile (ad esempio se stai avviando un'altra shell, forse via ssh / sudo / screen, e non intendete tornare a quello originale, o se il processo di shell su cui state eseguendo questo è un sub-shell che non ha mai lo scopo di eseguire più di un comando).
Ilmari Karonen,

7
Il set di istruzioni ha un significato molto specifico. E non è il significato in cui lo stai usando.
Andrew Savinykh

@IlmariKaronen Sarebbe utile negli script wrapper, in cui si desidera preparare argomenti e ambiente per un comando. E il caso che hai citato, dove bash non è mai pensato per eseguire più di un comando, in realtà bash -c 'grep foo bar'e chiamando exec c'è una forma di ottimizzazione che bash fa automaticamente per te
Sergiy Kolodyazhnyy,

3

Come da pts, controllalo tu stesso: in una shell, corri

echo $$ 

per conoscere il tuo ID processo (PID), ad esempio

echo $$
29296

Quindi eseguire ad esempio sleep 60e quindi, in un altro terminale

(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

Quindi no, in generale hai lo stesso tty associato al processo. (Nota che questo è tuo sleepperché ha la tua shell come genitore).


2

TL; DR : Perché questo è il metodo ottimale per creare nuovi processi e mantenere il controllo nella shell interattiva

fork () è necessario per processi e tubi

Per rispondere alla parte specifica di questa domanda, se grep blabla foodovesse essere chiamato exec()direttamente tramite genitore, il genitore si impadronirebbe di esistere e il suo PID con tutte le risorse verrebbe rilevato da grep blabla foo.

Tuttavia, parliamo in generale di exec()e fork(). Il motivo principale di tale comportamento è perché fork()/exec()è il metodo standard per creare un nuovo processo su Unix / Linux, e questa non è una cosa specifica; questo metodo è stato applicato sin dall'inizio e influenzato da questo stesso metodo da sistemi operativi già esistenti del tempo. Parafrasare in qualche modo la risposta di goldilocks su una domanda correlata, fork()per creare un nuovo processo è più semplice poiché il kernel ha meno lavoro da fare per quanto riguarda l'allocazione delle risorse e molte proprietà (come descrittori di file, ambiente, ecc.) essere ereditato dal processo parent (in questo caso da bash).

In secondo luogo, per quanto riguarda le shell interattive, non è possibile eseguire un comando esterno senza biforcazione. Per avviare un eseguibile che risiede sul disco (ad esempio /bin/df -h), è necessario chiamare una delle exec()funzioni familiari, come ad esempio execve(), che sostituirà il genitore con il nuovo processo, assumerà il suo PID e i descrittori di file esistenti, ecc. Per la shell interattiva, si desidera che il controllo ritorni all'utente e lasciare che la shell interattiva padre continui. Pertanto, il modo migliore è quello di creare un sottoprocesso tramite fork()e lasciare che tale processo venga acquisito tramite execve(). Quindi il PID 1156 della shell interattivo genererebbe un figlio tramite fork()PID 1157, quindi chiamerebbe execve("/bin/df",["df","-h"],&environment), che viene /bin/df -heseguito con PID 1157. Ora la shell deve solo attendere che il processo termini e restituisca il controllo.

Nel caso in cui sia necessario creare una pipe tra due o più comandi, ad esempio df | grep, è necessario un modo per creare due descrittori di file (ovvero leggere e scrivere end of pipe che provengono da pipe()syscall), quindi in qualche modo lasciare che due nuovi processi li ereditino. Questo viene fatto biforcando un nuovo processo e quindi copiando l'estremità di scrittura della pipe tramite dup2()call sul suo stdoutaka fd 1 (quindi se end di scrittura è fd 4, lo facciamo dup2(4,1)). Quando exec()per deporre le uova dfavviene il processo figlio penserà nulla del suo stdoute scrivere senza essere a conoscenza (a meno che attivamente controlli) che la sua uscita va in realtà un tubo. Stesso processo succede grep, tranne che fork(), prendere fine di lettura di tubo con fd 3 e dup(3,0)prima deposizione delle uova grepconexec(). Per tutto questo tempo il processo padre è ancora lì, in attesa di riprendere il controllo una volta completata la pipeline.

Nel caso di comandi integrati, generalmente la shell no fork(), ad eccezione del sourcecomando. Le sottostrutture richiedono fork().

In breve, questo è un meccanismo necessario e utile.

Svantaggi del fork e delle ottimizzazioni

Ora, questo è diverso per le shell non interattive , come ad esempio bash -c '<simple command>'. Nonostante fork()/exec()sia un metodo ottimale in cui devi elaborare molti comandi, è uno spreco di risorse quando hai un solo comando. Per citare Stéphane Chazelas da questo post :

Il fork è costoso, in termini di tempo di CPU, memoria, descrittori di file allocati ... Avere un processo shell che sta aspettando solo un altro processo prima di uscire è solo uno spreco di risorse. Inoltre, rende difficile riportare correttamente lo stato di uscita del processo separato che eseguirà il comando (ad esempio, quando il processo viene interrotto).

Pertanto, molte shell (e non solo bash) usano exec()per consentire che questo bash -c ''venga assunto da quel singolo semplice comando. Ed esattamente per i motivi sopra indicati, è meglio ridurre al minimo le pipeline negli script di shell. Spesso puoi vedere i principianti fare qualcosa del genere:

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

Certo, questo sarà fork()3 processi. Questo è un semplice esempio, ma considera un file di grandi dimensioni, nel raggio di Gigabyte. Sarebbe molto più efficiente con un processo:

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

Lo spreco di risorse in realtà può essere una forma di attacco Denial of Service, e in particolare le bombe a forcella vengono create tramite funzioni shell che si autodefiniscono in pipeline, che crea più copie di se stesse. Al giorno d'oggi, questo viene mitigato limitando il numero massimo di processi nei cgroup su systemd , che Ubuntu utilizza anche dalla versione 15.04.

Ovviamente ciò non significa che il fork sia solo male. È ancora un meccanismo utile come discusso in precedenza, ma nel caso in cui è possibile cavarsela con meno processi e consecutivamente meno risorse e quindi prestazioni migliori, è necessario evitare fork()se possibile.

Guarda anche


1

Per ogni comando (esempio: grep) che si emette sul prompt di bash, si intende effettivamente avviare un nuovo processo e quindi tornare al prompt di bash dopo l'esecuzione.

Se il processo di shell (bash) chiama exec () per eseguire grep, il processo di shell verrà sostituito con grep. Grep funzionerà bene ma dopo l'esecuzione, il controllo non può tornare alla shell perché il processo bash è già stato sostituito.

Per questo motivo, bash chiama fork (), che non sostituisce il processo corrente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.