Differenze tra fork e exec


199

Quali sono le differenze tra forke exec?


3
Un buon riepilogo dettagliato di fork, exec e altre funzioni di controllo del processo è disponibile su yolinux.com/TUTORIALS/ForkExecProcesses.html
Jonathan Fingland,

9
@Justin, perché vogliamo che SO diventi il posto dove andare per domande sulla programmazione.
paxdiablo,

4
@ Polaris878: oh, lo fa ora! : D
Janusz Lenar,

così forkè sostanzialmente la clonazione: O
Sebastian Hojas,

Risposte:


364

L'uso forke execesemplifica lo spirito di UNIX in quanto fornisce un modo molto semplice per avviare nuovi processi.

La forkchiamata 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 forkchiamata funzioni: in caso contrario, non viene creato alcun figlio e il genitore riceve un codice di errore.

La execchiamata è 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, forke execsono 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 findprogramma 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 forkse stesso senza execing 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 forkuna 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, exece quindi waitper il bambino. Possono semplicemente caricare il bambino direttamente nel suo spazio di processo.

Alcune implementazioni UNIX hanno un ottimizzato forkche utilizza ciò che chiamano copy-on-write. Questo è un trucco per ritardare la copia dello spazio di processo forkfino a quando il programma tenta di cambiare qualcosa in quello spazio. Questo è utile per quei programmi che usano solo forke non execperché 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 execchiamate ( execl, execle, execvee così via), ma execin un contesto qui significa nessuna di esse.

Il diagramma seguente illustra l' fork/execoperazione tipica in cui la bashshell viene utilizzata per elencare una directory con il lscomando:

+--------+
| 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

52

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 execdi 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, execpuò essere utilizzato dopo aver forkeseguito nel processo corrente l'eseguibile di destinazione.


6
c'è anche la condizione in cui pid < 0la fork()chiamata è fallita
Jonathan Fingland,

3
Questo non mi fa impazzire :-) Un pezzo di codice eseguito da due processi si verifica ogni volta che viene utilizzata una libreria o DLL condivisa.
paxdiablo,

31

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 execChiamate 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 corrente
  • exec() sostituisce il programma nel processo corrente con un altro programma

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


29

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

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 inserisci qui la descrizione dell'immagine

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})


1
È un po 'estraneo alla domanda ma questo codice sopra non causa una condizione di competizione se il processo figlio finisce per finire prima il codice? In tal caso, il processo genitore rimarrebbe per sempre in attesa che il bambino si interrompesse, giusto?
stdout,

7

Vengono utilizzati insieme per creare un nuovo processo figlio. Innanzitutto, chiamando forkcrea una copia del processo corrente (il processo figlio). Quindi, execviene 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
}

2
Nella settima riga si dice che la funzione exec () crea il processo figlio. È davvero così perché fork () ha già creato il processo figlio e la chiamata exec () sostituisce semplicemente il programma del nuovo processo appena creato
cbinder

4

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.


3

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.‖


1

inserisci qui la descrizione dell'immaginefork():

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:

  1. fork() restituisce l'identificatore del 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():

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;
}

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.)

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.