Nuovo quinto capitolo, parte prima

Nella revisione della guida si è deciso di accorpare i due capitoli in precedenza dedicati alle due interfacce di programmazione per l’accesso al contenuto dei file, quella dei file descriptor e quella degli stream, in un unico capitolo, il quinto, dedicato alla gestione del contenuto dei file in maniera generale.

Si sono ristrutturate le varie sezioni dedicando a ciascuna interfaccia due sezioni, di cui la prima volta ad illustrare architettura e funzioni di base, e la seconda volta ad illustrare le caratteristiche più avanzate di entrambe. Si è inoltre provveduto ad un aggiornamento dei contenuti, che è stato completato per quanto riguarda la prima parte, quella dedicata alla interfaccia nativa Unix dei file descriptor.

Si è revisionata la spiegazione sul significato dei file descriptor, si sono documentati in maniera dettagliata i flag di open, in particolare O_DIRECT e O_SYNC, si sono documentati i nuovi parametri SEEK_HOLE e SEEK_DATA di lseek, si sono accorpate le sezioni relative all’accesso concorrente dei processi ai file e documentati i dettagli non standard di dup2 e della nuova dup3. Si sono documentati meglio i comandi di fcntl ed aggiunti i nuovi comandi introdotti negli ultimi anni come F_GETOWN_EX, F_SETOWN_EX, F_SETPIPE_SZ e F_GETPIPE_SZ.

Alcuni dettagli di timerfd.

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.

This work by Simone Piccardi is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported.