Thursday, December 22, 2011

Volatile Qualifier in C

Describe the syntax and usage of VOLATILE qualifier in C

It is intended to prevent the compiler from applying any optimizations on objects that can change in ways that cannot be determined by the compiler.Objects declared as volatile are omitted from optimization because their values can be changed by code outside the scope of current code at any time. The system always reads the current value of a volatile object from the memory location rather than keeping its value in temporary register at the point it is requested, even if a previous instruction asked for a value from the same object.

How can value of a variable change in such a way that compiler cannot predict. 

This is due to problems encountered in real-time or embedded systems programming using C.Imagine that you are writing code that controls a hardware device by placing appropriate values in hardware registers at known absolute addresses.
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 the 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;    /* control & status */
        unsigned short  data;   /* data port */
};

/* bit patterns in the csr */
#define ERROR   0x1
#define READY   0x2
#define RESET   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 *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);
}
The technique of using a structure declaration to describe the device register layout and names is very common practice. Notice that there aren't actually any objects of that type defined, so the declaration simply indicates the structure without 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 instead.
However, a major problem with previous C compilers would be in the while loop which tests the status register and waits for the ERROR or READY bit to come on. Any self-respecting optimizing compiler would notice that the loop tests the same memory address over and over again. It would almost certainly arrange to reference 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 with 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 reference to such an object to be a genuine reference.
Here is how you would rewrite the example, making use of const and volatile to get 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 in the csr */
#define ERROR   0x1
#define READY   0x2
#define RESET   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);
}
Important points:
1.The rules about mixing volatile and regular types resemble those for const. A pointer 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 pointer results in undefined behaviour.

2.If an array, union or structure is declared with const or volatile attributes, then all of the members take on that attribute too. This makes sense when you think about it—how could a member of a const structure be modifiable?
That means that an alternative rewrite of the last example would be possible. Instead of declaring the device registers to be volatile in the structure, the pointer could have been declared to point to a volatile structure instead, like this:
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 is not permitted to optimize references through the pointer. Our feeling is that, although this would work, it is bad style. The volatile declaration belongs in the structure: it is the device registers which are volatile and that is where the information should be kept
So, for any object likely to be subject to modification either by hardware or asynchronous interrupt service routines, the volatile type qualifier is important.

3.A declaration like this:
volatile struct devregs{
      /* stuff */
}v_decl;
declares the type struct devregs and also a volatile-qualified object of that type, 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 two 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 */

Usage in different scenarios:
1) Global variables modified by an interrupt service routine outside the scope: For example, a global variable can represent a data port (usually global pointer referred as memory mapped IO) which will be updated dynamically. The code reading data port must be declared as volatile in order to fetch latest data available at the port. Failing to declare variable as volatile, the compiler will optimize the code in such a way that it will read the port only once and keeps using the same value in a temporary register to speed up the program (speed optimization). In general, an ISR used to update these data port when there is an interrupt due to availability of new data
2) Global variables within a multi-threaded application: There are multiple ways for threads communication, viz, message passing, shared memory, mail boxes, etc. A global variable is weak form of shared memory. When two threads sharing information via global variable, they need to be qualified with volatile. Since threads run asynchronously, any update of global variable due to one thread should be fetched freshly by another consumer thread. Compiler can read the global variable and can place them in temporary variable of current thread context. To nullify the effect of compiler optimizations, such global variables to be qualified as volatile
If we do not use volatile qualifier, the following problems may arise
1) Code may not work as expected when optimization is turned on.
2) Code may not work as expected when interrupts are enabled and used.

Important Links
http://www.barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword
http://embeddedgurus.com/barr-code/2009/03/coding-standard-rule-4-use-volatile-whenever-possible/
http://embeddedgurus.com/barr-code/2012/01/combining-cs-volatile-and-const-keywords/

4 comments:

  1. http://www.geeksforgeeks.org/archives/15764

    ReplyDelete
  2. Volatile--Used in Multi threaded programs
    http://drdobbs.com/cpp/184403766

    ReplyDelete
  3. where is volatile variables stored in memory layout ?

    In regards to storage, there is no difference between variables declared as volatile and non volatile variables. So the variable may be on stack,heap or in the data section of executable, depending on how it gets defined. The volatile qualifier just tells the compiler that this variable may change in ways that are not apparent to you (for example by another thread of execution etc). The compiler then disables some optimizations(like caching the variable in a register).

    ReplyDelete
  4. Can a variable be declared with both the 'const' and 'volatile' qualifiers ?

    we can have constant volatile variable.

    In this current context of code will not change the value of the variable but out side of the program i.e. hardware registers can change it.
    Registers are READ ONLY.

    ReplyDelete