read(2) auf seriellem Socket mit termios(3) - Puffergröße??

Herakles

Profifragensteller
Hallo liebe Hobbybastler!

Ich schraube gerade ein wenig an termios(3) herum und mache da interessante Entdeckungen, die mir gar wahrlich Freude bereiten. Folgenes Szenario:

Ich lese von der USB-Schnittstelle serielle Daten(btw.: unter Linux, nicht hauen! :) :eek: ). Diese kommen in Paketen zu 514 Bytes hereingesaust. Damit ich auf meinem nonblocking-socket nicht bei einem read(2) 0 Bytes lese, habe ich ein select(2) vorgeschaltet, dass nur auf dem seriellen Socket lauscht, ob Daten da sind. Wenn dem so ist, greife ich diese mit read(2) ab.

Nun besagt ja die manpage von read(2), dass "up to count bytes" gelesen werden - sprich "bis zu" und nicht "mindestens". Jetzt mache ich folgende Beobachtung:

Wenn mein select reagiert und ich zum Codeteil mit dem read(2) komme, dann bekomme ich in der Regel 496 Bytes zum Lesen. Kleine, aber feine Messungen mit gettimeofday(2) haben mir aber gezeigt, dass die 18 fehlenden Bytes (514-496 = 18) ohne nennenswerte Verzögerung verfügbar sind (zwischen zwei 514-bytes Paketen liegen etwa 25ms, zwischen einem gelesenen 496-Bytes und einem 18-Bytes Paket nur rund 1ms).

Meine Idee nun: select(2) reagiert darauf, dass die "ersten" Daten des Pakets am Socket verfügbar sind und read(2) greift diese ab. Wenn read(2) nun aber den Puffer des Sockets geleert hat, sind noch Daten auf der Leitung, die aber noch nicht im Puffer sind. Also werden die beim nächsten select(2) bemerkt und read(2) greift sie ab. So entstehen dann zwei Datenblöcke - einer mit 496 Bytes und einer mit 18 Bytes.

Was aber, wenn ich alle Daten in einem Rutsch mit read(2) abholen will? Ein usleep(1000) direkt zwischen dem select(2) und dem read(2) erweist mir den Dienst - read(2) findet die vollen 514 Bytes vor. Das erscheint mir aber doch als merkwürdiger Workaround. Hat jemand eine bessere Idee?

Viele Grüße
Herakles

EDIT: Ich meine natürlich nicht einen Socket, sondern einen Filedeskriptor, den ich mit open(2) angelegt habe...
 
Last edited:
Einen anderen Vorschlag hab ich nicht parat.

Aber eine Frage: Was ist so schlimm daran, erst die 496 Byte zu lesen und anschliessend nochmal die restlichen 18 Bytes?

Du brauchst dann zwar etwas mehr Code und musst Dir ein paar mehr Werte merken, aber in meinen Augen ist das der richtige Weg. Die Variante mit dem sleep() halte ich jedenfalls fuer vollkommen falsch. Vermutlich wird es funktionieren, aber stabil ist es sicherlich nicht.

Ich wuerde schon versuchen, das Verhalten von read() zu unterstuetzen. Damit sollten am wenigsten Probleme auftreten, falls sich die Hardware doch mal anders verhaelt oder Du die Daten doch via Socket oder aehnlich bekommen solltest.
 
Für mich klingt das als wolltest du die Semantik von blockierender I/O. Warum benutzt du nicht blockierende I/O für so etwas?
 
@xbit: Ich werde Deinen Gedanken weiterverfolgen, das ist ein guter Denkanstoß. Vielleicht ist das wirklich der stabilere Weg, ich werde heute mal genau schauen.

@Crest: Was wäre mein Vorteil, wenn ich blockierende I/O benutze? Dann fällt mein select(2) weg, aber ich würde nach wie vor eben nur die 496 Bytes empfangen. Meinem Verständnis nach sagt die blockierende I/O einfach nur "stehe mit dem read(2) so lange in der Gegend herum, bis Daten kommen", aber nicht "warte mit dem read(2) so lange, bist ein komplettes Datenpaket kommt". Irre ich da etwa? Wo also ist mein Vorteil beim blockiernden I/O? Ok, Gegenfrage erlaubt - wo ist mein Vorteil beim nonblocking-I/O? Hmmm - eine Antwort habe ich ehrlich gesagt nicht.

Grüße,
Herakles
 
