[successivo] [precedente] [inizio] [fine] [indice generale]
In caso si abbiano più canali di comunicazione (in rete e/o locali) aperti c'è il problema di controllarli contemporaneamente; si pensi ad esempio a un servente in attesa di dati in ingresso da vari clienti oppure a un processo che riceve dati sia da un utente, attraverso la tastiera (standard input), sia da un processo remoto.
Può succedere di rimanere bloccati in una operazione su un descrittore di file o di socket non pronto mentre ce ne potrebbe essere un altro disponibile, ritardando l'accesso a quest'ultimo e con il rischio di incorrere in un deadlock (se l'accesso al descrittore in attesa può essere sbloccato solo da qualcosa in arrivo su quello disponibile).
Una soluzione abbastanza semplice sarebbe quella di rendere l'accesso ai socket non bloccante in modo che le relative funzioni ritornino subito (con errore EAGAIN) e prevedere un meccanismo ciclico di interrogazione di tutti i flussi dati verso il nostro processo.
Questa soluzione, chiamata polling, usa in modo molto inefficiente le risorse del sistema in quanto la maggior parte del tempo macchina è perso ad interrogare flussi che non hanno dati.
Molto migliore è la soluzione chiamata I/O multiplexing con la quale si controllano vari flussi contemporaneamente, permettendo di bloccare un processo quando le operazioni volute non sono possibili, e di riprenderne l'esecuzione una volta che almeno una sia disponibile.
Il tutto viene realizzato con l'uso della funzione select() che ha il seguente prototipo:
int select(int n, fd_set *rfds, fd_set *wfds, fd_set *exfds, struct timeval *tim) //attende che uno dei descrittori di file degli insiemi indicati diventi attivo; //n è la dimensione degli insiemi di descrittori (sizeof(fd_set)); //rfds è l'insieme dei descrittori da controllare in lettura; //wfds è l'insieme dei descrittori da controllare in scrittura; //exfds è l'insieme dei descrittori da controllare per verifica di errori; //tim è il tempo dopo il quale la funzione ritorna se nessun flusso è attivo. |
Ritorna il numero di file descriptor, eventualmente anche 0, che sono attivi, oppure -1 se c'è errore, nel qual caso errno può valere:
EBADF: uno degli insiemi di file descriptor è sbagliato;
EINTR: la funzione è stata interrotta da un segnale;
EINVAL: n è negativo o tim ha un valore non valido.
La funzione mette il processo in stato di sleep, finché almeno uno dei descrittori degli insiemi specificati non diventa attivo, per un tempo massimo specificato da timeout.
Per valorizzare il parametro n si può usare la costante FD_SETSIZE definita in <sys/select.h>.
Gli insiemi di descrittori possono essere vuoti nel caso non si voglia controllare nulla in lettura, o in scrittura o per gli errori; anche il parametro tim può essere NULL con il significato di attesa indefinita da parte della funzione.
Il timeout tim viene indicato valorizzando i due campi della struttura timeval:
int tv_sec //secondi di attesa int tv_usec //microsecondi di attesa |
Per la selezione dei descrittori la funzione usa il «file descriptor set» corrispondente al tipo dati fd_set.
La gestione degli insiemi di descrittori è resa possibile dalle seguenti macro di preprocessore, definite in <sys/select.h>:
FD_ZERO(fd_set *set) //inizializza l'insieme set (vuoto); FD_SET(int fd, fd_set *set) //aggiunge fd all'insieme set; FD_CLR(int fd, fd_set *set) //elimina fd dall'insieme set; FD_ISSET(int fd, fd_set *set) //controlla se fd è presente nell'insieme set. |
Si deve prestare attenzione al fatto che la select() altera i valori degli insiemi di descrittori che quindi devono essere sempre impostati se si invoca la funzione ciclicamente.
La funzione modifica anche il valore di tim, impostandolo al tempo restante in caso di interruzione prematura; questo è utile perché permette di non ricalcolare il tempo rimanente quando la funzione, interrotta da un segnale (errore EINTR), deve essere invocata di nuovo.
Segue un frammento di codice con un esempio di uso della funzione select() dove si suppone di dover controllare due flussi di input corrispondenti alla tastiera e a un socket di rete sd:
fd_set set, set2, set3; while (1) { FD_ZERO (&set); FD_ZERO (&set2); FD_ZERO (&set3); FD_SET(fileno(stdin), &set); // con macro fileno ho il descrittore di stdin FD_SET(sd, &set); if (select(FD_SETSIZE, &set, &set2, &set3, NULL)<0) { // istruzioni di gestione dell'errore della select exit(1); } if (FD_ISSET(fileno(stdin), &set)) { // istruzione di gestione dell'input da tastiera } if (FD_ISSET (sd, &set)) { // istruzione di gestione dell'input dal socket } } // fine while(1) // le due ultime if non devono essere esclusive, possono // arrivare dati contemporaneamente dai due flussi |
Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome multiplexing_dell_x0027_input_output.html