Вы находитесь на странице: 1из 7

Introduction

Volatile is a qualifier that is applied to a variable when it is declared. It te


lls the compiler that the value of the variable may change at any time-without a
ny action being taken by the nearby code. The implications of this are quite ser
ious. Let's take a look at the syntax.
A variable should be declared volatile whenever its value could change unexpecte
dly. You should consider the following cases for using volatile variable :
1.Memory-mapped peripheral registers
2.Global variables modified by an interrupt service routine
3.Global variables within a multi-threaded application
If we do not use volatile qualifier the following problems may arise:
1.Code that works fine-until you turn optimization on
2.Code that works fine-as long as interrupts are disabled
3.Flaky hardware drivers
4.Tasks that work fine in isolation-yet crash when another task is enabled
Example:
static int var;
void test(void)
{
var = 0;
while (var != 255)
continue;
}
The above code sets the value in var to 0. It then starts to poll that value in
a loop until the value of var becomes 255.
An optimizing compiler will notice that no other code can possibly change the va
lue stored in 'var', and therefore assume that it will remain equal to 0 at all
times. The compiler will then replace the function body with an infinite loop,sim
ilar to this:
void test_opt(void)
{
var = 0;
while (TRUE)
continue;
}
Declaration of Volatile variable
Include the keyword volatile before or after the data type in the variable.
volatile int var;
int volatile var;
Pointer to a volatile variable
volatile int * var;
int volatile * var;
Above statements implicate 'var' is a pointer to a volatile integer.
Volatile pointers to non-volatile variables
int * volatile var; --> Herevar is a volatile pointer to a non-volatile variable/
object. This type of pointer are very rarely used in embedded programming.
Volatile pointers to volatile variables
int volatile * volatile var;
If we qualify astruct or union with avolatile qualifier, then the entire contents
of the struct/union becomes volatile. We can also apply the volatile qualifier t
o the individual members of the struct/union.
Usages of volatile qualifier
Peripheral registers
Most embedded systems consist of a handful of peripherals devices. The value of
the registers of these peripheral devices may change asynchronously. Lets say th
ere is an 8-bit status register at address 0x1234 in any hypothetical device. Wh
at we need to do is to poll this status register until it becomes non-zero. The
following code snippet is an incorrect implementation of this scenario/requireme
nt:
UINT1 * ptr = (UINT1 *) 0x1234;

// Wait for register to become non-zero.


while (*ptr == 0);
// Do something else.
Now no code in proximity attempts to change the value in the register whose addr
ess(0x1234) is kept in the 'ptr' pointer. A typical optimizing compiler(if optim
ization is turned ON) will optimize the above code as below:
movptr, #0x1234 -> move address 0x1234 to ptr
mova, @ptr -> move whatever stored at 'ptr' to accumulator
loop bz loop -> go into infinite loop
What the assumes while optimizing the code is easy to interpret. It simply takes
the value stored at the address location 0x1234(which is stored in 'ptr') into
accumulator and it never updates this value as because apparently the value at the
address 0x1234 never gets changed(by any nearby code). So, as the code suggests,
the compiler replaces it with an infinite loop (comparing the initial zero valu
e stored at the address 0x1234 with a constant 'zero'). As the value stored at t
his address would initially be zero and it is never updated, this loop goes fore
ver. The code beyond this point would never get executed and the system would go
into a hanged state.
So what we essentially need to do here is to force the compiler to update the va
lue stored at the address 0x1234 whenever it does the comparison operation. The
volatile qualifier does the trick for us. Look at the code snippet below:
UINT1 volatile * ptr = (UINT1 volatile *) 0x1234;
The assembly for the above code should be:
mov ptr, #0x1234 -> move the address 0x1234 to ptr
loopmova, @ptr -> move whatever stored @address to accumulator
bzloop -> branch to loop if accumulator is zero
So now at every loop the actual value stored at the address 0x1234(which is stor
ed in the 'ptr') is fetched from the peripheral memory and checked whether it's
zero or non-zero; as soon as the code finds the value to be non-zero the loop br
eaks. And that's what we wanted.
Subtler problems tend to arise with registers that have special properties. For
instance, a lot of peripherals contain registers that are cleared simply by read
ing them. Extra (or fewer) reads than you are intending can cause quite unexpect
ed results in these cases.
ISR(Interrupt Service Routine)
Sometimes we check a global variable in the main code and the variable is only c
hanged by the interrupt service routine. Lets say a serial port interrupt tests
each received character to see if it is an ETX character (presumably signifying
the end of a message). If the character is an ETX, theserial portISR sets a partic
ular variable, say 'etx_rcvd'. And from the main code somewhere else this 'etx_r
cvd' is checked in a loop and untill it becomes TRUE the code waits at this loop
.Now lets check the code snippet below:
int etx_rcvd = FALSE;
void main()
{
...
while (!ext_rcvd)
{
// Wait
}
...
}
interrupt void rx_isr(void)
{
...
if (ETX == rx_char)
{
etx_rcvd = TRUE;

}
...
}
This code may work with optimization turned off. But almost all the optimizing c
ompiler would optimize this code to something which is not intended here. Becaus
e the compiler doesn't even have any hint that etx_rcvd can be changed outside t
he code somewhere( as we saw within the serial port ISR). So the compiler assume
s the expression !ext_rcvdwould always be true and would replace the code with in
finite loop. Consequently the system would never be able to exit the while loop.
All the code after the while loop may even be removed by the optimizer or never
be reached by the program. Some compiler may throw a warning, or some may not,
depends completely on the particular compiler.
The solution is to declare the variable etx_rcvd to be volatile. Then all of you
r problems (well, some of them anyway) will disappear.
Multi-threaded applications
Often tasks/threads involved in a multi-threaded application communicate via a s
hared memory location i.e. through a global variable. Well, a compiler does not
have any idea about preemptive scheduling or to say, context switching or whatso
ever. So this is sort of same problem as we discussed in the case of an interrup
t service routine changing the peripheral memory register. Embedded Systems Prog
rammer has to take care that all shared global variables in an multi threaded en
vironment be declared volatile. For example:
int cntr;
void task1(void)
{
cntr = 0;
while (cntr == 0)
{
sleep(1);
}
...
}
void task2(void)
{
...
cntr++;
sleep(10);
...
}