Naja, bei non-blocking IO hast Du den Vorteil, dass Du nebenher auch andere Sachen machen kannst. Wenn Du von mehreren Schnittstellen lesen wollen wuerdest, koenntest Du das mit non-blocking IO relativ leicht realisieren.

Verwendest Du blocking IO, steht wenigstens Dein Thread, weil der darauf wartet, das read() mit Daten zurueckkommt.

Als "Zwischenloesung" koennte man die Variante von read() verwenden, die 0 liefert, wenn keine Daten vorhanden sind. Damit pollst Du aber die Schnittstelle, was natuerlich eine gewisse Last erzeugt. Da ist die select() Variante etwas besser.
 
Okay, ich habe es jetzt wie folgt gelöst: ich mache eine select(2) und sobald Daten kommen, nehme ich die komplett ab. Direkt nach diesem read(2) schalte ich ein zweites select(2) mit einem Timeout von 2ms. Kommen innerhalb dieser Zeit keine neuen Daten herein, sehe ich das Paket als beendet an und beginne erneut mit der Schleife. Das scheint recht stabil zu tun, was ich möchte.

Neues Problem: ich möchte nun gern auf der seriellen Schnittstelle schreiben. Das Einstellen der Baudrate beim Lesen war kein Problem, jetzt habe ich aber den Eindruck, dass die Baudrate für's Schreiben falsch eingestellt ist.

Wenn ich Daten auf die serielle schreibe, dann kann ich mit diesme Code überprüfen, wieviel Bytes im Puffer liegen:

Code:
    ioctl(fd, FIONREAD, &bytes);
printf("im puffer: %d\n",bytes);
	/*if( (ret=tcdrain( fd )) != 0 ) {
		printf("%s:%u error on tcdrain (%s), error-code: %d\n", __FILE__, __LINE__, strerror(errno), ret);
		return -1;
	}*/
	if( (ret=write( fd, buffer, buffersize)) <= 0 ) {
		printf("%s:%u error writing to serial line (%s), error-code: %d\n", __FILE__, __LINE__, strerror(errno), errno);
		return -1;
	}

Wie man sieht, habe ich noch ein tcdrain mit im Code, was aber auskommentiert ist - mal schauen, ob ich das nochmal brauche. So, ich schreibe nun in Intervallen von etwa 20ms jeweils an die 500 Bytes auf die serielle Schnittstelle. Das Unschöne dabei ist dieser output:

im puffer: 1
im puffer: 497
im puffer: 993
im puffer: 1489
im puffer: 1985

Der Puffer läuft also voll und kommt gar nicht mit dem "leerarbeiten" nach. Meine Folgerung ist daher, dass die Schnittstelle für's Schreiben offenbar zu langsam läuft.

Das verwunderliche dabei ist, dass die gleichen Einstellungen beim Lesen offenbar funktionieren (ich bekomme genau die Daten, die ich erwarte), sobald ich aber schreibe, läuft mein Puffer voll.

Hat jemand eine Idee, was ich falsch mache?

Grüße
Herakles
 
Okay, also die Lösung sieht wie folgt aus:

Code:
	if( (ret=write( fd, buffer, buffersize)) <= 0 ) {
		printf("%s:%u error writing to serial line (%s), error-code: %d\n", __FILE__, __LINE__, strerror(errno), errno);
		return -1;
	}
         tcflush( fd, TCIOFLUSH );
Das tcflush bewirkt, dass die Daten tatsächlich geschrieben werden. Merkwürdig daran ist allerdings die Definition von tcflush. Intensives suchen im Internet brachte mich immer wieder zu Darstellungen wie dieser:

Upon successful completion, tcflush() discards any data written to the specified file descriptor but that has not been transmitted, or data that has been received but not read, depending upon the value of queue_selector.

Demzufolge wirft tcflush alle Daten weg. Das geschieht jedoch gar nicht - im Gegenteil! Die Daten werden nur durch tcflush überhaupt erst an die serielle Schnittstelle weitergereicht. Merkwürdig! Was verstehe ich denn da falsch?

Im Sourcecode habe ich auch nur gefunden, dass es sich da wohl um Daten in einer linked list handelt. Hinter tcflush steckt der ioctl TCFLSH. Hier werden einfach nur die Pointer weitergeschoben auf das nächste Objekt der Liste und das gewünschte Objekt wird mit free gelöscht...

Also, es funktioniert ja jetzt. Aber wieso das so ist - entgegen jedweder Dokumentation - würde mich schon interessieren. Weiß jemand Rat?

Danke,
Herakles
 
Back
Top