2010-07-21 20 views
8

Ich arbeite an einem Embedded-Projekt (PowerPC-Ziel, Freescale Metrowerks Codewarrior Compiler), wo die Register Memory-Mapped und in nice Bitfields definiert sind, um die einzelnen Bit-Flags einfach zu drehen.Was macht der C-Compiler mit Bitfeldern?

Im Moment verwenden wir diese Funktion, um Interrupt-Flags zu löschen und die Datenübertragung zu steuern. Obwohl ich noch keine Fehler bemerkt habe, war ich neugierig, ob das sicher ist. Gibt es eine Möglichkeit, Bitfelder sicher zu verwenden, oder muss ich jedes in DISABLE_INTERRUPTS ... ENABLE_INTERRUPTS umbrechen?

Zur Klarstellung: der Header mit dem Mikro hat Felder geliefert wie

union { 
     vuint16_t R; 
     struct { 
      vuint16_t MTM:1;  /* message buffer transmission mode */ 
      vuint16_t CHNLA:1;  /* channel assignement */ 
      vuint16_t CHNLB:1;  /* channel assignement */ 
      vuint16_t CCFE:1;  /* cycle counter filter enable */ 
      vuint16_t CCFMSK:6;  /* cycle counter filter mask */ 
      vuint16_t CCFVAL:6;  /* cycle counter filter value */ 
     } B; 
    } MBCCFR; 

Ich gehe davon etwas in einem Bitfeld Einstellung nicht atomar ist. Ist das eine korrekte Annahme? Welche Art von Code erzeugt der Compiler tatsächlich für Bitfelder? Wenn ich die Maske selbst mit dem Feld R (Raw) ausführe, kann es leichter sein, sich daran zu erinnern, dass die Operation nicht atomar ist (man vergisst leicht, dass eine Zuweisung wie CAN_A.IMASK1.B.BUF00M = 1 nicht atomar ist).

Ihr Rat wird geschätzt.

+1

Bin ich richtig in der Annahme, dass das 'v' in' vuint16_t' für "volatile" steht? – JAB

+0

Ja, die Typen sind flüchtig, da es sich bei ihnen um Speicherkartenspeicherorte handelt. Es gibt auch Anweisungen für die Ausrichtung, die ich weggelassen habe, um die Dinge kurz zu halten. –

+0

Vermeiden Sie Bitfelder und verwenden Sie stattdessen Maske und Verschiebung. – starblue

Antwort

3

Die Atomarität hängt vom Ziel und vom Compiler ab. AVR-GCC versucht zum Beispiel, den Bit-Zugriff zu detektieren und bit-set- oder clear-Anweisungen auszugeben, wenn möglich. Überprüfen Sie die Assembler-Ausgabe sicher zu sein ...

EDIT: Hier ist eine Ressource für die Atom Anweisungen auf PowerPC direkt aus dem Maul des Pferdes:

http://www.ibm.com/developerworks/library/pa-atom/

+0

Wenn es möglich ist, aber nicht alle Adressen unterstützen dies, und dies kann nicht durchgeführt werden, es sei denn, die Adresse ist zur Kompilierzeit bekannt. – nategoose

+0

@nategoose Ja, besser, es so zu buchstabieren, wie du es getan hast, als zu implizieren, wie ich es tat. –

3

Es ist richtig, anzunehmen, dass bitfields Einstellung nicht ist atomar. Der C-Standard ist nicht besonders klar, wie Bitfelder implementiert werden sollten und verschiedene Compiler gehen auf verschiedene Arten auf ihnen vor.

Wenn Sie wirklich nur auf Ihre Zielarchitektur und Compiler achten, zerlegen Sie einige Objektcode.

Im Allgemeinen wird Ihr Code das gewünschte Ergebnis erzielen, aber viel weniger effizient sein als Code, der Makros und Verschiebungen verwendet. Das heißt, es ist wahrscheinlich lesbarer, Ihre Bitfelder zu verwenden, wenn Sie sich hier nicht um die Leistung kümmern.

Sie könnten immer eine Setter Wrapper-Funktion für die Bits schreiben, die atomar ist, wenn Sie Bedenken haben, dass zukünftige Programmierer (einschließlich Sie selbst) verwirrt sind.

0

Es hängt vollständig von der Architektur und dem Compiler ab, ob die Bitfeldoperationen atomar sind oder nicht. Meine persönliche Erfahrung sagt: Verwenden Sie keine Bitfelder, wenn Sie nicht müssen.

3

Ja, Ihre Annahme ist richtig, in dem Sinne, dass Sie Atomizität nicht annehmen können. Auf einer bestimmten Plattform können Sie es als Extra erhalten, aber Sie können sich in keinem Fall darauf verlassen.

Grundsätzlich führt der Compiler Maskierung und Dinge für Sie. Er könnte in der Lage sein, Eckkästen oder spezielle Anweisungen zu nutzen. Wenn Sie an Effizienz interessiert sind, schauen Sie sich den Assembler an, den Ihr Compiler damit erstellt, normalerweise ist er ziemlich lehrreich. Als Faustregel würde ich sagen, dass moderne Compiler Code erzeugen, der so effizient ist wie der durchschnittliche Programmieraufwand. Ein echtes Deep Bit Twiddeling für deinen spezifischen Compiler könnte dir vielleicht ein paar Zyklen einbringen.

0

Ich bin ziemlich sicher, dass auf powerpc nicht atomar ist, aber wenn Ihr Ziel ein Single-Core-System ist, dann können Sie nur:

void update_reg_from_isr(unsigned * reg_addr, unsigned set, unsigned clear, unsigned toggle) { 
    unsigned reg = *reg_addr; 
    reg |= set; 
    reg &= ~clear; 
    reg ^= toggle; 
    *reg_addr = reg; 
} 

