Quando un processo figlio creato da
vfork()
chiamaexec()
, nonexec()
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 unexec(3)
è fatta. Così, per una maggiore efficienza, BSD ha introdotto ilvfork()
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 aexecve(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.