Il fork()
e vfork()
i wrapper in glibc sono implementati tramite clone()
chiamata di sistema. Per comprendere meglio la relazione tra fork()
e clone()
, dobbiamo considerare la relazione tra processi e thread in Linux.
Tradizionalmente, fork()
duplicherebbe tutte le risorse di proprietà del processo genitore e assegnerebbe la copia al processo figlio. Questo approccio comporta un sovraccarico considerevole, che potrebbe essere inutile se il bambino chiama immediatamente exec()
. In Linux, fork()
utilizza copia su scrittura pagine per ritardare o evitare del tutto di copiare i dati che possono essere condivisi tra i processi padre e figlio. Pertanto, l'unico sovraccarico che si verifica durante un normale fork()
è la copia delle tabelle della pagina del genitore e l'assegnazione di una struttura descrittore di processo univoca, task_struct
, per il bambino.
Linux ha anche un approccio eccezionale ai thread. In Linux, i thread sono semplicemente processi ordinari che condividono alcune risorse con altri processi. Questo è un approccio radicalmente diverso ai thread rispetto ad altri sistemi operativi come Windows o Solaris, dove processi e thread sono tipi di bestie completamente diversi. In Linux, ogni thread ha un normale task_struct
di per sé che sembra essere impostato in modo tale da condividere determinate risorse, come uno spazio di indirizzi, con il processo genitore.
Il flags
parametro del clone()
la chiamata di sistema include un insieme di flag che indicano quali risorse, se ce ne sono, i processi padre e figlio dovrebbero condividere. Processi e thread sono entrambi creati tramite clone()
, l'unica differenza è l'insieme di flag che viene passato a clone()
.
Un normale fork()
potrebbe essere implementato come:
clone(SIGCHLD, 0);
Questo crea un'attività che non condivide alcuna risorsa con il suo genitore ed è impostata per inviare il SIGCHLD
segnale di terminazione al genitore quando esce.
Al contrario, un'attività che condivide lo spazio degli indirizzi, le risorse del filesystem, i descrittori di file e i gestori di segnale con il genitore, in altre parole un thread , potrebbe essere creato con:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
vfork()
a sua volta è implementato tramite un CLONE_VFORK
separato flag, che causerà la sospensione del processo genitore fino a quando il processo figlio non lo risveglierà tramite un segnale. Il figlio sarà l'unico thread di esecuzione nello spazio dei nomi del genitore, fino a quando non chiamerà exec()
o esce. Al bambino non è permesso scrivere nella memoria. Il clone()
corrispondente chiamata potrebbe essere la seguente:
clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0)
L'implementazione di sys_clone()
è specifico dell'architettura, ma il grosso del lavoro avviene in do_fork()
definito in kernel/fork.c
. Questa funzione chiama lo statico clone_process()
, che crea un nuovo processo come copia del genitore, ma non lo avvia ancora. clone_process()
copia i registri, assegna un PID alla nuova attività e duplica o condivide parti appropriate dell'ambiente di processo come specificato dal clone flags
. Quando clone_process()
restituisce, do_clone()
riattiverà il processo appena creato e ne programmerà l'esecuzione.
Il componente responsabile della traduzione delle funzioni di chiamata di sistema userland in chiamate di sistema del kernel sotto Linux è la libc. In GLibC, la libreria NPTL lo reindirizza al clone(2)
chiamata di sistema.