Warum klappt der Timeout nicht (POSIX)?

  • Warum klappt der Timeout nicht (POSIX)?

    Ich versuche auf ein serielles Device über einen BSD-Pfad zuzugreifen. Das klappt soweit auch.

    Allerdings bekomme ich es nicht hin, den "read" mit einem Timeout zu versehen.
    Eigentlich dachte ich, das würde über die "options.c_cc[VMIN] /options.c_cc[VTIME] " Parameter gesteuert. Funktioniert aber nicht.
    Dadurch bleibt mir die ganze Geschichte "hängen", wenn ich auf ein Device zugreife, das nicht antwortet.

    Wenn ich den File-Descriptor auf "non-blocking" setze und beim Read einen eigenen (manuellen) Timeout einbaue klappt's zwar wie gewünscht, aber das kanns ja eigentlich nicht sein.

    Hier der beteiligte Code:

    Quellcode

    1. - (BOOL)openSerialPort
    2. {
    3. BOOL success = YES;
    4. if(fileDescriptor == -1)
    5. {
    6. @try
    7. {
    8. struct termios options;
    9. fileDescriptor = open([bsdPath UTF8String], O_RDWR | O_NOCTTY | O_NONBLOCK);
    10. if (fileDescriptor == -1)
    11. [NSException raise:@"openSerialPort" format:@"Error opening serial port %@ - %s (%d).", bsdPath, strerror(errno), errno];
    12. if (ioctl(fileDescriptor, TIOCEXCL) == -1)
    13. [NSException raise:@"openSerialPort" format:@"Error setting TIOCEXCL on %@ - %s (%d).", bsdPath, strerror(errno), errno];
    14. // fileDescriptor auf "blocking setzen"
    15. // if (fcntl(fileDescriptor, F_SETFL, 0) == -1)
    16. // [NSException raise:@"openSerialPort" format:@"Error clearing O_NONBLOCK %@ - %s (%d).", bsdPath, strerror(errno), errno];
    17. if (tcgetattr(fileDescriptor, &originalTTYAttrs) == -1)
    18. [NSException raise:@"openSerialPort" format:@"Error getting tty attributes %@ - %s (%d).", bsdPath, strerror(errno), errno];
    19. options = originalTTYAttrs;
    20. cfmakeraw(&options);
    21. options.c_cc[VMIN] = 1;
    22. options.c_cc[VTIME] = 5;
    23. cfsetspeed(&options, B38400);
    24. options.c_cflag &= ~PARENB;
    25. options.c_cflag &= ~CSTOPB;
    26. options.c_cflag &= ~CSIZE;
    27. options.c_cflag |= CS8;
    28. options.c_cflag |= CCTS_OFLOW;
    29. options.c_cflag |= CRTS_IFLOW;
    30. if (tcsetattr(fileDescriptor, TCSANOW, &options) == -1)
    31. [NSException raise:@"openSerialPort" format:@"Error setting tty attributes %@ - %s (%d).", bsdPath, strerror(errno), errno];
    32. }
    33. @catch(NSException* exception)
    34. {
    35. PSErrorLog([exception reason],nil);
    36. [self closeSerialPort];
    37. success = NO;
    38. }
    39. }
    40. return success;
    41. }
    Alles anzeigen


    Sobald ich die Zeilen 17+18 nicht auskommentiere, müsste doch eigentlich der in den Zeilen 25+26 definierte Timeout gelten, oder nicht?
    Bei mir tut er's nicht, die nachfolgende Funktion hängt dann (beim Versuch auf ein nicht reagierendes Device zuzugreifen) auf unbestimmte Zeit in Zeile 23 fest.

    Zum Testen habe ich (wenn ich oben die Zeilen 17+18 auskommentiere, der File-Descriptor also auf NONBLOCKING bleibt) einen "manuellen" Timeout eingebaut (Zeilen 14+15 bzw. 37-41). Dann funktioniert auch alles wunderbar, aber ich dachte eigentlich, dass sich da der Kernel drum kümmern würde...

    Quellcode

    1. - (PacketResponse*)runCommand:(NSData*)packet
    2. {
    3. PacketResponse* response = nil;
    4. if (packet && packet.length >= 4)
    5. {
    6. NSLog(@"Sending %@", [packet description]);
    7. ssize_t numBytesSent = write(fileDescriptor, [packet bytes], [packet length]);
    8. if (numBytesSent == -1)
    9. {
    10. PSErrorLog(@"Error writing to device - %s (%d).", strerror(errno), errno);
    11. }
    12. // Manuelles Timeout
    13. BOOL timeout = NO;
    14. NSDate* startTime = [NSDate date];
    15. NSLog(@"Waiting for response");
    16. response = [[PacketResponse alloc] init];
    17. uint8_t buffer[256];
    18. ssize_t numBytesRead=0;
    19. do
    20. {
    21. numBytesRead = read(fileDescriptor, buffer, 255);
    22. if (numBytesRead == -1)
    23. {
    24. usleep(100);
    25. }
    26. else if (numBytesRead > 0)
    27. {
    28. if([response processBytes:buffer length:numBytesRead])
    29. break; // PacketResponse complete...
    30. }
    31. else
    32. NSLog(@"Nothing read.");
    33. // Manuelles Timeout
    34. timeout = (-[startTime timeIntervalSinceNow]>1.);
    35. if(timeout)
    36. PSErrorLog(@"*** Timeout");
    37. } while (numBytesRead > 0 && !timeout);
    38. if(numBytesRead == -1)
    39. PSErrorLog(@"Error reading from printer - %s (%d).", strerror(errno), errno);
    40. }
    41. return response;
    42. }
    Alles anzeigen


    Weiß jemand was ich falsch mache?
    Bevor man jemanden kritisiert, sollte man zuerst ein paar Meilen in dessen Schuhen gehen!
    Erstens ist man dann in sicherem Abstand und zweitens hat man die Schuhe...
  • RE: Warum klappt der Timeout nicht (POSIX)?

    Mhh.. mit terminals habe ich da noch nie was gemacht, nur mit "rohen" file-descriptoren. Time-out habe ich auch immer selbst gehandhabt, ist ja auch ansich kein grosses Problem. Wenn ich keine Nonblocking FDs will, benutz ich ueblicherweise alarm(3), damit mein Prozess ein Interrupt bekommt, wenn es zulange dauert, aber Deine Loesung scheint da fast eleganter.
    Ich kann zuhause noch einmal in meinen uralten Code schauen, vielleicht faellt mir noch was dazu ein, aber soweit habe ich auch keine Idee. Aber schlecht ist Deine Loesung soweit doch erstmal gar nicht?
    Und ansonsten kannst Du ja auch eine "hoehere" Library nehmen fuer den Zugriff auf die Devices, wenn Du Dich nicht so auf die Innereien einlassen willst, weil es da immer komische Probleme gibt... ;)
    C++
  • RE: Warum klappt der Timeout nicht (POSIX)?

    Ich bin mir grad gar nicht sicher, ob Du so ohne weiteres den FD mit open() aufmachen kannst, und dann das termios zeug damit verwenden kannst. Bist Du Dir sicher, dass das ueberhaupt so geht? Evtl. werden daher die Settings ignoriert? Evtl. brauchst Du soetwas wie openpty?
    C++
  • Hmmm, keine Ahnung, aber ich habe den Code auf Basis eines ADC-Beispiel-Projekts geschrieben ("SerialPortSample"). Und dort wird das von Apple so gemacht. Also nahm ich an, dass das eigentlich prinzipiell schon gehen sollte.
    Bevor man jemanden kritisiert, sollte man zuerst ein paar Meilen in dessen Schuhen gehen!
    Erstens ist man dann in sicherem Abstand und zweitens hat man die Schuhe...
  • eigentlich ist es einfach:

    Case A: MIN > 0, TIME > 0
    In this case TIME serves as an inter-byte timer and is activated after the first byte is received. Since it is an inter-byte timer, it is reset after a byte is received. The interaction between MIN and TIME is as follows: as soon as one byte is received, the inter-byte timer is started. If MIN bytes are received before the inter-byte timer expires (remember that the timer is reset upon receipt of each byte), the read is satisfied. If the timer expires before MIN bytes are received, the characters received to that point are returned to the user. Note that if TIME expires at least one byte is returned because the timer would not have been enabled unless a byte was received.In this case (MIN > 0, TIME > 0) the read blocks until the MIN and TIME mechanisms are activated by the receipt of the first byte, or a signal is received. If data is in the buffer at the time of the read(), the result is as if data had been received immediately after the read().

    man 4 termios

    Grüsse Jürgen
  • Danke @manoh und @ManInMac, VMIN=0 löst das Problem.

    Ich hatte wohl die "falsche" Doku bei meiner Recherche im Internet erwischt.

    If VMIN is non-zero, VTIME specifies the time to wait for the first character read. If a character is read within the time given, any read will block (wait) until all VMIN characters are read. That is, once the first character is read, the serial interface driver expects to receive an entire packet of characters (VMIN bytes total). If no character is read within the time allowed, then the call to read returns 0.

    (ulisse.elettra.trieste.it/services/doc/serial/config.html)

    Das klang für mich so, dass read zurückkehrt wenn entweder VMIN Zeichen gelesen wurden ODER VTIME Wartezeit vergangen ist. Falsch gedacht.
    Der man-page Text ist da (etwas) klarer.
    Bevor man jemanden kritisiert, sollte man zuerst ein paar Meilen in dessen Schuhen gehen!
    Erstens ist man dann in sicherem Abstand und zweitens hat man die Schuhe...
  • Original von psog
    ...

    Das klang für mich so, dass read zurückkehrt wenn entweder VMIN Zeichen gelesen wurden ODER VTIME Wartezeit vergangen ist. Falsch gedacht.
    Der man-page Text ist da (etwas) klarer.


    Ich dachte am Anfang auch das VTIME unabhängig von VMIN ist, is aber ned so...

    Habs bei netzmafia nochmal nachgelesen und darum gefragt ... (bei den Codebeispielen könnte die Negation fehlen). Finde die Seite Serial Programming Guide for POSIX Operating Systems auch nicht schlecht.