Volatile (keyword)
The volatile
keyword gives an indication to the compiler/optimizer that it should always perform a read or write to a variable or memory without caching it locally. It also ensures that the order of changes is preserved and not altered by the optimizer, and that apparently redundant code is not optimized away. The keyword however doesn't guarantee that memory access isn't reordered by the processor on runtime. To fix possible issues due to this, make sure you use atomic operations or memory barriers to manipulate memory.
The basics
The volatile
keyword can be used in type definitions as well as function arguments, variable definitions and typecasts. Similar to the const
keyword, it either makes the variable itself or the data it points to volatile
, or both.
/* Ensures the changes to/reads from x are always performed */
volatile int x;
/* Ensures that changes to/reads from the data that ptr points to
(but not the value itself) are always performed */
volatile int *ptr;
int volatile *ptr;
/* Ensures that changes to/reads from the pointer ptr (but not the
data it points to) are always performed */
int * volatile ptr;
/* Ensures that changes to/reads from the pointer ptr and also
the data it points to are always performed */
volatile int * volatile ptr;
int volatile * volatile ptr;
Examples
Loops
This is an example of a function that is supposed to create a very short delay, but might be optimized away by the compiler entirely because to the optimizer this code seems redundant:
static void some_delay(void)
{
int i;
/* Should be declared as '''volatile int i;''' to prevent
the loop or even the entire function from being optimized
away */
for (i = 0; i < 100; i++)
{
/* Do nothing, just wait a little while so that
some hardware can respond. Using this technique
is rarely neccessary and should be avoided! */
}
}
This example is supposed to poll a byte until it is 0
(e.g. waiting for a Spinlock to be released). Obviously another thread is supposed to change that memory, but since the compiler has no clue about this, we need to ensure that the code isn't optimized away in any case:
typedef unsigned char * spin_lock;
/* Should be defined as '''typedef volatile unsigned char * spin_lock;'''
to prevent reading/writing to spin_lock variables being cached
in CPU registers! */
static void poll_spinlock(spin_lock lock)
{
while (*lock != 0)
{
/* Do nothing, just poll... */
}
}
/* Alternatively, if spin_lock wasn't defined as a pointer to volatile data
you can use it in a typecast instead: */
static void poll_spinlock(spin_lock lock)
{
while (*(volatile unsigned char *)lock != 0)
{
/* Do nothing, just poll... */
}
}
Dereferencing memory
This is an example of code that is supposed to touch a piece of memory (e.g. to make it become resident or to check if it's valid and doesn't raise a page fault):
static void touch_mem(void *ptr)
{
char *data = (char *)ptr;
/* Should be declared as '''volatile char *data = (volatile char *)ptr;'''
to prevent the code from being optimized away */
/* It is assumed that the memory is not used by other threads at the same time */
*data = *data;
}
Also see the APIC example code where the volatile
keyword is crucial (reading/writing to hardware registers).
Suppose you have one thread "A" that loops until another thread "B" wants thread "A" to terminate gracefully. They share a structure with a field "terminate" that thread "A" polls to see if another thread wants it to terminate. Unless that field or the entire structure is declared volatile
, there's no guarantee that the code works as expected because the compiler/optimizer has no clue about that field being touched by someone else while looping. So it could generate code that reads the value once and caches it, resulting in an infinite loop. Use volatile
to prevent wrong code being generated:
typedef struct {
int terminate; /* Should be volatile int terminate; */
} shared_thread_data;
/* This is the code run by thread "B" to get thread "A" to terminate gracefully */
void stop_worker_thread(shared_thread_data *thread)
{
thread->terminate = 1;
/* Techni<cally incorrect, you should use a function that guarantees
an atomic operation (e.g. lock xchg)
}
/* This is the thread "A" loop */
void worker_thread_body(shared_thread_data *thread)
{
while (thread->terminate == 0)
{
/* Do some work here... */
}
}