-+  Documenti
 |-  Bibliografia
 |-  Articoli
 |-  Perlfunc
 |-  F.A.Q.
 |-  F.A.Q. iclp
-+  Eventi
-+  Contatti
-+  Blog
-+  Link



 

Versione stampabile.


Fabrizio Cardarello
Fabrizio Cardarello studia Ingegneria Informatica e lavora a Roma come sistemista Solaris e Linux. Ama il retro-computing e quando le cose si fanno ripetitive cerca di perdere molto piu' tempo aiutandosi con il Perl.

Avete mai pensato di voler rendere indipendente il vostro vecchio script per il collezionamento dati che gira ogni 10 minuti tramite il crontab? Oppure avete mai voluto aggiungere delle funzionalità in base a determinati eventi o semplicemente voglia di scrivere un demone in Perl? Allora questo è proprio l'articolo che fa al caso vostro.

Prima di tutto mi preme precisare che questo articolo non deve essere interpretato come una esauriente guida, ma vuole solamente riportare la mia esperienza diretta riguardo ad alcuni dei problemi che giornalmente ho affrontato e che grazie al Perl ho risolto. Un'altra avvertenza, infine: è sottinteso che si sta parlando di un sistema tipo Unix!

Demoni

I Demoni sono processi in grado di vivere a lungo: sono spesso creati partire durante la fase di boot-strap e terminati durante il processo di shutdown.

Essi non hanno un terminale di controllo in quanto la loro caratteristica è di girare in background. I sistemi di tipo UNIX hanno numerosi demoni che svolgono numerose attività giornaliere. Un classico esempio è il crond, che permette la schedulazione a tempo dei vari job del sistema.

Prima di procedere diamo uno sguardo alle principali caratteristiche dei demoni tramite il comando ps -axj. Il flag -a rappresenta tutti i processi del sistema, -x mostra i processi che non hanno un terminale di controllo ed infine il -j ci riporta le relative informazioni: session ID, process group ID, terminale di controllo ed in fine il terminal process group ID. Di seguito riporto un breve output di una linux-box.

PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
   0     1     0     0 ?           -1 S        0   0:01 init [2]
   1     2     0     0 ?           -1 S        0   0:00 [migration/0]
   1     3     0     0 ?           -1 SN       0   0:00 [ksoftirqd/0]
   1     4     0     0 ?           -1 S        0   0:00 [watchdog/0]
   1  4346  4346  4346 ?           -1 Ss       0   0:00 /usr/sbin/cron

Tutti i processi con un PPID uguale a 0 riguardano i processi kernel, e non fanno parte della normale procedura di boot. I processi kernel sono speciali in quanto, rimarranno in vita per tutto il tempo del sistema, gireranno con i privilegi del super utente e non avranno mai nessun terminale di controllo.

Il processo con PID 1 è il demone init il quale è responsabile di far partire i servizi del sistema nei vari run levels. Il processo con il PID 4346, è il cron daemon di cui vi parlavo prima.

Caratteristiche comuni

Ci sono alcune regole di "buon comportamento" per la scrittura di un demone che ci permettono di delineare delle linee guida da seguire. Nelle righe successive daremo uno sguardo a queste regole e come implementarle in Perl.

Se non volete implementare da zero il demone, è possibile utilizzare una valida alternativa. Su CPAN troverete una serie di moduli, Proc, che permettono una semplificazione della gestione dei processi e in particolare Proc::Daemon per la creazione di un demone.

Sessioni

Una sessione è un insieme di uno o piu' gruppi di processi, per chiarirci le idee possiamo dare uno sguardo al seguente output.

PPID PID PGID SID TTY COMMAND
1 4067 4067 4067 ? /usr/sbin/hald
4067 4068 4067 4067 ? \_ hald-runner 4068 4073 4067 4067 ? \_ /usr/lib/hal/hald-addon-acpi 4068 4127 4067 4067 ? \_ /usr/lib/hal/hald-addon-keyboard 4068 4137 4067 4067 ? \_ /usr/lib/hal/hald-addon-storage 4068 4138 4067 4067 ? \_ /usr/lib/hal/hald-addon-storage

Come vediamo il demone hald è leader della sessione 4067 e tutti i sui gruppi di processi sottostanti hanno il suo stesso SID (session id).

