a.a. 2003/04 Problema I/O Multiplexing Prof. Vincenzo Auletta auletta@dia.unisa.it http://www.dia.unisa.it/professori/auletta/ Università degli studi di Salerno Laurea in Informatica 1 Un applicazione deve gestire più input simultaneamente Es. il echo gestisce due flussi di input Standard input (leggere da tastiera) un socket (leggere dal socket) Mentre l applicazione è bloccata su un descrittore non si accorge di quello che succede sull altro il echo bloccato sulla da stdin non legge il messaggio FIN ricevuto dal server In genere una funzione di input si blocca se non ci sono dati da leggere può rimanere bloccata per molto tempo l altro descrittore non può essere controllato Serve un meccanismo per poter esaminare più canali di input contemporaneamente Il primo canale che produce dati viene letto Modelli di I/O Struttura delle Operazioni di Lettura 2 UNIX implementa diversi modelli di I/O Modelli sincroni: il processo quando esegue una operazione di o scrittura si blocca fino al completamento dell operazione Modelli asincroni: il processo può effettuare altre operazioni mentre l operazione di /scrittura viene effettuata Modelli sincroni I/O bloccante I/O non bloccante I/O multiplexing I/O guidato da segnali 3 In una operazione di da un canale di I/O possiamo distinguere due fasi 1. Attesa per i dati da parte del kernel 2. Copia dei dati dal kernel al processo che deve usarli
I/O Bloccante I/O non Bloccante applicazione kernel applicazione kernel 4 elabora i dati BLOCCATA Restituisce OK ATTESA COPIA Non ci sono dati pronti dati pronti inizia copia copia completata 5 elabora i dati ATTESA EWOULDBLOCK EWOULDBLOCK EWOULDBLOCK Restituisce OK ATTESA COPIA Non ci sono dati pronti dati pronti inizia copia copia completata I/O Multiplexing I/O Guidato da Segnali applicazione kernel applicazione kernel 6 select (aspetta dati su un qualsiasi descrittore) elabora i dati BLOCCATA BLOCCATA Restituisce descrittore pronto Restituisce OK ATTESA COPIA Non ci sono dati pronti dati pronti inizia copia copia completata 7 setta handler per SIGIO SIGIO handler elabora i dati continua esecuzione BLOCCATA sigaction system call return SIGIO Restituisce OK COPIA Non ci sono dati pronti dati pronti inizia copia copia completata
I/O Asincrono Funzione select 8 asincrona handler del segnale elabora i dati applicazione continua esecuzione system call return invia segnale kernel COPIA Non ci sono dati pronti dati pronti inizia copia copia completata 9 #include <sys/select.h> #include <sys/time.h> int select(int maxfd, fd_set readset, fd_set writeset, fd_set exceptionset, struct timeval *timeout); Restituisce se errore 0 se scaduto il timeout numero di descrittori pronti Permette di controllare contemporaneamente uno o più descrittori per, scrittura o gestione errori 10 Timeout della Select timeout è il tempo massimo che la system call attende per individuare un descrittore pronto è una struct timeval struct timeval { long tv_sec; /* numero di secondi */ long tv_usec; /* numero di microsecondi */ ; timeout = 0 aspetta fino a quando un descrittore è pronto timeout = { 3; 5; aspetta fino al timeout e poi esce anche se non ci sono descrittori pronti alcuni S.O. arrotondano a multipli di 10 microsecondi timeout = { 0; 0; controlla i descrittori ed esce immediatamente (polling) 11 Insiemi di Descrittori Insiemi di descrittori da controllare readset: pronti per la writeset: pronti per la scrittura exceptionset: condizioni di eccezione Arrivo di dati fuori banda su un socket Informazioni di controllo da uno pseudo terminale readset, writeset e exceptionset sono variabili di tipo fd_set in genere è un array di interi in cui ogni bit rappresenta un descrittore primo elemento dell array rappresenta descrittori da 0 a 31 secondo elemento dell array rappresenta descrittori da 32 a 63 dettagli implementativi nascosti nella definizione
Operazioni su Insiemi di Descrittori void FD_ZERO(fd_set *fdset) Azzera la struttura fdset void FD_SET(int fd, fd_set *fdset) Mette a 1 il bit relativo a fd void FD_CLR(int fd, fd_set *fdset) Mette a 0 il bit relativo a fd int FD_ISSET(int fd, fd_set *fdset) Controlla se il bit relativo a fd è a 1 Macro utilizzate per operare sugli insiemi di descrittori La costante FD_SETSIZE è il numero di descrittori in fd_set definita in <sys/select.h> (solitamente 1024) in genere si usano meno descrittori [0, maxd] è l intervallo di descrittori effettivamente utilizzati utilizzati Es. se siamo interessati ai descrittori 1,4,7,9 maxd = 10 12 i descrittori iniziano da 0 13 Descrittori Pronti La select rileva i descrittori pronti significato diverso per ciascuno dei tre gruppi Un socket è pronto in se ci sono almeno LWM (low-water mark) byte da leggere LWM selezionabile tramite opzioni del socket per default è 1 il socket è stato chiuso in (è stato ricevuto il FIN) l operazione di ritorna EOF Il socket è un socket di ascolto e ci sono delle connessioni completate c è un errore pendente sul socket L operazione di ritornerà 14 Descrittori Pronti Un socket è pronto in scrittura se Il numero di byte liberi nel buffer di spedizione del socket è maggiore di LWM LWM selezionabile tramite opzioni del socket per default è 2048 L operazione di scrittura restituisce il numero di byte effettivamente passati al livello di trasporto Il socket è stato chiuso in scrittura Un operazione di scrittura genera SIGPIPE C è un errore L operazione di scrittura ritornerà e errno specificherà l errore Un socket è pronto per un eccezione se Arrivo di dati fuori banda 15 Client echo con select 1 void str_clisel_echo(file *fd, int sockd) { int maxd; fd_set rset; char sendline[maxline], recvline[maxline]; int n; FD_ZERO(&rset); (1) for( ; ; ) { FD_SET(fileno(fd), &rset); FD_SET(sockd, &rset); (2) maxd = MAX(fileno(fd), sockd) + 1; (3) if( select(maxd, &rset, NULL, NULL, NULL) < 0 ) (4) err_sys("errore nella select"); 1. azzera l array dei descrittori da controllare in 2.setta il descrittore del socket e del file per 3.calcola il massimo descrittore da controllare 4.invoca la select
16 0) Client echo con select 2 if( FD_ISSET(sockd, &rset) ) { (5) if ( (n = readline(sockd, recvline, MAXLINE)) < err_sys("errore nella readline"); if (n == 0) err_quit("str_clisel_echo: server morto prematuramente"); if( fputs(recvline, stdout) == EOF ) err_sys("errore nella fputs"); if( FD_ISSET(fileno(fd), &rset) ) { (6) if( fgets(sendline, MAXLINE, fd) == NULL) return; if( (writen(sockd, sendline, strlen(sendline))) < 0) err_sys("errore nella write"); 5.controlla se il socket è leggibile legge dal socket e stampa su stdout 6.controlla se il file è leggibile legge da stdin e scrive sul socket 17 Condizioni Gestite dal Client echo Condizioni gestite da select in su stdin ed un socket dal socket se il server invia dati il socket è leggibile e readline restituisce > 0 se il server manda FIN il socket è leggibile e readline restituisce 0 se il server manda RST il socket è leggibile e readline restituisce < 0 da stdin se l utente invia dati il file è leggibile e fgets restituisce > 0 se l utente invia EOF il file è leggibile e fgets restituisce 0Condizioni gestite da select in su stdin ed un socket Data o EOF stdin RST TCP sock et data Client FIN 18 I/O Multiplexing nel Server Possiamo usare l I/O multiplexing anche nel server per ascoltare su più socket contemporaneamente un unico processo iterativo ascolta sia sul socket di ascolto che su tutti i socket di connessione Client1 Server listend() connd1() connd2() connd3() Client2 Client3 19 Strutture Dati Utilizzate dal Server Un insieme di descrittori rset (di tipo fd_set) contiene la lista dei descrittori socket utilizzati dal server (sia di quello di ascolto che quelli di connessione) Un array di interi contiene i descrittori utilizzati entrambe di dimensione FD_SETSIZE FD_SETSIZE 0 1 2 4 rset fd0 fd1 fd2 fd3 fd4 fd5 0 0 0 1 1 0 maxd + 1
Esempio 1 Il server crea il socket di ascolto setta il bit corrispondente in rset (supp. sockd = 3) maxd = 3 Il server utilizza select per controllare la leggibilità di tutti i descrittori di rset Esempio 2 Il server accetta due richieste di connessione e crea i socket di connessione salva i descrittori del socket nelle prime posizioni di e setta i bit corrispondenti in rset maxd = 5 Il server utilizza select per controllare la leggibilità di tutti i descrittori di rset 0 1 2 rset fd0 fd1 fd2 fd3 fd4 fd5 0 0 0 1 0 0 0 1 2 4 5 rset fd0 fd1 fd2 fd3 fd4 fd5 0 0 0 1 1 1 20 FD_SETSIZE maxd + 1 21 FD_SETSIZE maxd + 1 22 Esempio 3 Il server chiude la connessione sul socket 4 cancella il descrittore da e azzera il bit corrispondente in rset maxd = 5 Il server utilizza select per controllare la leggibilità di tutti i descrittori di rset FD_SETSIZE 0 1 2 5 rset fd0 fd1 fd2 fd3 fd4 fd5 0 0 0 1 0 1 maxd + 1 23 Server echo con select 1 int main(int argc, char **argv) { int listend, connd, sockd; int i, maxi, maxd; int ready, [FD_SETSIZE]; char buff[maxline]; fd_set rset, allset; ssize_t n; struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; /* esegue socket(), bind() e listen() */ maxd = listend; (1) [0] = listend; (2) maxi = ; for ( i = 1; i < FD_SETSIZE; i++) (3) [i] = ; FD_ZERO(&allset); (4) FD_SET(listend, &allset); (5) 1. inizializza il numero di descrittori 2. inserisce listend in 3. inizializza il resto dell array a 1 4. azzera l insieme di descrittori registrati per la select 5. registra listend
24 Server echo con select 2 for ( ; ; ) { rset = allset; (5) if( (ready = select(maxd+1, &rset, NULL, NULL, NULL)) < 0 ) (6) err_sys("errore nella select"); 5.setta l insieme dei descrittori da controllare in 6.chiama la select esce quando un descrittore è pronto restituisce il numero di descrittori pronti 25 Server echo con select 3 if( FD_ISSET(listend, &rset) ) { (7) cliaddr_len = sizeof(cliaddr); if( (connd = accept(listend, (struct sockaddr *) &cliaddr, &cliaddr_len)) < 0) (8) err_sys("errore nella accept"); for(i = 0; i < FD_SETSIZE; i++) (9) if( [i] < 0 ) { [i] = connd; break; if( i == FD_SETSIZE ) (10) err_quit("troppi "); FD_SET(connd, &allset); (11) if( connd > maxd ) maxd = connd; if( i > maxi ) maxi = i; if( --ready <= 0 ) continue; 7. controlla se il socket di ascolto è leggibile 8. invoca la accept 9. inserisce il socket di connessione in un posto libero di 10. se non ci sono posti segnala errore 11. registra il socket ed aggiorna maxd 26 Server for( i = 0; i <= maxi; echo i++ ) { con select (12) 4 0) { (14) (15) if( (sockd = [i]) < 0 ) continue; if ( FD_ISSET(sockd, &rset) ) { (13) if ( (n = readline(sockd, buff, MAXLINE)) == if( close(sockd) == ) err_sys("errore nella close"); FD_CLR(sockd, &allset); [i] = ; else if( writen(sockd, buff, n) < 0 ) err_sys("errore nella write"); if ( --ready <= 0 ) break; 12. controlla tutti i socket di ascolto se sono leggibili 13. se un socket è leggibile invoca la readline 14. se ha letto l EOF chiude il socket e lo cancella da e allset 15. altrimenti fa l echo 27 Funzione poll #include <sys/poll.h> int poll(struct pollfd *fdarray, unsigned long nfds, int timeout); Restituisce se errore 0 se scaduto il timeout numero di descrittori pronti simile a select invece di utilizzare gli insiemi di descrittori fd_set utilizza un array di strutture pollfd consente di specificare le condizioni da testare per ogni descrittore
28 Parametri della poll struct pollfd* fdarray array di strutture che contengono informazioni sui descrittori da controllare e sugli eventi da rilevare unsigned long nfds lunghezza dell array fdarray int timeout timeout della poll specificato in millisecondi < 0 (aspetta per sempre) = 0 (esce immediatamente) > 0 (aspetta per il numero di millisecondi specificato) 29 Struttura pollfd struct pollfd { int fd; /* descrittore */ short events; /* eventi da controllare */ short revents; /* eventi riscontrati dalla poll */ ; per ogni descrittore è possibile definire gli eventi che devono essere verificati dalla poll events è un array di flag un flag per ogni tipo di evento quando la poll termina scrive in revents gli eventi rilevati sul descrittore 30 Eventi Rilevati da poll POLLIN /* dati normali o a priorità disponibili in */ POLLOUT /* dati normali disponibili in scrittura */ POLLERR /* è stato rilevato un errore */ POLLHOP /* è stata rilevata la chiusura della connessione */ POLLNVAL /* descrittore non corrisponde ad un socket aperto */ solo POLLIN e POLLOUT possono essere specificati come eventi da rilevare gli altri eventi vengono settati in revents dalla poll() se rileva un errore POSIX 1.g prevede anche altri eventi che non sono supportati da Linux 31 Server echo con poll 1 int main(int argc, char **argv) { int listend, connd, sockd; int i, maxi, ready; char buff[maxline]; ssize_t n; struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; struct pollfd [OPEN_MAX]; /* invoca socket(), bind() e listen() */ [0].fd = listend; (1) [0].events = POLLIN; for ( i = 1; i < OPEN_MAX; i++) (2) [i].fd = ; maxi = 0; for ( ; ; ) { if( (ready = poll(, maxi + 1, )) < 0 ) (3) err_sys("errore nella poll"); 1.registra listend in [0] e specifica che deve essere controllato in 2.inizializza il resto dell array a 1 3.invoca la poll
32 Server echo con poll 2 if( [0].revents & POLLIN ) { (4) cliaddr_len = sizeof(cliaddr); if( (connd = accept(listend, (struct sockaddr *) &cliaddr, &cliaddr_len)) < 0) (5) err_sys("errore nella accept"); for(i = 1; i < OPEN_MAX; i++) (6) if( [i].fd < 0 ) { [i].fd = connd; break; if( i == OPEN_MAX ) (7) err_quit("troppi "); [i].events = POLLIN; (8) if( i > maxi ) maxi = i; (9) if( --ready <= 0 ) continue; 4. controlla se il socket di ascolto è leggibile 5. invoca la accept 6. inserisce il socket di connessione in un posto libero di 7. se non ci sono posti segnala errore 8. registra gli eventi da rilevare sul nuovo descrittore 9. aggiorna maxi 33 Server echo con poll 3 (11) (12) (13) for( i = 1; i <= maxi; i++ ) { (10) if( (sockd = [i].fd) < 0 ) continue; if ( [i].revents & (POLLIN POLLERR) ) { if ( (n = readline(sockd, buff, MAXLINE)) < 0) { if( errno = ECONNRESET ) { if( close(sockd) == ) err_sys("errore nella close"); [i].fd = ; else err_sys("errore nella readline"); else if( n == 0 ) { if( close(sockd) == ) err_sys("errore nella close"); [i].fd = ; else if( writen(sockd, buff, n) < 0 ) err_sys("errore nella writen"); if ( --ready <= 0 ) break; 10. controlla tutti i socket di ascolto se sono leggibili 11. se un socket è leggibile o ha un errore pendente invoca la readline 12. se ha letto l EOF o un RST chiude il socket e lo cancella da 13. altrimenti fa l echo Attacchi Denial of Service Soluzioni per Attacchi DOS 34 Server iterativi che utilizzano l I/O multiplexing sono soggetti ad attacchi di tipo DOS (Denial of Service) un malizioso può far in modo che il server non possa rispondere alle richieste degli altri Esempio Un si connette, spedisce un solo byte (che non sia un newline) e non fa più nulla Il server rileva che il socket di connessione è leggibile ed invoca la readline la readline si blocca in attesa di un newline Il server è bloccato e nessun altro riceverà il servizio 35 Usare un singolo processo per ogni Utilizzare un timeout sulle operazioni di I/O Usare I/O non-bloccante
36 Stop-and-wait e Input Batch Il echo opera in modalità stop-and-wait: Spedisce una linea di input e si blocca in attesa della risposta del server echo particolarmente inefficiente quando l utente deve inviare molti dati Se l utente fornisce l input in modalità batch il programma segnala un errore (es. _sel_echo 127.0.0.1 < nome_file) quando la fgets legge l EOF il chiude la connessione e termina il server potrebbe ancora richiedere ritrasmissioni di segmenti o inviare risposte 37 Shutdown della Connessione Quando il legge l EOF dal file di chiude il socket solo in scrittura (half-close) rimane in ascolto per le risposte del server risponde alle eventuali richieste di ritrasmissioni di segmenti o di invii di ACK L operazione di half-close è implementata dalla system call shutdown() 38 Funzione shutdown #include <sys/socket.h> int shutdown(int sd, int howto); Restituisce se errore 0 se OK l operazione della funzione dipende dal valore di howto SHUT_RD: chiude il socket solo in non riceve più nulla ed ignora anche i dati nel buffer eventuali altri dati sono riscontrati da TCP ma scartati SHUT_WR: chiude il socket solo in scrittura non scrive più nulla ma invia i dati attualmente nel buffer chiusura effettuata indipendentemente dal valore del reference counter SHUT_RDWR: entrambe le due opzioni 39 Client echo con shutdown 1 void str_cliselshut_echo(file *fd, int sockd) { int maxd, stdineof; fd_set rset; char sendline[maxline], recvline[maxline]; int n; stdineof = 0; (1) FD_ZERO(&rset); for( ; ; ) { if( stdineof == 0 ) (2) FD_SET(fileno(fd), &rset); FD_SET(sockd, &rset); maxd = MAX(fileno(fd), sockd) + 1; if( select(maxd, &rset, NULL, NULL, NULL) < 0 ) (3) err_sys("errore nella select"); 1. inizializza stdineof a 0 2. se l utente non ha inviato l EOF setta fd leggibile 3. invoca la select
40 0) Client echo con shutdown 2 if( FD_ISSET(sockd, &rset) ) { (4) if ( (n = readline(sockd, recvline, MAXLINE)) < err_sys("errore nella readline"); if( n == 0 ) { (5) if( stdineof == 1 ) return; else err_quit("str_clisel_echo: server morto prematuramente"); if( fputs(recvline, stdout) == EOF ) (6) err_sys("errore nella fputs"); 4. controlla se il socket è leggibile 5. se il server ha inviato l EOF ed il aveva già chiuso la connessione esce altrimenti segnala errore 6. altrimenti stampa su stdout 41 Client if( FD_ISSET(fileno(fd), echo &rset) con ) { shutdown 3 (8) (7) if( fgets(sendline, MAXLINE, fd) == NULL) { stdineof = 1; if( shutdown(sockd, SHUT_WR) < 0 ) err_sys("errore nella shutdown"); FD_CLR(fileno(fd), &rset); (9) continue; if( (writen(sockd, sendline, strlen(sendline))) < 0)(10) err_sys("errore nella write"); 7. controlla se fd è leggibile 8. se ha letto l EOF setta stdineof ed esegue la shutdown chiude la connession e in scrittura 9. cancella fd da rset 10. altrimenti scrive sul socket