Forum: Mikrocontroller und Digitale Elektronik Grösse von Mailbox für Task bestimmen


von Tobias P. (hubertus)


Lesenswert?

Hallo,
ich habe da mal eine Prinzipielle Frage.
Ich setze das uC/OS-III RTOS ein. Ich habe mehrere Tasks am laufen; 
unter anderem ein Task, der ein selber entworfenes 
Kommunikationsprotokoll ausführt. Der UART-Driver empfängt die 
eingehenden Bytes, verpackt diese in einen "Frame" und sendet ihn per 
Message an die Mailbox des Protokoll-Tasks.
Diesem Task werden jedoch auch noch von anderen Tasks Messages 
geschickt. Wenn z.B. ein anderer Task einen Frame senden möchte, dann 
wird dieser auch als Message der Mailbox des Protokoll-Tasks übergeben. 
Auch Retransmission-Timeouts etc. werden so gehandhabt.
Jetzt ist es so, dass beim uC/OS-III die Mailbox nur eine bestimmte 
maximale Anzahl von Messages aufnehmen kann. Wenn jetzt aber z.B. sehr 
viele Frames auf einmal eintreffen, oder wenn ein Task nicht gleich 
jeden Frame sofort abholt, dann kann es passieren, dass die Mailbox des 
Protokoll-Tasks überläuft. Jetzt ist die Frage: wie bestimme ich die 
optimale Grösse dieser Mailbox? Je grösser sie natürlich ist, desto mehr 
Memory wird verheizt.

Ausserdem stellt sich ein weiteres Problem.
Meine Messages, die ich dem Protokoll-Task sende, haben ungefähr 
folgendes Format:
1
typedef enum
2
{
3
  msg_type_timeout,
4
  msg_type_rx_frame,
5
  msg_type_tx_frame
6
} msg_type;
7
8
typedef struct
9
{
10
  msg_type type; /* type of message */
11
  union
12
  {
13
    struct
14
    {
15
      uint8 msg_id;
16
    } timeout;
17
    
18
    struct
19
    {
20
        uint8* tx_data; /* data to transmit */
21
        uint8 dst_addr; /* destination device */
22
        uint8 length; /* data length */
23
    } tx_data;
24
    
25
      struct
26
      {
27
        uint8* rx_data; /* received data */
28
        uint8 dst_addr; /* destination device */
29
        uint8 src_addr; /* source addr */
30
        uint8 length; /* data length */
31
      } rx_data;
32
  } data;
33
} msg;

diese Messages werden dynamisch alloziiert (sowas ähnliches wie 
malloc()). Anhand des msg_type kann der Protokoll-Task dann entscheiden, 
welche felder der "data"-union relevant sind.
Zwar werden diese messages dynamisch alloziiert, dennoch muss ich ja 
schon im Voraus genug Memory reservieren, damit ausreichend Messages 
alloziiert werden können. Hier stellt sich ja dann dieselbe Frage: Wie 
bestimme ich, wie viele Messages gleichzeitig maximal alloziiert sind? 
Muss diese Menge grösser, kleiner oder gleich gross sein, wie die 
Mailbox des Protokoll-Tasks?

von Karl H. (kbuchegg)


Lesenswert?

Tobias Plüss schrieb:

> jeden Frame sofort abholt, dann kann es passieren, dass die Mailbox des
> Protokoll-Tasks überläuft. Jetzt ist die Frage: wie bestimme ich die
> optimale Grösse dieser Mailbox?

Was optimal ist, liegt oft im Auge des Betrachters.
In deinem Fall musst du dich fragen: Dürfen Messages verloren gehen und 
wenn ja wieviele erlaube ich so im Durchschnitt.

Da es sich um ein dynamisches System handelt, sind solche Zahlen nur 
sehr schwer auf theoretischem Weg zu ermitteln. Einfacher ist es, wenn 
man im Programm erst mal eine Statistik mitführt und in ein paar 
repräsentiven Testläufen die relevanten Zahlen ermittelt.

Optimal kann auch bedeuten: da darf nichts verloren gehen. In dem Fall 
muss dann die Messagebox das maximale Aufkommen bewältigen können.


> diese Messages werden dynamisch alloziiert (sowas ähnliches wie
> malloc()).

Hoffentlich aus einem Pool und nicht über die aufwändige allgemeine 
Allokierung.

> Zwar werden diese messages dynamisch alloziiert, dennoch muss ich ja
> schon im Voraus genug Memory reservieren, damit ausreichend Messages
> alloziiert werden können.

Scheint sowas wie ein Pool zu sein

> Hier stellt sich ja dann dieselbe Frage: Wie
> bestimme ich, wie viele Messages gleichzeitig maximal alloziiert sind?

So viele wie du allozierst, so viele sind alloziert.
(Klingt jetzt nach einer Milchmädchenrechnung. Aber mehr kann man da 
wirklich nicht darüber sagen)

