Differenze tra fork ed exec

Quali sono le differenze tra fork ed exec ?

L’uso di fork ed exec esemplifica lo spirito di UNIX in quanto fornisce un modo molto semplice per avviare nuovi processi.

La chiamata a fork pratica fa un duplicato del processo corrente, identico in quasi tutti i modi (non tutto viene copiato, ad esempio, i limiti delle risorse in alcune implementazioni, ma l’idea è di creare il più vicino ansible una copia).

Il nuovo processo (figlio) ottiene un diverso ID di processo (PID) e ha il PID del vecchio processo (genitore) come PID padre (PPID). Poiché i due processi stanno eseguendo esattamente lo stesso codice, possono dire quale è il codice di ritorno della fork : il bambino ottiene 0, il genitore ottiene il PID del figlio. Questo è tutto, ovviamente, assumendo che la chiamata a fork funzioni – in caso contrario, nessun bambino viene creato e il genitore riceve un codice di errore.

La chiamata exec è un modo per sostituire sostanzialmente l’intero processo corrente con un nuovo programma. Carica il programma nello spazio del processo corrente e lo esegue dal punto di ingresso.

Quindi, fork e exec vengono spesso utilizzati in sequenza per ottenere un nuovo programma in esecuzione come figlio di un processo corrente. I gusci tipicamente lo fanno ogni volta che si tenta di eseguire un programma come find – le shell fork, quindi il bambino carica il programma di find in memoria, impostando tutti gli argomenti della riga di comando, l’I / O standard e così via.

Ma non devono essere usati insieme. È perfettamente accettabile per un programma eseguire il fork senza exec se, ad esempio, il programma contiene sia il codice genitore che quello figlio (è necessario fare attenzione a ciò che si fa, ogni implementazione può avere restrizioni). Questo è stato usato parecchio (e lo è ancora) per i demoni che semplicemente ascoltano su una porta TCP e fork una copia di se stessi per elaborare una richiesta specifica mentre il genitore torna ad ascoltare.

Allo stesso modo, i programmi che sanno che sono finiti e vogliono solo eseguire un altro programma non hanno bisogno di fork , exec e quindi wait il bambino. Possono semplicemente caricare il bambino direttamente nel loro spazio di processo.

Alcune implementazioni UNIX hanno un fork ottimizzato che utilizza ciò che chiamano copy-on-write. Questo è un trucco per ritardare la copia dello spazio del processo nella fork finché il programma non tenta di cambiare qualcosa in quello spazio. Questo è utile per quei programmi che usano solo fork e non exec in quanto non devono copiare un intero spazio del processo.

Se l’ exec viene chiamato seguendo fork (e questo è ciò che accade per lo più), ciò provoca una scrittura nello spazio del processo e viene quindi copiato per il processo figlio.

Si noti che esiste un’intera famiglia di chiamate exec ( execl , execle , execve e così via), ma exec in context qui significa qualsiasi di esse.

Il seguente diagramma illustra la tipica operazione fork/exec in cui viene utilizzata la shell bash per elencare una directory con il comando ls :

 +--------+ | pid=7 | | ppid=4 | | bash | +--------+ | | calls fork V +--------+ +--------+ | pid=7 | forks | pid=22 | | ppid=4 | ----------> | ppid=7 | | bash | | bash | +--------+ +--------+ | | | waits for pid 22 | calls exec to run ls | V | +--------+ | | pid=22 | | | ppid=7 | | | ls | V +--------+ +--------+ | | pid=7 | | exits | ppid=4 | <---------------+ | bash | +--------+ | | continues V 

