In molti programmi e pagine man di Linux, ho visto usare del codice fork()
. Perché dobbiamo usarlo fork()
e qual è il suo scopo?
Risposte:
fork()
è il modo in cui crei nuovi processi in Unix. Quando chiami fork
, stai creando una copia del tuo processo che ha il proprio spazio degli indirizzi . Ciò consente a più attività di essere eseguite indipendentemente l'una dall'altra come se ognuna di esse avesse tutta la memoria della macchina per sé.
Ecco alcuni esempi di utilizzo di fork
:
fork
per eseguire i programmi che invochi dalla riga di comando.fork
per creare più processi server, ognuno dei quali gestisce le richieste nel proprio spazio degli indirizzi. Se uno muore o perde memoria, gli altri non vengono influenzati, quindi funziona come un meccanismo per la tolleranza ai guasti.fork
per gestire ogni pagina all'interno di un processo separato. Ciò impedirà al codice lato client su una pagina di portare inattivo l'intero browser.fork
viene utilizzato per generare processi in alcuni programmi paralleli (come quelli scritti utilizzando MPI ). Nota che questo è diverso dall'uso dei thread , che non hanno il proprio spazio degli indirizzi ed esistono all'interno di un processo.fork
indirettamente per avviare i processi figlio. Ad esempio, ogni volta che usi un comando come subprocess.Popen
in Python, sei fork
un processo figlio e leggi il suo output. Ciò consente ai programmi di lavorare insieme.L'utilizzo tipico di fork
in una shell potrebbe essere simile a questo:
int child_process_id = fork();
if (child_process_id) {
// Fork returns a valid pid in the parent process. Parent executes this.
// wait for the child process to complete
waitpid(child_process_id, ...); // omitted extra args for brevity
// child process finished!
} else {
// Fork returns 0 in the child process. Child executes this.
// new argv array for the child process
const char *argv[] = {"arg1", "arg2", "arg3", NULL};
// now start executing some other program
exec("/path/to/a/program", argv);
}
La shell genera un processo figlio utilizzando exec
e attende che venga completato, quindi continua con la propria esecuzione. Nota che non devi usare fork in questo modo. Puoi sempre generare molti processi figli, come potrebbe fare un programma parallelo, e ognuno potrebbe eseguire un programma contemporaneamente. Fondamentalmente, ogni volta che stai creando nuovi processi in un sistema Unix, stai usando fork()
. Per l'equivalente di Windows, dai un'occhiata a CreateProcess
.
Se vuoi più esempi e una spiegazione più lunga, Wikipedia ha un sommario decente. E qui ci sono alcune diapositive su come funzionano i processi, i thread e la concorrenza nei sistemi operativi moderni.
fork()
è il modo per creare un nuovo processo in UNIX, ma per essere pedanti, c'è almeno un altro: posix_spawn()
.
fork () è il modo in cui Unix crea nuovi processi. Nel punto in cui hai chiamato fork (), il tuo processo viene clonato e due diversi processi continuano l'esecuzione da lì. Uno di loro, il figlio, avrà fork () return 0. L'altro, il genitore, farà fork () restituendo il PID (ID processo) del figlio.
Ad esempio, se digiti quanto segue in una shell, il programma della shell chiamerà fork (), quindi eseguirà il comando che hai passato (telnetd, in questo caso) nel figlio, mentre il genitore mostrerà di nuovo il prompt, pure come messaggio che indica il PID del processo in background.
$ telnetd &
Per quanto riguarda il motivo per cui crei nuovi processi, è così che il tuo sistema operativo può fare molte cose allo stesso tempo. È per questo che puoi eseguire un programma e, mentre è in esecuzione, passare a un'altra finestra e fare qualcos'altro.
fork () viene utilizzato per creare un processo figlio. Quando viene chiamata una funzione fork (), verrà generato un nuovo processo e la chiamata della funzione fork () restituirà un valore diverso per il figlio e il genitore.
Se il valore di ritorno è 0, sai di essere il processo figlio e se il valore di ritorno è un numero (che sembra essere l'id del processo figlio), sai di essere il genitore. (e se è un numero negativo, il fork non è riuscito e non è stato creato alcun processo figlio)
fork () è fondamentalmente usato per creare un processo figlio per il processo in cui stai chiamando questa funzione. Ogni volta che chiami un fork (), restituisce uno zero per l'id figlio.
pid=fork()
if pid==0
//this is the child process
else if pid!=0
//this is the parent process
in questo modo è possibile fornire azioni diverse per il genitore e il bambino e utilizzare la funzionalità di multithreading.
fork () creerà un nuovo processo figlio identico al genitore. Quindi tutto ciò che esegui nel codice dopo verrà eseguito da entrambi i processi, molto utile se ad esempio hai un server e desideri gestire più richieste.
Probabilmente non è necessario utilizzare fork nella programmazione quotidiana se si scrivono applicazioni.
Anche se vuoi che il tuo programma avvii un altro programma per fare qualche operazione, ci sono altre interfacce più semplici che usano il fork dietro le quinte, come "system" in C e perl.
Ad esempio, se desideri che la tua applicazione avvii un altro programma come bc per eseguire dei calcoli, potresti usare "system" per eseguirlo. Il sistema fa un "fork" per creare un nuovo processo, quindi un "exec" per trasformare quel processo in bc. Al termine di bc, il sistema restituisce il controllo al programma.
Puoi anche eseguire altri programmi in modo asincrono, ma non ricordo come.
Se stai scrivendo server, shell, virus o sistemi operativi, è più probabile che tu voglia utilizzare fork.
system()
. Stavo leggendo fork()
perché voglio che il mio codice C esegua uno script Python.
La chiamata di sistema fork () viene utilizzata per creare processi. Non richiede argomenti e restituisce un ID di processo. Lo scopo di fork () è creare un nuovo processo, che diventa il processo figlio del chiamante. Dopo aver creato un nuovo processo figlio, entrambi i processi eseguiranno l'istruzione successiva che segue la chiamata di sistema fork (). Pertanto, dobbiamo distinguere il genitore dal bambino. Questo può essere fatto testando il valore restituito da fork ():
Se fork () restituisce un valore negativo, la creazione di un processo figlio non ha avuto successo. fork () restituisce uno zero al processo figlio appena creato. fork () restituisce un valore positivo, l'ID di processo del processo figlio, al genitore. L'ID processo restituito è di tipo pid_t definito in sys / types.h. Normalmente, l'ID del processo è un numero intero. Inoltre, un processo può utilizzare la funzione getpid () per recuperare l'ID di processo assegnato a questo processo. Pertanto, dopo la chiamata di sistema a fork (), un semplice test può dire quale processo è il figlio. Si noti che Unix farà una copia esatta dello spazio degli indirizzi del genitore e la darà al bambino. Pertanto, i processi padre e figlio hanno spazi di indirizzi separati.
Comprendiamolo con un esempio per chiarire i punti precedenti. Questo esempio non distingue i processi padre e figlio.
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#define MAX_COUNT 200
#define BUF_SIZE 100
void main(void)
{
pid_t pid;
int i;
char buf[BUF_SIZE];
fork();
pid = getpid();
for (i = 1; i <= MAX_COUNT; i++) {
sprintf(buf, "This line is from pid %d, value = %d\n", pid, i);
write(1, buf, strlen(buf));
}
}
Supponiamo che il programma precedente venga eseguito fino al punto della chiamata a fork ().
Se la chiamata a fork () viene eseguita con successo, Unix creerà due copie identiche degli spazi degli indirizzi, una per il genitore e l'altra per il figlio. Entrambi i processi inizieranno la loro esecuzione all'istruzione successiva che segue la chiamata fork (). In questo caso, entrambi i processi inizieranno la loro esecuzione al momento dell'assegnazione
pid = .....;
Entrambi i processi iniziano la loro esecuzione subito dopo la chiamata di sistema fork (). Poiché entrambi i processi hanno spazi degli indirizzi identici ma separati, quelle variabili inizializzate prima della chiamata fork () hanno gli stessi valori in entrambi gli spazi degli indirizzi. Poiché ogni processo ha il proprio spazio di indirizzi, qualsiasi modifica sarà indipendente dalle altre. In altre parole, se il genitore cambia il valore della sua variabile, la modifica interesserà solo la variabile nello spazio degli indirizzi del processo genitore. Gli altri spazi di indirizzi creati dalle chiamate fork () non saranno influenzati anche se hanno nomi di variabili identici.
Qual è il motivo dell'utilizzo di write anziché printf? È perché printf () è "bufferizzato", il che significa che printf () raggrupperà insieme l'output di un processo. Durante il buffering dell'output per il processo genitore, il figlio può anche usare printf per stampare alcune informazioni, che verranno anch'esse memorizzate nel buffer. Di conseguenza, poiché l'output non verrà inviato immediatamente allo schermo, potresti non ottenere l'ordine corretto del risultato atteso. Peggio ancora, l'output dei due processi può essere mescolato in modi strani. Per superare questo problema, potresti considerare di utilizzare la scrittura "senza buffer".
Se esegui questo programma, potresti vedere quanto segue sullo schermo:
................
This line is from pid 3456, value 13
This line is from pid 3456, value 14
................
This line is from pid 3456, value 20
This line is from pid 4617, value 100
This line is from pid 4617, value 101
................
This line is from pid 3456, value 21
This line is from pid 3456, value 22
................
L'ID processo 3456 può essere quello assegnato al genitore o al figlio. A causa del fatto che questi processi vengono eseguiti contemporaneamente, le loro linee di output sono mescolate in un modo piuttosto imprevedibile. Inoltre, l'ordine di queste righe è determinato dallo scheduler della CPU. Quindi, se esegui di nuovo questo programma, potresti ottenere un risultato completamente diverso.
Il multiprocessing è fondamentale per il computing. Ad esempio, il tuo IE o Firefox possono creare un processo per scaricare un file per te mentre stai ancora navigando in Internet. Oppure, mentre stampi un documento con un elaboratore di testi, puoi comunque guardare pagine diverse e apportare ancora alcune modifiche.
Fork () viene utilizzato per creare nuovi processi come ogni corpo ha scritto.
Ecco il mio codice che crea processi sotto forma di albero binario ....... Chiederà di scansionare il numero di livelli fino a cui vuoi creare processi in albero binario
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
int t1,t2,p,i,n,ab;
p=getpid();
printf("enter the number of levels\n");fflush(stdout);
scanf("%d",&n);
printf("root %d\n",p);fflush(stdout);
for(i=1;i<n;i++)
{
t1=fork();
if(t1!=0)
t2=fork();
if(t1!=0 && t2!=0)
break;
printf("child pid %d parent pid %d\n",getpid(),getppid());fflush(stdout);
}
waitpid(t1,&ab,0);
waitpid(t2,&ab,0);
return 0;
}
PRODUZIONE
enter the number of levels
3
root 20665
child pid 20670 parent pid 20665
child pid 20669 parent pid 20665
child pid 20672 parent pid 20670
child pid 20671 parent pid 20670
child pid 20674 parent pid 20669
child pid 20673 parent pid 20669
Per prima cosa bisogna capire cos'è la chiamata di sistema fork (). Lasciatemi spiegare
La chiamata di sistema fork () crea il duplicato esatto del processo genitore, crea il duplicato di stack genitore, heap, dati inizializzati, dati non inizializzati e condivide il codice in modalità di sola lettura con il processo genitore.
La chiamata di sistema Fork copia la memoria sulla base della copia su scrittura, significa che il bambino crea una pagina di memoria virtuale quando è necessario copiare.
Ora Scopo della forcella ():
fork()
viene utilizzato per generare un processo figlio. In genere viene utilizzato in tipi di situazioni simili al threading, ma ci sono differenze. A differenza dei thread, fork()
crea interi processi separati, il che significa che il bambino e il genitore mentre sono copie dirette l'uno dell'altro nel punto in cuifork()
viene chiamato, sono completamente separati, nessuno dei due può accedere allo spazio di memoria dell'altro (senza andare ai normali guai vai per accedere alla memoria di un altro programma).
fork()
è ancora utilizzato da alcune applicazioni server, principalmente da quelle eseguite come root su una macchina * NIX che rilascia i permessi prima di elaborare le richieste degli utenti. Ci sono ancora altri casi d'uso, ma ora la maggior parte delle persone è passata al multithreading.
La logica alla base di fork () rispetto al solo fatto di avere una funzione exec () per avviare un nuovo processo è spiegata in una risposta a una domanda simile sullo scambio di stack unix .
Essenzialmente, poiché fork copia il processo corrente, tutte le varie opzioni possibili per un processo sono stabilite di default, quindi il programmatore non le deve fornire.
Nel sistema operativo Windows, al contrario, i programmatori devono utilizzare la funzione CreateProcess che è MOLTO più complicata e richiede di popolare una struttura multiforme per definire i parametri del nuovo processo.
Quindi, per riassumere, la ragione per il fork (rispetto all'esecuzione) è la semplicità nella creazione di nuovi processi.
La chiamata di sistema Fork () viene utilizzata per creare un processo figlio. È il duplicato esatto del processo genitore. Fork copia la sezione stack, la sezione heap, la sezione dati, la variabile di ambiente, gli argomenti della riga di comando dal genitore.
fare riferimento a: http://man7.org/linux/man-pages/man2/fork.2.html
La funzione fork () viene utilizzata per creare un nuovo processo duplicando il processo esistente da cui viene chiamato. Il processo esistente da cui viene chiamata questa funzione diventa il processo padre e il processo appena creato diventa il processo figlio. Come già affermato, il bambino è una copia duplicata del genitore ma ci sono alcune eccezioni.
Il bambino ha un PID univoco come qualsiasi altro processo in esecuzione nel sistema operativo.
Il bambino ha un ID processo genitore che è lo stesso del PID del
processo che lo ha creato.
L'utilizzo delle risorse e i contatori del tempo della CPU vengono azzerati nel processo figlio.
Il set di segnali in sospeso nel bambino è vuoto.
Il bambino non eredita alcun timer dal genitore
Esempio :
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int var_glb; /* A global variable*/
int main(void)
{
pid_t childPID;
int var_lcl = 0;
childPID = fork();
if(childPID >= 0) // fork was successful
{
if(childPID == 0) // child process
{
var_lcl++;
var_glb++;
printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
else //Parent process
{
var_lcl = 10;
var_glb = 20;
printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
}
else // fork failed
{
printf("\n Fork failed, quitting!!!!!!\n");
return 1;
}
return 0;
}
Ora, quando il codice sopra viene compilato ed eseguito:
$ ./fork
Parent process :: var_lcl = [10], var_glb[20]
Child Process :: var_lcl = [1], var_glb[1]