Morgen, ich mache mir gerade Gedanken über eine Software. Hintergrund: über eine Schnittstelle gelangen Binärdaten in den uC. Diese werden in einem Puffer byteweise gesammelt (Puffer ist uint8_t[]). Anschließend soll der Puffer einem Protokoll-Parser übergeben werden, der eben den Datenstrom auswertet. Das Protokoll besitzt einen Header, außerdem sind, abhängig von diesem Header, die Nutzdaten zu interpretieren. Ich habe daran gedacht mir für die verschiedenen Nutzdaten-Typen jeweils eine Struktur zu definieren. Nachdem der Typ bestimmt wurde würde ich mir einen Zeiger auf die Nutzdaten besorgen und in einen Strukturzeiger casten. Dann kann ich bequem auf die Felder zugreifen. Das kann natürlich mächtig ins Auge gehen, zwecks Padding und Byteorder. Padding kann man per Attribut abschalten, die Byteorder kann man per Makro oder Inline-Funktion richtig drehen, wobei das Makro im Idealfall einfach garnix macht. Gibt es bei diesem Vorgehen noch andere Stolpersteine? Gibt es ein schöneres Vorgehen? Ich habe mal gegoogelt und folgenden Thread gefunden, wo sich auch Herr Buchegger geäußert hat: Beitrag "Aufbau von Structs im Speicher" Das liest sich ja als gäbs keine weiteren Probleme. Oder verletze ich damit schon die strict-aliasing Regel?
Wenn du etwas über ein Netzwerk sendest solltest du dir angewöhnen die Daten immer in einem festen Format zu senden. Im Fall von integer oder longs hat es sich eingebürgert eine feste "Net Byte Order" zu benutzen, dafür gibt es in den TCP/UTP Stacks auch Makros. Weiter solltest du dir mal "Unions" anschauen. Die dienen genau dazu einen Speicherbereich in mehrere logisch unterschiedliche Gliederungen zu unterteilen.
Udo Schmitt schrieb: > Wenn du etwas über ein Netzwerk sendest solltest du dir angewöhnen die > Daten immer in einem festen Format zu senden. Im Fall von integer oder > longs hat es sich eingebürgert eine feste "Net Byte Order" zu benutzen, > dafür gibt es in den TCP/UTP Stacks auch Makros. > Weiter solltest du dir mal "Unions" anschauen. Die dienen genau dazu > einen Speicherbereich in mehrere logisch unterschiedliche Gliederungen > zu unterteilen. Leider nicht das was ich gefragt habe. 1) Da meine Binärdaten als Protokoll zu interpretieren sind besitzen diese bereits ein "festes Format". Der Sender regelt das bereits alles. Wenn ich von Byteorder-Problemen rede dann gehts mir darum, den empfangenen Frame für die Zielarchitektur aufzubereiten. Der Empfänger muss unter Umständen die Bytes erst neu verdrösseln. 2) Ich kenne Unions. Diese haben aber mit den gleichen Problemen zu kämpfen wie meine Lösung. Laut C-Standard darf man auch nur den Member lesen, der zuletzt geschrieben wurde (salopp ausgedrückt).
Peter II schrieb: > mach doch einfach ein memcpy in die Structur rein. Damit umgehe ich die mögliche Verletzung des strict-aliasing (falls es zu so einer kommt, da bin ich mir nicht sicher). Das behebt weder das Padding, noch das Byteorder Problem.
ben schrieb: > Das behebt weder das Padding, das kann man mit einen mit einen pragma verhindern. > noch das Byteorder Problem. damit muss man halb leben, wenn man Daten als Struct versenden will.
ben schrieb: > 1) Da meine Binärdaten als Protokoll zu interpretieren sind besitzen > diese bereits ein "festes Format". gut. Der wichtigste Punkt ist, dass es ein für alle mal definiert und festgenagelt wird, wie sich die Byte Order auf der Protokoll Ebene präsentiert. > Der Sender regelt das bereits alles. Auch gut > Wenn ich von Byteorder-Problemen rede dann gehts mir darum, den > empfangenen Frame für die Zielarchitektur aufzubereiten. Der Empfänger > muss unter Umständen die Bytes erst neu verdrösseln. Ja, das kann dir passieren. Hast du denn so viele unterschiedliche Empfänger-Architekturen, dass das zum Problem werden könnte? > 2) Ich kenne Unions. Diese haben aber mit den gleichen Problemen zu > kämpfen wie meine Lösung. Laut C-Standard darf man auch nur den Member > lesen, der zuletzt geschrieben wurde (salopp ausgedrückt). Im Prinzip ja. Es gibt nur eine Ausnahme. Und die ist, wenn man unsigned char, signed char oder char Arrays mit etwas anderem überlagert. Also genau der Fall den du hast. Wobei der C-Standard nichts darüber aussagt, ob es Padding Bytes gibt oder geben könnte, bzw. wie dann die Byte-Order aussieht. Der C-Standard legitimiert im Grunde
1 | union convert |
2 | {
|
3 | long i; |
4 | unsigned char a[sizeof(long)]; |
5 | };
|
in der Form, dass es zwar zulässig ist, das Verhalten aber von der Implementierung bestimmt wird. > Oder verletze ich damit schon die strict-aliasing Regel? Genau darum gehts bei dieser Ausnahme einer Union von irgendwas mit char-Arrays: die werden immer als aliased angenommen. Mehr zu dem Thema "struct aliasing" http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html > Gibt es ein schöneres Vorgehen? Ob schöner oder nicht, ist Geschmackssache. Eine Übertragung in Textform kennt diese Probleme so nicht, erzeugt aber mehr Overhead. Will man binär Übertragen, dann hat man diese "Probleme" und solange sich die Chip-Schmieden bzw. Compilerbauer dieser Welt nicht darüber einigen, welches die einzige wahre Byte-Order ist, bzw. wieviele Padding Bytes von jeder einzelnen Architektur immer gleich und an welcher Stelle eingefügt werden sollen, solange wird es System-Programmierer geben, die an den Schnittstellen nach aussen die entsprechenden Anpassungen einprogrammieren müssen. Das ist ein ganz normaler Vorgang, also nichts wovor du groß Bammel haben musst. Sorge dafür, dass dein eingehender Datenstrom möglichst rasch und frühzeitig in die Systematik deines konkreten Prozessors gebracht wird, so dass darüber liegende Softwareschichten sich nicht mehr damit herumschlagen müssen. Kapsle diesen "Anpass-Code" einigermassen gut ein, mach es für nachfolgende Programmierergenerationen leicht, das alles auf eine andere Architektur anzupassen. Mehr kannst du nicht tun.
:
Bearbeitet durch User
ben schrieb: > Gibt es ein schöneres Vorgehen? Man kann Binärdaten auch explizit einlesen... Man definiert sich im Protokoll genau in welchem Byte welche Daten stehen und welche Byte-Reihenfolge die Integer haben. Dann füllt man jedes Byte einzeln im Code manuell, und zerlegt dabei Integer mit Bitshifts. Beim Empfänger macht mans umgekehrt, und holt jedes Byte einzeln raus und schreibt es in die entsprechenden C-Variablen. Integer setzt man aus mehreren Bytes zusammen mit Bitshifts und "|". Das ist zwar ziemlich aufwändig, stellt aber sicher dass sich der Code auf jeder Plattform genau gleich verhält und somit das Protokoll überall gleich verstanden wird...
Danke dir, Karl-Heinz. D.h., folgender Code wäre in Ordnung?
1 | void protocolParser(uint8_t *data, uint8_t length) |
2 | {
|
3 | uint8_t payloadType; |
4 | |
5 | payloadType = getPayloadType(data, length); |
6 | |
7 | if (payloadType == PAYLOAD_TYPE_A) |
8 | {
|
9 | domeSomething_A( (payloadA_t *) data); |
10 | }
|
11 | else if (payloadType == PAYLOAD_TYPE_B) |
12 | {
|
13 | domeSomething_B( (payloadB_t *) data); |
14 | }
|
15 | else if (payloadType == PAYLOAD_TYPE_C) |
16 | {
|
17 | domeSomething_C( (payloadC_t *) data); |
18 | }
|
19 | else
|
20 | {
|
21 | //error
|
22 | }
|
23 | }
|
Wobei payloadA_t, payloadB_t und payloadC_t Strukturen mit attribute_packed sind. Oder sollte man vor dem Cast + Funktionsaufruf doch noch ein memcpy() spendieren?
ben schrieb: > Oder sollte man vor dem Cast + Funktionsaufruf doch noch ein memcpy() > spendieren? Was würde dir der memcpy bringen, ausser das ein und dieselbe Bytefolge von A nach B kopiert wird? Nach
1 | void protocolParser(uint8_t *data, uint8_t length) |
2 | {
|
3 | uint8_t payloadType; |
4 | |
5 | payloadType = getPayloadType(data, length); |
6 | |
7 | if (payloadType == PAYLOAD_TYPE_A) |
8 | {
|
9 | payloadA_t convData; |
10 | memcpy( convData, data, length ); |
11 | |
12 | domeSomething_A( &convData ); |
13 | }
|
14 | ...
|
hat sich ja nichts grundsätzliches geändert, ausser dass dieselben Bytes jetzt an einer anderen Stelle im Speicher stehen. Es kann natürlich Gründe geben, warum man das tun möchte. Spontan fallen mir ein * wenn data asychron befüllt wird und als Buffer daher möglichst schnell für die Aufnahme der nächsten Übertragung wieder zur Verfügung stehen soll * wenn die Bytes in domeSomething_A die Daten verändert werden und diese Veränderung nicht nach aussen an den Originalen sichtbar sein soll.
:
Bearbeitet durch User
Karl Heinz schrieb: > Was würde dir der memcpy bringen, ausser das ein und dieselbe Bytefolge > von A nach B kopiert wird? Hatte Bedenken auf den selben Speicher via zwei verschiedene Pointer zuzugreifen. Nur weil ich während der Parameterübergabe caste und der Zugriff in der Unterfunktion stattfindet ändert es ja nichts daran dass es der selbe Zeiger ist. Ob es notwendig ist (oder schöner) kann ich nicht beurteilen.
Ja nach Architektur läuftst du auf segmentation fault (oder wie die ensprechende Trap dort heißen mag) wegen Alignment-Problemen. Mit memcpy in ein korrekt aligntes Zielobjekt funktioniert es dann hingegen.
ben schrieb: > D.h., folgender Code wäre in Ordnung? Streng genommen nicht. Karl Heinz schrieb: > Mehr zu dem Thema "struct aliasing" > http://cellperformance.beyond3d.com/articles/2006/... Aus dem Artikel: The converse is not true. Casting a char* to a pointer of any type other than a char* and dereferencing it is usually in volation of the strict aliasing rule.
:
Bearbeitet durch User
Johann L. schrieb: > Ja nach Architektur läuftst du auf segmentation fault (oder wie die > ensprechende Trap dort heißen mag) wegen Alignment-Problemen. Mit > memcpy in ein korrekt aligntes Zielobjekt funktioniert es dann hingegen. ich habe ein ähnliches problem. ich verstehe deine aussage so, du hast ein uint8 Array und kopierst das mittels memcpy in eine struktur (so wie der TE). Wenn du das auf einem 32Bitter machst, dann landen doch die einzelnen bytes irgendwo, aber sicher nicht da wo sie sollen, oder irre ich mich? Bei einer packed- Struktur kannst du in einen fault laufen. Ich sehe also immer noch keine Lösung des Problems??
schau dir mal die kleinen IP stacks an .. die machen quasi deine errste idee die Daten liegen in einem
1 | uint8_t eth_rxbuf[1536]; |
Dann gibt es dirverse typedef structs
1 | typedef struct __attribute__((packed)){ |
2 | uint64_t dst_mac : 48; //48bit Dst MAC |
3 | uint64_t src_mac : 48; //48bit Src MAC |
4 | unsigned int type : 16; //16bit Type |
5 | } ETH_Header; |
6 | |
7 | typedef struct __attribute__((packed)){ |
8 | unsigned int hd_len : 4; // 4bit Version |
9 | unsigned int ver : 4; // 4bit Header Len |
10 | unsigned int tos : 8; // 8bit Type of Service |
11 | unsigned int len : 16; //16bit Packet Len |
12 | unsigned int id : 16; //16bit ID |
13 | unsigned int flg_offs : 16; // 3bit Flags |
14 | unsigned int ttl : 8; // 8bit Time to Live |
15 | unsigned int proto : 8; // 8bit Protocol |
16 | unsigned int checksum : 16; //16bit Checksum |
17 | unsigned long src_ip : 32; //32bit IP Src |
18 | unsigned long dst_ip : 32; //32bit IP Dst |
19 | } IP_Header; |
in den funktionen wird der eth_rxbuf als parameter übergeben
1 | ETH_Header *rx_eth; |
2 | IP_Header *rx_ip; |
3 | |
4 | rx_eth = (ETH_Header*) ð_rxbuf[0]; |
5 | rx_ip = (IP_Header*) ð_rxbuf[14]; |
6 | |
7 | switch(rx_eth->type){ |
8 | case ETH_TYPE_IP: |
9 | switch(rx_ip->proto){ |
10 | case IP_PROTO_ICMP: |
11 | rx_icmp = (ICMP_Header*) ð_rxbuf[ICMP_OFFSET]; |
12 | if(rx_icmp->type == ICMP_ECHO_REQ){ |
13 | }
|
14 | break; |
15 | }
|
16 | break; |
17 | |
18 | case ETH_TYPE_ARP: |
19 | break; |
ben schrieb: > Das behebt weder das Padding, noch das Byteorder Problem. Casten ändert nicht an den Daten. Insofern ist dies kein Ansatz für die Lösung deines Problems.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.