close

【C語言內"volatile"的用法和功用】

當物件宣告包含 volatile 修飾詞時,宣告所引入的物件為 Volatile 物件。

對 於非 Volatile 物件,重新排列指令的最佳化技巧,可能會在多重執行緒的程式中導致未預期與無法預料的結果,因為這類程式無法使用如 lock 陳述式所提供的同步化來存取物件。這些最佳化可由編譯器、Runtime 系統或硬體來執行。對於 Volatile 物件,這類重新排列最佳化有下列限制:

讀取 Volatile 物件稱為 Volatile 讀取。Volatile 讀取具有「取得語意」;即保證 Volatile 讀取會發生在任何對記憶體進行參考之前,而任何參考是依指令順序發生在 Volatile 讀取之後。

寫入 Volatile 物件稱為 Volatile 寫入。Volatile 寫入具有「釋放語意」;即保證 Volatile 寫入發生於任何記憶體進行參考之後,而任何記憶體參考則是依指令順序發生在 Volatile 寫入之前。

這些限制可確保所有的執行緒,將能夠觀察到任何其他執行緒都依其執行順序執行 Volatile 寫入。遵循此限制的實作並不需要提供單一整體的 Volatile 寫入順序,就如同執行的所有執行緒所見。Volatile 物件的型別必須為下列之一:

參考型別。

型別 byte、sbyte、short、ushort、int、uint、char、float 或 bool。
具有列舉基底型別 byte、sbyte、short、ushort、int 或 uint 的列舉型別。


下列範例

using System;
using System.Threading;
class Test
{
  

public static int result;
public static volatile bool finished;
static void Thread2() {

 result = 143;
finished = true;

 }

static void Main() {

finished = false;
// Run Thread2() in a new thread
new Thread(new ThreadStart(Thread2)).Start();
// Wait for Thread2 to signal that it has a result by setting
// finished to true.
for (;;) {

if (finished) {

Console.WriteLine("result = {0}", result);
return;

}

}

}

}

會產生下列輸出:

result = 143


在 此範例中,方法 Main 將啟動新的執行緒來執行方法 Thread2。此方法會將數值儲存到稱為 result 的非 Volatile 物件內,接著將 true 儲存於 finished 的 Volatile 物件內。主執行緒將等待物件 finished 設成 true後,再接著讀取物件 result。因為 finished 已宣告成 volatile,主執行緒必須從物件 result 中讀取數值 143。若物件 finished 未宣告成 volatile,主執行緒就可能在看到存至 finished 之後,才會看到儲存至 result,因此主執行緒會從物件 result 中讀到數值 0。將 finished 宣告成 volatile 物件可以防止這類不一致的狀況發生。


Volatile這玩意兒用在單晶片的C語言較多
例如:Volatile char wait;

void xxx(void)
{
wait=1;
while (wait!=0);
.....
.....
}
void timer0(void) interrupt 1
{
wait=0;
.......
}


xxx()中就是等一個timer中斷才執底下工作.
如果不寫Volatile會被編譯器省略掉.
因為wait=1;何來wait會等於0


"volatile"是一個關鍵字(keyword),用來修飾詞資料型態,與const有對應的關係。
是語言關鍵字的話,就不會出現在標頭檔內的定義。
"volatile sig_atomic_t read_flag = 1;"
read_flag是一個為sig_atomic_t的資料型態,透過volatile的修飾,說明sig_atomic_t在程式中,可以在任何的時間下被個別的 thread 所修改
也就是說,你的程式可能同一時間不止一個程序在使用。
可能主程式main在使用的同時,I/O Device或另一個thread也在利用或指定這個read_flag 來透過溝通的作用。


【"關於volatile關鍵字的說明以及測試"】

volatile關鍵字是一種類型修飾符,用它宣告的類型變數表示可以被某些編譯器未知的因素更改,比如:作業系統、硬體或者其他執行緒等。遇到這個關鍵字宣告的變數,編譯器對存取該變數的程式碼就不再進行最佳化,從而可以提供對特殊位址的穩定存取。

使用該關鍵字的例子如下:

int volatile nVint;

  當要求使用volatile 宣告的變數的值的時候,系統總是重新從它所在的記憶體讀取資料,即使它前面的指令剛剛從該處讀取過資料。而且讀取的資料立刻被保存。

例如:

volatile int i=10;
int a = i;

...
//其他代碼,並未明確告訴編譯器,對i進行過操作

