NOME |
perlfork - L'emulazione di fork()
fornita dal Perl
NOTA: Con la versione 5.8.0, l'emulazione di fork() e` considerevolmente maturata. Comunque, ci sono ancora qualche bug conosciuto, e delle differenze rispetto alla fork() reale, che potrebbero riguardarvi. Consultate le sezioni "BUG" e "AVVERTIMENTI E LIMITAZIONI" piu` avanti.
Il Perl fornisce una funzione fork()
che corrisponde alla chiamata di
sistema Unix con lo stesso nome. Sulla maggior parte dei sistemi
tipo Unix su cui la chiamata di sistema fork()
è disponibile, Perl
altro non fa che chiamarla.
Su alcuni sistemi come Windows, dove la chiamata di sistema fork()
non
è disponibile, Perl può essere compilato per emulare fork()
a livello di interprete. Benché l'emulazione sia progettata per
essere, a livello del programma Perl, il più compatibile possibile
con la vera fork(), ci sono alcune importanti differenze, che derivano dal
fatto che tutti i ``processi'' pseudofigli creati in questo modo, in
realtà, per quanto concerne il sistema operativo, vivono nello
stesso processo.
Questo documento offre una panoramica generale delle caratteristiche e
delle limitazione dell'emulazione di fork(). Va notato che le questioni
discusse qui non sono applicabili alle piattaforme sulle quali è
disponibile una vera fork()
e Perl è stato configurato per usarla.
L'emulazione di fork()
è implementata a livello di interprete Perl.
Questo, in generale, significa che fork()
di fatto clona l'interprete
in esecuzione e tutto il suo stato, e manda in esecuzione l'interprete
clonato in un thread separato, iniziandone l'esecuzione subito dopo
il punto in cui fork()
era stata chiamata nel processor genitore. D'ora
in avanti chiameremo pseudoprocesso il thread che implementa questo
``processo'' figlio.
Tutto ciò è stato progettato in modo da essere trasparente
al programma Perl che ha chiamato fork(). Il genitore ritorna dalla fork
con un ID di pseudoprocesso, il quale può in seguito essere usato
in qualsiasi funzione di manipolazione del processo; il figlio ritorna
dalla fork()
con un valore 0
per indicare che è lui lo
pseudoprocesso figlio.
La maggior parte delle caratteristiche del Perl si comportano in maniera normale all'interno dello pseudoprocesso.
chdir()
e tutte le altre funzioni integrate che accettano nomi di filechdir()
è visibile
solamente all'interno di tale pseudoprocesso, ed in qualsiasi processo
(o pseudoprocesso) lanciato da esso. Tutti gli accessi a file e
directory effettuati dallo pseudoprocesso effettueranno correttamente
la mappatura della directory di lavoro virtuale alla reale directory di
lavoro.
wait()
e waitpid()
wait()
e waitpid()
può essere passato un ID di pseudoprocesso
restituito da fork(). Le chiamate a queste funzioni attenderanno, correttamente,
il termine del pseudoprocesso e restituiranno il suo stato.
kill()
kill()
può essere utilizzato per terminare uno pseudoprocesso passando
ad essa l'ID restituito da fork(). Essa non dovrebbe essere
usata tranne che in circostanze estreme poiché, quando un thread viene
terminato, il sistema operativo potrebbe non garantire l'integrità
delle risorse del processo. Ricordate che l'uso di kill()
su uno pseudoprocesso
può causare dei memory leak [perdita di memoria, NdT], poiché
il thread che implementa lo pseudoprocesso non ha avuto la possibilit<agrave> di
effettuare la pulizia delle sue risorse.
exec()
exec()
all'interno di uno pseudoprocesso in realtà
lancia l'eseguibile richiesto in un processo separato ed attende
che esso termini l'esecuzione prima di uscire con lo stesso valore
di uscita del processo. Ciò significa che l'ID del processo indicato
all'interno del programma in esecuzione sarà diverso da ciò che
la funzione fork()
di Perl può aver restituito in precedenza.
Analogamente, ogni funzione di manipolazione del proceso applicata all'ID
restituito da fork(), influenzerà lo pseudoprocesso generato dalla
chiamata ad exec(), non il vero processo che sta attendendo il ritorno di
exec().
exit()
exit()
fa sempre uscire solamente lo pseudoprocesso in esecuzione, dopo aver
automaticamente atteso il termine di qualsiasi pseudoprocesso figlio
ancora in esecuzione. Notate che ciò significa che il processo
principale non uscirà a meno che tutti i pseudoprocessi non abbiano
terminato la loro esecuzione.
Agli occhi del sistema operativo, gli pseudoprocessi creati tramite
l'emulazione di fork()
sono semplicemente thread nello stesso processo.
Ciò significa che qualsiasi limite a livello di processo imposto al
sistema operativo vale per tutti gli pseudoprocessi messi assieme. Questo
include qualsiasi limite imposto dal sistema operativo sul numero di file,
directory e socket aperti, limiti sull'uso di spazio su disco, limiti
sulle dimensioni in memoria, limiti sull'uso della CPU, ecc.
Se il processo genitore viene ucciso (usando la funzione kill()
di Perl,
o utilizzando qualche metodo esterno) anche tutti gli pseudoprocessi
vengono uccisi, e l'intero processo esce.
Durante il normale corso degli eventi, il processo genitore e tutti gli pseudoprocessi da esso generati attenderanno, prima di uscire, che i rispettivi pseudofigli terminino la loro esecuzione. Ciò significa che il genitore ed ogni suo pseudofiglio che è anche uno pseudogenitore usciranno solo dopo che i loro pseudofigli hanno terminato l'esecuzione.
Un modo per marcare gli pseudoprocessi cosicché siano eseguiti separatemente dal loro genitore (cosicché il genitore non debba attendere il loro termine se non lo desidera) verrà fornito in futuro.
fork()
non funziona in maniera del tutto corretta
se è chiamata dall'interno di un blocco BEGIN. La copia ottenuta
tramite fork()
eseguirà i contenuti del blocco BEGIN, ma non
continuerà il parsing del codice sorgente dopo tale blocco.
Ad esempio, considerate il seguente codice:
BEGIN { fork and exit; # il padre crea un figlio ed esce print "interno\n"; } print "esterno\n";
Questo stamperà:
interno
anziché, come ci si aspetta:
interno esterno
Questa limitazione nasce da fondamentali difficoltà tecniche nel clonare e far ripartire gli stack usati dal parser Perl nel corso di un'operazione di parsing.
fork()
viene duplicato. Dunque,
i file possono essere chiusi indipendentemente nel padre e nel figlio,
ma ricordate che gli handle duplicati continueranno a condividere
il puntatore che indica la posizione all'interno del file. Cambiare
tale posizione nel padre la cambierà nel figlio, e viceversa. Questo
può essere evitato aprendo separatamente, nei processi figli, i file
che necessitano di puntatori diversi.
open()
con pipe verso fork non ancora implementataopen(PIPPO, "|-")
e open(PLUTO, "-|")
non sono
ancora stati implementati. Questa limitazione può essere facilmente
aggirata via codice, creando esplicitamente una pipe. Il seguente
esempio mostra come creare un figlio derivante da una fork():
# simula open(PIPPO, "|-") sub pipe_verso_fork ($) { my $genitore = shift; pipe my $figlio, $genitore or die; my $pid = fork(); die "fork() fallita: $!" unless defined $pid; if ($pid) { close $figlio; } else { close $genitore; open(STDIN, "<&=" . fileno($figlio)) or die; } $pid; }
if (pipe_verso_fork('PIPPO')) { # genitore print PIPPO "pipe_verso_fork\n"; close PIPPO; } else { # figlio while (<STDIN>) { print; } close STDIN; exit(0); }
E questo legge dal figlio:
# simula open(PLUTO, "-|") sub pipe_da_fork ($) { my $genitore = shift; pipe $genitore, my $figlio or die; my $pid = fork(); die "fork() fallita: $!" unless defined $pid; if ($pid) { close $figlio; } else { close $genitore; open(STDOUT, ">&=" . fileno($figlio)) or die; } $pid; }
if (pipe_da_fork('PLUTO')) { # genitore while (<PLUTO>) { print; } close PLUTO; } else { # figlio print "pipe_da_fork\n"; close STDOUT; exit(0); }
La funzione open()
con pipe verso fork sarà supportata in futuro.
fork()
può comportarsi in maniera imprevista quando
viene eseguita in un'applicazione che contiene un interprete Perl
e chiama delle API Perl che eseguono frammenti di codice. Ciò
deriva dal fatto che l'emulazione conosce esclusivamente le strutture
dati dell'interprete Perl, e non sa invece nulla sullo stato
dell'applicazione che fa da contenitore. Per esempio, qualsiasi
stato memorizzato nello stack dell'applicazione è fuori portata.
fork()
esegue codice in thread multipli, le
estensioni che effettuano chiamate a librerie che non sono thread-safe
possono funzionare in maniera non affidabile qualora venga chiamata
fork(). Man mano che il supporto ai thread del Perl si sta diffondendo anche
sui sistemi in cui esiste una fork()
nativa, tali estensioni saranno
probabilmente corrette in modo da essere thread-safe.
-1
, poiché per le funzioni wait()
e waitpid()
si
tratta di un caso speciale. L'assunzione tacita, nell'implementazione corrente,
è che il sistema non alloca mai un ID di thread di 1
per i thread
utente. Una rappresentazione migliore per gli ID di pseudoprocesso
sarà implementata in futuro.
In alcuni casi, gli handle a livello di sistema operativo creati dagli
operatori pipe(), socket()
e accept()
non sono duplicati correttamente
negli pseudoprocessi. Ciò accade solo in alcune situazioni, ma quando
accade, può portare a degli stalli tra l'estremo di lettura e quello
di scrittura delle handle di pipe, o l'impossibilità di inviare o
ricevere dati attraverso handle di socket.
Questo documento può essere incompleto in alcuni aspetti.
Il supporto per interpreti concorrenti per l'emulazione di fork()
sono
stati implementati da ActiveState, grazie a fondi forniti da Microsoft
Corporation.
Questo documento è scritto e mantenuto da Gurusamy Sarathy <gsar@activestate.com>.
fork in the perlfunc manpage, the perlipc manpage
La versione su cui si basa questa traduzione è ottenibile con:
perl -MPOD2::IT -e print_pod perlfork
Per maggiori informazioni sul progetto di traduzione in italiano si veda http://pod2it.sourceforge.net/ .
Traduzione a cura di Michele Beltrame.
Revisione a cura di Michele Beltrame e dree.
NOME |