Forum: PC-Programmierung threads und read()


von Le X. (lex_91)


Lesenswert?

Hallo,
ich würde gerne unter Linux mit einem C-Programm von der seriellen 
Schnittstelle lesen.

Auf dem uC befüll ich im RX Interrupt einen Ringpuffer, von dem das 
Hauptprogramm dann lesen kann.

Am PC hab ich ja erstmal keine Interrupts. Ich würd aber gerne ähnliche 
Schnittstellen zu meinem Puffer verwenden wie vom uC gewohnt.

Deshalb frag ich mich, wie ich die Daten von der seriellen Schnittstelle 
holn kann.

read() blockiert ja bis n Zeichen empfangen wurden. fread() ist bereits 
gepuffert (und returned sofort?).

Ich überleg nun, in einem separaten pthread mit read() in einer 
Endlosschleife zu pollen. Immer nur auf 1 Zeichen warten und dieses dann 
in den Ringpuffer legen. Mein "Hauptprogramm" könnte ich dann so 
konzipieren wie vom uC gewohnt. Ich habe einen Ringpuffer, der im 
Hintergrund von irgendwas (Thread anstatt Interrupt) befüllt wird.

Ist diese Strategie zu empfehlen?
Oder Schwachsinn und Overkill?

Vorteil: Ich hätt mal nen Grund mich mit pthreads zu beschäftigen.

Grüße, lex

von Udo S. (urschmitt)


Lesenswert?

Wenn du in deinem Hauptprogramm Teile hast die auch mal mehrere 
Millisekunden dauern können dann kannst du nicht dort auf die 
Schnittstelle pollen.
Wenn man also eine Schnittstelle hat die Zeichen nicht 
zwischenspeichert, rel. langsam ist und man nicht weiss wann genau Daten 
ankommen, dan ist es durchaus normal daß man einen eigenen Thread macht, 
der eben auf diese Schnittstelle pollt und die Daten dann in einen 
Puffer stellt.

Man muss nur beachten, daß die Zugriffe auf den Puffer zwischen Thread 
und Hauptprogramm synchronisiert sind (Wie beim µC auch, Stcichwort 
"Atomar")

Eleganter ist es natürlich wenn man bei dem Betriebssystem einen 
Schnittstellenhandler registrieren kann, den das Betriebssystem immer 
dann aufruft, wenn ein Zeichen angekommen ist bzw. wenn der Sendepuffer 
leer geworden ist.
Diese Handler übernehmen dann das Befüllen des Puffers, bzw Senden des 
nächsten Bytes aus dem Sendepuffer des Programms.

von Klaus W. (mfgkw)


Lesenswert?

le x. schrieb:
> read() blockiert ja bis n Zeichen empfangen wurden.

In der Voreinstellung ja.
Man kann den Filedeskriptor aber auch auf "non blocking" setzen.
Das sage ich nur ungern, weil ich fürchte, daß du jetzt damit in rinrt 
Endlosschleife pollst - in aller Regel ist das die schlechteste Lösung.

Bessere Alternativen wären, wie bereits erwähnt, einen eigenen Thread zu 
machen, der beim Lesen ruhig blockieren kann, oder man nimmt select().

von Le X. (lex_91)


Lesenswert?

Danke euch erstmal!
Also dieses nicht-blockierende Lesen lass ich glaub ich bleiben.

Aber wenn ich in einem separaten Thread blockierend in einer 
Endlosschleife lese, das geht in Ordnung? Wird dadurch nicht die 
CPU-Last erhöht? Das ist meine Sorge.

Wie würde die Methode mit select() funktionieren?

von Klaus W. (mfgkw)


Lesenswert?

le x. schrieb:
> Aber wenn ich in einem separaten Thread blockierend in einer
> Endlosschleife lese, das geht in Ordnung? Wird dadurch nicht die
> CPU-Last erhöht? Das ist meine Sorge.

Nein.
Der blockierte Thread braucht natürlich keine CPU-Zeit, solange nichts 
zu lesen ist, und die anderen Threads halt je nachdem, was sie machen.

>
> Wie würde die Methode mit select() funktionieren?

Dafür gibt es viele Beispiele und Dolu im Netz.

Kurz:
Man füttert ein Feld mit Filedeskriptoren, von denen man lesen möchte, 
ein weiteres mit solchen, auf die man schreiben möchte, füllt eine 
struct aus, wie lange man maximal warten möchte, und ruft select() auf.
Außerdem übergibt man noch den höchsten aller vorkommenden fd plus 1.

Sobald einer oder mehrere der fd lesen bzw. schreiben können, ohne zu 
blockieren, kehrt select() zurück, in den Feldern stehen dann die 
bereiten fds. Mit denen kann man dann read() bzw write() zu machen, ohne 
hängen zu bleiben.

