Newsgroup: it.comp.os.linux.development
Titolo: Ricevere n byte da seriale con timeout

Riesco ad aprire, scrivere e leggere da una porta seriale utilizzando
open(), write() e read().
Quello che voglio implementare ora è la possibilità di uscire dalla
read() se non ricevo i byte richiesti entro un certo periodo di tempo
(timeout), per evitare che l'applicazione si blocchi se, per un
qualsiasi motivo, il dispositivo collegato alla porta seriale è
spento.
Leggendo la documentazione della libc, mi sembra di capire che posso
utilizzare varie strade.
Una possibilità è quella di gestire le operazioni di I/O in maniera
bloccante ed impostare un timer che genera un segnale. Non appena
l'handler del segnale ritorna, anche la read() ritorna. Se il numero
di byte letti effettivamente (valore di ritorno di read()) è minore di
quello richiesto, la read() è stata interrotta dal timeout.
--
  n = 10;
  nr = read( fd, buf, n );
  if( nr<n )
    /* Timeout scaduto senza aver ricevuto gli n byte richiesti */
  else
    /* Tutti i byte richiesti sono stati ricevuti */
--
Un'altra possibilità è quella di gestire le operazioni di I/O in
maniera non bloccante in modo che la read() ritorni subito anche se
non ci sono tutti i byte voluti immediatamente disponibili. Il timeout
dovrei implementarlo leggendo i timeticks del kernel (i secondi
passati dal 1970) e facendo una differenza.
--
  tt = timeticks();
  n = 10;
  while( n && (timeticks()-tt<timeout) ) {
    nr = read( fd, buf, n );
    buf += nr;
    n -= nr;
  }
  if( n ) {
    /* Timeout scaduto... */
  else
    /* Tutti i byte sono stati ricevuti */
--
E vedo anche un'altra possibilità e cioè quella delle operazioni di
input "Interrupt-driven". Dovrei impostare il flag O_ASYNC
all'apertura del file. La read() ritorna subito e devo aspettare il
segnale di SIGIO per completare l'operazione.
--
  tt = timeticks();
  n = 10;
  flag = 0;
  nr = read( fd, buf, n );
  if( nr<n ) {
    /* Ci sono altri byte da ricevere */
    n -= nr;
    buf += nr;
    while( timeticks()-tt<timeout )
      if( flag ) {
        nr = read( fd, buf, n );
        n -= nr;
        buf += nr;
        break;
      }
  }
  if( n )
    /* Timeout scaduto... */
  else
    /* Tutti i byte ricevuti */
--
Secondo voi qual è il metodo migliore tra questi? Ce ne sono altri più
efficienti?
La seconda mi sembra più semplice perchè non fa uso dei segnali, ma è
solo una impressione.

 spento.
In realtà, leggendo meglio la documentazione, mi sembra di capire che
la select() è la soluzione migliore.
Mi era sfuggita perchè era indicata principalmente per gestire l'input
da diversi file descriptors, ma può essere
usata ugualmente bene per un solo file e gestisce in automatico ed
efficientemente il timeout.

 la select() è la soluzione migliore.
Concordo: la select() o la poll() (io preferisco la poll) sono
la soluzione migliore. Potresti anche provare con un thread
separato, ma ti ci vorrebbe una mailbox o una fifo piu` il
protocollo annesso. La poll e` decisamente piu` facile.
Ah, occhio a gestire bene gli errori ;-)
Ciao,
  -- Roberto

 Ah, occhio a gestire bene gli errori ;-)
Cioè...?

 Cioè...?
La poll() ti ritorna canale pronto anche quando questo e` stato
semplicemente chiuso... Quindi bisogna verificare come mai la
poll() ritorna. Inoltre la read ti ritorna zero sia nel caso
non ci siano dati sia nel caso il canale abbia dei problemi.
Di nuovo, bisogna saper distinguere tra le due cose. Non e`
complicato, ma va fatto bene.
Ciao,
  -- Roberto

 complicato, ma va fatto bene.
Sì, hai ragione. A questo punto mi chiedo se esiste da qualche
parte l'elenco degli errori da prevedere nel caso la read() esca
subito senza aver ricevuto byte.

pozz wrote:

mi fermo quì perchè non ho tempo per leggere tutto, ma la select diminuisce il 
valore della timeval che gli passi come parametro coerentemente col tempo 
trascorso nella chiamata a select. Ad esempio, se passi 10" come timeout e la 
select ritorna che un fd è leggibile dopo 4", la struttura timeval ora 
contiene 6". Non è difficile implementare quello che tu vuoi tenendo conto di 
questo
-- 
SF
Facciamo quello per cui siamo stati addestrati, per cui siamo stati cresciuti, 
per cui siamo nati. Niente prigionieri, nessuna pietà. Inizio memorabile.

 questo
Perfetto, questo proprio non lo sapevo e non è spiegato nella
documentazione che
sto leggendo.

pozz wrote:

ma che documentazione è? io ne ho lette due a caso (di cui uno è una manpage 
di redhat 7.3, per dire ...) e lo cita in entrambe! Se davvero non c'è 
scritto, buttale


 non c'è scritto, buttale
info libc
Ed è anche (non) scritto qui:
http://www.gnu.org/software/libc/manual/html_node/Waiting-for-I_002fO.html#index-select-1281
Effettivamente nelle pagine di manuale (comando man) c'è scritto qualcosa in 
più, ma in contrasto con quello che dici tu (per esempio, dal primo link di 
Google cercando "man select", 
http://www.hmug.org/man/2/select.php
):
"Timeout is not changed by select(), and may be reused on subsequent calls, 
however it is good style to re-initialize it before each invocation of select()."
E in fondo alla pagina di manuale viene anche chiarito questo aspetto:
"Select() should probably have been designed to return the time remaining
from the original timeout, if any, by modifying the time value in place.
However, it is unlikely this semantic will ever be implemented, as the
change would cause source code compatibility problems.  In general it is
unwise to assume that the timeout value will be unmodified by the
select() call, and the caller should reinitialize it on each invocation."
Quindi sembra che il mio problema rimanga...

 quello che tu vuoi tenendo conto di questo
Se non sbaglio, la cosa non è però standard.
Manlio Perillo

 subito senza aver ricevuto byte.
Facile: il canale o e` aperto oppure e` chiuso. La poll() ti
dira` tutto.
Ciao,
  -- Roberto

Manlio Perillo wrote:

a questo punto non lo so, secondo le manpage della mia distribuzione il 
comportamento è questo, se sia standard o meno lo ignoro.


 comportamento è questo, se sia standard o meno lo ignoro.
SUSv3 (Single Unix Specification) dice solo che:
Upon successful completion, the select() function may modify the object 
pointed to by the timeout argument.
E l'APUE (Advanced Programming in the UNIX Environment) dice che FreeBSD 
5.2.1, MacOS X 10.3 e Solaris 9 non modificano il timeout, solo Linux 
2.4.22 lo modifica.
Manlio Perillo

 2.4.22 lo modifica.
uhm.. a questo punto posso solo ringraziarti della segnalazione, provvederò a 
modificare i miei sorgenti per renderli più aderenti allo standard.
