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.
- $$ o $PROCESS_ID
-
Questa variabile speciale viene correttamente impostata all'ID dello
pseudoprocesso. Può essere usata per identificare uno pseudoprocesso
all'interno di una determinata sessione. Va notato che questo valore è
soggetto al riutilizzo se degli pseudoprocessi vengono lanciati dopo
che è stata attesa la fine di altri tramite wait().
- %ENV
-
Ogni pseudoprocesso ha il suo ambiente virtuale. Le modifiche a
%ENV influenzano l'ambiente virtuale, e sono visibili solamente
all'interno di tale pseudoprocesso, ed in qualsiasi processo (o
pseudoprocesso) lanciato da esso.
chdir() e tutte le altre funzioni integrate che accettano nomi di file
-
Ogni pseudoprocesso ha la sua idea della directory corrente. Un eventuale
cambiamento della directory corrente utilizzando
chdir() è 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()
-
A
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()
-
La chiamata ad
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.
- Handle aperti verso file, directory e socket
-
Tutti gli handle aperti sono duplicati negli pseudoprocessi, dunque
la chiusura di un handle in un processo non influisce sugli altri.
Più avanti sono riportare alcune limitazioni.
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.
- blocchi BEGIN
-
L'emulazione di
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.
- Filehandle aperti
-
Qualsiasi filehandle aperto al momento del
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 implementata
-
I costrutti
open(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.
- Stato globale mantenuto dalle XSUB
-
Le subroutine esterne (XSUB) che mantengono il loro proprio stato
globale, possono funzionare in maniera non corretta. Tali XSUB avranno
il bisogno di conservare i lock per proteggere l'accesso simultaneo
a dati globali da differenti pseudoprocessi, oppure dovranno conservare
il loro stato sulla tabella dei simboli del Perl, che viene copiata
di per sé quando viene chiamata fork(). Un meccanismo di callback
[Un callback è un riferimento ad una subroutine che viene passato
come argomento nella chiamata ad un'altra subroutine, in modo da renderla
più flessibile, NdT] che fornisce alle estensioni una via per clonare
il loro stato sarà fornito in futuro.
- Interprete all'interno di un'applicazione più grande
-
L'emulazione di
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.
- Thread-safety delle estensioni [compatibilità delle estensioni con i thread, NdT]
-
Siccome l'emulazione di
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.
-
Avere interi negativi come ID di pseudoprocesso causa problemi con il
numero intero
-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>.
perlfunc/``fork'', perlipc
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.
Mon Jun 11 22:02:16 2012
|