La differenza tra fork (), vfork (), exec () e clone ()


197

Stavo cercando di trovare la differenza tra questi quattro su Google e mi aspettavo che ci fosse un'enorme quantità di informazioni su questo, ma in realtà non c'era nessun solido confronto tra le quattro chiamate.

Ho iniziato a provare a compilare una sorta di sguardo d'insieme sulle differenze tra queste chiamate di sistema ed ecco cosa ho ottenuto. Tutte queste informazioni sono corrette / mi sto perdendo qualcosa di importante?

Fork : La chiamata fork sostanzialmente fa un duplicato del processo corrente, identico in quasi tutti i modi (non tutto viene copiato, ad esempio, i 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 del fork: il figlio ottiene 0, il genitore ottiene il PID del figlio. Tutto questo, ovviamente, presupponendo che la chiamata fork funzioni - in caso contrario, non viene creato alcun figlio e il genitore riceve un codice di errore.

Vfork: La differenza di base tra vfork e fork è che quando viene creato un nuovo processo con vfork (), il processo parent viene temporaneamente sospeso e il processo child potrebbe prendere in prestito lo spazio degli indirizzi del parent. Questo strano stato di cose continua fino a quando non termina il processo figlio o chiama execve (), a quel punto il processo genitore continua.

Ciò significa che il processo figlio di un vfork () deve fare attenzione per evitare di modificare in modo imprevisto le variabili del processo genitore. In particolare, il processo figlio non deve tornare dalla funzione contenente la chiamata vfork () e non deve chiamare exit () (se deve uscire, dovrebbe usare _exit (); in realtà, questo vale anche per il figlio di una forcella normale ()).

Exec :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. exec () sostituisce il processo corrente con un eseguibile indicato dalla funzione. Il controllo non ritorna mai al programma originale a meno che non ci sia un errore exec ().

Clone :Il clone, come fork, crea un nuovo processo. A differenza del fork, queste chiamate consentono al processo figlio di condividere parti del suo contesto di esecuzione con il processo chiamante, come lo spazio di memoria, la tabella dei descrittori di file e la tabella dei gestori di segnale.

Quando il processo figlio viene creato con clone, esegue la funzione application fn (arg). (Differisce dal fork, dove l'esecuzione continua nel child dal punto della chiamata fork originale.) L'argomento fn è un puntatore a una funzione chiamata dal processo child all'inizio della sua esecuzione. L'argomento arg viene passato alla funzione fn.

Quando ritorna l'applicazione della funzione fn (arg), il processo figlio termina. Il numero intero restituito da fn è il codice di uscita per il processo figlio. Il processo figlio può anche terminare esplicitamente chiamando exit (2) o dopo aver ricevuto un segnale fatale.

Modulo di informazione ottenuta:

Grazie per aver dedicato del tempo a leggere questo! :)


2
Perché vfork non deve chiamare exit ()? O non tornare? Exit () non usa solo _exit ()? Sto anche cercando di capire :)
LazerSharks,

2
@Gnuey: perché è potenzialmente (se implementato diversamente da fork(), che è in Linux, e probabilmente tutti i BSD) prendendo in prestito lo spazio degli indirizzi dei suoi genitori. Tutto ciò che fa, oltre a chiamare execve()o _exit(), ha un grande potenziale per confondere il genitore. In particolare, exit()chiama i atexit()gestori e altri "finalizzatori", ad esempio: elimina i flussi stdio. Il ritorno da un vfork()bambino potrebbe potenzialmente (stessa avvertenza di prima) rovinare lo stack del genitore.
ninjalj,

Mi chiedevo cosa succede ai thread del processo genitore; Sono tutti clonati o solo il thread che chiama il forksyscall?
Mohammad Jafar Mashhadi,

@LazerSharks vfork produce un processo simile a thread in cui la memoria è condivisa senza protezioni da copia su scrittura, quindi fare cose dello stack potrebbe rovinare il processo genitore.
Jasen,