> Muss diese Menge grösser, kleiner oder gleich gross sein, wie die
> Mailbox des Protokoll-Tasks?

Wenn du weniger Messages verfügbar hast, als die Mailbox groß ist, wie 
kann dann die Mailbox jemals voll werden?

von Tobias P. (hubertus)


Lesenswert?

Hi Karl-Heinz,
danke. Ja, ich vergass zu erwähnen, dass die dynamische alloziierung 
über einen Pool geschieht! Das OS stellt da entsprechende Funktionen 
bereit.
Die alloziierung muss ja schnell sein, denn schliesslich wird auch im 
Interrupt Memory alloziiert, damit man eine Message senden kann (das ist 
ein zulässiges Vorgehen, oder? Ich mache selten direkt was im Interrupt, 
sondern sende grundsätzlich eine Message).

Optimal würde in meinem Fall heissen: Memory-Verbrauch für den Pool so 
klein wie möglich, aber auch möglichst wenig verlorene Messages. Wenn 
ein paar einzelne Messages verloren gehen, macht das nichts.

Also würdest du sagen, die Mailbox muss so gross sein, wie ich Messages 
zur Verfügung habe - das heisst, umfasst mein Pool 100 Messages, dann 
muss die Mailbox auch 100 fassen können. Klingt ansich logisch. 
Allerdings kann es ja auch sein, dass ein Task eine Message alloziiert, 
und noch bevor er diese senden kann, wird er von einem höher 
priorisierten Task unterbrochen - dann kann er die Message nicht 
absenden, somit ist es ja eigentlich auch theoretisch möglich, dass mehr 
Messages alloziiert sind, als dass der Protokoll-Task überhaupt 
empfangen könnte. Oder?

Später muss ich mir dann noch überlegen, was ein Task machen soll, wenn 
er kein Memory bekommt. Bisher habe ich da einen Assert eingefügt. Aber 
eine entsprechende Fehlerbehandlung wäre wohl angebrachter (ein Zähler, 
der hochgezählt wird, und dann könne man z.B. im Debugger sehen, wenn 
Messages verloren gehen?)

von Karl H. (kbuchegg)


Lesenswert?

Tobias Plüss schrieb:

> Also würdest du sagen, die Mailbox muss so gross sein, wie ich Messages
> zur Verfügung habe - das heisst, umfasst mein Pool 100 Messages, dann
> muss die Mailbox auch 100 fassen können.

Das würde ich nicht sagen.
Ich würde sagen, wenn deine Laufzeitmessungen zeigen, dass von den 100 
Messages im Durchschnitt nie mehr als zb 15 im Gebrauch sind (und in 
Einzelfällen 20 oder 25), dann würde ich die Mailbox so um die 15 
Messages groß machen.

Du kannst es drehen wie du willst. Ohne belastbare Zahlen kann man da 
nur allgemeine Rumschwurbeln. Zahlen müssen her.

von Karl H. (kbuchegg)


Lesenswert?

Tobias Plüss schrieb:

> Später muss ich mir dann noch überlegen, was ein Task machen soll, wenn
> er kein Memory bekommt. Bisher habe ich da einen Assert eingefügt. Aber
> eine entsprechende Fehlerbehandlung wäre wohl angebrachter (ein Zähler,
> der hochgezählt wird, und dann könne man z.B. im Debugger sehen, wenn
> Messages verloren gehen?)

Kann man machen.
Cool wäre auch eine LED, die bei einem Allokierungsfehler angeschaltet 
wird und bei der nächsten gelungenen Allokierung wieder ausgeht.

Flackert die LED nur ab und zu ein bischen, bist du im grünen Bereich. 
Ist die LED aber auf Dauerein, dann hat dein Programm ein Problem :-)

von Peter D. (peda)


Lesenswert?

Tobias Plüss schrieb:
> unter anderem ein Task, der ein selber entworfenes
> Kommunikationsprotokoll ausführt.

Dann bist Du auch dafür verantwortlich, daß diese Protokoll 
atombombenfest ist.
In der Regel hat ein Protokoll 2 Arten von Nachrichten. Einmal welche, 
um den Datenfluß zu steuern und einmal die eigentlichen Datenpakete.
Die Steuer-Messages müssen sofort bearbeitet und beantwortet werden, 
dafür ist ein extra Puffer reserviert. Und damit kann der Master 
kontrollieren, ob er überhaupt was senden darf und ob das erfolgreich 
empfangen wurde.
Z.B. kann der Master abfragen, wieviel Messagebuffer noch frei sind.
Der Puffer für die Datenpakete wird fest für die geplante Maximalzahl 
reserviert, muß also nicht dynamisch zugewiesen werden.

Daß ein Paket verworfen werden muß, darf bei einem ordentlichen 
Protokoll nie vorkommen.


Peter

von Volker Z. (vza)


Lesenswert?