Tramite la chiamata di sistema setsid è possibile stabilire una nuova sessione, ovvero permettere al processo che invoca setsid di staccarsi dall'attuale sessione e creare una nuova sessione leader.

La funzione setsid restituisce un errore se il processo che la sta invocando è già un leader di una sessione. Per essere sicuri che questo non avvenga si genera un figlio tramite la chiamata di sistema fork e si fa terminare il padre, in questo modo il figlio non sarà mai il leader del gruppo in quanto il PGID del padre è ereditato dal figlio che avrà un nuovo PID. Pertanto sarà impossibile per il figlio avere un PID uguale al process group ID. Inoltre, se il demone parte da shell, far terminare il padre permette alla shell di credere che tutto sia andato a buon fine.

Detto questo possiamo passare alla parte pratica, prima di tutto nel nostro script dobbiamo caricare il modulo POSIX in modo da avere a disposizione la chiamata di sistema setsid:

use POSIX qw(setsid); # Nell'intestazione del programma
defined(my $pid = fork)
    or die "Non posso eseguire la fork(): $!";
exit if $pid;           ## termino il padre
setsid
    or die "Non posso far partire la nuova sessione: $!";
POSIX è l'acronimo di Portable Operating System Interface, che è uno standard per i sistemi operativi di tipo UNIX. Si occupa di delineare le chiamate di sistema che un sistema operativo UNIX-like deve avere.

Directory di lavoro

La seconda cosa da fare è cambiare in root la directory di lavoro del demone, tramite la funzione chdir. Infatti se il demone girerà su un file system differente da root, quel filesystem non potrà mai essere smontato e risulterà occupato dal demone stesso.

chdir '/'
    or die "Non posso eseguire il chdir /: $!";

Maschera di creazione dei file

La funzione umask che ci permetterà di impostare la maschera di creazione dei file. In questo modo, saremo sicuri che i file generati dal nostro demone avranno i giusti permessi, che non si baseranno sull'umask che l'utente ha normalmente quando utilizza la shell, ma sui valori passati alla funzione Perl.

L'istruzione

umask 0;

permette la creazione dei file con i permessi di lettura e scrittura per l'utente, il gruppo e tutti gli altri. Il valore è espresso in ottale e si specificano i permessi che si vogliono negare, di seguito riporto una tabella riassuntiva per i soli permessi dell'utente.

Mask permessi
0400 lettura utente
0200 scrittura utente
0100 esecuzione utente

È anche possibile assegnare una maschera per il gruppo e per tutti gli altri utenti del sistema seguendo la stessa logica. Ecco un esempio:

umask 0077;

In questo caso, abbiamo fatto in modo che solo l'utente che sta eseguendo il demone possa effettuare operazioni sul file, mentre stiamo inibendo la possibilità ad altri utenti del gruppo o del sistema di fare qualsiasi operazione sui file creati dal demone.

Rilascio del terminale

Caratteristica del demone è quella di girare in background ed essere svincolato dal terminale. Per evitare che stampi a video il suo output e bloccare ogni tipo di interferenza, i canali standard di comunicazione (STDIN,STDOUT,STDERR) vengono chiusi o al più rediretti verso un file come nel caso dello STDERR. Ogni file handler ereditato dal padre deve essere chiuso in modo da evitare che il demone lo tenga occupato inutilmente.

close STDIN;
open STDERR,">>/tmp/daemon.err";

Per quanto riguarda STDOUT, possiamo chiuderlo subito dopo aver stampato a video il PID del processo.

print "Stampa il pid: $$\n";
close STDOUT;

Attività indefinita

A questo punto il nostro demone è realizzato, quello che ci resta da ultimare è il corpo del nostro script. Un demone è un processo la cui attività ha una durata indefinita, per cui avremo bisogno di un ciclo (potenzialmente) infinito, all'interno del quale inseriamo delle operazioni di sleep per ridurre il carico sulla CPU.

while(1){
    sleep 3;
    ...
}

SEGNALI

Ora che abbiamo costruito lo scheletro del nostro demone, dobbiamo lasciare aperta qualche porta per poterci comunicare, soprattutto ricordando che abbiamo rilasciato qualunque connessione con il terminale. A questo scopo ci aiutano i segnali.

Cosa sono i segnali?

