Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1140341
  • 博文数量: 322
  • 博客积分: 10010
  • 博客等级: 上将
  • 技术积分: 3276
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-17 09:21
文章分类

全部博文(322)

文章存档

2010年(155)

2009年(167)

我的朋友

分类: 嵌入式

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!
阅读(1134) | 评论(0) | 转发(0) |
0

上一篇:volatile、jiffies

下一篇:在内核中“sleep”

给主人留下些什么吧!~~