int b = i;


  volatile 指 出 i是隨時可能發生變化的,每次使用它的時候必須從i的位址中讀取,因而編譯器生成的組合語言程式碼會重新從i的位址讀取資料放在b中。而最佳化做法是,由 於編譯器發現兩次從i讀數據的程式碼之間的程式碼沒有對i進行過操作,它會自動把上次讀的資料放在b中。而不是重新從i裡面讀。這樣以來,如果i是一個寄 存器變數或者表示一個埠資料就容易出錯,所以說volatile可以保證對特殊位址的穩定存取。
  注意,在vc6中,一般偵錯模式沒有進行程式碼最佳化,所以這個關鍵字的作用看不出來。下面通過插入組合語言程式碼,測試有無volatile關鍵字,對程式最終程式碼的影響:
  
  首先,用classwizard建一個win32 console專案,插入一個voltest.cpp檔,輸入下面的程式碼:
 
#include <stdio.h>
void main()
{

int i=10;
int a = i;

printf("i= dn",a);

//下面組合語言語法的作用就是改變記憶體中i的值,但是又不讓編譯器知道
__asm {
mov dword ptr [ebp-4], 20h
}


int b = i;
printf("i= dn",b);

}

然後,在偵錯版本模式運行程式,輸出結果如下:
i = 10
i = 32
然後,在release版本模式運行程式,輸出結果如下:
i = 10
i = 10
輸出的結果明顯表明,release模式下,編譯器對程式碼進行了最佳化,第二次沒有輸出正確的 i 值。下面,我們把 i的宣告加上volatile關鍵字,看看有什麼變化:

#include <stdio.h>
void main()
{

volatile int i=10;
int a = i;

printf("i= dn",a);

__asm {
mov dword ptr [ebp-4], 20h
}

int b = i;
printf("i= dn",b);

}

分別在偵錯版本和release版本運行程式,輸出都是:

i = 10
i = 32

這說明這個關鍵字發揮了它的作用!


Use of volatile

The compilation system tries to reduce code size and execution time on all machines, by optimizing code. It is programmer responsibility to inform compilation system that certain code (or variable) should not be optimized. Many programmers do not understand when to use volatile to inform compilation system that certain code should not be optimized or what it does. Although (no doubt) their program work, they do not exploit the full power of the language.

A variable should be declared volatile whenever its value can be changed by something beyond the control of the program in which it appears, such as a concurrently executing thread. Volatile, can appear only once in a declaration with any type specifier; however, they cannot appear after the first comma in a multiple item declaration. For example, the following declarations are legal

/* Let T denotes some data type */
typedef volatile T i;
volatile T i;
T volatile i ;

And the following declaration is illegal

T i, volatile vi ;

Volatile qualifiers can be used to change the behavior of a type. For example,

volatile int p = 3;

declares and initializes an object with type volatile int whose value will be always read from memory.

Syntax

Keyword volatile can be placed before or after the data type in the variable definition. For example following declaration are identical:

Volatile T a =3;
T volatile a=3;

The declaration declares and initializes an objet with type volatile T whose value will be always read from memory

Pointer declaration

Volatile T * ptr; 
T volatile * ptr;

Ptr is a a pointer to a volatile T:

volatile pointer to a volatile variable  
int volatile * volatile ptr;

volatile can be applied to derived types such as an array type ,in that case, the element is qualified, not the array type. When applied to struct types entire contents of the struct become volatile. You can apply the volatile qualifier to the individual members of the struct. Type qualifiers are relevant only when accessing identifiers as l-values in expressions. volatile does not affects the range of values or arithmetic properties of the object.. To declare the item pointed to by the pointer as volatile, use a declaration of the form:

volatile T *vptr;



To declare the value of the pointer - that is, the actual address stored in the pointer - as volatile, use a declaration of the form:

T* volatile ptrv;

Use of volatile

- An object that is a memory-mapped I/O port
- An object variable that is shared between multiple concurrent processes
- An object that is modified by an interrupt service routine
- An automatic object declared in a function that calls setjmp and whose value is-changed between the call to setjmp and a corresponding call to longjmp

# An object that is a memory-mapped I/O port

an 8 bit , memory -mapped I/O port at physical address 0X15 can be declared as

char const ptr=(char*)0X15 ;

*ptr access the port consider following code fragment to set the third bit of the output port at periodic intervals

*ptr = 0;
while(*ptr){
*ptr = 4 ;
*ptr = 0 ;
}
the above code may be optimize as
*ptr = 0
while(0) {
}

