Zum Hauptinhalt springen

C - Volatile

Eine gute Erklärung von VOLATILE kann unter https://blog.regehr.org/archives/28 gefunden werden. Hier ein kleiner Auszug:

For every read from a volatile variable by the abstract machine, the actual machine must load from the memory address corresponding to that variable. Also, each read may return a different value. For every write to a volatile variable by the abstract machine, the actual machine must store to the corresponding address. Otherwise, the address should not be accessed (with some exceptions) and also accesses to volatiles should not be reordered (with some exceptions).

Der generelle Gebrauch wird unter diesen Links gut beschrieben:

 1// NEGATIVE EXAMPLE
 2    // Main
 3        void main()
 4        {
 5            foo = 0;
 6 
 7            for(;;)
 8            {
 9                while(!foo);       // could be replace by while(true)
10                foo = 0;           // -> endless loop
11            }
12        }
13 
14    // ISR
15        int foo;                   // globally shared variable
16 
17        void isr()
18        {
19            // ...
20 
21            foo = 1;
22        }
23 
24// BAD PROGRAMMING STYLE
25    // Main
26        /* The function would need to load the variable from the
27           original location in case of external function declaration.
28           It doesn't know the preceding program code. Thus no
29           optimizations can be made.
30           Anyway in case the compiler optimization is more
31           sophisticated it may fail.
32        */
33        void wait()
34        {
35            while(!foo);
36        }
37 
38        // static void main()
39        // inline void wait()
40        /* In case of inline or static function declaration the
41           compiler already knows all preceding program code and
42           could optimize something.
43        */
44 
45        void main()
46        {
47            foo = 0;
48 
49            for(;;)
50            {
51                wait();
52                foo = 0;
53            }
54        }
55 
56    // ISR
57        int foo;                   // globally shared variable
58 
59        void isr()
60        {
61            // ...
62 
63            foo = 1;
64        }
 1// volatile integer
 2    volatile int foo;
 3    int volatile foo;
 4 
 5// pointer to volatile integer
 6    volatile int * pFoo;
 7    int volatile * pFoo;
 8 
 9// volatile pointer to volatile integer
10    volatile int * volatile pFoo;
11    int volatile * volatile pFoo;
12 
13// pointer to volatile struct
14    // separate volatile declaration for each variable
15        typedef struct
16        {
17        } foo_s;
18        foo_s volatile *pFoo = 0x10000;
19 
20        // disadvantage: have to be placed within
21        //               function declarations too
22        void doSmth(foo_s volatile *pFoo);
23 
24    // each variable declared volatile
25        typedef volatile struct
26        {
27        } foo_s;
28        foo_s *pFoo = 0x10000;
29 
30        // advantage: automatically added at each declaration
31        void doSmth(foo_s *pFoo);
32 
33// volatile struct member
34    typedef struct
35    {
36        volatile int a;
37        int          b;
38    } foo_s;
39 
40    typedef struct
41    {
42        int volatile a;
43        int          b;
44    } foo_s;
45 
46    // advantage: all other accesses can be optimized
47 
48// volatile const variable
49    const volatile int * p;
50 
51    // usage: the program isn't allowed to store to it,
52    //        however the register value may change from HW side

Beispiel für einen non-volatilen Buffer-Zugriff zw. ISR und Main

Bei einem Austausch über Double-Buffering passiert der Zugriff immer nur von einer Seite (Synchronisation vorausgesetzt). Das ermöglicht Compiler-Optimierungen, da ein direkter Lese/Schreib-Zugriff nicht mehr immer notwendig ist.

 1// Main
 2    void main()
 3    {
 4        int * local;
 5 
 6        collect  = &a;
 7        process  = &b;
 8        new_data = 0;
 9 
10        IE();               // interrupts enabled
11 
12        for(;;)
13        {
14            while(!new_data);
15            local = process;
16            new_data = 0;
17 
18            // read/write access
19            x = *local;
20            y = *local;     // could be optimized
21            *local = x/2;
22        }
23    }
24 
25// ISR
26    int a,b;                // double buffering
27    int * volatile collect;
28    int * volatile process;
29 
30    int volatile new_data;  // sync variable
31 
32    void isr()
33    {
34        int * local = collect;
35 
36        // ...
37 
38        *local  = y;
39        *local += 2;       // could be optimized
40 
41        // ...
42 
43        // swap buffer and inform main
44        // (main should already be within while-loop)
45        if (process == &a)
46        {
47            process = &b;
48            collect = &a
49        }
50        else
51        {
52            process = &a;
53            collect = &b;
54        }
55       new_data = 1;
56    }

Beispiel für einen volatilen Zugriff von non-volatile Variablen

Eine Variable, welche nur in der ISR verwendet wird, kann grundsätzlich non-volatile deklariert werden. Wird sie aber während der Initialisierung in der Main-Routine beschrieben, so muss sichergestellt werden, dass der Zugriff volatile passiert (direkter Zugriff an genau diesem Zeitpunkt) und die ISR noch nicht aufgerufen werden kann. Weiterführendes über volatile-casts kann unter https://bytes.com/topic/c/answers/221923-cast-volatile nachgelesen werden.

 1// Main
 2    void main()
 3    {
 4        *((int volatile *)&foo) = 1;
 5 
 6        IE();               // interrupts enabled
 7 
 8        // ...
 9    }
10 
11// ISR
12    int foo = 0;        // globally shared variable
13 
14    void isr()
15    {
16        if (foo == 0)
17        {
18            foo = 1;
19        }
20        else
21        {
22            foo = 0;
23        }
24 
25        // ...
26    }

Unterschiede bei der Kernel-Programmierung: