Quali sono le differenze tra fork
e exec
?
fork
è sostanzialmente la clonazione: O
Quali sono le differenze tra fork
e exec
?
fork
è sostanzialmente la clonazione: O
Risposte:
L'uso fork
e exec
esemplifica lo spirito di UNIX in quanto fornisce un modo molto semplice per avviare nuovi processi.
La fork
chiamata sostanzialmente fa un duplicato del processo corrente, identico in quasi tutti i modi. Non tutto viene copiato (ad esempio, limiti di risorse in alcune implementazioni) ma l'idea è quella di creare una copia il più vicino possibile.
Il nuovo processo (figlio) ottiene un diverso ID processo (PID) e ha il PID del vecchio processo (padre) come PID padre (PPID). Poiché ora i due processi eseguono esattamente lo stesso codice, possono stabilire quale sia il codice di ritorno di fork
: il figlio ottiene 0, il genitore ottiene il PID del figlio. Ovviamente, tutto ciò presuppone che la fork
chiamata funzioni: in caso contrario, non viene creato alcun figlio e il genitore riceve un codice di errore.
La exec
chiamata è 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
sono spesso usati in sequenza per far funzionare un nuovo programma come figlio di un processo corrente. Le shell in genere lo fanno ogni volta che si tenta di eseguire un programma come find
: le shell si biforca, quindi il bambino carica il find
programma 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 a fork
se stesso senza exec
ing se, ad esempio, il programma contiene sia codice padre che figlio (è necessario fare attenzione a ciò che si fa, ogni implementazione può avere delle restrizioni). Questo è stato usato parecchio (e lo è ancora) per i demoni che semplicemente ascoltano su una porta TCP e fork
una loro copia per elaborare una richiesta specifica mentre il genitore torna ad ascoltare.
Allo stesso modo, i programmi che sanno di aver finito e vogliono semplicemente eseguire un altro programma non sono necessari fork
, exec
e quindi wait
per il bambino. Possono semplicemente caricare il bambino direttamente nel suo spazio di processo.
Alcune implementazioni UNIX hanno un ottimizzato fork
che utilizza ciò che chiamano copy-on-write. Questo è un trucco per ritardare la copia dello spazio di processo fork
fino a quando il programma tenta di cambiare qualcosa in quello spazio. Questo è utile per quei programmi che usano solo fork
e non exec
perché non devono copiare un intero spazio di processo.
Se exec
viene chiamato segue fork
(e questo è ciò che accade principalmente), ciò provoca una scrittura nello spazio del processo e viene quindi copiato per il processo figlio.
Si noti che non v'è una intera famiglia di exec
chiamate ( execl
, execle
, execve
e così via), ma exec
in un contesto qui significa nessuna di esse.
Il diagramma seguente illustra l' fork/exec
operazione tipica in cui la bash
shell viene utilizzata per elencare una directory con il ls
comando:
+--------+
| 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. O in altre parole, il tuo bel programma lineare e facile da pensare 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ò in qualche modo farti saltare la testa. Ora hai un pezzo di codice con stato praticamente identico eseguito da due processi. Il processo figlio eredita tutto il codice e la memoria del processo che lo ha appena creato, incluso a partire da dove era stata fork()
interrotta la chiamata. L'unica differenza è il fork()
codice di ritorno che ti dice se sei il genitore o il figlio. Se sei il genitore, il valore restituito è l'id del figlio.
exec
è un po 'più facile da capire, basta dire 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 utilizzato dopo aver fork
eseguito nel processo corrente l'eseguibile di destinazione.
pid < 0
la fork()
chiamata è fallita
Penso che alcuni concetti di "Advanced Unix Programming" di Marc Rochkind siano stati utili per comprendere i diversi ruoli di fork()
/ exec()
, in particolare per qualcuno abituato al CreateProcess()
modello di Windows :
Un programma è una raccolta di istruzioni e dati conservati in 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)
.
È impossibile comprendere le chiamate di sistema exec o fork senza comprendere appieno la distinzione tra un processo e un programma. Se questi termini sono nuovi per te, potresti voler tornare indietro e rivedere la Sezione 1.1.2. Se sei pronto per procedere ora, riassumeremo la distinzione in una frase: un processo è un ambiente di esecuzione che comprende segmenti di istruzioni, dati utente e dati di sistema, nonché molte altre risorse acquisite in fase di runtime , mentre un programma è un file contenente istruzioni e dati utilizzati per inizializzare i segmenti di istruzioni e dati utente di un processo. (da 5.3
exec
Chiamate di sistema)
Una volta compresa la distinzione tra un programma e un processo, il comportamento fork()
e la exec()
funzione possono essere riassunti come:
fork()
crea un duplicato del processo correnteexec()
sostituisce il programma nel processo corrente con un altro programma(questa è essenzialmente una versione 'for dummies' semplificata della risposta molto più dettagliata di paxdiablo )
Fork crea una copia di un processo di chiamata. generalmente segue la struttura
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 di chiamata) il processo figlio esegue il codice in caso di blocco.
EXEC sostituisce il processo corrente con codice, dati, stack del nuovo processo. generalmente segue la struttura
int cpid = fork( );
if (cpid = = 0)
{
//child code
exec(foo);
exit(0);
}
//parent code
wait(cpid);
// end
(dopo che exec chiama unix kernel cancella il testo, i dati, lo stack e riempie il processo figlio con testo / dati relativi al processo foo), quindi il processo figlio è con un codice diverso (codice foo {non uguale al genitore})
Vengono utilizzati insieme per creare un nuovo processo figlio. Innanzitutto, chiamando fork
crea una copia del processo corrente (il processo figlio). Quindi, exec
viene chiamato all'interno del processo figlio per "sostituire" la copia del processo padre con il nuovo processo.
Il processo procede in questo modo:
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<char**>(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 da subito dopo la chiamata fork (). Dopo il fork (), sono identici, ad eccezione del valore restituito della funzione fork (). (RTFM per maggiori dettagli.) I due processi possono quindi divergere ulteriormente, senza che uno sia 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 (), tranne per il fatto che un exec () spesso segue fork () quando ciò che si desidera è avviare un processo figlio diverso, anziché sostituire quello corrente.
La differenza principale tra fork()
e exec()
è che,
La fork()
chiamata di sistema crea un clone del programma attualmente in esecuzione. Il programma originale continua l'esecuzione con la riga di codice successiva dopo la chiamata alla funzione fork (). Il clone avvia anche l'esecuzione alla riga di codice successiva. Guarda il seguente codice che ho ottenuto da http://timmurphy.org/2014/04/26/using-fork-in-cc-a-minimum-working-example/
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
printf("--beginning of program\n");
int counter = 0;
pid_t pid = fork();
if (pid == 0)
{
// child process
int i = 0;
for (; i < 5; ++i)
{
printf("child process: counter=%d\n", ++counter);
}
}
else if (pid > 0)
{
// parent process
int j = 0;
for (; j < 5; ++j)
{
printf("parent process: counter=%d\n", ++counter);
}
}
else
{
// fork failed
printf("fork() failed!\n");
return 1;
}
printf("--end of program--\n");
return 0;
}
Questo programma dichiara una variabile contatore, impostata su zero, prima di fork()
ing. Dopo la chiamata fork, abbiamo due processi in esecuzione in parallelo, entrambi incrementando la propria versione del contatore. Ogni processo verrà eseguito fino al completamento e all'uscita. Poiché i processi si svolgono in parallelo, non abbiamo modo di sapere quale finirà per primo. L'esecuzione di questo programma stamperà qualcosa di simile a quanto mostrato di seguito, sebbene i risultati possano variare da una corsa all'altra.
--beginning of program
parent process: counter=1
parent process: counter=2
parent process: counter=3
child process: counter=1
parent process: counter=4
child process: counter=2
parent process: counter=5
child process: counter=3
--end of program--
child process: counter=4
child process: counter=5
--end of program--
Il exec()
famiglia di chiamate di sistema sostituisce il codice attualmente in esecuzione di un processo con un altro pezzo di codice. Il processo mantiene il suo PID ma diventa un nuovo programma. Ad esempio, considera il seguente codice:
#include <stdio.h>
#include <unistd.h>
main() {
char program[80],*args[3];
int i;
printf("Ready to exec()...\n");
strcpy(program,"date");
args[0]="date";
args[1]="-u";
args[2]=NULL;
i=execvp(program,args);
printf("i=%d ... did it work?\n",i);
}
Questo programma chiama il execvp()
funzione per sostituire il suo codice con il programma data. Se il codice è memorizzato in un file chiamato exec1.c, quindi eseguendolo produce il seguente output:
Ready to exec()...
Tue Jul 15 20:17:53 UTC 2008
Il programma emette la riga ―Pronto per exec (). . . ‖ E dopo aver chiamato la funzione execvp (), sostituisce il suo codice con il programma data. Si noti che la riga -. . . ha funzionato‖ non viene visualizzato, perché a quel punto il codice è stato sostituito. Invece, vediamo l'output dell'esecuzione di ―date -u.‖
Crea una copia del processo in esecuzione. Il processo in esecuzione si chiama processo padre e il processo appena creato si chiama processo figlio . Il modo per differenziare i due è guardando il valore restituito:
fork()
restituisce l'identificatore del processo (pid) del processo figlio nel genitore
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()
:
All'avvio di un nuovo programma è innanzitutto quello di fork()
creare un nuovo processo e quindi exec()
(cioè caricare in memoria ed eseguire) il programma binario che dovrebbe essere eseguito.
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;
}
L'esempio principale per comprendere il concetto fork()
e exec()
è la shell , il programma di interpretazione dei comandi che gli utenti eseguono in genere dopo l'accesso al sistema. La shell interpreta la prima parola della riga di comando come comando nome di
Per molti comandi, la shell 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. Innanzitutto, un comando può essere un file eseguibile che contiene il codice oggetto prodotto dalla compilazione del codice sorgente (ad esempio un programma C). 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 shell interno (invece di un file eseguibile ex-> cd , ls ecc.)