Memory-Mapped-I/O und volatile bei structs



  • Hallo,

    ich beschäftige mich gerade etwas ausführlicher mit volatile. Dieser Qualifier wird ja verwendet wenn eine Variable sich durch Effekte ändern kann, die der "abstakten Maschine" nicht bekannt sind. Also wird ein Optimierer nie irgendwelche "Read" oder "Write" Operationen einsparen, wenn die Variable als volatile markiert ist. Soweit so gut.

    Allerdings ist volatile, wie ich gelesen habe, nicht geeignet um bei Parallelisierung race conditions zu vermeiden. Das liegt wohl unter anderem daran, dass volatile kein "atomic" impliziert. Stattdessen ist die Anwendung gedacht für Memory-Mapped-I/O oder signal handling,

    Ich verstehe aber diese Unterscheidung nicht wirklich, was wohl daran liegt dass ich nicht weiß wie genau Memory-Mapped-I/O funktioniert.

    Als Beispiel, angenommen man hat folgende Daten:

    struct Data
    {
        int a = 0, b = 0;
    };
    volatile const Data data;
    

    Hier lassen sich ja sowohl data.a und data.b sich "von außen" ändern. Angenommen, der Code "von außen" will jetzt folgendes machen:

    void set_ab()
    {
        set(a, 1); // Setze a = 1
        set(b, 1); // Setze b = 1
    }
    

    Warum kann es jetzt kein Problem geben, wenn ich in meinem Code auf data zugreife, dass z.B. a = 1 aber b = 0 gilt, also dass der Zugriff auf data genau zwischen den oben genannten set Operationen passiert ist?

    Oder funktioniert das bei Memory-Mapped-I/O grundsätzlich anders?



  • also ich glaube du hast da wirklich etwas nicht verstanden.

    mit mmio kannst du echte speicheradressen, wie z.b. die adresse 0x0815 deines arbeitsspeichers, oder auch die gpio-pins vom raspberry pi, in dein programm einbinden bzw. einer zeigervariable zuweisen und daten einlesen und schreiben.

    wenn jetzt ein zweites programm auch die adresse 0x0815 einbindet, können die beiden programme darüber direkt kommunizieren und daher würde sich die variable dann "von außen" bzw. besser "außerhalb des normalen programmablaufs" verändern und weil der compiler das ja nicht wissen kann, verwendet man dann "volatile".



  • @happystudent sagte in Memory-Mapped-I/O und volatile bei structs:

    Warum kann es jetzt kein Problem geben, wenn ich in meinem Code auf data zugreife, dass z.B. a = 1 aber b = 0 gilt, also dass der Zugriff auf data genau zwischen den oben genannten set Operationen passiert ist?

    Das Problem kann es eh geben. Und man muss es dann halt irgendwie lösen. Das wird von Gerät zu Gerät unterschiedlich gemacht. Eine Möglichkeit ist z.B. ein Change-Counter. Das funktioniert gut wenn die Werte sich selten genug ändern. Wobei es oft schon "selten genug" ist wenn zwischen Änderungen ein paar Tausend oder Zigtausend CPU-Zyklen vergehen.

    Man liest dann erst den Change-Counter, dann alle Werte, und dann nochmal den Change-Counter. Wenn die beiden Change-Counter identisch sind, kann man mit guter Sicherheit davon ausgehen dass die Werte die man gelesen alle zusammenpassen.

    Nochmal was Atomizität angeht: die braucht man bei memory mapped IO meist genau so. Zumindest Zugriffe auf die einzelnen Register sollten meist schon atomar sein. Das regelt der Standard IIRC einfach dadurch dass er jeder Implementierung freistellt bestimmte Dinge über die im Standard vorgeschriebenen Garantien hinaus strenger zu spezifizieren. D.h. eine Implementierung kann jederzeit sagen "OK, bei mir sind volatile int32_t Zugriffe immer atomar, so lange sie innerhalb einer Cache-Line liegen" - oder ähnliches. Und da Hardwarezugriffe eh immer implementierungsabhängig sind, reicht das auch so.



  • @hustbaer Ok, das heißt dann quasi, der wesentliche Unterschied zwischen Parallelisierung und Memory-Mapped-I/O (in diesem Kontext) ist, dass bei ersterem die "abstrakte Maschine" (also "mein" Code) für Atomizität verantwortlich ist und bei letzterem die "konkrete Maschine" (also der "Geräte" Code). Daher ist volatile alleine auch nicht ausreichend für Parallelisierung, wohl aber für MMIO.

    Habe ich das soweit richtig interpretiert?



  • volatile alleine ist nie ausreichend. volatile plus diverse über den Standard hinausgehende Garantien einer konkreten Implementierung kann ausreichend sein.

    Wobei letzteres auch für Parallelgedöns ausreichend sein kann (MSVC mit passenden Compiler-Switches z.B. garantiert dass volatile acquire bzw. release Semantik hat und dass volatile loads/stores bei passendem Alignment atomar sind). Nur dass man Parallelgedöns halt meist nicht auf eine eine konkrete Implementierung abhängig machen möchte.



  • Ok, vielen Dank 👍


Log in to reply