*ptr is assigned 0 before value 4 is used ( and value of *ptr never changes ( same constant is always assigned to it)
volatile keyword is used to suppress these optimization the compiler assumes that the value can change any time , even if no explicit code modify it to suppress all optimization declare ptr as

 volatile char * const ptr = (volatile char*)0x16

this declaration say the object at which ptr points can change without notice , but that ptr itself is a constant whose value never change

# An object that is shared between multiple concurrent processes

if two threads/tasks concurrently assign distinct values to the same shared non-volatile variable, a subsequent use of that variable may obtain a value that is not equal to either of the assigned values, but some implementation-dependent mixture of the two values. so a global variable references by multiple thread programmers must declare shared variable as volatile.

#include <thread.h>
#include <stdio.h>
volatile int num ;

void* foo()
{
while(1) {
++num ;
sleep(1000);
}
}

main()
{
int p ;
void *targ =NULL ;
thread_t id ;
num = 0;
p = thr_create((void*)NULL , 0,foo,targ,0,&id);
if(!p) printf(" can not create thread ");

while(1)
{
printf("%d" , num ) ;
}
}

the compiler may use a register to store the num variable in the main thread , so the change to the value by the second thread are ignored . The volatile modifier is a away of telling the compiler that no optimization applied to the variable its value be not placed in register and value may change outside influence during evaluation

# An automatic object declared in a function that calls setjmp and whose value is-changed between the call to setjmp and a corresponding call to longjmp

Variables local to functions that call setjmp is most often used. When an automatic object is declared in a function that calls setjmp, the compilation system knows that it has to produce code that exactly matches what the programmer wrote. Therefore, the most recent value for such an automatic object will always be in memory (not just in a register) and as such will be guaranteed to be up-to-date when longjmp is called. Without volatile variable is undefined by the standard whether the result one see differs with or without optimization simply reflects that fact Consider following code

#include <setjmp.h>
static jmp_buf buf ;
main( )
{
volatile int b;
b =3 ;
if(setjmp(buf)!=0) {
printf("%d ", b) ;
exit(0);
}
b=5;
longjmp(buf , 1) ;
}

volatile variable isn't affected by the optimization. So value of b is after the longjump is the last value variable assigned. Without volatile b may or may not be restored to its last value when the longjmp occurs. For clear understanding assembly listing of above program by cc compiler has been given below.

/*Listing1: Assembly code fragment of above program
Produced by cc compiler when volatile is used*/



.file "vol.c"
main:
pushl %ebp
movl %esp, %ebp
subl $20, %esp
movl $3, -4(%ebp)
pushl $buf
call _setjmp
addl $16, %esp
testl %eax, %eax
je .L18
subl $8, %esp
movl -4(%ebp), %eax
pushl %eax
pushl $.LC0
call printf
movl $0, (%esp)
call exit
.p2align 2
.L18:
movl $5, -4(%ebp)
subl $8, %esp
pushl $1
pushl $buf
call longjmp


 /*Listing 2:Assemply code fragment of  above program
produced by cc compile witout volatile keword */

.file "wvol.c"
main:
pushl %ebp
movl %esp, %ebp
subl $20, %esp
pushl $buf
call _setjmp
addl $16, %esp
testl %eax, %eax
je .L18
subl $8, %esp
pushl $3
pushl $.LC0
call printf
movl $0, (%esp)
call exit
.p2align 2
.L18:
subl $8, %esp
pushl $1
pushl $buf
call longjmp


/listing 3: difference between listing 1 and listing 3 ***/

< .file "vol.c"
---
> .file "wvol.c"
< movl $3, -4(%ebp)
< movl -4(%ebp), %eax
< pushl %eax
> pushl $3
< movl $5, -4(%ebp) / * store in stack */


From above listing 3 it is clear that when you use setjmp and longjmp, the only automatic variables guaranteed to remain valid are those declared volatile.

# An object modified by an interrupt service routine

Interrupt service routines often set variables that are tested in main line code. One problem that arises as soon as you use interrupt is that interrupt routines need to communicate with rest of the code .A interrupt may update a variable num that is used in main line of code .An incorrect implementation of this might be:

static lon int  num ;
void interrupt update(void)
{
++num ;
}
main()
{
long val ;
val = num ;
while(val !=num)
val = num ;
rturn val ;
}

When compilation system execute while statement, the optimizer in compiler may notice that it read the value num once already and that value is still in the register. Instead of re reading the value from memory, compiler may produce code to use the (possibly messed up) value in the register defeating purpose of original C program. Some compiler may optimize entire while loop assuming that since the value of num was just assigned to val , the two must be equal and condition in while statement will therefore always be false .To avoid this ,you have to declare num to be volatile that warns compilers that certain variables may change because of interrupt routines.

static volatile  long int num ;

With volatile keyword in the declaration the compiler knows that the value of num must b read from memory every time it is referenced.

Conclusion

When using exact semantics is necessary, volatile should be used. If you are given a piece of touchy code given as above. It is a good idea in any case to look the compiler outputting listing at the assembly language to be sure that compiler produce code that makes sense.

About the author

Ashok K. Pathak a member ( Research Staff ) at Bharat Electronics Limited (CRL) , Ghaziabad .He has been developing embedded application for the past five years. Ashok holds a M.E in computer science and engineering . Ashok recently completed a book about' "Advanced Test in C and Embedded System Programming" , Published by BPB , ND .He can be reached at pathak@indiya.com.


延伸閱讀:


arrow
arrow
    全站熱搜

    Bluelove1968 發表在 痞客邦 留言(0) 人氣()