Atomare (C99) "struktur"



  • Hallo Leute ich habe eine Strucktur, bspw:

    typedef struct { int x, int y;} Point;
    

    nun möchte ich lesen und schreiben des Struktur Object atomar machen. In C++
    könnte ich ja std::atomic<Point> atomicPoint nehmen
    oder in C11 c_Atomic(Point)tomicPoint; ...

    Aber in C99 ? habe bissel recherciert je nach platform könnte ich ja locks ein baunen,was mir nich so gefällt.. dann bin ich auf inline ASM gestoßen. ungefäahr so:

    void atomic_assign(Point *dest, Point src)
     {
        #if defined(__x86_64__) || defined(_M_X64)
        // x86_64 architecture
        __asm__ __volatile__ (
            "lock; cmpxchg16b %0"
            : "=m" (*dest)
            : "m" (*dest), "d" (src.b), "a" (src.a)
            : "memory"
        );
    
        #elif defined(__aarch64__)
        // ARM64 architecture
        __asm__ __volatile__ (
            "ldxr x0, [%0]\n"
            "stxr w1, %1, [%0]\n"
            : "=m" (*dest)
            : "r" (&src), "m" (*dest)
            : "x0", "w1", "memory"
        );
    
        #else
        #error "Unsupported architecture"
        #endif
    }
    

    nun die frage ist das straight-forward oder gebastelt? wie löse ich das schön .. ja ich würde natürlich die Funktion c void atomic_assign(Point *dest, Point src) extern machen und je nach Plattform die entsprechend bauen/linken .

    Danke schonmal:)



  • @SoIntMan
    Hast du dir mal die Concurrency support library angeschaut?

    https://en.cppreference.com/w/c/thread.html



  • @Quiche-Lorraine sagte in Atomare C "struktur":

    @SoIntMan
    Hast du dir mal die Concurrency support library angeschaut?
    https://en.cppreference.com/w/c/thread.html

    Aber ist das nich C11 ?? ich bin in C99 unterwegs... (noch)



  • @SoIntMan
    Sorry, mein Fehler. Zu schnell gelesen.

    Ich mag keine selbst gebastelten Atomic und Co. Datenstrukturen, da diese schwer zu testen sind. Ja sie funktionierten am Anfang immer gut, aber unter Volllast gibt es da schon so manche Überraschung. Man muss ja bedenken dass der Algo an jeder Stelle unterbrochen werden kann.



  • @Quiche-Lorraine sagte in Atomare (C99) "struktur":

    Man muss ja bedenken dass der Algo an jeder Stelle unterbrochen werden kann.

    aber wird genau das nicht mit dem inline asm befehl vermieden..? weil so wie ich gelesen habe wird das ja genau so unter haube bswp. bei std::atomic gelöst?



  • @SoIntMan
    Es könnte für x86_64 passen, aber für ARM64 sehe ich da zwei Befehle. Das müsste man mal durchspielen.



  • Von welcher Art Software sprechen wir denn, muss das für verschiedene OS funktionieren?



  • @DocShoe sagte in Atomare (C99) "struktur":

    Von welcher Art Software sprechen wir denn, muss das für verschiedene OS funktionieren?

    ja, das war die Idee.. also arm und rtos ....



  • Noch Analog dazu :

    habe ich 2 threads,

    static int i=0;

    void thread1() 
    {
    while(1)
    {
      if (i == 1) 
      {
      /* do anything */
      i =0;
      }
    /* yield*/
    }
    }
    
    void thread2()
     {
    i=1;
    }
    

    und thread 2 "sporadisch" ein request absettze den dann thread 1 ausführt.. dafür ist eine atomare int notwendig oder? bin bald etwa zu verkopft



  • @SoIntMan sagte in Atomare (C99) "struktur":

    dafür ist eine atomare int notwendig oder?

    Dein Beispiel könnte vermutlich funktionieren, auch wenn es mehr ein Hack wäre.

    Das Problem ist folgendes: Nehmen wir mal eine 8-Bit CPU an. Unter der Annahme dass das Setzen von i mehrere Assembler-Befehle benötigt, kann auch während des Setzens von i der Ablauf unterbrochen werden. Wenn nun die Abfrage if (i == 1) losläuft steht in i ein inkonsistener Wert, welche je nach Abfrage heftige Fehler auslösen kann.

    In meinem Fall hatte ich auf einen kleinen Controller einen Zähler implementiert, welche die Anzahl der Millisekunden seit Start zurückliefert. Und alle paar Minuten sprang der Zähler zurück.



  • @Quiche-Lorraine sagte in Atomare (C99) "struktur":

    Dein Beispiel könnte vermutlich funktionieren, auch wenn es mehr ein Hack wäre.
    Das Problem ist folgendes: Nehmen wir mal eine 8-Bit CPU an. Unter der Annahme dass das Setzen von i mehrere Assembler-Befehle benötigt, kann auch während des Setzens von i der Ablauf unterbrochen werden. Wenn nun die Abfrage if (i == 1) losläuft steht in i ein inkonsistener Wert, welche je nach Abfrage heftige Fehler auslösen kann.
    In meinem Fall hatte ich auf einen kleinen Controller einen Zähler implementiert, welche die Anzahl der Millisekunden seit Start zurückliefert. Und alle paar Minuten sprang der Zähler zurück.

    ok das habe ich verstanden, nehmen wir an ich mach ein int32_t draus ,bedeutet dass den "genrell" das auf eine 32bit/64bit hw da setzen und vergleichen nur ein asm befehlt braucht, oder kann ich davon nicht ausgehen?

    Da ich eben C99 verwende, kann ich je nach compiller (hier GGC) ja sowas machen für ne zuweisung:

    int atomic_exchange(int *ptr, int new_value) {
        return atomic_exchange(ptr, new_value);
    }
    

    wäre das übertrieben? im prinzip habe ich nur "flags" die ich prüfen und ggf. zurücksetzen möchte.



  • @SoIntMan sagte in Atomare (C99) "struktur":

    Nehmen wir an ich mach ein int32_t draus ,bedeutet dass den "genrell" das auf eine 32bit/64bit hw da setzen und vergleichen nur ein asm befehlt braucht, oder kann ich davon nicht ausgehen?

    Generell nein.

    Was sind da deine Gedankengänge? Vermutest du da Atomarheit?


    int atomic_exchange(int *ptr, int new_value) {
        return atomic_exchange(ptr, new_value);
    }
    

    Was möchtest du mit dieser Endlosschleife ausdrücken? 🙂



  • @Quiche-Lorraine sagte in Atomare (C99) "struktur":

    Was möchtest du mit dieser Endlosschleife ausdrücken?

    sorry hab da nicht richtig hingeschaut, ehr sowas:

    int atomic_compare_and_swap(int32_t *ptr, int32_t old_val, int32_t new_val) {
        return __atomic_compare_exchange_n(ptr, &old_val, new_val, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
    }
    


  • @SoIntMan sagte in Atomare (C99) "struktur":

    Da ich eben C99 verwende, kann ich je nach compiller (hier GGC) ja sowas machen für ne zuweisung:
    ...
    wäre das übertrieben?

    Potenziell nicht. Nutze ihn aber nur dann, wenn du ihn auch benötigst. Und achte auf volatile, nicht dass der Optimierer parallelen Code wegoptimiert.



  • @Quiche-Lorraine sagte in Atomare (C99) "struktur":

    Potenziell nicht. Nutze ihn aber nur dann, wenn du ihn auch benötigst. Und achte auf volatile, nicht dass der Optimierer parallelen Code wegoptimiert.

    ok super verstehe, hier mal was cross-Os gebautes:

    #include <stdint.h>
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    volatile int32_t atomic_exchange(volatile int32_t *ptr, int32_t new_val);
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif // ATOMIC_EXCHANGE_H
    

    Linux.c

    #include "atomic_exchange.h"
    
    volatile int32_t atomic_exchange(volatile int32_t *ptr, int32_t new_val) {
        return __sync_lock_test_and_set(ptr, new_val);
    }
    

    Windows:

    #include "atomic_exchange.h"
    #include <windows.h>
    
    volatile int32_t atomic_exchange(volatile int32_t *ptr, int32_t new_val) {
        return InterlockedExchange((volatile LONG *)ptr, new_val);
    

    rtos:

    #include "atomic_exchange.h"
    #include "FreeRTOS.h"
    #include "task.h"
    
    volatile int32_t atomic_exchange(volatile int32_t *ptr, int32_t new_val) {
        taskENTER_CRITICAL();
        volatile int32_t old_val = *ptr;
        *ptr = new_val;
        taskEXIT_CRITICAL();
        return old_val;
    }
    
    }
    

    so hätte ich das jetzt gemacht, was meint hier.. richtiger weg?;)

    EDIT: klar , fehelen noch die compiler flags für OS selection



  • @SoIntMan sagte in Atomare (C99) "struktur":

    volatile int32_t atomic_exchange(volatile int32_t *ptr, int32_t new_val) {
        return InterlockedExchange((volatile LONG *)ptr, new_val);

    Da hast du einen Fehler. Ein int32_t Pointer darfst du nicht auf einen LONG Pointer casten.

    Ein LONG wird bei mir auf long umgemünzt. Und ein long kann zwischen 32 und 64 Bit belegen.

    https://en.cppreference.com/w/cpp/language/types.html



  • @Quiche-Lorraine sagte in Atomare (C99) "struktur":

    Und ein long kann zwischen 32 und 64 Bit belegen.

    Die Norm garantiert nur, dass ein long mindestens 32Bit groß ist, es können 64Bit, 128Bit oder sogar mehr sein.

    Mein Favorit als historisches Beispiel für ausgefallene Definitionen ist UNICOS. Da galt sizeof(char)==sizeof(short)==sizeof(int)==sizeof(long) und alles war 64Bit groß. Nach C Norm war das ok, UNIX wurde mittlerweile anders definiert. So dass für den 32Bit Modus ILP32 und für den 64Bit Modus LP64 gilt. Mal sehen wann sie einen 128Bit Modus definieren.

    Der ganze Thread ist eine Ansammlung von Mutmaßungen wie sich eine CPU verhalten wird. Man kann immer nur Aussagen über eine bestimmte Plattform treffen. Daher sollte es im Beispiel auch nicht Linux heißen, sondern Linux (Aarch64) oder Linux (Intel64). Wer sagt denn, dass das für Linux (unbekannte Plattform) auch gültig ist?



  • @john-0 sagte in Atomare (C99) "struktur":

    Der ganze Thread ist eine Ansammlung von Mutmaßungen wie sich eine CPU verhalten wird. Man kann immer nur Aussagen über eine bestimmte Plattform treffen. Daher sollte es im Beispiel auch nicht Linux heißen, sondern Linux (Aarch64) oder Linux (Intel64). Wer sagt denn, dass das für Linux (unbekannte Plattform) auch gültig ist?

    Das ist korrekt, deswegen versuche ich auch ein klares Bild hier zu bekommen. Um die Platformen etwas einzuschränken. Linux (ARM64) ,windows (X86 64bit),rtos (Arm64).

    @Quiche-Lorraine sagte in Atomare (C99) "struktur":

    Da hast du einen Fehler. Ein int32_t Pointer darfst du nicht auf einen LONG Pointer casten.
    Ein LONG wird bei mir auf long umgemünzt. Und ein long kann zwischen 32 und 64 Bit belegen.

    hmm aber ist windows nicht das LLP64 data model long == 32bit?



  • @SoIntMan sagte in Atomare (C99) "struktur":

    aber ist windows nicht das LLP64 data model long == 32bit?

    Stimmt.

    Dann würde ich aber trotzdem eine Assertion (assert(sizeof(LONG) == sizeof(*ptr));) hier hinzufügen. Einfach um sicher zu gehen.


Anmelden zum Antworten