This code will likely fail once the compiler's optimizer is enabled. Declaring '
cntr' to be volatile is the proper way to solve the problem.
Some compilers allow you to implicitly declare all variables as volatile. Resist
this temptation, since it is essentially a substitute for thought. It also lead
s to potentially less efficient code.
Can you have constant volatile variable? You can have a constant pointer to a vo
latile variable but not a constant volatile variable.
Consider the following two blocks of a program, where second block is the same a
s first but with volatile keyword. Gray text between lines of C code means i386/
AMD64 assembler compiled from this code.
{
BOOL flag = TRUE;
while( flag );
repeat:
jmp repeat
}
{
volatile BOOL flag = TRUE;
movdword ptr [flag], 1

while( flag );
repeat:
moveax, dword ptr [flag]
testeax, eax
jnerepeat
}
In first block variable 'flag' could be cached by compiler into a CPU register,
because it does not havevolatile qualifier. Because no one will change value at a
register, program will hang in an infinite loop (yes, all code below this block
is unreachable code, and compiler such as Microsoft Visual C++ knows about it).
Also this loop was optimized in equivalent program with the same infinite loop,
but without involving variable initialization and fetching. 'jmp label' means t
he same as 'goto label' in C code.
Second block have volatile qualifier and have more complex assembler output (initial
izing 'flag' with 'mov' instruction, in a loop fetching this flag into CPU regis
ter 'eax' with a 'mov' instruction, comparing fetched value with zero with 'test
' instruction, and returning to the beginning of the loop if 'flag' was not equa
l to zero. 'jne' means 'goto if not equal'). This is all because volatile keywor
d prohibits compiler to cache variable value into CPU register, and it is fetche
d in all loop iterations. Such code is not always is an infinite loop, because a
nother thread in the same program potentially could change value of variable 'fl
ag' and first thread will exit the loop.
It is important to understand that volatile keyword is just a directive for compiler
and it works only at a compile-time. For example, the fact of using interlocked
operation differs from just a compiler option, since special assembler commands
are produced. Thus, interlocked instructions are most like to hardware directiv
es, and they work at a run-time.
A volatile qualifier must be used when reading the contents of a memory location
whose value can change unknown to the current program.
A volatile qualifier must be used for shared data modified in signal handlers or
interrupt service routines.

After const, we treat volatile. The reason for having this type qualifier is mai
nly to do with the problems that are encountered in real-time or embedded system
s programming using C. Imagine that you are writing code that controls a hardwar
e device by placing appropriate values in hardware registers at known absolute a
ddresses.
Let's imagine that the device has two registers, each 16 bits long, at ascending
memory addresses; the first one is the control and status register (csr) and th
e second is a data port. The traditional way of accessing such a device is like
this:
/* Standard C example but without const or volatile */
/*
* Declare the device registers
* Whether to use int or short
* is implementation dependent
*/
struct devregs{
unsigned short csr;
unsigned short data;
};
/* bit patterns
#define ERROR
#define READY
#define RESET

in the csr */
0x1
0x2
0x4

/* control & status */


/* data port */

/* absolute address of the device */


#define DEVADDR ((struct devregs *)0xffff0004)
/* number of such devices in system */
#define NDEVS 4
/*
* Busy-wait function to read a byte from device n.
* check range of device number.
* Wait until READY or ERROR
* if no error, read byte, return it
* otherwise reset error, return 0xffff
*/
unsigned int read_dev(unsigned devno){
struct devregs *dvp = DEVADDR + devno;
if(devno >= NDEVS)
return(0xffff);
while((dvp->csr & (READY | ERROR)) == 0)
; /* NULL - wait till done */
if(dvp->csr & ERROR){
dvp->csr = RESET;
return(0xffff);
}
return((dvp->data) & 0xff);
}
Example8.4
The technique of using a structure declaration to describe the device register l
ayout and names is very common practice. Notice that there aren't actually any o
bjects of that type defined, so the declaration simply indicates the structure w
ithout using up any store.
To access the device registers, an appropriately cast constant is used as if it
were pointing to such a structure, but of course it points to memory addresses i
nstead.
However, a major problem with previous C compilers would be in the while loop wh
ich tests the status register and waits for the ERROR or READY bit to come on. A
ny self-respecting optimizing compiler would notice that the loop tests the same
memory address over and over again. It would almost certainly arrange to refere
nce memory once only, and copy the value into a hardware register, thus speeding
up the loop. This is, of course, exactly what we don't want; this is one of the
few places where we must look at the place where the pointer points, every time
around the loop.
Because of this problem, most C compilers have been unable to make that sort of
optimization in the past. To remove the problem (and other similar ones to do wi
th when to write to where a pointer points), the keyword volatile was introduced
. It tells the compiler that the object is subject to sudden change for reasons
which cannot be predicted from a study of the program itself, and forces every r
eference to such an object to be a genuine reference.
Here is how you would rewrite the example, making use of const and volatile to g
et what you want.
/*
* Declare the device registers
* Whether to use int or short
* is implementation dependent
*/

struct devregs{
unsigned short volatile csr;
unsigned short const volatile data;
};
/* bit patterns
#define ERROR
#define READY
#define RESET

in the csr */
0x1
0x2
0x4

/* absolute address of the device */


#define DEVADDR ((struct devregs *)0xffff0004)
/* number of such devices in system */
#define NDEVS 4
/*
* Busy-wait function to read a byte from device n.
* check range of device number.
* Wait until READY or ERROR
* if no error, read byte, return it
* otherwise reset error, return 0xffff
*/
unsigned int read_dev(unsigned devno){
struct devregs * const dvp = DEVADDR + devno;
if(devno >= NDEVS)
return(0xffff);
while((dvp->csr & (READY | ERROR)) == 0)
; /* NULL - wait till done */
if(dvp->csr & ERROR){
dvp->csr = RESET;
return(0xffff);
}
return((dvp->data) & 0xff);
}
Example8.5
The rules about mixing volatile and regular types resemble those for const. A po
inter to a volatile object can be assigned the address of a regular object with
safety, but it is dangerous (and needs a cast) to take the address of a volatile
object and put it into a pointer to a regular object. Using such a derived poin
ter results in undefined behaviour.
If an array, union or structure is declared with const or volatile attributes, t
hen all of the members take on that attribute too. This makes sense when you thi
nk about ithow could a member of a const structure be modifiable?
That means that an alternative rewrite of the last example would be possible. In
stead of declaring the device registers to be volatile in the structure, the poi
nter could have been declared to point to a volatile structure instead, like thi
s:
struct devregs{
unsigned short csr;
/* control & status */
unsigned short data; /* data port */
};
volatile struct devregs *const dvp=DEVADDR+devno;
Since dvp points to a volatile object, it not permitted to optimize references t

hrough the pointer. Our feeling is that, although this would work, it is bad sty
le. The volatile declaration belongs in the structure: it is the device register
s which are volatile and that is where the information should be kept; it reinfo
rces the fact for a human reader.
So, for any object likely to be subject to modification either by hardware or as
ynchronous interrupt service routines, the volatile type qualifier is important.
Now, just when you thought that you understood all that, here comes the final tw
ist. A declaration like this:
volatile struct devregs{
/* stuff */
}v_decl;
declares the type struct devregs and also a volatile-qualified object of that ty
pe, called v_decl. A later declaration like this
struct devregs nv_decl;
declares nv_decl which is not qualified with volatile! The qualification is not
part of the type of struct devregs but applies only to the declaration of v_decl
. Look at it this way round, which perhaps makes the situation more clear (the t
wo declarations are the same in their effect):
struct devregs{
/* stuff */
}volatile v_decl;
If you do want to get a shorthand way of attaching a qualifier to another type,
you can use typedef to do it:
struct x{
int a;
};
typedef const struct x csx;
csx const_sx;
struct x non_const_sx = {1};
const_sx = non_const_sx;

/* error - attempt to modify a const */

Вам также может понравиться