fork() divide il processo corrente in due processi. In altre parole, il tuo semplice lineare facile pensare al programma diventa improvvisamente due programmi separati che eseguono un pezzo di codice:

  int pid = fork(); if (pid == 0) { printf("I'm the child"); } else { printf("I'm the parent, my child is %i", pid); // here we can kill the child, but that's not very parently of us } 

Questo può farti impazzire. Ora hai un pezzo di codice con uno stato praticamente identico che viene eseguito da due processi. Il processo figlio eredita tutto il codice e la memoria del processo che lo ha appena creato, incluso l’inizio da dove è stata interrotta la chiamata a fork() . L’unica differenza è il codice di ritorno fork() per dirti se sei il genitore o il bambino. Se sei il genitore, il valore di ritorno è l’id del bambino.

exec è un po ‘più facile da capire, basta dire a exec di eseguire un processo usando l’eseguibile di destinazione e non si hanno due processi che eseguono lo stesso codice o ereditano lo stesso stato. Come dice @Steve Hawkins, exec può essere usato dopo aver forato per eseguire nel processo corrente il file eseguibile di destinazione.

Penso che alcuni concetti di “Advanced Unix Programming” di Marc Rochkind siano stati utili per comprendere i diversi ruoli di fork() / exec() , specialmente per chi è abituato al modello di Windows CreateProcess() :

Un programma è una raccolta di istruzioni e dati conservati su un normale file su disco. (da 1.1.2 Programmi, processi e thread)

.

Per eseguire un programma, al kernel viene prima chiesto di creare un nuovo processo , che è un ambiente in cui viene eseguito un programma. (anche da 1.1.2 Programmi, processi e thread)

.

È imansible comprendere le chiamate di sistema exec o fork senza una completa comprensione della distinzione tra un processo e un programma. Se questi termini sono nuovi per te, puoi tornare indietro e rivedere la Sezione 1.1.2. Se sei pronto a procedere ora, riassumeremo la distinzione in una frase: Un processo è un ambiente di esecuzione che comprende segmenti di istruzioni, dati utente e di dati di sistema, nonché molte altre risorse acquisite in fase di esecuzione , mentre un programma è un file contenente istruzioni e dati che vengono utilizzati per inizializzare l’istruzione e i segmenti di dati utente di un processo. (da 5.3 exec sistema exec )

Una volta compresa la distinzione tra un programma e un processo, il comportamento della funzione fork() ed exec() può essere riassunto come:

  • fork() crea un duplicato del processo corrente
  • exec() sostituisce il programma nel processo corrente con un altro programma

(questa è essenzialmente una versione semplificata “for dummies” della risposta molto più dettagliata di paxdiablo )

Fork crea una copia di un processo di chiamata. generalmente segue la struttura inserisci la descrizione dell'immagine qui

 int cpid = fork( ); if (cpid = = 0) { //child code exit(0); } //parent code wait(cpid); // end 

(per il testo del processo figlio (codice), i dati, lo stack è uguale al processo chiamante) il processo figlio esegue il codice in se block.

EXEC sostituisce il processo corrente con il codice, i dati, lo stack del nuovo processo. generalmente segue la struttura inserisci la descrizione dell'immagine qui

 int cpid = fork( ); if (cpid = = 0) { //child code exec(foo); exit(0); } //parent code wait(cpid); // end 

(dopo la chiamata exec il kernel di unix cancella il testo del processo figlio, i dati, lo stack e riempie di testo / dati relativi al processo foo), quindi il processo figlio è con codice diverso (codice di foo {non uguale a genitore})

Sono usati insieme per creare un nuovo processo figlio. Innanzitutto, il fork chiamata crea una copia del processo corrente (il processo figlio). Quindi, exec viene chiamato all’interno del processo figlio per “sostituire” la copia del processo genitore con il nuovo processo.

Il processo è simile a questo:

 child = fork(); //Fork returns a PID for the parent process, or 0 for the child, or -1 for Fail if (child < 0) { std::cout << "Failed to fork GUI process...Exiting" << std::endl; exit (-1); } else if (child == 0) { // This is the Child Process // Call one of the "exec" functions to create the child process execvp (argv[0], const_cast(argv)); } else { // This is the Parent Process //Continue executing parent process } 

fork () crea una copia del processo corrente, con l’esecuzione nel nuovo figlio a partire subito dopo la chiamata a fork (). Dopo il fork (), sono identici, ad eccezione del valore restituito dalla funzione fork (). (RTFM per ulteriori dettagli.) I due processi possono quindi divergere ulteriormente, con uno in grado di interferire con l’altro, tranne eventualmente attraverso eventuali handle di file condivisi.

exec () sostituisce il processo corrente con uno nuovo. Non ha nulla a che fare con fork (), ad eccezione del fatto che un exec () segue spesso fork () quando ciò che si desidera è avviare un processo figlio diverso, piuttosto che sostituire quello corrente.

inserisci la descrizione dell'immagine qui fork() :

Crea una copia del processo in esecuzione. Il processo in esecuzione è chiamato processo genitore e il processo appena creato è chiamato processo figlio . Il modo per differenziare i due è guardando il valore restituito:

  1. fork() restituisce l’identificatore di processo (pid) del processo figlio nel genitore

  2. fork() restituisce 0 nel figlio.

exec() :

Avvia un nuovo processo all’interno di un processo. Carica un nuovo programma nel processo corrente, sostituendo quello esistente.

fork() + exec() :

Quando si avvia un nuovo programma, innanzitutto fork() , creando un nuovo processo e quindi exec() (cioè carica in memoria ed esegue) il programma binario che si suppone debba eseguire.

 int main( void ) { int pid = fork(); if ( pid == 0 ) { execvp( "find", argv ); } //Put the parent to sleep for 2 sec,let the child finished executing wait( 2 ); return 0; } 

Il primo esempio per comprendere il concetto di fork() ed exec() è la shell , il programma interprete dei comandi che gli utenti tipicamente eseguono dopo aver effettuato l’accesso al sistema. La shell interpreta la prima parola della riga di comando come nome del comando

Per molti comandi, il fork fork e il processo figlio eseguono il comando associato al nome trattando le parole rimanenti sulla riga di comando come parametri del comando.

La shell consente tre tipi di comandi. Primo, un comando può essere un file eseguibile che contiene un codice object prodotto dalla compilazione del codice sorgente (un programma C per esempio). In secondo luogo, un comando può essere un file eseguibile che contiene una sequenza di righe di comando della shell. Infine, un comando può essere un comando di shell interno (invece di un file eseguibile ex-> cd , ls ecc.)