I don't know how to do it on a PC (if you find out, please do come back and post your own answer), but what you need is what I call "atomic access guards." In other words, you need a mechanism to force atomic access to a given variable for a set amount of time. What that means is that you essentially force all threads/processes to pause for a moment, while 1 and only 1 thread gets access to a variable. It then does its thing with the variable (ex: reads, modifies, writes to it), then re-enables the other threads when done. In this way, you guarantee atomic access to that variable by that thread during those operations. Now, all race conditions are solved.
In C, this is highly architecture-dependent I believe, and relies on C functions written in inline assembly code via something like the __asm
keyword, and/or it relies on setting bits in specific hardware registers to certain values in order to enforce certain behavior. Example of using the __asm
keyword: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.100748_0606_00_en/ddx1471430827125.html.
Sample of inline assembly code wrapped up in a C function:
int add(int i, int j)
{
int res = 0;
__asm ("ADD %[result], %[input_i], %[input_j]"
: [result] "=r" (res)
: [input_i] "r" (i), [input_j] "r" (j)
);
return res;
}
Once you have your "atomic access guard" functions to give you atomic access, you can do something like the following:
// atomic access guard ON
// Do whatever you want here: it's all atomic now!
// Read, modify, write, etc.
// - CAUTION: NO OTHER THREADS CAN RUN DURING THIS TIME, SO GET OUT OF THIS QUICKLY
// atomic access guard OFF
On single-core systems, such as the microcontrollers I'm familiar with (STM32 and AVR/Arduino), atomic access is ensured simply by turning off all interrupts. Ex: on ARM-core STM32 microcontrollers, do it as follows by using the necessary CMSIS (ARM-provided) functions:
// Read PRIMASK register, check interrupt status before you disable them
// Returns 0 if they are enabled, or non-zero if disabled
uint32_t prim = __get_PRIMASK();
// Disable interrupts
__disable_irq();
// Do some stuff here which can not be interrupted
// Enable interrupts back, but only if they were previously enabled (prevents nesting problems)
if (!prim)
{
__enable_irq();
}
Source: https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/
If using FreeRTOS (Free Real-Time Operating System), do the following:
taskENTER_CRITICAL() // This supports nested calls, and ends up calling `portDISABLE_INTERRUPTS()` anyway.
// do your atomic access here
taskEXIT_CRITICAL()
See: https://www.freertos.org/taskENTER_CRITICAL_taskEXIT_CRITICAL.html
If using AVR-core microcontrollers, such as the ATmega328 (basic Arduino Uno processor), do the following:
uint8_t SREG_bak = SREG; //save global interrupt state
cli(); //clear (disable) interrupts
//atomic variable access guaranteed here
SREG = SREG_bak; //restore interrupt state
See my answer here: https://stackoverflow.com/a/39693278/4561887
So now you need to go do some research (and please do post back), on how to enforce such a principle in C on your given operating system and/or architecture, and/or via some special calls to your kernel or something. This may even require that you write your own inline assembly to do this stuff, then wrap it up in a C function to call.
I look forward to seeing how you accomplish it.
Update: I just did some digging in the FreeRTOS source code to see how they disable interrupts, and here's what I found for the ARM Cortex M3 processor, such as STM32 microcontrollers, when using the GCC compiler:
From "FreeRTOSv9.0.0/FreeRTOS/Source/portable/GCC/ARM_CM3/portmacro.h":
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
portFORCE_INLINE static void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI;
__asm volatile
(
" mov %0, %1 \n" \
" msr basepri, %0 \n" \
" isb \n" \
" dsb \n" \
:"=r" (ulNewBASEPRI) : "i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY )
);
}