I am working on the RTOS support in our SW components.
There are some scenarios when it makes sense to create a copy of global variables into local variables in a critical section (e.g. protected by mutex) and then use the local copy further in time consuming operations outside of critical section.
I am afraid that C compiler might optimize the local variable assignments and use the global variable directly which would undermine the efforts of eliminating the race conditions from the code.
I wrote an oversimplified LCD display example code to illustrate the problem.
I have the following questions:
- How could it be guaranteed that the local variables won't be optimized?
- How could it be guaranteed that the order of lock-unlock and copy happens as intended?
- Would volatile type qualifier help in this case (local variables)?
uint8_t page_sel = 0;
char lcd_text[PAGE_CNT][ROW_CNT][COLUMN_CNT];
void lcd_print_text(uint8_t page, uint8_t row, const char* text)
{
lock();
// Store text in global variable which represents
// the text on the display
copy_text_to_lcd_text(page, row, text);
unlock();
// Display update request to run the lcd background task
refresh_semaphore_set();
}
void lcd_select_page(uint8_t page)
{
lock();
// Store the selected page
page_sel = page;
unlock();
// Display update request to run the lcd background task
// If the selected page changes then lcd shall be updated
refresh_semaphore_set();
}
void lcd_task(void)
{
while(1) {
// Update the display only if there are modifications
refresh_semaphore_get();
refresh();
}
}
void refresh(void)
{
char page_lcd_text[ROW_CNT][COLUMN_CNT]
uint8_t page;
lock();
// Page number and text shall be consistent
// so critical section is necessary
page = page_sel;
// Copy is created to avoid partial overwrites during
// display update
copy_page(page_lcd_text, lcd_text, page);
// It is essential to have a local copy before
// the critical section is left
unlock();
// Create pixel data in frame buffer from text (slow)
render_text(page_lcd_text);
// Create pixel data in frame buffer to display (slow)
// selected page number on display
render_page_num(page);
// Transfer pixel data to LCD driver chip (even slower)
lcd_spi_transfer();
}
lock(); local = global; unlock(); <use local>?lock()andunlock()should theoretically have memory barriers, that will ensure theglobalvalue is read between them and not elsewhere, so even iflocalis somehow optimized, the access to global is still in the locked section.asm("":::"memory"). It is telling the compiler that this instruction is clobbering memory (which is a lie, but the compiler must believe it), so it must ensure all of the memory operations that were performed before it must complete at this point. It serves two purposes - prevents the reordering of instructions before and after the barrier. So if you make it a part of your lock/unlock functionality it will make sure that the global is read between the two, and not elsewhere.