I am writing code for a single processor 32 bit microcontroller using gcc.
I need to consume time-stamped objects from a linked list. Another part of the code which could be asynchronous (maybe in an ISR) adds them to the list.
The critical section is implemented by turning interrupts off and using the barrier()
function.
I'm confused where gcc optimization could break my code by cacheing pointers to the list items (next most recent item to remove, list head, or free list). I dont want anything inside the while loop to be cached from a previous time around the loop. Will the memory barrier protect me from the compiler deciding to load a pointer once at the start of the function and never reload it again? All these list pointers might be modified in the critical section of the producer code (not shown). I am trying to understand if pqueue_first
should be a volatile pointer, for example.
Presumably, if there was no loop (which is the case for adding to the list), I am ok if all the code in a function is in a critical section?
Please don't just point me to some generic article about volatile or critical sections because I have read a bunch of them, but I am having trouble seeing how to apply it to this specific code. I understand that volatile ensures that the compiler will reload the variable every time it is referenced. But I don't 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
...
}
}
}
barrier
function is defined/standardized for all processors and operating systems. If not, then the second question is: where is the official documentation for the specific processor and operating system that you're using. – Baptistery__asm__ __volatile__ ("": : :"memory")
, as in the answer below. This is an AVR32. – Langland