A partire dal kernel 2.6.25 è stata introdotta una interfaccia di gestione alternativa per i timer di sistema che consente la ricezione delle notifiche di scadenza attraverso dei file descriptor invece che tramite segnali o thread, chiamata convenzionalmente timerfd.
L’interfaccia è specifica di Linux e si unisce ad altre due (signalfd e eventfd) che seguono la stessa filosofia per la ricezione di segnali e di eventi dal kernel. Il grande vantaggio è che queste possono essere usate in combinazione con le funzioni dell’I/O multiplexing (poll
, select
ed epoll
) semplificando notevolmente la gestione l’attesa contemporanea di eventi e dati.
L’interfaccia timerfd segue da vicino quella introdotta da POSIX.1-2001 che è trattata nella sezione 9.5.2 della guida, che però fa ricorso a meccanismi di notifica classici. Nel trattare la nuova interfaccia mi è sorto un dubbio relativo al funzionamento della stessa quando si esegue una fork.
Con i timer ordinari infatti un processo figlio non eredita dopo una fork
nessuno dei timer del padre. La semantica Unix prevede però che un file descriptor sia duplicato attraverso una fork
, e dato che in questo caso un timer viene associato ad un file descriptor la domanda su cosa accada al timer nel processo figlio sorge spontanea.
La documentazione dice che il file descriptor associato ad un timer continua a seguire la semantica ordinaria e viene duplicato nel figlio. Viene cioè preferita la semantica dei file descriptor rispetto a quella dei timer ordinari. Sorge però spontanea la domanda di cosa succede se padre e figlio iniziano a leggere entrambi da tale file descriptor per ricevere la notifica delle scadenze dei timer.
La documentazione delle pagine di manuale, almeno nella versione della mia Debian, non dice nulla, ma “a naso” ci si aspetterebbe che accada la stessa cosa che accade per un file descriptor duplicato, e cioè che chi legge per primo riceve i dati, lasciando “a bocca asciutta” l’altro processo.
Per verificarlo ho comunque scritto un programma di test, test_timerfdfork.c, che arma un timer con timerfd esegue una fork
ed usa epoll per l’attesa in entrambi i processi. Il programma prevede delle opzioni per specificare i valori della struttura itimerspec
che impostano prima scadenza e ripetizione del timer.
Se lo eseguiamo mettendo un tempo di prima scadenza a due secondi, ed un tempo di ripetizione ad un secondo, otterremo qualcosa del tipo:
piccardi@hain:~/gapil/sources$ gcc test_timerfdfork.c
piccardi@hain:~/gapil/sources$ ./a.out -i1 -t2
Timer interval 2 sec, timer time 1 sec
Got 1 events, pid 12513, time 1308661930
Timer expired in pid 12513:
Expired 1 times in pid 12513
Got 1 events, pid 0, time 1308661930
Timer expired in pid 0:
Got 1 events, pid 0, time 1308661931
Timer expired in pid 0:
Expired 1 times in pid 0
Got 1 events, pid 0, time 1308661932
Timer expired in pid 0:
Expired 1 times in pid 0
Got 1 events, pid 0, time 1308661933
Timer expired in pid 0:
Expired 1 times in pid 0
e come ci si aspettava viene letta una scadenza alla volta, nel caso la prima volta nel figlio e le successive nel padre (ma la cosa è casuale e rilanciandolo di possono ottenere diversi risultati), cosa che corrisponde al fatto che il primo processo che esegue una lettura sul file descriptor riceve il dato della scadenza.