Hallo zusammen!!!!
Ich habe eine kurze Verständnisfrage.
Prämisse: an meinem Linux-Rechner (Raspberry) habe ich einen Sensor
angeschlossen, aus dem ich Daten über UART problemlos verschicken und
empfangen kann. Das von mir geschriebene C Programm ist relativ
einfach gestaltet und dank solcher Beiträge (wie diesem hier:
http://stackoverflow.com/a/15455672/2302911) kann man Interrupt relativ
schnell aktivieren und benutzen.
Dadurch muss ich den UART-Port nicht ständig testen (duch polling). Wird
ein Zeichen empfangen, dann wird ein Interrupt ausgelöst. Bisher alles
gut...
Nun habe ich dennoch das folgende Problem => ich habe angefangen, mein
Programm in C++ umzuschreiben, weil ich gerne eine Klasse möchte, aus
der ich UART-Objekte ableiten kann.
Also:
1
#include"klass.h"
2
3
SERIALclassUart1;
4
5
....
Nun stehe ich aber vor einem großen Dilemma. Wie kann ich die Methoden
in meiner Klasse bei Auslösen eines Interrupts abrufen? Dies ist
eigentlich nicht machbar...selbst wenn die Methoden als public
deklariert werden. Der einzige Weg ist - meiner Meinung nach - die
Methode als static zu deklarieren. Dadurch kann ich beim Eintreten eines
Ereignisses die statische Methode aufrufen.
Ist der obige Lösungsweg die einzige Möglichkeit?
Wie kann ich mein Problem anderweitig lösen?
Gruß
Dafür würde ich mit Threads abreiten und nicht mit Signalen. Das Passt
einfach nicht zur OOP Programmierung.
Die Klasse SERIALclass macht intern einfach einen Threads auf und
arbeitet dort blockierend schon ist das Problem gelöst.
Du kannst unter Linux keine Interrupts nutzen. Das was in dem Artikel
beschrieben wird, sind Unix-Signale. Diese sind relativ problematisch,
da sie Interrupts nicht unähnlich, das Programm an irgendeiner Stelle
unterbrechen können. Für eine saubere OOP-basierte Behandlung von
Serial-Ports ist die Verwendung einer main()-Schleife z.B. mit select()
oder epoll() sinnvoller:
Du machst eine extra Hauptschleifenklasse (z.B. "MainLoop"). Du
erstellst eine weitere Klasse "Port", von der für jeden offenen
Serial-Port oder z.B. Sockets eine Instanz angelegt wird. Jede
Port-Instanz wird der einzigen MainLoop-Instanz bekannt gemacht. Wenn
die Anwendung nichts zu tun hat, ruft sie eine "run" Funktion in
MainLoop auf, welche per select() oder epoll() darauf wartet, dass Daten
angekommen sind. Dann ruft sie Callbacks in den jeweiligen
Port-Instanzen auf, welche dann wiederum Callbacks im Usercode aufrufen,
der dann auf angekommene Daten reagieren kann. So kannst du asynchron
sauber sogar mehrere Ports ohne Polling abhandeln.
Solltest du tatsächlich nur genau 1 Port brauchen, und auch sonst nicht
auf irgendwelche Eingaben warten, reicht es per read() auf Daten zu
warten. Das sieht dann aus wie Polling, aber da die Funktion blockiert
bis Daten ankommen machts nichts.
Seeehrrrr geile Lösung.
Allerdings mir ist es noch nicht so ganz klar: mit dem Thread kann ich
die Peripherie UART blockieren. Wie kann ich die richtige Methode
aufrufen, wenn der Interrupt ausgelöst wird?
Peter II schrieb:> Dafür würde ich mit Threads abreiten und nicht mit Signalen. Das Passt> einfach nicht zur OOP Programmierung.>> Die Klasse SERIALclass macht intern einfach einen Threads auf und> arbeitet dort blockierend schon ist das Problem gelöst.
Und wo ist da der Vorteil gegenüber einem Signal? Mit den Thread macht
man doch das gleiche, nur umständlicher.
Rolf M. schrieb:> Und wo ist da der Vorteil gegenüber einem Signal? Mit den Thread macht> man doch das gleiche, nur umständlicher.
Bei Thread(s) und Aufruf der blockierenden Funktion weiß man, welcher
der Ports betroffen ist. Somit erübrigt sich das Ausgangsproblem.
Außerdem kann man mit den üblichen Mitteln zur Thread-Synchronisation
die Zugriffe auf Variablen außerhalb des Threads sichern. Bei Signalen
geht das nicht, denn die kommen einfach irgendwann, und man hat keine
Ahnung wo das Programm gerade ist und ob irgendeine der Variablen einen
gültigen Inhalt hat.
Peter II schrieb:> Dafür würde ich mit Threads abreiten und nicht mit Signalen. Das Passt> einfach nicht zur OOP Programmierung.
So ein Unsinn. Es widerspricht in keinster Weise dem OOP-Ansatz, mit
Ereignissen zu arbeiten, was man allein schon daran sieht, dass
praktisch alle OOP-Sprachen eben solche im Sprachkonzept vorsehen.
Und auch ein Signal ist letztlich nur ein (asynchrones) Ereignis. Es
muss also ggf. einfach nur an geeigneter Stelle synchronisiert werden.
Und wenn ein Signal für mehrere Instanzen einer Klasse relevant ist,
dann muß man halt den Signalhandler statisch implementieren und der muß
in der Lage sein, die passende Instanz herauszufinden um dann darin den
zuständigen Eventhandler bzw. die zuständige Callback-Methode
aufzurufen.
Sowas zu implementieren gehört zu den grundlegenden und absolut
erforderlichen Fähigkeiten der OOP-Programmierung.
Nur weil du es nicht beherrschst, kannst du doch nicht behaupten, dass
es nicht zur OOP-Programmierung passt. Es passt bloß nicht zum Stand
deiner diesbezüglichen Fähigkeiten, das ist alles.
c-hater schrieb:> Und wenn ein Signal für mehrere Instanzen einer Klasse relevant ist,> dann muß man halt den Signalhandler statisch implementieren und der muß> in der Lage sein, die passende Instanz herauszufinden um dann darin den> zuständigen Eventhandler bzw. die zuständige Callback-Methode> aufzurufen.
genau das statisch implementieren finde ich nicht OOP konform. Damit ist
ein Objekt nicht mehr eigenständig.
> Nur weil du es nicht beherrschst, kannst du doch nicht behaupten, dass> es nicht zur OOP-Programmierung passt. Es passt bloß nicht zum Stand> deiner diesbezüglichen Fähigkeiten, das ist alles.
was soll immer diesen persönlichen Angriffe? Woher willst du wissen das
ich das nicht kann, nur weil ich es persönlich anders mache? Heute hat
jede CPU mehre Kerne und Threads gehören genauso zu Standard der
Programmierung.
Und wenn man mal zu anderes Sprachen schaut (Java, .net) das wird es
genauso gelöst, also scheinst es doch auch sinn zu machen.
Ich sehe auch nicht wirklich warum Signale nicht zur OOP passen sollten.
Richtig ist, dass das C/UNIX Signalhandling "fehleranfällig" ist. Man
sollte schon ziemlich genau wissen was man da macht, ob in C oder in
C++.
Ich verzichte gern darauf, wenn möglich.
Ansonsten, ist (m)ein typischer Ansatz in einer "main loop" (dispatcher)
auf Ereignisse (FD,Signals,timer queue,whatever) zu warten (poll/select
mit ungeblockten Signalen) und die Events dann zu verarbeiten (während
der Verarbeitung werden die Signale geblockt). Das geht sehr schön mit
C++ wo man sich eine Dispatcher class schreibt, an der sich Clients für
ausgewählte Events registrieren können.
Nichts gegen Threads. Kann man sicher auch dafür benutzen.
Synchronisierung und Debugging machen das Leben aber nicht unbedingt
einfacher. ;-)
S. J. schrieb:> Nichts gegen Threads. Kann man sicher auch dafür benutzen.> Synchronisierung und Debugging machen das Leben aber nicht unbedingt> einfacher. ;-)
ich finde es sogar einfacher, wenn alles in Main-Loop verarbeitet wird,
muss man ständig aufpassen NIE zu blockieren. Mal schnell eine Datenbank
abfragen und schon gehen Daten vom UART verloren. Obwohl sie nichts
miteinander zu tun haben.
Alles klar. Bezog sich also nicht auf deine Behauptung, dass Signale
nicht zur OOP passen (was der Ausgangspunkt für mein Post war), und auch
nicht darauf, dass man bei Verwendung von Threads die Synchronisierung
bedenken muss und das Debuggen schwieriger wird (der zitierte Text);
sondern dass man das Problem des TS "einfacher" mit Threads löst als mit
einer Main Loop. Falls der TS Interesse hat, kann man diese Behauptung
sicherlich diskutieren. ;-)
Um einmal auf die ursprüngliche Frage zurück zu kommen:
Selbstverständlich kannst Du aus einem Signalhandler heraus die Methoden
einer Klasse aufrufen. Das Problem ist nur, dass Du dazu eine Referenz
oder einen Pointer auf die Instanz der Klasse benötigst. Da Du dem
Signalhandler aber keine Parameter übergeben kannst, bleibt Dir nur die
Möglichkeit einer globalen Variable, mit allen Nachteilen die das hat.
Hinzu kommt, dass Du Dir mit Signalen echte Nebenläufigkeit in Dein
Programm holst. Wie andere bereits beschrieben haben zieht das einen
ganzen Rattenschwanz weiterer Probleme nach sich. Insbesondere kann ein
Signal Dein Programm an jeder beliebigen Stelle unterbrechen. Du wirst
also entweder die Methoden, die auch aus dem Signalhandler heraus
angesprochen werden können, atomar machen oder dafür sorgen müssen, dass
beim Aufruf dieser Methoden aus dem Hauptprogramm heraus die
betreffenden Signale geblockt werden. (Das entspricht dann etwa einem
Disable Interrupt.)
Die bereits vorgeschlagene main-loop mit select hat vor allem den
Vorteil, diese Nebenläufigkeit zu vermeiden. Die Handler der main-loop
werden streng sequentiell aufgerufen. Außerdem kannst Du ihnen beliebige
Parameter mitgeben und vermeidest so globale Variablen.
Für den Fall, dass es unbedingt Signale sein sollen (oder müssen)
schlagen sowohl Stroustrup als auch Alexandrescu die Verwendung von
Proxy-Klassen vor. Das sind Klassen, in denen man Konstruktor und
Destruktor nutzt, um bestimmte Aktionen auszuführen. In Deinem Fall böte
sich an, eine Handlerklasse zu schreiben, deren Instanz statisch und
global gemeinsam mit dem Signalhandlers in einer eigenen Source-Datei
definiert ist. Für den Zugriff aus dem Hauptprogramm gibt es dann eine
Funktion, die einen Pointer-Proxy liefert. Der Konstruktor dieses
Pointer-Proxys sperrt dann das (oder die) relevante(n) Signal(e), der
Destruktor gibt es (oder sie) wieder frei. Also etwa so (ungetestet):
my_signal_handler.h:
Im Hauptprogramm verhält sich ein Pointer-Proxy dann wie ein normaler
Pointer.
Aber wie gesagt, ich würde eine main-loop mit select in diesem Fall
definitiv vorziehen.
Übrigens, Threads wären in diesem Fall wohl mindestens ein absoluter
Overkill, wenn nicht sogar kontraproduktiv. Threads wurden erfunden, um
explizit Nebenläufigkeit in Programme einzuführen und bieten daher
sicher bessere Möglichkeiten diese zu synchronisieren. Du möchtest aber
(trust me) eine bereits vorhandene Nebenläufigkeit los werden oder
zumindest so kapseln, dass sie Dir nicht mehr auf die Füße fällt. Dafür
ist select genau das Mittel der Wahl.
Das meiste ist schon gesagt.
SIGIO ist (nicht ohne Grund) ein Stiefkind der Unix-API. Es korrekt zu
benutzen ist sehr schwierig (sowohl das Beispiel auf Stackoverflow als
auch das aus der Serial-Programming-HOWTO ist fehlerhaft) und wirkliche
Vorteile gegenüber select/poll/epoll muss man mit der Lupe suchen. Und
auf die Idee, dafür einen extra Thread zu erstellen, käme ein
Unix-Programmierer nie!
Nimm das übliche select oder poll und dein Problem mit statischen
Methoden verschwindet von selbst.
A. H. schrieb:> Die bereits vorgeschlagene main-loop mit select hat vor allem den> Vorteil, diese Nebenläufigkeit zu vermeiden. Die Handler der main-loop> werden streng sequentiell aufgerufen. Außerdem kannst Du ihnen beliebige> Parameter mitgeben und vermeidest so globale Variablen.
Vielen Dank an allen für Eure Antworten.
Durch das Lesen habe ich dennoch den Eindruck, dass ich - vielleicht -
mein Problem nicht ganz deutlich geschildert habe. Oder verstehe ich
Euch nicht ganz richtig.
Jedenfalls:
Mein Programm sieht momentan folgendermaßen aus:
[c]
int main( int argc, char **argv )
{
SerialClass UART1;
UART1.init(<serial_port>, <baud_rate> );
while(1)
{
if(UART1.availableSerial() > 0)
{
unsigned char *data = UART1.readSingleCharSerial();
}
usleep( 500000 ); // Dieser wird durch einen richtigen Timer
ersetzt. Hier nur ein Beispiel.
}
}
[\c]
Das ist ein - sogenannte - minimalbeispiel.
Ich hätte nämlich kein Problem, bei jedem Loop zu prüfen, ob neue Daten
über UART empfangen wurden oder nicht. Allerdings was mich stört ist,
dass ich keine "beliebige" Funktion nun in dem Loop aufrufen kann.
Warum? Weil ich nicht wissen kann, wie lange ich die while-Schleife
zeitlich ausdehnen kann. Ich möchte nicht, dass Zeichen sich in dem
seriellen Puffer überschreiben. Außerdem weiß ich nicht mit welcher
Frequenz Daten per UART empfangen werden. Der Baudrate ist konstant,
dennoch können Strings mit mehr oder wenigen Zeichnen empfangen werden.
Ihr sprecht oft von main-loop und epool() und pool(). Ich muss ehrlich
sein und sagen, ich habe nicht ganz verstanden, wie ihr es meint. Mein
Beispiel oben ist, meiner M. nach, die Implementierung einer
poll()-Funktion. Ist genau das, was ihr auch meint?
Fall ja, ist es mir immer noch nicht klar, wie ich meine Daten asynchron
empfangen kann. Zwar kann ich eine derartige Funktion in meiner Klasse
erstellen, aber ich werden nie wissen, mit welcher max. Frequenz der
main-loop laufen darf.
> Für den Fall, dass es unbedingt Signale sein sollen (oder müssen)> schlagen sowohl Stroustrup als auch Alexandrescu die Verwendung von> Proxy-Klassen vor.
Sehr interessantes Thema. Ich kannte es nicht. Es sieht aber
komplizierter als das, was ich vorhatte.
> Aber wie gesagt, ich würde eine main-loop mit select in diesem Fall> definitiv vorziehen.
Falls ich einen Denkfehler begehe, könnt ihr mir bitte ein
minimal-Beispiel zeigen? Das wäre für mich sehr hilfreich.
> Du möchtest aber (trust me) eine bereits vorhandene Nebenläufigkeit los werden
oder
> zumindest so kapseln, dass sie Dir nicht mehr auf die Füße fällt. Dafür> ist select genau das Mittel der Wahl.
Richtig.
Daher...bevor ich mit dem Programm loslege, möchte ich ein paar
Programmier-Technicken mehr lernen.
Gruß
Dave Anadyr schrieb:> Warum? Weil ich nicht wissen kann, wie lange ich die while-Schleife> zeitlich ausdehnen kann. Ich möchte nicht, dass Zeichen sich in dem> seriellen Puffer überschreiben.
genau das ist das große Problem wenn man mit select/poll/epoll arbeitet.
Aus dem Grund von mir der Vorschlag mit den Threads. Da ist ein Thread
nur für die Serielle Schnittstelle vorhanden, damit gehen keine Daten
verloren, weil es unabhängig davon ist was in der Main gemacht wird.
Hallo Dave,
grundsätzlich sieht die Verarbeitung von Events folgendermaßen aus...
while (true)
* wait for event (e.g poll)
* block signals
* identify event (what signal, which fd, timeout?)
* process event
* unblock signals
Jetzt kann es passieren, dass ein neues Ereignis während der
Verarbeitung des vorherigen Ereignisses eintritt. Meist ist dies kein
Problem, weil die Daten, die mit dem Ereignis zusammenhängen, gepuffert
werden. Sie liegen auch noch beim nächsten Schleifendurchlauf vor.
Die wichtige Frage ist somit: Was genau benutzt du um die UART
auszulesen?
Ich kenn' nicht den Treiber und habe auch noch nix mit UART gemacht;
tendenziell würde ich aber vermuten, dass der Puffer dort mehr als
ausreichend ist. De es sich wohl um ein slow device handelt, sollte eine
"normale" Ereignisbehandlung (wie oben beschrieben) keine Probleme haben
(natürlich muss die Verarbeitungzeit im Durchschnitt unter der Anzahl
der Ereignisse je Zeiteinheit liegen). Vielleicht kennt sich jemand hier
im Detail mit UART aus und kann konkrete Zahlenwerte nennen.
Dave Anadyr schrieb:> Falls ich einen Denkfehler begehe, könnt ihr mir bitte ein> minimal-Beispiel zeigen? Das wäre für mich sehr hilfreich.
Tippe doch einfach mal man select in Deine Unix-Shell oder Deinen
Browser. Viele select (2) man-pages enthalten bereits ein
Programmbeispiel. Auch die englischsprachige Wikipedia hat eine Seite zu
select mit einem ausführlichen Beispiel.
Es gibt sicher auch etliche Online-Tutorial.
Ansonsten wird ein gutes Buch über Unix-Systemprogrammierung helfen.
ISBN 0131411543 oder ISBN 013937681X zum Beispiel wären ein guter
Einstieg. Die gibt es beide auch in deutscher Übersetzung und ganz
sicher auch in der Bibliothek Deines Vertrauens.
Dave Anadyr schrieb:> Sehr interessantes Thema. Ich kannte es nicht. Es sieht aber> komplizierter als das, was ich vorhatte.
Tröste Dich, das Leben ist meistens komplizierter als das, was man
vorhatte :-)
S. J. schrieb:> Ich kenn' nicht den Treiber und habe auch noch nix mit UART gemacht;> tendenziell würde ich aber vermuten, dass der Puffer dort mehr als> ausreichend ist.
falsch, die Puffer sind meist recht klein. Und wenn man dort Daten recht
schnell überträgt, darf man sich keine großen pausen erlauben.
Peter II schrieb:> S. J. schrieb:> Ich kenn' nicht den Treiber und habe auch noch nix mit UART gemacht;> tendenziell würde ich aber vermuten, dass der Puffer dort mehr als> ausreichend ist.>> falsch, die Puffer sind meist recht klein. Und wenn man dort Daten recht> schnell überträgt, darf man sich keine großen pausen erlauben.
Dann ist der Treiber schlecht programmiert. Das Betriebssystem hat ja
mehr als genug Speicher dafür zur Verfügung.
Rolf Magnus schrieb:> Dann ist der Treiber schlecht programmiert. Das Betriebssystem hat ja> mehr als genug Speicher dafür zur Verfügung.
nur ist es nicht die Aufgabe von BS Fehler in der Anwendungssoftware
auszugleichen.
Peter II schrieb:> S. J. schrieb:>> Ich kenn' nicht den Treiber und habe auch noch nix mit UART gemacht;>> tendenziell würde ich aber vermuten, dass der Puffer dort mehr als>> ausreichend ist.>> falsch, die Puffer sind meist recht klein. Und wenn man dort Daten recht> schnell überträgt, darf man sich keine großen pausen erlauben.
Tatsächlich? Quelle? Was heißt eigentlich schnell. Also UART und RPi.
Jo, das kann kein RPi schaffen, braucht man wahrscheinlich 'nen
Supercomputer. ;-)
Natürlich ist die durchschnittliche Processing Time eines Events von
Bedeutung. Wenn die dann aber tatsächlich so hoch ist, dass es einen
Backlog gibt, dann hilft auch "deine" Thread Lösung nix. Ich bin
übrigens ein großer Fan von Threads, allerdings ein noch größerer Fan
von Verhältnismäßigkeit. Guck Dir nochmal an was der TS machen will.
Und jetzt OT: Du programmierst schon, oder? Ich sehe von dir hier im
Forum häufig nur (imho sinnlose) Eskalationen. Schön hier im Thread zu
sehen: (1) Signale passen nicht zur OOP. (2) Beste Lösung sind Threads.
(3) Puffer sind zu klein. Ist alles grundsätzlich ok, wenn man es denn
zur Diskussion stellt. Aber immer nur weiter eskalieren... Habe ich
keinen Bock drauf. Darum bin ich raus aus dem Thread. /OT
Peter II schrieb:> Rolf Magnus schrieb:>> Dann ist der Treiber schlecht programmiert. Das Betriebssystem hat ja>> mehr als genug Speicher dafür zur Verfügung.>> nur ist es nicht die Aufgabe von BS Fehler in der Anwendungssoftware> auszugleichen.
Im Fall "Treiber schlecht programmiert" würde es sich nicht um einen
Fehler in der Anwendungssoftware, sondern um einen in der
Treibersoftware handeln.
Dave Anadyr schrieb:
> Ich hätte nämlich kein Problem, bei jedem Loop zu prüfen, ob neue Daten> über UART empfangen wurden oder nicht. Allerdings was mich stört ist,> dass ich keine "beliebige" Funktion nun in dem Loop aufrufen kann.> Warum? Weil ich nicht wissen kann, wie lange ich die while-Schleife> zeitlich ausdehnen kann. Ich möchte nicht, dass Zeichen sich in dem> seriellen Puffer überschreiben.
Dir ist aber schon klar, dass das Kernel bereits einen mehrere Kilobyte
großen Empfangsbuffer bereitstellt? Hast du überhaupt schon mal das
triviale blocking-Read ausprobiert und da Überläufe bemerkt? Für die
meisten Programme reicht der Kernelbuffer aus. Solltest du doch
zwischen zwei reads eine 5-Minuten Kaffeepause einlegen wollen, hilft eh
kein Buffer (jeder Buffer läuft irgendwann über) - dann benutzt man
Flusskontrolle (bei UARTs RTS/CTS oder XON/XOFF) um den Sender zu
bremsen. Dann darfst du in der Loop auch stundenlang rumbummeln ohne
Daten zu verlieren.
Bei der ganzen Diskussion um SIGIO/select/poll (nicht pool btw ;-)) geht
es darum, effizient mehrere Geräte gleichzeitig bedienen zu können. Das
scheint bei dir (nach dem Minimalbeispiel) aber gar nicht das Thema zu
sein.
Mit SIGIO könnte man theoretisch den Eingangsbuffer weiter vergrößern
ohne die "beliebige Funktion" ändern zu müssen. Aber, auch der Buffer
läuft irgendwann über (vergrößern geht im Signalhandler nicht), die
Implementation ist nicht trivial und es ist eine gute Methode,
mangelhafte EINTR-Behandlung in "beliebige Funktion" zu finden ;-)
>> Ein Konstruktor ist zur Initialisierung da. Warum nicht:>>
1
>SerialClassUART1(<serial_port>,<baud_rate>);
2
>
>> ?
Richtig.
Und es ist auch so,wie du es beschrieben hast. Ich wollte durch init()
die Struktur des Codes für alle klar machen.
Die Gliederung macht doch in der Praxis keinen Sinn.
asdfasd schrieb:> Das> scheint bei dir (nach dem Minimalbeispiel) aber gar nicht das Thema zu> sein.
Deswegen habe ich das Minimalbeispiel gepostet. Irgendwie habe ich
gemerkt, dass die Diskussion völlig anders läuft als erwartet. Die
vorgeschlagenen Lösungen sind zwar sehr interessant, aber letztendlich
geht es eher darum, die Daten asynchron zu empfangen. Polling finde ich
sehr ineffizient.
Hätte ich das gleiche Programm für PICs oder Atmel geschrieben, dann
hätte ich die Interrupts verwendet.
Gruß
S. J. schrieb:> Tatsächlich? Quelle? Was heißt eigentlich schnell. Also UART und RPi.> Jo, das kann kein RPi schaffen, braucht man wahrscheinlich 'nen> Supercomputer. ;-)
nein, das hat auch niemand behauptet. Es gibt auch Funktionen die lange
brauchen ohne CPU zu nutzen. (z.b. Namensauflösung über DNS)
> Natürlich ist die durchschnittliche Processing Time eines Events von> Bedeutung. Wenn die dann aber tatsächlich so hoch ist, dass es einen> Backlog gibt, dann hilft auch "deine" Thread Lösung nix.
darum geht es doch überhaupt nicht. Er will im LOOP beliebige Funktionen
aufrufen ohne zu wissen wie lange sie laufen und wenn es nur ein
Sleep(1000) ist, schon läuft der Puffer über.
> Ich bin> übrigens ein großer Fan von Threads, allerdings ein noch größerer Fan> von Verhältnismäßigkeit.
merkt man.
> Guck Dir nochmal an was der TS machen will.
er will beliebige Funktion aufrufen ohne angst haben das sein Puffer
überläuft.
> Und jetzt OT: Du programmierst schon, oder? Ich sehe von dir hier im> Forum häufig nur (imho sinnlose) Eskalationen. Schön hier im Thread zu> sehen: (1) Signale passen nicht zur OOP. (2) Beste Lösung sind Threads.> (3) Puffer sind zu klein. Ist alles grundsätzlich ok, wenn man es denn> zur Diskussion stellt. Aber immer nur weiter eskalieren... Habe ich> keinen Bock drauf. Darum bin ich raus aus dem Thread. /OT
Ich fand die Diskussion recht sachlich, und stehe auch dazu das ich
Signale und OOP nicht richtig zusammenpassen. Ein Signal ist erst mal
nur eine Funktion ohne Kontext. Damit kann man kein Objekt direkt
ansprechen.
asdfasd schrieb:> Dir ist aber schon klar, dass das Kernel bereits einen mehrere Kilobyte> großen Empfangsbuffer bereitstellt?https://www.raspberrypi.org/forums/viewtopic.php?f=44&t=61955
gerade mal 4k. Bei 1Mbaud sind das nur 40ms. Wenn man da eine Datenbank
abfragen will, oder Daten per http versenden will wird das verdammt eng.
Dave A. schrieb:> Deswegen habe ich das Minimalbeispiel gepostet. Irgendwie habe ich> gemerkt, dass die Diskussion völlig anders läuft als erwartet.
Das könnte daran liegen, dass aus Deinem Minimalbeispiel leider nicht
hervor geht, was Du mit den empfangen Daten tun willst, was Deine
„beliebige Funktion“ tun soll und wie beides zusammen hängt.
Du schreibst aber, dass Du Daten senden und empfangen möchtest.
Unterstellen wir mal, dass die zu sendenden Daten nicht gerade durch
einen Zufallszahlengenerator oder ähnliches erzeugt werden sollen, dann
müssen sie ja irgendwo her kommen. Nehmen wir ferner mal an, dass Du
dafür nicht unbedingt Mittel und Methoden der fortgeschrittenen
Interprozesskommunikation verwenden möchtest (also Signale, Messages,
Semaphore, Shared Memory oder ähnliches), dann wirst Du dafür wohl einen
Filedescriptor benötigen, womit Du insgesamt schon mal zwei hast und wir
wieder bei poll oder select sind.
Sollte Deine „beliebige Funktion“ so beliebig sein, dass sie mit den
empfangen Daten gar nichts zu tun hat, stellt sich natürlich zunächst
einmal die Frage, warum Du überhaupt beide Funktionen in ein Programm
packen möchtest. Wahrscheinlicher ist aber, dass sie nur in dem Sinne
„beliebig“ ist, dass sie sich um den zu sendenden Datenstrom kümmern
soll. Lang anhaltende Berechnungen sind in solchen Fällen eher
ungewöhnlich. Wahrscheinlicher ist, dass die Laufzeit Deiner „beliebige
Funktion“ nur deshalb unvorhersehbar ist, weil sie auf ein externes
Event wartet. Damit wären wir wieder bei der bereits mehrfach erwähnten
main(-event)-loop.
Du kannst das Problem durchaus asynchron lösen, siehe oben. In manchen
Fällen mag das sogar sinnvoll sein. Das hat aber seine Tücken. Das
Betriebssystem bietet Dir Werkzeuge, diese Tücken zu umgehen und Du bist
in der Regel gut beraten, diese Werkzeuge zu nutzen.
Ob das in Deinem Fall tatsächlich so ist kann man an Hand Deines
Beispiels leider nicht abschließend beurteilen.
A. H. schrieb:> Das könnte daran liegen, dass aus Deinem Minimalbeispiel leider nicht> hervor geht, was Du mit den empfangen Daten tun willst, was Deine> „beliebige Funktion“ tun soll und wie beides zusammen hängt.
Leute...ruhig...wieso diese Aufregung?
Ich habe mehrere Sensoren an einem Arduino angeschlossen.
Diese werden eingelesen, in einer Nachricht verpackt und dem Rpi per
UART übertragen. Allerdings werden die Sensoren mit verschiedenen
Taktraten abgefragt. Eine Vereinheitlichung scheint zur Zeit nicht
möglich.
Die Daten werden dann verarbeitet (Rpi). Der Arduino dient nur dazu, die
Sensorenausgaben in einer Nachricht (Protokoll) zusammenzufügen. Jede
Sensorausgabe hat eine entsprechende msgid - also ihre eigene Nachricht
-.
Das ist der erste Schritt.
Es funktioniert alles wunderbar, wenn ich es in C teste.
Ich habe alles schon am Laufen und bisher gab es gar kein Problem. Alles
einfach und trivial.
Das Problem - wie ich in meinem ersten Post geschrieben habe - ist, dass
ich mein Programm nun in C++ umschreiben will (soll).
Da aber ich gerne eine Klasse für die serielle Kommunikation hätte, habe
ich das Problem, dass ich nicht weiß, wie ich Daten asynchron empfangen
kann. WEIL die OOP eine ganz andere Softwarestruktur als C erfordert.
Auf dem Rpi werden wohl andere Programme für die Datenverarbeitung
implementiert. Die existieren nämlich noch nicht.
Irgendjemand hatte die boost:asio hier erwähnt.
Vielleicht ist diese die bessere Lösung.
Dave A. schrieb:> Polling finde ich sehr ineffizient.
poll() ist kein Polling. (Ja, toller Name ...)
> Hätte ich das gleiche Programm für PICs oder Atmel geschrieben, dann> hätte ich die Interrupts verwendet.
Vereinfacht gesagt: poll() wartet (ohne CPU-Belastung), bis ein
Interrupt ankommt und dadurch mindestens ein Byte lesbar wird. Das ist
letztendlich genau das gleiche (aber mit einer standardisierten API,
mehreren Interrupt-Quellen, Timeout etc.).
Dave A. schrieb:> Das Problem - wie ich in meinem ersten Post geschrieben habe - ist, dass> ich mein Programm nun in C++ umschreiben will (soll).
C ist (fast) vollständig eine Untermenge von C++. Folglich ist (fast)
jedes C Programm auch ein gültiges C++ Programm. Du versuchst also ein
Problem zu lösen, das es gar nicht gibt.
Dave A. schrieb:> Da aber ich gerne eine Klasse für die serielle Kommunikation hätte, habe> ich das Problem, dass ich nicht weiß, wie ich Daten asynchron empfangen> kann.
Es spricht ja nichts dagegen, Klassen zu verwenden. Klassen bieten in
vielen Situationen viele Vorteile. Aber eben nicht in allen. Wenn Du uns
nicht mehr über Deinen Code sagen willst – was Dein gutes Recht ist –
dann schau ihn Dir an und entscheide, wo er durch Klassen einfacher und
sicherer wird. Daraus ergibt sich dann meist von alleine, wie die Klasse
aussehen muss. Wenn Dein Code durch Klassen komplexer und
undurchsichtiger wird, dann lass ihn wie er ist. Ein Klasse nur um der
Klasse willen zu haben ist so ziemlich die schlechteste
Design-Entscheidung, die Du treffen kannst. (Das kannst Du ruhig auch
dem sagen, der eine Klasse von Dir will, falls es den gibt.)
Grundsätzlich ist jede Methode einer Klasse nichts anderes als eine
Funktion mit einem impliziten Pointer-Argument. Du kannst also jede
C Funktion, deren Signatur Dir nicht explizit vorgeben ist, in die
Methode einer Klasse umwandeln, sofern Du das implizite Argument liefern
kannst. Wie man das im Falle eines Signalhandlers machen kann, haben wir
oben schon diskutiert. Darüber hinaus läuft die asynchrone Kommunikation
in C++ ganz genau so wie in C.
Dave A. schrieb:> Leute...ruhig...wieso diese Aufregung?
Also ich für meinen Teil bin gerade völlig entspannt. :-)
Dave A. schrieb:> WEIL die OOP eine ganz andere Softwarestruktur als C erfordert.
Diese Aussage dürfte nur auf einer sehr abstrakten Ebene gelten und auch
nur dann, wenn man die Meinung teilt, dass der Begriff der OOP wohl der
am meisten missverstandene Begriff der ganzen Informatik ist.
Grady Booch definiert in ISBN 020189551X ein Objekt als eine
Dateneinheit, die durch Identität, Zustand und Verhalten eindeutig
bestimmt ist. Im Gegensatz zu einer Variable, die durch Identität, Type
(i.e. der Menge aller möglichen Werte) und Zustand (i.e. einem Wert aus
der Menge aller möglichen Werte) bestimmt ist und deren Zustand nur
durch explizite Zuweisung von außen geändert werden kann, bestimmt ein
Objekt die Zustandsübergänge selbst. Diese werden in der Regel zwar
durch externe Ereignisse (Messages) getriggert. Das muss aber nicht
zwangsläufig so sein. Ein Objekt kann sein Zustand durchaus auch
selbsttätig ändern, zum Beispiel nach Ablauf einer gewissen Zeit. Daraus
folgt aber, das ein Objekt einen eigenen Steuerfluss hat. Bereits in
Simula waren die Koroutinen ein zentrales, wenn nicht das zentrale
Element. Damit wäre das objektorientierte Modell im Kern ein
Nebenläufigkeitsmodell und die Programmstruktur eines
objektorientierten Programmes dann tatsächlich fundamental anders als
die der klassischen imperativen Programmierung.
Nun ist allerdings meines Wissens nach keine der derzeit gebräuchlichen
und als objektorientiert bezeichneten Sprachen objektorientiert in
diesem Sinne, auch C++ nicht. Abgesehen vielleicht von Exceptions – die
Du in asynchron gerufen Funktionen aber ohnehin nicht verwenden darfst –
gibt es in C++ nichts, dass Du nicht auch in C implementieren könntest.
Insofern gibt es auch kein C++ Programm, dass nicht unter Beibehaltung
der grundsätzlichen Struktur in ein äquivalentes C Programm
transformieren werden könnte.
Ich möchte damit keinesfalls die C++ spezifischen Sprachmittel als im
Prinzip überflüssigen „syntactical sugar“ deklassieren. Sie haben
absolut ihre Berechtigung denn sie machen das Leben des Programmierers
einfacher und vor allem sicherer und effizienter. (Ich bin begeisterter
C++ Programmierer!) An der grundsätzlichen Programmstruktur aber ändern
sie nichts. Wer's nicht glaubte schaue sich mal die VFS-Schicht des
Linux Kernels an: ein ganz klassischer objektorientierter Entwurf in
klassischem C, allerdings mit viel Overhead und einigen hässlichen
Casts.
Dave Anadyr schrieb:
> [...] dass ich nicht weiß, wie ich Daten asynchron empfangen kann.
Das macht doch das Kernel schon für dich. Solange der Gerät geöffnet
ist, nimmt das Kernel die Daten von der UART entgegen und packt sie in
einen Kernel-internen Empfangsbuffer (also genau das, was du auf einem
Mikrocontroller per Interruptroutine auch machen würdest). Dabei ist es
egal, was dein Programm gerade macht (oder auch nicht macht -
Multitasking!). Diesen Buffer musst du nur ab und zu per read
auslesen/leeren. Analog läuft es auch beim Senden: per write schreibst
du in einen Kernel-internen Sendebuffer der dann im Hintergrund
Byte-für-Byte übertragen wird.
> Es funktioniert alles wunderbar, wenn ich es in C teste. Ich habe alles> schon am Laufen und bisher gab es gar kein Problem.
Mit SIGIO? Dann bin ich mir ziemlich sicher, dass du die
Probleme/Fehler bisher nur noch nicht gefunden hast ;-)
Programmierer schrieb:> Du kannst unter Linux keine Interrupts nutzen.
Doch klar, für den Rpi kannste z.B. nen Interrupt Handler für jeden GPIO
auflegen.
Macht aber keinen Sinn für UART.
Peter II schrieb:> Ein Signal ist erst mal> nur eine Funktion ohne Kontext. Damit kann man kein Objekt direkt> ansprechen.
Das ist richtig.
> und stehe auch dazu das ich> Signale und OOP nicht richtig zusammenpassen.
Das ist trotzdem grundfalsch.
Du vergisst einfach, das OOP nicht nur aus Objekten besteht, sondern
ganz wesentlich auch aus Klassen. Objekte sind Instanzen von Klassen,
die den Code der Klasse sharen und obendrein die Daten der Klasse. Denn
in allen mir bekannten OOP-Sprachen dürfen auch Klassen Daten besitzen!
Eben genau die, die für alle ihre Instanzen gleichermaßen relevant sind.
Es wäre doch völliger Schwachsinn, wenn z.B. eine SerialPort-Klasse
NICHT die Information darüber bereitstellen würde, welche Instanzen
überhaupt möglich sind, denn das hängt doch davon ab, wieviele
Schnittstellen zur Laufzeit (!!!) überhaupt verfügbar sind. Und noch
größerer Schwachsinn wäre, wenn jede Instanz das erneut ermitteln würde.
Nein, natürlich ist der einzige sinnvolle Ansatz, solche "globalen" (nur
klassenglobalen!) Daten in der Klasse selber zu ermitteln und zu
verwalten.
Und diese Daten müssen keinesfalls wirklich "global" sein, wie im Thread
schwachsinnigerweise (sicher von einem C-ler: der Seuche an sich)
geschrieben wurde, also global im Sinne von: Für jeden Arsch
RW-zugreifbar. Nein, ihre Sichtbarkeit und Zugreifbarkeit kann natürlich
genauso fein kontrolliert werden wie der auf die Daten von
Klassen-Instanzen (also: Objekten).
Ich bleibe dabei: deine OOP-Kenntnisse sind absolut unzureichend! Deine
Erfahrung beschränkt sich ganz eindeutig auf die Rolle als OOP-Anwender,
du hast niemals als OOP-Infrastruktur-Ersteller gearbeitet. Du versuchst
jetzt offensichtlich gerade, in diese Rolle zu schlüpfen, und dein
bisher angesammeltes Scheinverständnis des OOP-Konzeptes löst sich dabei
einfach mal in Luft auf. Das (und nur das) ist im Kern deines Problems.
Abhilfe: Lernen.
A. H. schrieb:> Grady Booch definiert in ISBN 020189551X ein Objekt als eine> Dateneinheit, die durch Identität, Zustand und Verhalten eindeutig> bestimmt ist. Im Gegensatz zu einer Variable, die durch Identität, Type> (i.e. der Menge aller möglichen Werte) und Zustand (i.e. einem Wert aus> der Menge aller möglichen Werte) bestimmt ist und deren Zustand nur> durch explizite Zuweisung von außen geändert werden kann, bestimmt ein> Objekt die Zustandsübergänge selbst.
Gut.
Vielen Dank Euch alle, die Diskussion war sehr hilfreich und
interessant. Besonders Interessant fand ich den letzten Beitrag von
A.H..
Mittlerweile habe ich die von Boost entwickelten Bibliotheken in meinem
Programm implementiert. Sie funktionieren einwandfrei... Ich habe mich
an dieses Beispiel orientiert: https://gist.github.com/yoggy/3323808