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


11

Concetti sul sistema operativo e APUE dicono

Con vfork (), il processo parent viene sospeso e il processo child utilizza lo spazio degli indirizzi del parent. Poiché vfork () non utilizza la funzione di copia su scrittura, se il processo figlio modifica le pagine dello spazio degli indirizzi del genitore, le pagine modificate saranno visibili al genitore una volta ripreso. Pertanto, vfork () deve essere usato con cautela per garantire che il processo figlio non modifichi lo spazio degli indirizzi del genitore.

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

Come devo capire l'ultima frase?

Quando un processo figlio creato da vfork()chiamate exec(), non exec()modifica lo spazio degli indirizzi del processo principale, caricando il nuovo programma?

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

Preferisco Linux.

Grazie.

Risposte:


15

Quando un processo figlio creato da vfork()chiamate exec(), non exec()modifica lo spazio degli indirizzi del processo principale, caricando il nuovo programma?

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

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

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

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

Tuttavia, a meno che davvero sa cosa si sta facendo, si dovrebbe non utilizzare 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 una copia completa dello spazio dati del chiamante, spesso inutilmente, poiché di solito immediatamente dopo exec(3)viene fatto. Pertanto, per una maggiore efficienza, BSD ha introdotto la vfork() chiamata di sistema, che non ha copiato completamente lo spazio degli indirizzi del processo padre, ma ha preso in prestito la memoria del genitore e il thread di controllo fino a quando non si è verificata una chiamata execve(2)o un'uscita. Il processo padre è stato sospeso mentre il bambino utilizzava le sue risorse. L'uso di vfork()era complicato: ad esempio, non modificare i dati nel processo genitore dipendeva dal sapere quali variabili erano contenute in un registro.


Grazie. "exec () fornisce un nuovo spazio indirizzo per il nuovo programma;" Il normale comportamento di exec () è quello di caricare un programma nello spazio degli indirizzi del processo? Non ho trovato nei due collegamenti dove crea un nuovo spazio di indirizzi né normalmente né in particolare per vfork ().
Tim

1
La cosa divertente è che vfork () sta vincendo contro tutto il resto ora. È ridicolmente più veloce di fork () quando hai un gigabyte di memoria scrivibile.
Giosuè,

2
Per favore, non dire alle persone di usare posix_spawn. È significativamente più difficile scrivere il codice corretto usando posix_spawnche con il semplice vecchio fork, e se ci provi, potresti imbatterti nel muro di mattoni in quanto non c'è un'azione di file o un attributo che fa la cosa che devi fare tra forke exec. E non è garantito che abbia un'efficienza simile a vfork, quindi non risolve nemmeno il problema che la gente vuole che risolva.
zwol,

1
@zwol: è davvero un brutto consiglio. Sebbene posix_spawnpossa mancare la funzionalità desiderata (è possibile risolverlo tramite un programma di supporto intermedio, scritto in C o script di shell inline-on-cmdline), qualsiasi tentativo di ottenere ciò che si desidera vforkinvoca un comportamento pericoloso non definito. La specifica per vforknon consente di chiamare funzioni casuali per impostare lo stato che il figlio erediterà prima execve, e i tentativi di farlo potrebbero corrompere lo stato del genitore.
R .. GitHub FERMA AIUTANDO ICE

1
@Joshua: un'implementazione moderna di posix_spawnfunziona vforkpiù o meno come nella maggior parte delle condizioni. Quelli in cui c'è una differenza tendono ad essere esattamente i casi in cui vforkè altamente pericoloso: dove ci sono gestori di segnali installati che posix_spawndevono sopprimere l'esecuzione nel figlio prima di exec.
R .. GitHub FERMA AIUTANDO ICE

4

Quando si chiama 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 ciò non consente returndalla funzione che ha chiamato vfork().

Mentre il bambino è in esecuzione, il processo padre viene bloccato, poiché il bambino ha preso in prestito lo spazio degli indirizzi del padre.

Indipendentemente da ciò che fai, tutto ciò che accede allo stack modifica solo lo stack privato del bambino. Se tuttavia si modificano i dati globali, questo 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

  • modifica delle impostazioni del segnale

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

  • ...

Una volta chiamato _exit()(importante, non chiamare mai exit()), il figlio termina e il controllo viene restituito al genitore.

Se chiamate una funzione dalla exec*()famiglia, viene creato un nuovo spazio indirizzo con un 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é lo spazio degli indirizzi non è più utilizzato da un altro processo.

Importante: su Linux non esiste vfork()un'implementazione reale . Linux implementa piuttosto in vfork()base al fork()concetto Copy on Write introdotto da SunOS-4.0 nel 1988. Per far credere agli utenti che usano vfork(), Linux imposta semplicemente i dati condivisi e sospende il genitore mentre il bambino non ha chiamato _exit()o una delle 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 reale vfork(), è in genere 3 volte più veloce di fork()e influisce sulle prestazioni delle shell che usano vfork()- ksh93, il recente Bourne Shelle csh.

Il motivo per cui non dovresti mai chiamare exit()dal vfork()figlio ed è che exit()scarica lo stdio nel caso in cui ci siano dati non scaricati dal momento prima di chiamare vfork(). Ciò potrebbe causare strani risultati.

A proposito: posix_spawn()è implementato sopra vfork(), quindi vfork()non verrà rimosso dal sistema operativo. È stato menzionato 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 qualunque cosa gli piaccia. L'implementazione di Solaris utilizza la memoria condivisa per il frame dello stack della funzione che chiama vfork(). Nessuna implementazione garantisce l'accesso alle parti più vecchie dello stack dal genitore.


4
Né la libreria GNU C né la libreria musl C implementano posix_spawn()su Linux vfork(). Lo implementano entrambi __clone().
JdeBP,

1
@JdeBP: Sai vfork()solo chiamate clone()giuste? È letteralmente una riga nel kernel.
Joshua,

1
"Importante: su Linux non esiste una vera implementazione di vfork ()." <- Questo non è vero e non è vero da almeno un decennio. Se il benchmark della shell non sta osservando alcuna differenza di prestazioni tra vforke forksu Linux, sta facendo qualcosa di sbagliato.
zwol,

1
La seconda parte di questa risposta che inizia con "Importante: su Linux, non esiste una vera implementazione di vfork ()" è per lo più o del tutto errata.
R .. GitHub FERMA AIUTANDO ICE

1
Si prega di non presentare reclami senza verificare. L'attuale Bourne Shell può essere compilata con e senza il supporto di vfork, quindi anche se si ritiene che le funzionalità di debug di Linux non siano in grado di fornire risultati affidabili, è possibile confrontare i tempi di esecuzione di una chiamata di configurazione con e con vfork nella shell. Uso uno script di configurazione con 800 test. Su Solaris, la Bourne Shell che utilizza vfork richiede in totale il 30% di tempo in meno di CPU del sistema rispetto alla custodia a forcella. Su Linux, lo stesso test si traduce in meno del 10% in meno di tempo di CPU del sistema. Su Solaris non è 3x poiché sono incluse molte chiamate al compilatore.
schily
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.