Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






The volatile Keyword

The volatile keyword in C indicates that the value associated with a variable can be changed by something outside the control of the current thread.

The compiler reads the value of a volatile variable from memory each time the variable is referenced and writes the value of the variable to memory each time it is assigned. Without the volatile keyword, the compiler might optimize access to the variable by combining read or write operations or by reordering references to the variable in code.

Consider the following code snippet:

int Var1, Var2;

 

Var1=0;

Var2=1;

/* more code intervenes

.

.

.

Var1=Var1+Var2;

 

In the example, neither Var1 nor Var2 is declared as volatile. Therefore, after the compiler writes these variables according to the first two assignment statements, it might not read them again before it performs the addition in the last statement. If neither value changes in the meantime, this optimization works without error. However, if the value of either Var1 or Var2 is modified elsewhere in the driver after the current code assigns a value, the addition might be performed with stale or incorrect data.

Assume that Var2 is changed by another driver routine that is running concurrently. The following snippet would then ensure that the compiler reads the current value of Var2 before adding it to Var1:

int Var1;

volatile int Var2;

 

Var1=0;

Var2=1;

/* more code intervenes

.

.

.

Var1=Var1+Var2;

 

In addition, declaring a variable as volatile prevents the compiler from reordering references to that variable relative to any other volatile variables. However, it does not prevent the reordering of references to nonvolatile variables relative to the volatile variable. Consider the following example:

volatile ULONG Vol1, Vol2;

ULONG NonVol;

 

Vol1 = 1;

NonVol = 2;

Vol2 = 3;

 

Vol1 and Vol2 are declared volatile, and NonVol is not. Given these declarations, the compiler could generate code that performs the assignments in the following order:

NonVol = 2;

Vol1 = 1;

Vol2 = 3;

 

In this case, the compiler has reordered the assignment to NonVol so that it precedes the assignments to Vol1 and Vol2. The assignments to Vol1 and Vol2 remain in the same relative positions; that is, the assignment to Vol1 still precedes the assignment to Vol2. The following order is also valid:

Vol1 = 1;

Vol2 = 3;

NonVol = 2;

 

Here the compiler has reordered the assignment to NonVol so that it follows the assignments to Vol1 and Vol2. As in the preceding example, the assignments to Vol1 and Vol2 remain in the same relative positions; that is, the assignment to Vol1 still precedes the assignment to Vol2. The compiler would not, however, reorder the assignments as follows:

NonVol = 2;

Vol2 = 3;

Vol1 = 1;

 

This ordering is not valid because the assignment to Vol2 now precedes the assignment to Vol1. The compiler always keeps references to volatile variables in the same relative order.



Details of how to implement the volatile keyword are not specified in the ANSI language standards, but are left to compiler developers. Therefore, implementations may vary from compiler to compiler. Never assume that every C compiler—or even every version of a single manufacturer’s C compiler—implements volatile in the same way.

The Microsoft C compiler generates code for each assignment to or reference of a volatile variable, even if the code appears to have no effect. If you declare a variable as volatile, the compiler considers it volatile for the lifetime of the variable. You can also cast a single reference as volatile; in this case, the compiler is guaranteed to generate code for that reference only.

Consider the following example, which defines a queue of items:

typedef struct GENERIC_QUEUE {

struct GENERIC_QUEUE * volatile Next;

PQUEUE_ITEM volatile QueueItem;

} MY_QUEUE, *PMY_QUEUE;

 

In this structure, Next points to the next item in the queue, so its value changes whenever an item is added or deleted from the queue. Next is declared volatile because concurrent driver routines may read or write the value. The QueueItem field, which is a pointer to the current item, is declared volatile for the same reason. The volatile keyword ensures that the compiler generates a read or write instruction, as appropriate, each time the field is read or written.

However, declaring these fields as volatile is not sufficient to ensure proper driver operation in all situations. For example, if the driver adds an item to the queue and increments the Next pointer, it must lock the queue to ensure that both pointers are updated together, in a single, atomic operation.

In drivers, the volatile keyword is most useful in the following situations:

· A single variable of the native machine size (32 bits wide on 32-bit hardware or 64 bits wide on 64-bit hardware) has one writer and multiple readers. Because the variable is the native machine size, the processor reads and writes it in an atomic operation; such reads and writes do not require synchronization. However, because the writer and one or more readers can run concurrently, it must nevertheless be read every time it is used.

· Instead of using the READ_REGISTER_* and WRITE_REGISTER_* macros, some drivers create a structure that mimics the layout of the device’s hardware registers and call MmMapIoSpace to map the structure on top of the registers themselves. To read and write the device registers, the driver simply reads and writes the members of the structure. However, this technique does not work correctly on hardware that reorders memory accesses. To ensure working code, the driver must declare each member of the structure as volatile (to prevent compiler reordering) and use KeMemoryBarrier (to prevent hardware reordering) between any two accesses that must occur in program order. See “Windows Kernel-Mode Memory Barrier Routines” for details.

 

If you look at the sample drivers shipped with the Windows DDK, you will see that volatile appears infrequently. In general, volatile is of limited use in driver code for the following reasons:

· Using volatile prevents optimization only of the volatile variables themselves. It does not prevent optimizations of nonvolatile variables relative to volatile variables. For example, a write to a nonvolatile variable that precedes a read from a volatile variable in the source code might be moved to execute after the read.

· Using volatile does not prevent the reordering of instructions by the processor hardware.

· Using volatile correctly is not enough on a multiprocessor system to guarantee that all CPUs see memory accesses in the same order.

Windows synchronization mechanisms are more useful in preventing all these potential problems.


Date: 2015-12-24; view: 740


<== previous page | next page ==>
Reentrant and Concurrent Routines | Memory Barrier Semantics
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.007 sec.)