Risposte:


159
  • vfork()è un'ottimizzazione obsoleta. Prima di una buona gestione della memoria, fork()creava una copia completa della memoria del genitore, quindi era piuttosto costosa. poiché in molti casi è fork()stato seguito un exec(), che scarta l'attuale mappa di memoria e ne crea una nuova, era una spesa inutile. Al giorno d'oggi, fork()non copia la memoria; è semplicemente impostato come "copia su scrittura", quindi fork()+ exec()è altrettanto efficace di vfork()+ exec().

  • clone()è la syscall utilizzata da fork(). con alcuni parametri, crea un nuovo processo, con altri crea un thread. la differenza tra loro è solo quali strutture di dati (spazio di memoria, stato del processore, stack, PID, file aperti, ecc.) sono condivise o meno.



22
vforkevita la necessità di trasferire temporaneamente molta più memoria solo per poter eseguire exec, ed è ancora più efficiente di fork, anche se non quasi altrettanto in alto grado. Pertanto, si può evitare di dover sovraccaricare la memoria, così un grosso programma può generare un processo figlio. Quindi, non solo un aumento delle prestazioni, ma potrebbe renderlo fattibile a tutti.
Deduplicatore

5
In realtà, ho visto in prima persona come fork () è tutt'altro che economico quando il tuo RSS è grande. Presumo che ciò avvenga perché il kernel deve ancora copiare tutte le tabelle delle pagine.
Martina Ferrari,

4
Deve copiare tutte le tabelle delle pagine, impostare tutta la memoria scrivibile copy-on-write in entrambi i processi , svuotare il TLB e quindi ripristinare tutte le modifiche al genitore (e ripulire nuovamente il TLB) exec.
zwol,

3
vfork è ancora utile in cygwin (un kernel che emula dll, che gira su Windows di Microsoft). cygwin non può implementare un fork efficiente, poiché il sistema operativo sottostante non ne ha uno.
ctrl-alt-delor,

80
  • execve() sostituisce l'immagine eseguibile corrente con un'altra caricata da un file eseguibile.
  • fork() crea un processo figlio.
  • vfork()è una versione storica ottimizzata di fork(), pensata per essere utilizzata quando execve()viene chiamata direttamente dopo fork(). Si è rivelato funzionare bene nei sistemi non MMU (dove fork()non può funzionare in modo efficiente) e durante fork()i processi con un ingombro di memoria enorme per eseguire alcuni piccoli programmi (pensa a Java Runtime.exec()). POSIX ha standardizzato la posix_spawn()sostituzione di questi ultimi due usi più moderni di vfork().
  • posix_spawn()fa l'equivalente di a fork()/execve(), e permette anche qualche gioco di destrezza tra. Dovrebbe sostituire fork()/execve(), principalmente per piattaforme non MMU.
  • pthread_create() crea una nuova discussione.
  • clone()è una chiamata specifica per Linux, che può essere utilizzata per implementare qualsiasi cosa da fork()a pthread_create(). Dà molto controllo. Ispirato a rfork().
  • rfork()è una chiamata specifica per Plan-9. Dovrebbe essere una chiamata generica, che consente diversi gradi di condivisione, tra processi e thread completi.

2
Grazie per aver aggiunto più informazioni di quelle effettivamente richieste, mi ha aiutato a risparmiare tempo
Neeraj

5
Il piano 9 è una tale presa in giro.
JJ

1
Per coloro che non ricordano cosa significhi MMU: "Unità di gestione della memoria" - ulteriori letture su Wikipedia
mgarey,

