Do I need to use the volatile keyword to access memory in a critical section?

I am writing code for a 32-bit single-processor microcontroller using gcc.

I need to use timestamped objects from a linked list. Another piece of code that may be asynchronous (possibly in ISR) adds them to the list.

The critical section is implemented by disabling interrupts and using the barrier() function.

I am confused when gcc optimization can break my code by caching pointers to list items (the next last item to delete, a list of headers or a free list). I do not want the previous cycle time to be cached inside the while loop. Will the memory barrier protect me from the compiler by deciding to load the pointer once at the beginning of the function and not reload it again? All of these list pointers can be changed in the critical section of the manufacturer code (not shown). I am trying to figure out if pqueue_first should be a volatile pointer, for example.

Presumably, if there was no loop (which is suitable for adding to the list), am I fine if all the code in the function is in the critical section?

Please do not just point me to some general article about volatile or critical sections, because I read a bunch of them, but I had problems with how to apply it to this particular code. I understand that volatile ensures that the compiler will reload the variable each time it is referenced. But I do not understand the likely scope of optimization and its interaction with memory barriers.

 typedef struct { EV_EventQueueEntry_t *pqueue_alloc; // allocation (never changes) EV_EventQueueEntry_t *pqueue_head; // head of active queue (ISR can change it) EV_EventQueueEntry_t *pqueue_free; // head of free list (ISR can change it) EV_EventQueueEntry_t *pqueue_first; // soonest item in queue (ISR can change it) EV_EventQueueEntry_t *pqueue_first_prev; // back pointer from soonest item (ISR can change it) EV_UInt_t max_event_count; } EV_EventQueue_t; void RunLoop(EV_EventQueue_t *pev) { while(not timeout) { // Enter critical section disable_interrupts(); barrier(); // item with most recent timestamp // this can be changed by ISR add to queue operation EV_EventQueueEntry_t *pfirst = pev->pqueue_first; if(pfirst!=NULL && EV_PortIsFutureTime(pfirst->event.timestamp, EV_PortGetTime())) { // Copy out message EV_Event_t e = pfirst->event; // Remove event from queue if(pev->pqueue_first_prev != NULL) pev->pqueue_first_prev->pnext = pfirst->pnext; else pev->pqueue_head = pfirst->pnext; // Put event back on free list pfirst->pnext = pev->pqueue_free; pev->pqueue_free = pfirst; pfirst->event.message.type = EV_MESSAGE_NULL; // Find next soonest message to process after this one pev->pqueue_first = ...; pev->pqueue_first_prev = ...; // back pointer // Exit critical section barrier(); enable_interrupts(); // Dispatch message ... } else { // Exit critical section barrier(); enable_interrupts(); // waste some time ... } } } 
+2
source share
2 answers

C ++ 11 has a standard function for this: std::atomic_signal_fence . C11 has a similar function without a namespace qualifier. It is suitable if your program uses only one thread, and you are just trying to stop the compiler from moving goods / storages through the fence. Use std::atomic_signal_fence(memory_order_acquire) before the critical section and std:atomic_signal_fence(memory_order_release) after the critical section.

If you are not using C ++ 11 or C11, but just using gcc or a compiler that understands gms asms, you can use __asm__ __volatile__ ("": : :"memory") for the compiler barrier. Asm says that it cannot be deleted and threatens to modify the memory in mysterious ways, so the compiler will not be able to move loads / stores on top of it.

+4
source

The word volatile tells the compiler to retrieve a value from memory, not from the cache, and store the values ​​in memory, not in the cache. This is done when several cores can work in the same memory, so you do not have to guarantee that the cache is fresh.

In your case, the ISR changes memory, so any variables that it refers to should be marked as mutable. This is not a compiler optimization that will cause a problem. The problem is caused by the context switch that occurs when your ISR occurs. The state of the processor (register value) is maintained after the ISR is restored, which means that the values ​​in the registers will be the same as they were before the ISR.

+2
source

Source: https://habr.com/ru/post/1495605/


All Articles