I segnali possono essere definiti come degli interrupts software, ovvero messaggi che i programmi possono raccogliere e interpretare a run-time, modificando il proprio comportamento sulla base dell'evento associato al messaggio. Un esempio classico è la rilettura di un file di configurazione, spesso può essere utile mandare un segnale ad un demone il cui evento associato preveda la rilettura del file di configurazione.

Ogni segnale ha un nome che inizia con 3 caratteri rappresentativi SIG e ad ogni segnale è anche associato un numero intero. Per vedere una lista completa dei segnali che si possono gestire nel sistema si può lanciare il comando

shell$ kill -l   # si tratta della lettera L minuscola
 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT
17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU
25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH
29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN
35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4
39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12
47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14
51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6
59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

Molti segnali rappresentano eventi predefiniti un classico esempio è il SIGINT che viene generato dalla shell quanto si preme a terminale Ctrl-c o il SIGSEGV che rappresenta un'eccezione Hardware, esempio eseguire una divisione per 0 o un referenziamento errato della memoria. Quando un evento di questo tipo avviene il Kernel ne prende atto e genera il segnale nei confronti del processo. Altri segnali, invece, sono liberi e possono essere usati per diversi scopi come il SIGUSR1 e SIGUSR2.

Il comportamento dei processi che non gestiscono i segnali è solitamente quello di terminare il processo che li riceve e per alcuni, SIGSEGV ad esempio, generare un file core associato. Solamente due segnali non possono essere ignorati: SIGKILL e SIGSTOP. Alla ricezione di un segnale, un processo può essere impostato per comportarsi in tre modi differenti:

  • Ignorare il Segnale, esclusi SIGKILL e SIGSTOP.
  • Ricevere il segnale ed eseguire la funzione associata.
  • Applicare la funzione di default, di solito il processo viene terminato.

I segnali in Perl

Il Perl permette la gestione dei segnali in un modo molto semplice, bisogna infatti inserire nel corpo del nostro demone i vari segnali che si vogliono usare e associarli alle funzioni da richiamare.

$SIG{'USR1'}= \&usr1;
$SIG{'USR2'}= \&usr2;

usr1 e usr2 saranno le nostre due funzioni associate ai segnali SIGUSR1 e SIGUSR2.

sub usr1 {
   if (my $pid=fork) {
      open my $file,">>/tmp/test.txt";
         or die "Non posso aprire il file: $!\n"
      flock $file,LOCK_EX;       ## lock
      sleep 6;
      print $file "pid padre: $$\n";
      flock $file,LOCK_UN;       ## unlock
      close $file;
      waitpid($pid,0);
   } else {
      sleep 3;
      open my $file,">>/tmp/test.txt";
         or die "Non posso aprire il file: $!\n";
      flock $file,LOCK_EX;
      print $file "pid figlio: $$\n";
      flock $file,LOCK_UN;
      close $file;
      exit 0;
   }
}

sub usr2 {
   warn "SIGUSR2 ricevuto";
   exit 0;
}

usr2 stamperà sullo STDERR un messaggio di ricezione del segnale.

Regolare l'accesso ai file

Avrete sicuramente notato che nella funzione usr1 abbiamo utilizzato la funzione flock - vediamo a cosa può esserci utile.

Se avete necessità di scrivere su file tra i vari processi del demone o tra i vari demoni, è sempre utile garantire un minimo di organizzazione di scrittura. La funzione flock ci permette di bloccare il file che stiamo scrivendo fino a che non decidiamo di rilasciarlo. Il prototipo di utilizzo è il seguente:

flock FILEHANDLE, OPERATION

I valori che si possono passare a OPERATION sono LOCK_SH, LOCK_EX o LOCK_UN che rappresentano i valori 1,2, 8 che possono essere combinati a loro volta con LOCK_BN il cui valore è 4. I diversi hanno i seguenti significati:

  • LOCK_SH è un tipo di lock condiviso;
  • LOCK_EX richiede un lock exclusivo;
  • LOCK_UN rilascia definitivamente un lock.
  • LOCK_NB viene utilizzato in or con i valori precedenti per creare un lock non bloccante, ovvero flock uscirà immediatamente invece di aspettare il lock.

Le costanti sopra citate fanno parte del modulo Fcntl, per ulteriori chiarimenti potete consultare la pagina di manuale.

Nel nostro demone ho utilizzato il segnale SIGUSR1 per creare un figlio il cui compito è quello di scrivere su un file "test.txt" ovviamente la scrittura è organizzata tramite la chiamata flock. Attraverso l'uso degli sleep si fa in modo che il padre blocchi il file per primo, in modo che il figlio aspetti finchè il padre non lo rilascerà.

sub usr1 {
   if (my $pid=fork) {
      open my $file,">>/tmp/test.txt";
         or die "Non posso aprire il file: $!\n"
      flock $file,LOCK_EX;       ## lock
      sleep 6;
      print $file "pid padre: $$\n";
      flock $file,LOCK_UN;       ## unlock
      close $file;
      waitpid($pid,0);
   } else {
      sleep 3;
      open my $file,">>/tmp/test.txt";
         or die "Non posso aprire il file: $!\n";
      flock $file,LOCK_EX;
      print $file "pid figlio: $$\n";
      flock $file,LOCK_UN;
      close $file;
      exit 0;
   }
}

La funzione waitpid mi permetterà di aspettare la corretta terminazione del processo figlio, in modo da non generare processi zombie.

Mettiamo tutto insieme

Questo che segue è lo script completo. Oltre a seguire i consigli che vi ho dato nell'articolo, vi suggerisco anche di scrivere sempre la documentazione POD per i vostri script... vi sarà utile quando cercherete di capire cosa può fare questo demone dopo qualche mese che non vedete più il codice!

#!/usr/bin/perl
## Author Fabrizio Cardarello
## email  hunternet@tin.it 
## location: Rome - Italy
##########################
#

use POSIX qw(setsid);
use strict;
use warnings;
use Fcntli qw( :flock );


sub usr1 {
   if (my $pid=fork){
      open my $file,">>/tmp/test.txt";
	 or die "Non posso aprire il file: $!\n";
      flock $file,LOCK_EX;	## lock 
      sleep 6;
      print $file "pid padre: $$\n";
      flock $file,LOCK_UN;	## unlock
      close $file;
      waitpid($pid,0);
   }else {
      sleep 3;
      open my $file,">>/tmp/test.txt"
         or die "Non posso aprire il file: $!\n";
      flock $file,LOCK_EX;
      print $file "pid figlio: $$\n";
      flock $file,LOCK_UN;
      close $file;
      exit 0;
   }
}

sub usr2 {
   warn "SIGUSR2 ricevuto";
   exit 0;
}

chdir '/'
   or die "Non posso eseguire il chdir /: $!\n";
umask 0;

close STDIN;
open STDERR,">>/tmp/daemon.err";

defined(my $pid = fork)   
   or die "Non posso eseguire la fork(): $!";
exit if $pid;		## termino il padre
setsid                    
   or die "Non posso far partire la nuova sessione: $!";


print "Stampa il pid: $$\n";	
close STDOUT;

while(1) {
   sleep 3;
   $SIG{'USR1'}= \&usr1;
   $SIG{'USR2'}= \&usr2;
  
}

__END__

=head1 NAME

dattero.pl - Script per la creazione di un demone.

=head1 SYNOPSIS

 ./dattero.pl

=head1 DESCRIPTION

Questo script fa parte di un articolo esemplificativo
per la creazione di un demone in Perl, la gestione di 
alcuni segnali SIGUSR1 SIGUSR2, e la scrittura controllata
di un file tra i vari processi.

=head1 AUTHOR

Fabrizio Cardarello 

=head1 SEE ALSO

Proc::Daemon, perlipc

=cut

Concludendo...

Demoni, segnali, chiamate di sistema, programmazione concorrente, queste sono alcune delle fondamenta di UNIX ed il Perl si dimostra uno strumento eccellente per svelarci tutti i segreti di questo sistema operativo.

Per ulteriori approfondimenti, possono essere d'aiuto i seguenti link:

In questi siti potete trovare una visione alternativa delle problematiche affrontate in questo articolo. In particolare, i primi due affrontano la tematica della scrittura di demoni in ambienti UNIX, mentre l'ultimo offre una panoramica sulla progammazione concorrente.

Buon Perl a tutti!


Ti è piaciuto questo articolo? Iscriviti al feed!


Inviato da Giuseppe il December 29, 2006 2:16 PM

Grande!
Proprio un bell'articolo, molto utile e di facile comprensione!










Devo ricordare i dati personali?






D:
Annunci Google