43
  1. fork()- crea un nuovo processo figlio, che è una copia completa del processo principale. I processi figlio e padre utilizzano spazi di indirizzi virtuali diversi, inizialmente popolati dalle stesse pagine di memoria. Quindi, man mano che vengono eseguiti entrambi i processi, gli spazi degli indirizzi virtuali iniziano a differire sempre di più, poiché il sistema operativo esegue una copia lenta delle pagine di memoria che vengono scritte da uno di questi due processi e assegna una copia indipendente delle pagine modificate di memoria per ogni processo. Questa tecnica si chiama Copy-On-Write (COW).
  2. vfork()- crea un nuovo processo figlio, che è una copia "rapida" del processo genitore. A differenza della chiamata di sistema fork(), i processi figlio e padre condividono lo stesso spazio di indirizzi virtuali. NOTA! Utilizzando lo stesso spazio di indirizzi virtuale, sia il genitore che il figlio usano lo stesso stack, il puntatore dello stack e il puntatore dell'istruzione, come nel caso del classico fork()! Per evitare interferenze indesiderate tra genitore e figlio, che utilizzano lo stesso stack, l'esecuzione del processo parent viene bloccata fino a quando il bambino chiamerà exec()(creare un nuovo spazio di indirizzi virtuale e una transizione a uno stack diverso) o _exit()(terminazione dell'esecuzione del processo ). vfork()è l'ottimizzazione del modello fork()"fork-and-exec". Può essere eseguito 4-5 volte più veloce di fork(), perché a differenza difork()(anche tenendo presente COW), l'implementazione della vfork()chiamata di sistema non include la creazione di un nuovo spazio di indirizzi (allocazione e impostazione di nuove directory di pagine).
  3. clone()- crea un nuovo processo figlio. Vari parametri di questa chiamata di sistema, specificano quali parti del processo padre devono essere copiate nel processo figlio e quali parti saranno condivise tra loro. Di conseguenza, questa chiamata di sistema può essere utilizzata per creare tutti i tipi di entità di esecuzione, a partire dai thread e finendo con processi completamente indipendenti. In effetti, la clone()chiamata di sistema è la base utilizzata per l'implementazione di pthread_create()tutte le fork()chiamate della famiglia di sistema.
  4. exec()- reimposta tutta la memoria del processo, carica e analizza il file binario eseguibile specificato, imposta il nuovo stack e passa il controllo al punto di ingresso dell'eseguibile caricato. Questa chiamata di sistema non restituisce mai il controllo al chiamante e serve per caricare un nuovo programma nel processo già esistente. Questa chiamata di fork()sistema con chiamata di sistema formano insieme un classico modello di gestione dei processi UNIX chiamato "fork-and-exec".

2
Si noti che i requisiti BSD e POSIX per vforksono così deboli che sarebbe legale fare vforkun sinonimo di fork(e POSIX.1-2008 rimuove completamente vforkdalle specifiche). Se ti capita di testare il tuo codice su un sistema che li sinonimo (ad es. La maggior parte dei BSD post-4.4 oltre a NetBSD, kernel Linux pre-2.2.0-pre6, ecc.), Potrebbe funzionare anche se viola il vforkcontratto, quindi esplodere se lo esegui altrove. Alcuni di coloro che simulano con fork(ad esempio, OpenBSD) garantire ancora il genitore non riprende l'esecuzione fino a quando il bambino execs o _exits. È ridicolmente non portatile.
ShadowRanger,

2
riguardo all'ultima frase del tuo terzo punto: ho notato su Linux usando strace che mentre in effetti il ​​wrapper glibc per fork () chiama il clone syscall, il wrapper per vfork () chiama vfork syscall
ilstam

7

Fork (), vfork () e clone () chiamano tutti do_fork () per fare il vero lavoro, ma con parametri diversi.

asmlinkage int sys_fork(struct pt_regs regs)
{
    return do_fork(SIGCHLD, regs.esp, &regs, 0);
}

asmlinkage int sys_clone(struct pt_regs regs)
{
    unsigned long clone_flags;
    unsigned long newsp;

    clone_flags = regs.ebx;
    newsp = regs.ecx;
    if (!newsp)
        newsp = regs.esp;
    return do_fork(clone_flags, newsp, &regs, 0);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0);
}
#define CLONE_VFORK 0x00004000  /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_VM    0x00000100  /* set if VM shared between processes */