void update_reg(unsigned * reg_addr, unsigned set, unsigned clear, unsigned toggle) { 
    interrupts_block(); 
    update_reg_from_isr(reg_addr, set, clear, toggle); 
    interrupts_enable(); 
} 

Ich erinnere mich nicht, wenn die powerpc Interrupt-Handler unterbrechbare sind, aber wenn sie dann sind, solltest du immer die zweite Version benutzen.

Wenn Ihr Ziel ein Multiprozessorsystem ist, sollten Sie Sperren (Spinlocks, die Interrupts auf dem lokalen Prozessor deaktivieren und darauf warten, dass alle anderen Prozessoren mit der Sperre fertig sind) den Zugriff auf Dinge wie Hardwareregister und Acquire schützen die erforderlichen Sperren, bevor Sie auf das Register zugreifen, und dann die Sperren sofort nach dem Aktualisieren des Registers (oder der Register) freigeben.

Ich habe einmal gelesen, wie Sperren in PowerPC zu implementieren - es beteiligt den Prozessor auf den Speicher-Bus für eine bestimmte Adresse zu sehen, während Sie einige Operationen und dann am Ende dieser Operationen überprüft, um zu sehen, ob die Watch-Adresse wurde von einem anderen Kern geschrieben. Wenn es nicht so war, war Ihre Operation erfolgreich; wenn es dann war, musste man die Operation wiederholen. Dies war in einem Dokument für Compiler, Bibliothek und OS-Entwickler geschrieben. Ich kann mich nicht erinnern, wo ich es gefunden habe (wahrscheinlich irgendwo auf IBM.com), aber ein wenig Jagd sollte es aufdrehen. Es hat wahrscheinlich auch Informationen darüber, wie man atomare Bit-Twiddling macht.

+0

Wow, das klingt wie ein Rezept für Deadlock: Prozessor A beginnt kritischen Abschnitt, schaut auf Adresse. Prozessor B startet kritischen Abschnitt, schaut auf Adresse. Prozessor A beendet das Schreiben. Prozessor B beendet das Schreiben. Prozessor A betrachtet die Adresse. Es hat sich geändert! Prozessor A schreibt Daten neu. Prozessor B sieht Adresse an. Es hat sich geändert! Prozessor B schreibt Daten neu. Weiter in der Endlosschleife. (Sorry über die Formatierung. Kommentare scheinen nicht zu mögen.) – nmichaels

+0

@Nathon: Es ist ein bisschen komplizierter und einfacher als das, aber es ist eine Weile her, seit ich die Dokumentation gelesen habe. Es gab wirklich etwas, das es davon abhielt, eine Deadlock-Situation zu sein, aber auch mehr Flexibilität als der auf anderen Architekturen verwendete Ansatz mit einem einzelnen atomaren Befehl erlaubte. Es kann sein, dass das endgültige Schreiben in den RAM nicht erfolgte, wenn ein anderer Prozessor an diese Adresse geschrieben hatte, nachdem dieser Prozessor ihn angeschaut hatte. – nategoose

+1

Bei einigen Architekturen wird es als "load linked/store conditional" bezeichnet. PPC nennt es 'lwarx' (Ladewort und Reserve indiziert) und' stwcx.' (Speicherwort bedingt indiziert (und Aufnahme)). Der Laden ist davon abhängig, dass niemand in den Speicher geschrieben hat; Ein Prozessor wird immer gewinnen. Zusätzlich wird die "Reservierung" über Kontextschalter hinweg gelöscht. So oder so, Sie wollen es nicht auf Hardware-Bitfeldern tun ... –

3

Ich denke, dass die Verwendung von Bitfeldern zum Modellieren von Hardware-Registern keine gute Idee ist.

Soviel darüber, wie Bitfelder von einem Compiler gehandhabt werden, ist implementierungsdefiniert (einschließlich der Behandlung von Feldern, die sich über Byte- oder Wortgrenzen erstrecken, Endianess-Probleme und genau, wie Bits gesetzt, gesetzt und gelöscht werden). Siehe C/C++: Force Bit Field Order and Alignment

Um zu überprüfen, dass Registerzugriffe behandelt werden, wie Sie erwarten oder behandelt werden müssen, müssten Sie die Compiler-Dokumente sorgfältig studieren und/oder den ausgegebenen Code betrachten. Ich nehme an, dass, wenn die Header, die mit dem Mikroprozessor-Toolset geliefert werden, sie verwenden, Sie davon ausgehen können, dass die meisten meiner Sorgen erledigt sind. Allerdings würde ich vermuten, dass atomarer Zugriff nicht unbedingt ...

Ich denke, es ist am besten, diese Art von Bit-Level-Zugriffen von Hardware-Registern mit Funktionen (oder Makros, wenn Sie müssen), die explizites Lesen durchführen/modify/write Operationen mit der Bitmaske, die Sie benötigen, wenn Ihr Prozessor dies benötigt.

Diese Funktionen könnten für Architekturen modifiziert werden, die atomare Bit-Level-Zugriffe unterstützen (wie die "Bit-Banding" -Adressierung des ARM Cortex M3). Ich weiß nicht, ob der PowerPC so etwas unterstützt - der M3 ist der einzige Prozessor, mit dem ich es zu tun habe, der es allgemein unterstützt. Und selbst das M3-Bit-Banding unterstützt 1-Bit-Zugriffe; Wenn Sie mit einem Feld arbeiten, das 6 Bits breit ist, müssen Sie zum Szenario Lesen/Ändern/Schreiben zurückkehren.