Hallo Tobias,
du brauchst genau eine Mailbox, egal wie viele Tasks da reinschreiben.
Vorausgesetzt das der nachfolgende Task die Messages schneller 
verarbeiten kann, als sie hereinkommen.

Soll eine Message in eine volle Mailbox gestellt werden, so wird der 
Task blockiert und macht erst weiter wenn sie geleert wurde.

Also berechne die maximale Frequenz aller Ereignisse die zu einem 
Eintrag führen und messe die Verarbeitungszeit deines Übergeordneten 
Tasks.

Da du von einem UART-Protokoll redest, sollte die Voraussetzung erfüllt 
sein.
Nur im umgekehrten Fall (Verarbeitung dauert länger) braucht mein eine 
Que / FIFO oder sonstiges.

Volker

von Tobias P. (hubertus)


Lesenswert?

Hallo,
sorry dass es so lange gedauert hat bis ich antworten konnte.

Es scheint der Eindruck entstanden zu sein, dass ich mehrere Mailboxen 
habe. Das ist natürlich nicht so, der Protokoll-Task hat nur eine 
einzige Mailbox. Aber die Messages, die kommen aus einem Pool, von wo 
sie dynamisch alloziiert werden.

Peter,
du hast natürlich recht, eigentlich darf es nie vorkommen, dass Pakete 
verworfen werden. Wenn ich aber z.B. den Ethernet-MAC anschaue, den ich 
auch Benutze bei meinem ARM-Controller, dann stelle ich fest, dass im 
dümmsten Fall die Ethernet-Frames schneller eintreffen können, als der 
Prozessor sie verarbeiten kann. Da kann man absolut nichts dagegen 
machen, denn der Pufferspeicher für die Frames ist ja begrenzt.

Noch eine andere prinzipielle Frage.
Gewisse Daten, die ich übertrage, müssen innerhalb eines bestimmten 
Timeouts beantwortet werden. Dieses Timeout realisiere ich, in dem beim 
Senden der Daten ein Timer gestartet wird, der dann bei seinem ersten 
Aufruf wieder an den Protokoll-Task eine entsprechende Meldung sendet 
und diesen so über das Timeout informiert. Wenn die Daten vor Ablauf des 
Timers empfangen und bestätigt wurden, wird der Timer angehalten, 
ansonsten aber sendet er die Message, der Protokoll-Taks bemerkt dies 
und kann die Daten noch einmal senden. Ist das eine zulässige Praxis, 
oder gibts da bessere Methoden?

Allgemein: Gibt es eigentlich Literatur oder so, wie man solche 
RTOS-Anwendungen aufbaut? Ich implementiere da immer irgendwie so nach 
meinem Gusto, aber es gibt ja immer 1000 Lösungen, wie man was machen 
kann. Das Timeout z.B. könnte ja auch mit einem Semaphor oder so gelöst 
werden.

von Karl H. (kbuchegg)


Lesenswert?

Tobias Plüss schrieb:

> dümmsten Fall die Ethernet-Frames schneller eintreffen können, als der
> Prozessor sie verarbeiten kann. Da kann man absolut nichts dagegen
> machen, denn der Pufferspeicher für die Frames ist ja begrenzt.

Richtig.
Allerdings ist da das ganze darüber liegende Protokoll auch darauf hin 
ausgelegt. Der Empfänger muss dem Sender bestätigen, dass er das Paket 
erhalten hat. Wenn er nicht bestätigt, dann schickt der Sender erneut. 
Das alles bedeutet natürlich Overhead und funktioniert im Grunde nur 
deshalb, weil zb TCP/IP von Anfang an darauf ausgelegt war, dass auf der 
Datenverbindung von New York nach San Franzisko das direkte Kabel in der 
Mitte irgendwo durch Atombombeneinschlag zerstört wurde.

Ob du dir diesen AUfwand auf deiner dann doch eher lokal begrenzten 
Platine auf der sich mehrere Prozesse unterhalten ebenfalls antun 
willst, na ich weiß nicht.

> Noch eine andere prinzipielle Frage.
> Gewisse Daten, die ich übertrage, müssen innerhalb eines bestimmten
> Timeouts beantwortet werden. Dieses Timeout realisiere ich, in dem beim
> Senden der Daten ein Timer gestartet wird, der dann bei seinem ersten
> Aufruf wieder an den Protokoll-Task eine entsprechende Meldung sendet
> und diesen so über das Timeout informiert. Wenn die Daten vor Ablauf des
> Timers empfangen und bestätigt wurden, wird der Timer angehalten,
> ansonsten aber sendet er die Message, der Protokoll-Taks bemerkt dies
> und kann die Daten noch einmal senden. Ist das eine zulässige Praxis,
> oder gibts da bessere Methoden?

Ist zulässig. TCP/IP macht das ja auch so. Das Problem: In der Message 
muss sowas wie eine eindeutige Kennung enthalten sein, damit der 
Empfänger erkennen kann: Ooops, diese Nachricht hatte ich schon 
bearbeitet.

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.