分类: 嵌入式
2010-04-24 11:49:18
volatile中文的意思是易于挥发的。在C语言中,如果一个变量被声明为volatile,
则说明这个变量每回都要从内存读到寄存器中,操作完以后,再将值写回到内存中,
编译器(例如gcc)并不将值cache在寄存器中。对于多线程或SMP系统,要注意使用
volatile。
int wait = TRUE;
thread1() thread2()
{ {
while (wait) { wait = FALSE;
sleep(1000); ...
} }
}
当编译器看到thread1调用sleep的时候,它认为sleep是一个外部模块的函数,不可
能改变wait的值,所以它"可能"就进行代码优化,将wait的值cache在寄存器中,以提
高效率。假如另外一个线程thread2开始运行,将wait改成FALSE,如果CPU没有预测到
wait变量的依赖关系的话,thread1就会一直sleep下去。在这种情况下,volatile可
以强制编译器不将wait缓存到寄存器中。注意,一般情况下,CPU是能检测到wait被该
过了,从而将寄存器刷新,但是这和CPU的体系结构有关,Linux是能支持多种体系结
构的,所以经常使用volatile。 有网友问为什么在do_timer()函数中,有一句(*(unsigned long *)&jiffies)++,
为什么不直接用jiffies++呢? 这个问题和两个原因有关: * jiffiers是volatile变量 * CPU可能会重新排序指令 我编了3个小程序来测试,
jiffier1.c int main() { unsigned long jiffiers = 777; jiffiers++; return 0; }
gcc -S jiffer1.c,生成的汇编为
.file "jiffier1.c" .version "01.01" gcc2_compiled.: .text .align 4 .globl main .type main,@function main: pushl %ebp movl %esp, %ebp subl , %esp movl 7, -4(%ebp) leal -4(%ebp), %eax incl (%eax) movl , %eax leave ret .Lfe1: .size main,.Lfe1-main .ident "GCC: (GNU) 2.96 20000731 (Red Hat Linux 7.3 2.96-110)"
从汇编中,可以看到jiffiers的值是直接在内存里加一的。
jiffier2.c int main() { volatile unsigned long jiffiers = 777; jiffiers++; return 0; }
生成的汇编为
.file "jiffier2.c" .version "01.01" gcc2_compiled.: .text .align 4 .globl main .type main,@function main: pushl %ebp movl %esp, %ebp subl , %esp movl 7, -4(%ebp) movl -4(%ebp), %eax incl %eax movl %eax, -4(%ebp) movl , %eax leave ret .Lfe1: .size main,.Lfe1-main .ident "GCC: (GNU) 2.96 20000731 (Red Hat Linux 7.3 2.96-110)"
当jiffiers被声明为volatile时,在加一之前,会先从内存中将值读到寄存器%eax中, 直接都寄存器操作,完了以后不做cache,将值写回内存。它需要3条指令才完成了加一 操作,
movl -4(%ebp), %eax incl %eax movl %eax, -4(%ebp)
也就是说,jiffiers++并不是原子操作,在多处理器环境中,
问题就出来了,jiffers有可能被加了两次,内存中却只是加了1。
另外的原因是这3条指令执行的时候,可能会被重新排序,从而破坏了操作的原子性。
当然我们也可以直接调用汇编指令加一,但是do_timers函数是独立于体系结构的,
所以Linux使用的一种最简单的方法,也就是(*(unsigned long *)&jiffiers)++
来解决这些问题。
jiffier3.c
int main()
{
volatile unsigned long jiffiers = 777;
(*(unsigned long *)&jiffiers)++;
return 0;
}
生成的汇编为
.file "jiffier3.c" .version "01.01" gcc2_compiled.: .text .align 4 .globl main .type main,@function main: pushl %ebp movl %esp, %ebp subl , %esp movl 7, -4(%ebp) leal -4(%ebp), %eax incl (%eax) movl , %eax leave ret .Lfe1: .size main,.Lfe1-main .ident "GCC: (GNU) 2.96 20000731 (Red Hat Linux 7.3 2.96-110)"
可以看出来,生成的指令和jiffer1.c的一模一样,也就是说, (*(unsigned long *)&jiffiers)++将volatile的jiffiers转换成一般的内存变量, 避免了用寄存器做cache,从而保证了jiffiers加一操作的原子性。 Linux一开始也是直接用jiffiers++的,到后来的版本才改成
(*(unsigned long *)&jiffiers)++, 可想而知写一个牢固的OS是不容易的,要解决的隐含细节非常多。 Good Luck, Linux!