select() springt schon vorher zurück, wenn ein angegebener Timeout 
abgelaufen ist, oder wenn ein Signal das Warten unterbricht.
Ggf. füllt man die Felder wieder, und ruft damit nochmal select() auf.

Zum Füllen und Abfragen der Felder gibt es Makros, die man nehmen 
sollte.

von Klaus W. (mfgkw)


Lesenswert?

PS:

Die fd, die man in die Felder schreibt und auf die man wartet, müssen 
keine Dateien sein unter Linux bzw. Unix.
Es können alle möglichen fd sein: pipes, sockets, ...

Nur unter Windows kann man damit leider nur auf sockets warten.

von Le X. (lex_91)


Lesenswert?

OK, danke dir nochmal.
Denke dass da meine Thread-Lösung sowohl elegant als auch praktisch ist 
:-)
War mir nur nicht sicher ob das "warten" im Thread rechenintensiv ist. 
Für mich ist read() ja erstmal ne Blackbox, ich weis ja nicht, WIE die 
Funktion wartet, deswegen die Nachfrage.

Noch eine Frage:
Wenn meine "Hauptschleife" grad nichts zu tun hat weil sie drauf wartet 
dass im Empfangspuffer zB ein komplettes Kommando liegt, kann ich sie 
irgendwie schlafen legen?

Ich bin mir in Sachen PC-Programmierung recht unsicher da ich einfach 
nicht weis was noch alles passiert, bzw. was ein "freundliches" Programm 
so alles tun sollte...

von Norbert (Gast)


Lesenswert?

$ man 3 usleep

NAME
     usleep − suspend execution for microsecond intervals
SYNOPSIS
     #include <unistd.h>
     int usleep(useconds_t usec);

von Rolf Magnus (Gast)


Lesenswert?

le x. schrieb:
> OK, danke dir nochmal.
> Denke dass da meine Thread-Lösung sowohl elegant als auch praktisch ist
> :-)

Da bin ich mir nicht so sicher, denn du muß dann die gelesenen Daten 
wieder zwischen den Threads austauschen, mit passender Synchronisation.

> War mir nur nicht sicher ob das "warten" im Thread rechenintensiv ist.
> Für mich ist read() ja erstmal ne Blackbox, ich weis ja nicht, WIE die
> Funktion wartet, deswegen die Nachfrage.

Im Betriebssystem-Scheduler wird der Thread als wartend markiert und 
nicht mehr ausgeführt, bis Daten ankommen.

> Noch eine Frage:
> Wenn meine "Hauptschleife" grad nichts zu tun hat weil sie drauf wartet
> dass im Empfangspuffer zB ein komplettes Kommando liegt, kann ich sie
> irgendwie schlafen legen?

Ahem, du könntest sie einfach in read() blockieren lassen, bis Daten 
ankommen.

> Ich bin mir in Sachen PC-Programmierung recht unsicher da ich einfach
> nicht weis was noch alles passiert, bzw. was ein "freundliches" Programm
> so alles tun sollte...

Norbert schrieb:
> $ man 3 usleep
>
> NAME
>      usleep − suspend execution for microsecond intervals
> SYNOPSIS
>      #include <unistd.h>
>      int usleep(useconds_t usec);

Und wenn dann im anderen Thread Daten ankommen, muß hier in usleep 
gewartet werden, bis die Wartezeit zuende ist, bevor auf die Daten 
reagiert werden kann. Nicht grad sehr elegant.

