Hallo
Ich habe folgendes Problem:
Ich möchte unter Linux über die serielle Schnittstelle kommunizieren und dazu benutze ich den canonical read. Wenn jetzt die Gegenseite nichts sendet bleibt das Programm am read hängen. Um das zu umgehen habe ich mittels alarm() einen timeout gesetzt. Das Programm sieht etwa so aus:
jmp_buf env;
a_function(){
signal(SIGALRM, TimeoutHandler); while(1){ if(setjmp(env) == 0){ ... alarm(1); /* 1 sec timeout */ read(fd,buf,255); /* blocking read */ alarm(0); /* timeout off */ ... } else{ ... } } }
void TimeoutHandler(int sigint){
longjmp(env,1); }
Das Programm funktioniert auch einwandfrei, allerdings nur genau einmal. Dann funktioniert der SIGALRM nicht mehr, das Programm bleibt bei read stehen. Ich hab schon alle Varianten von alarm() an und aus an verschiedenen Stellen versucht, genauso ein Rücksetzen des Signals mittels signal(SIGALRM, SIG_DFL). Außerdem hab ich das ganze mittels setitimer anstatt alarm versucht, kein Erfolg. Es wird immer nur einmal durchlaufen. Bei verschiedenen Tests hab ich dann festgestellt, dass ohne setjmp/longjmp der alarm auch öfter funktioniert, also wenn im TimeoutHandler nur ne printf Anweisung steht.
Kann mir irgendwer dazu nen Tipp geben? Muss man das SIGALRM Signal irgendwie rücksetzen oder vielleicht die env-Variable reseten?
Oder gibts noch ne andere möglichkeit einen Timeout für das serielle Lesen zu realisieren? Ich hab ein paar Versuche mit select() gemacht, das hat allerdings garnicht funktioniert.
Jegliche Hilfe ist willkommen.
Mfg. Erisch
On Friday 31 March 2006 17:49, Erik Trauschke wrote:
Ich möchte unter Linux über die serielle Schnittstelle kommunizieren und dazu benutze ich den canonical read. Wenn jetzt die Gegenseite nichts sendet bleibt das Programm am read hängen. Um das zu umgehen habe ich mittels alarm() einen timeout gesetzt. Das Programm sieht etwa so aus:
[cut]
Das Programm funktioniert auch einwandfrei, allerdings nur genau einmal. Dann funktioniert der SIGALRM nicht mehr,
man sigaction
das Programm bleibt bei read stehen. Ich hab schon alle Varianten von alarm() an und aus an verschiedenen Stellen versucht, genauso ein Rücksetzen des Signals mittels signal(SIGALRM, SIG_DFL). Außerdem hab ich das ganze mittels setitimer anstatt alarm versucht, kein Erfolg. Es wird immer nur einmal durchlaufen. Bei verschiedenen Tests hab ich dann festgestellt, dass ohne setjmp/longjmp der alarm auch öfter funktioniert, also wenn im TimeoutHandler nur ne printf Anweisung steht.
Kann ich mir nicht vorstellen.
Kann mir irgendwer dazu nen Tipp geben? Muss man das SIGALRM Signal irgendwie rücksetzen oder vielleicht die env-Variable reseten?
Wenn Du signal verwendest musst Du jedesmal wieder den Handler registrieren. Bei sigaction kannst Du von vorneherein angeben, ob es bleiben soll.
Ausserdem ist es keine gute Idee die Variable "env" zu nennen - es gibt eine env-Variable in der libc.
Oder gibts noch ne andere möglichkeit einen Timeout für das serielle Lesen zu realisieren? Ich hab ein paar Versuche mit select() gemacht, das hat allerdings garnicht funktioniert.
select() sollte eigentlich die Lösung sein. Allerdings solltest Du dazu den Filedescriptor in den non-blocking-mode versetzen (man fcntl).
Wild im Programm herumzuspringen ist auf jeden Fall keine gute Idee - es kann zuviel dabei schiefgehen.
//open int fd=open("/dev/ttyS0",O_RDONLY); //set non-block int flag; fcntl(fd,F_GETFL,&flag); flag|=O_NONBLOCK; fcntl(fd,F_SETFL,&flag); //read while(1){ //select for read on fd int max=fd+1; fd_set rd; FD_ZERO(&rd); FD_SET(fd,&rd); //timeout: 5s 20ms struct timeval tmout; tmout.tv_sec=5; tmout.tv_usec=20000; //select int ret=select(max,&rd,0,0,&tmout); //check whether we can read if(ret>0 && FD_ISSET(fd,&rd)){ char buf[256]; ret=read(fd,buf,sizeof(buf)); if(ret>0)dosomething(buf,ret); else if(ret<0){ //error switch(errno){ case EAGAIN: case EINTR: //hmm, hickups, ignore it break; case EIO: fprintf(stderr,"I/O errors, panic!"); exit(1); break; //... } }else{//if(ret==0) fprintf(stderr,"End of stream on serial..."); exit(0); } }else{ fprintf(stderr,"timeout!"); gotosomewhereelse(); } }
Warnung: der Code ist nicht getestet, aber sollte Dir hoffentlich zeigen, wo der Fehler lag.
Warnung 2: ich habe C++-Syntax verwendet. Viel Spass beim Umstellen.
Konrad
Hallo
Wild im Programm herumzuspringen ist auf jeden Fall keine gute Idee - es kann zuviel dabei schiefgehen.
Das waren auch meine bedenken, den Tip mit setjmp hab ich aber von nem Kollegen, den ich als ziemlich guten Programmierer ansehe.
//open int fd=open("/dev/ttyS0",O_RDONLY); //set non-block int flag; fcntl(fd,F_GETFL,&flag); flag|=O_NONBLOCK; fcntl(fd,F_SETFL,&flag); //read while(1){ //select for read on fd int max=fd+1; fd_set rd; FD_ZERO(&rd); FD_SET(fd,&rd); //timeout: 5s 20ms struct timeval tmout; tmout.tv_sec=5; tmout.tv_usec=20000; //select int ret=select(max,&rd,0,0,&tmout); //check whether we can read if(ret>0 && FD_ISSET(fd,&rd)){ char buf[256]; ret=read(fd,buf,sizeof(buf)); if(ret>0)dosomething(buf,ret); else if(ret<0){ //error switch(errno){ case EAGAIN: case EINTR: //hmm, hickups, ignore it break; case EIO: fprintf(stderr,"I/O errors, panic!"); exit(1); break; //... } }else{//if(ret==0) fprintf(stderr,"End of stream on serial..."); exit(0); } }else{ fprintf(stderr,"timeout!"); gotosomewhereelse(); } }
Perfekt, genau das hab ich gesucht. Das funktioniert auch einwandfrei. Ich hab das mit dem select() am Anfang nich so richtig verstanden, weil auch alle Beispiele nur auf die Benutzung mehrere filedescriptors eingingen.
Achso, es hilft nicht den Handler neu zu initialisieren und setjmp liefert auch immer den richtigen return-Wert, es wird ganz einfach der alarm() nicht mehr ausgelöst.
Mfg. Erisch
lug-dd@mailman.schlittermann.de