SIGCHLD means the child should send this signal to its father when exit.

Per fork, il figlio e il padre hanno la tabella di pagine VM indipendente, ma poiché l'efficienza, fork non copierà davvero nessuna pagina, imposta semplicemente tutte le pagine scrivibili su readonly per il processo figlio. Quindi, quando il processo figlio vuole scrivere qualcosa su quella pagina, si verifica un'eccezione di pagina e il kernel assegnerà una nuova pagina clonata dalla vecchia pagina con permesso di scrittura. Si chiama "copia in scrittura".

Per vfork, la memoria virtuale è esattamente di figlio e padre --- solo per questo, padre e figlio non possono essere svegli contemporaneamente poiché si influenzeranno a vicenda. Quindi il padre dormirà alla fine di "do_fork ()" e si sveglierà quando il bambino chiamerà exit () o execve () da allora possiederà una nuova tabella di pagine. Ecco il codice (in do_fork ()) che il padre dorme.

if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;

Ecco il codice (in mm_release () chiamato da exit () ed execve ()) che sveglia il padre.

up(tsk->p_opptr->vfork_sem);

Per sys_clone (), è più flessibile poiché puoi inserire qualsiasi clone_flags. Quindi pthread_create () chiama questa chiamata di sistema con molti clone_flags:

int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM);

Riepilogo: fork (), vfork () e clone () creeranno processi figlio con differenti mount di condivisione delle risorse con il processo padre. Possiamo anche dire che vfork () e clone () possono creare thread (in realtà sono processi poiché hanno task_struct indipendente) poiché condividono la tabella delle pagine VM con il processo padre.


-4

in fork (), il processo figlio o genitore verrà eseguito in base alla selezione della cpu .. Ma in vfork (), sicuramente il figlio verrà eseguito per primo. dopo che il figlio è terminato, il genitore eseguirà.


3
Sbagliato. vfork()può essere semplicemente implementato come fork().
ninjalj,

dopo AnyFork (), non è definito chi esegue il primo genitore / figlio.
AjayKumarBasuthkar

5
@Raj: hai dei malintesi concettuali se pensi che dopo il fork ci sia una nozione implicita di ordine seriale. Forking crea un nuovo processo e quindi restituisce il controllo a entrambi i processi (ognuno restituendo un diverso pid): il sistema operativo può pianificare l'esecuzione parallela del nuovo processo se una cosa del genere ha senso (ad esempio più processori). Se per qualche motivo hai bisogno che questi processi vengano eseguiti in un particolare ordine seriale, allora hai bisogno di una sincronizzazione aggiuntiva che il fork non fornisce; francamente, probabilmente non vorrai nemmeno una forchetta in primo luogo.
Andon M. Coleman,

In realtà @AjayKumarBasuthkar e @ninjalj, entrambi avete torto. con vfork(), il bambino corre per primo. È nelle pagine man; l'esecuzione dei genitori è sospesa fino a quando il bambino o muore o execno. E ninjalj cerca il codice sorgente del kernel. Non c'è modo di implementare vfork()come fork()perché passano diversi argomenti do_fork()all'interno del kernel. Tuttavia, è possibile implementare vforkcon il clonesyscall
Zac Wimer,

@ZacWimer: vedi il commento di ShadowRanger ad un'altra risposta stackoverflow.com/questions/4856255/... Vecchio Linux ha fatto synonimize loro, come a quanto pare BSD diversi da NetBSD (che tende ad essere portato a un sacco di sistemi non-MMU) fare. Dalla manpage di Linux: in 4.4BSD è stato reso sinonimo di fork (2) ma NetBSD l'ha introdotto di nuovo; vedi ⟨netbsd.org/Documentation/kernel/vfork.html⟩ . In Linux, è stato equivalente a fork (2) fino a 2.2.0-pre6 o giù di lì.
ninjalj
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.