von Norbert (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Und wenn dann im anderen Thread Daten ankommen, muß hier in usleep
> gewartet werden, bis die Wartezeit zuende ist, bevor auf die Daten
> reagiert werden kann. Nicht grad sehr elegant.

Die ursprüngliche Frage lautete:

> Wenn meine "Hauptschleife" grad nichts zu tun hat weil sie drauf wartet
> dass im Empfangspuffer zB ein komplettes Kommando liegt, kann ich sie
> irgendwie schlafen legen?

Ich denke das usleep/nanosleep usw. präzise zu diesem Wunsch passen.

Man legt sie (die Hauptschleife) natürlich nicht ne viertel Stunde am 
Stück schlafen, sondern häppchenweise (mit kontinuierlichem 
nachschauen)...

Natürlich kann man mit message queues, semaphores, usw. arbeiten, aber 
der OP schrieb ja das er sich gerade erst in die PC Programmierung 
einarbeitet.

(und das macht er, wie ich finde, schon recht gut)

von Rolf Magnus (Gast)


Lesenswert?

Norbert schrieb:
> Rolf Magnus schrieb:
>> Und wenn dann im anderen Thread Daten ankommen, muß hier in usleep
>> gewartet werden, bis die Wartezeit zuende ist, bevor auf die Daten
>> reagiert werden kann. Nicht grad sehr elegant.
>
> Die ursprüngliche Frage lautete:
>
>> Wenn meine "Hauptschleife" grad nichts zu tun hat weil sie drauf wartet
>> dass im Empfangspuffer zB ein komplettes Kommando liegt, kann ich sie
>> irgendwie schlafen legen?

Nein, ursprünglich ging es darum:

le x. schrieb:
> Ich überleg nun, in einem separaten pthread mit read() in einer
> Endlosschleife zu pollen.

Das wollte er tun, damit er nicht in read() blockieren muß. Und erst 
danach wollte er wissen, wie er den Haupt-Thread schlafen legt, bis 
Daten angekommen sind. Die Kombination von beidem ist aber irgendwie 
Unsinn. Das Blockieren in read()  macht ja eigentlich schon genau das, 
also wozu es überhaupt in eine separaten Thread auslagern?  Das macht 
die Sache doch nur unnötig kompliziert.

> Ich denke das usleep/nanosleep usw. präzise zu diesem Wunsch passen.

Es wartet nicht, bis Daten da sind, sondern einfach für eine gewisse 
Zeit.

> Man legt sie (die Hauptschleife) natürlich nicht ne viertel Stunde am
> Stück schlafen, sondern häppchenweise (mit kontinuierlichem
> nachschauen)...

Diese Variante ist aber nie optimal. Einerseits wacht der Thread nicht 
sofort auf, wenn Daten da sind, sondern muß bis zum nächsten Zyklus 
warten, andererseits wacht er regelmäßig auf, owohl nix da ist. Man muß 
dann mit einem Kompromiss zwischen eigentlich unnötiger Verzögerung und 
ebenfalls unnötiger Prozessorlast leben. Je kürzer die Wartezeit, desto 
öfter wacht er auf, je länger, desto mehr Verzögerung habe ich.

> Natürlich kann man mit message queues, semaphores, usw. arbeiten, aber
> der OP schrieb ja das er sich gerade erst in die PC Programmierung
> einarbeitet.
>
> (und das macht er, wie ich finde, schon recht gut)

Er stellt auf jeden Fall die richtigen Fragen.

von Norbert (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Man muß
> dann mit einem Kompromiss zwischen eigentlich unnötiger Verzögerung und
> ebenfalls unnötiger Prozessorlast leben.

Rein theoretisch hast du natürlich Recht, aber mit weniger als einem 
einem Zehntel bis ein Prozent Prozessorlast kann man heutzutage wirklich 
gut leben.
1
$ time ./a.out 
2
proctime:   20.000ms
3
percentage: 0.067%
4
proctime:   290.000ms
5
percentage: 0.967%
6
7
real  1m3.983s
8
user  0m0.036s
9
sys  0m0.288s

Und hier der quick'n'dirty code:
1
//    kate:encoding utf8; tab-width 4; show-tabs 1; indent-width 4
2
//
3
//    gcc -Wall testcode.c
4
//    time ./a.out
5
6
#include <stdio.h>
7
#include <time.h>
8
#include <unistd.h>
9
10
volatile unsigned char the_buffer_contains_data = 0;
11
12
int main(int argc, char * argv[])
13
{
14
    int loopcount;
15
    clock_t t0, t1;
16
    double zeit;
17
    struct timespec ts;
18
19
    //-----[ 30 second loop using usleep ]-----
20
    // 3000 loops 10ms sleep
21
    //
22
    loopcount = 3000;
23
    t0 = clock();
24
    while(loopcount--)
25
    {
26
        usleep(10000); // this systems granularity: 10ms
27
        if(the_buffer_contains_data)
28
            break;
29
    }
30
    t1 = clock();
31
    zeit = (t1-t0) * 1000.0 / CLOCKS_PER_SEC;
32
    printf("proctime:   %.3fms\n", zeit);
33
    printf("percentage: %.3f%%\n", zeit / 30000.0 * 100.0);
34
35
    //-----[ 30 second loop using nanosleep ]-----
36
    // 30000 loops 1ms sleep
37
    loopcount = 30000;
38
    ts.tv_sec = 0;
39
    ts.tv_nsec = 1000000; // 1ms
40
    t0 = clock();
41
    while(loopcount--)
42
    {
43
        nanosleep(&ts, NULL);
44
        if(the_buffer_contains_data)
45
            break;
46
    }
47
    t1 = clock();
48
    zeit = (t1-t0) * 1000.0 / CLOCKS_PER_SEC;
49
    printf("proctime:   %.3fms\n", zeit);
50
    printf("percentage: %.3f%%\n", zeit / 30000.0 * 100.0);
51
52
    return 0;
53
}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.