GNU/Linux >> Linux Esercitazione >  >> Linux

Perché vfork() deve essere utilizzato quando il processo figlio chiama exec() o exit() immediatamente dopo la creazione?

Quando un processo figlio creato da vfork() chiama exec() , non exec() modificare lo spazio degli indirizzi del processo genitore, caricando il nuovo programma?

No, exec() fornisce un nuovo spazio di indirizzi per il nuovo programma; non modifica lo spazio degli indirizzi padre. Vedi ad esempio la discussione sul exec funzioni in POSIX e Linux execve() manpage.

Quando un processo figlio creato da vfork() chiama exit(), exit() non modifica lo spazio degli indirizzi del processo genitore quando termina il figlio?

Semplice exit() might – esegue gli hook di uscita installati dal programma in esecuzione (comprese le sue librerie). vfork() è più restrittivo; quindi, su Linux, impone l'uso di _exit() che non chiama le funzioni di pulizia della libreria C.

vfork() si è rivelato piuttosto difficile da ottenere correttamente; è stato rimosso nelle versioni correnti dello standard POSIX e posix_spawn() dovrebbe essere usato invece.

Tuttavia, a meno che tu non davvero sai cosa stai facendo, non dovresti non usa vfork() o posix_spawn(); attenersi al buon vecchio fork() e exec() .

La manpage di Linux collegata sopra fornisce più contesto:

Tuttavia, ai vecchi tempi un fork(2) richiederebbe di fare una copia completa dello spazio dati del chiamante, spesso inutilmente, poiché di solito subito dopo un exec(3) è fatta. Così, per una maggiore efficienza, BSD ha introdotto il vfork() chiamata di sistema, che non copiava completamente lo spazio degli indirizzi del processo genitore, ma prendeva in prestito la memoria e il thread di controllo del genitore fino alla chiamata a execve(2) o si è verificata un'uscita. Il processo genitore è stato sospeso mentre il figlio ne utilizzava le risorse. L'uso divfork() era complicato:per esempio, non modificare i dati nel processo genitore dipendeva dal sapere quali variabili erano contenute in un registro.


Quando chiami vfork() , viene creato un nuovo processo e quel nuovo processo prende in prestito l'immagine del processo del processo padre ad eccezione dello stack. Al processo figlio viene assegnata una nuova stella dello stack, tuttavia non consente return dalla funzione che ha chiamato vfork() .

Mentre il figlio è in esecuzione, il processo genitore è bloccato, poiché il figlio ha preso in prestito lo spazio degli indirizzi del genitore.

Indipendentemente da ciò che fai, tutto ciò che accede allo stack modifica solo lo stack privato del bambino. Se invece modifichi i dati globali, ciò modifica i dati comuni e quindi influisce anche sul genitore.

Le cose che modificano i dati globali sono ad esempio:

  • chiamando malloc() o free()

  • usando stdio

  • modificare le impostazioni del segnale

  • modificando variabili che non sono locali alla funzione che ha chiamato vfork() .

  • ...

Dopo aver chiamato _exit() (importante, non chiamare mai exit() ), il figlio viene terminato e il controllo viene restituito al genitore.

Se chiami qualsiasi funzione dal exec*() famiglia, viene creato un nuovo spazio indirizzi con nuovo codice programma, nuovi dati e una parte dello stack dal genitore (vedi sotto). Una volta che questo è pronto, il bambino non prende più in prestito lo spazio degli indirizzi dal bambino, ma utilizza un proprio spazio degli indirizzi.

Il controllo viene restituito al genitore, poiché il suo spazio degli indirizzi non è più utilizzato da un altro processo.

Importante:su Linux, non esiste un vero vfork() implementazione. Linux invece implementa vfork() basato sulla copia su scrittura fork() concetto introdotto da SunOS-4.0 nel 1988. Per far credere agli utenti che usano vfork() , Linux imposta solo i dati condivisi e sospende il genitore mentre il figlio non ha chiamato _exit() o uno dei exec*() funzioni.

Linux quindi non beneficia del fatto che un vero vfork() non ha bisogno di impostare una descrizione dello spazio degli indirizzi per il figlio nel kernel. Ciò si traduce in un vfork() che non è più veloce di fork() . Sui sistemi che implementano un vero vfork() , in genere è 3 volte più veloce di fork() e influenza le prestazioni delle shell che usano vfork() - ksh93 , il recente Bourne Shell e csh .

Il motivo per cui non dovresti mai chiamare exit() dal vfork() ed child è quel exit() scarica stdio nel caso in cui ci siano dati non scaricati prima della chiamata a vfork() . Ciò potrebbe causare strani risultati.

A proposito:posix_spawn() è implementato sopra vfork() , quindi vfork() non verrà rimosso dal sistema operativo. È stato detto che Linux non usa vfork() per posix_spawn() .

Per lo stack, c'è poca documentazione, ecco cosa dice la pagina man di Solaris:

 The vfork() and vforkx() functions can normally be used  the
 same  way  as  fork() and forkx(), respectively. The calling
 procedure, however, should not return while running  in  the
 child's  context,  since the eventual return from vfork() or
 vforkx() in the parent would be to a  stack  frame  that  no
 longer  exists. 

Quindi l'implementazione può fare quello che vuole. L'implementazione di Solaris utilizza la memoria condivisa per lo stack frame della funzione che chiama vfork() . Nessuna implementazione garantisce l'accesso alle parti precedenti dello stack dal genitore.


Linux
  1. Nuovo processo genitore quando il processo genitore muore?

  2. Perché Sigint non viene propagato al processo figlio quando viene inviato al processo padre?

  3. Perché il meccanismo di creazione del processo predefinito è fork?

  4. Perché lo script Bash non si chiude dopo l'esecuzione?

  5. Perché Systemd interrompe il servizio immediatamente dopo l'avvio?

La differenza tra fork(), vfork(), exec() e clone()

Perché il processo figlio è ancora vivo dopo che il processo genitore è stato ucciso in Linux?

Come uccidere un processo figlio python creato con subprocess.check_output() quando il genitore muore?

perché l'elenco di fratelli viene utilizzato per ottenere task_struct durante il recupero dei figli di un processo

perché un ciclo while bash non esce durante il piping al sottocomando terminato?

Esiste una distribuzione Linux o una patch del kernel che cancella lo spazio di memoria di un processo dopo che il processo è terminato?