专注电子技术学习与研究
当前位置:单片机教程网 >> MCU设计实例 >> 浏览文章

volatile与嵌入式程序员千丝万缕的联系

作者:CHEERS   来源:RTOS-CHEERS   点击数:  更新时间:2014年06月08日   【字体:

       像大家更熟悉的const一样,volatile是一个类型修饰符(type specifier)。它是被设计用来修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。

        一个嵌入式软件工程师经常打交道的有芯片、中断、RTOS,关系到整个系统协调有序运转,多线程顾名思义就是多任务,就像一个人吃饭的时候要去用大脑控制手夹食物,然后要张开嘴送进食物,最后咀嚼咽食,多道工序严密配合才能吃饭,同样智能系统的CPU也是一样,这个过程一个系统要处理实现一件事,往往需要高效的处理方式,需要多任务处理,多任务之间更需要合理的调度方式,一般都需要嵌入一种操作系统,这时候volatile的作用体现出来了,定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地在内存中重新读取这个变量的值,而不是使用保存在寄存器里的备份。如果没有volatile那整个系统将会乱成一锅粥,根本无法实现目标。

      很多人会问为什么啊,这个关键在于多任务处理这个事情的时候会共享系统的一些资源,包括一些重要的变量,这就出现了问题,编译器在编译程序后都会进行优化,程序运行时本次任务访问CPU时调用一些参数变量,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中,进行缓存;以后再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致,当变量在因别的线程而改变了值,这个线程中该寄存器的值不会相应改变,从而造成应用程序以后读取的值和实际的变量值不一致,当该寄存器在因此线程而改变了值,原内存中变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致,所以会发生意想不到的危险,有些人会问,那这个值为什么会变啊,我只想告诉你这是一个嵌入式系统,比如说伺服电机的控制系统,采样得到的转速和位置会不会随时改变,可能因为在调速进程中改变,可能在会在制动进程中改变,这个系统的处理必须实时性极强,不然不可能实现高精度控制!

举一个不太准确的例子:发薪资时,会计每次都把员工叫来登记他们的银行卡号;一次会计为了省事,没有即时登记,用了以前登记的银行卡号;刚好一个员工的银行卡丢了,已挂失该银行卡号;从而造成该员工领不到工资

员工 -- 原始变量地址

银行卡号 -- 原始变量在寄存器的备份

另外很多人可能还是对volatile认识不清,我想说,用volatile修饰的全局变量,不是因为被volatile修饰 了就会发生改变,不修饰就不会改变,这个变量的改变与否是外因引起的,比如像伺服控制中的转速,抑或是PID 调节增量,无论这些变量是否被修饰,他都会因为外部原因运行一些线程的时候改变,而volatile的作用就是让它相对性不变,始终在上一个线程执行完后让当前执行线程调用这个数据的时候,都会小心的从内存地址中调用它最实时准确的数据,保证系统运行流畅,而不是去选择CASH--缓冲寄存器中保留的那个原始备份,若果选择系统选择备份文件,可能导致上边例子中说的“领不到钱”的尴尬,就是用了一个错误的数据执行了任务,如果伺服控制中出现这种情况可能出现电机不可控场面,后果不堪设想,所以对于嵌入式软件人员来说系统的协调处理和实时性必须严格。

这种变量经常会出现在:

1). 并行设备的硬件寄存器

2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

3). 多线程应用中被几个任务共享的变量

补充:volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人;

“易变”是因为外在因素引起的,像多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;

volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

这里它与const不同,const一般是对一些变量进行保护,它修饰的一些变量一般不让改变,主要针对是多个程序员在进行一个项目的开发时,各自的工作不同,不同的人为了保证自己这部分功能的绝对可靠,往往需要用const来保护自己的某些对象,防止别的程序员无意中改变它。

下面给出三个问题:

1). 一个参数既可以是const还可以是volatile吗?解释为什么。

2). 一个指针可以是volatile 吗?解释为什么。

3). 下面的函数被用来计算某个整数的平方,它能实现预期设计目标吗?如果不能,试回答存在什么问题:

1

2

3

4

int square( volatile int *ptr )

{

    return *ptr * *ptr;

}

下面是答案:

1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

2). 是的。尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。

3). 这段代码是个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

1

2

3

4

5

6

7

int square( volatile int *ptr )

{

    int a,b;

    a = *ptr;

    b = *ptr;

    return a * b;

}

由于*ptr的值可能在两次取值语句之间发生改变,因此a和b可能是不同的。结果,这段代码可能返回的不是你所期望的平方值!正确的代码如下:

1

2

3

4

5

6

long square( volatile int *ptr )

{

    int a;

    a = *ptr;

    return a * a;

}

同样在网上看到两个比较好的例子,说volatile的用法,在这里和大家分享:

 

嵌入式编程中经常用到 volatile这个关键字,的用法可以归结为以下两点:

 

一:告诉compiler不能做任何优化

 

   比如要往某一地址送两指令: 

   int *ip =...; //设备地址 

   *ip = 1; //第一个指令 

   *ip = 2; //第二个指令 

   以上程序compiler可能做优化而成: 

   int *ip = ...; 

   *ip = 2; 

   结果第一个指令丢失。如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意: 

   volatile int *ip = ...; 

   *ip = 1; 

   *ip = 2; 

   即使你要compiler做优化,它也不会把两次付值语句间化为一。它只能做其它的优化。

 

这对device driver程序员很有用。

 

二:表示用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,

 

而不能把他放在cache或寄存器中重复使用。

 

   如   volatile char a;   

        a=0; 

       while(!a){ 

//do some things;   

       }   

       doother(); 

   如果没有 volatile doother()不会被执行。

 

以上个人见解,有不对的地方,希望各位博友指正,和大家一起进步一起探讨,谢谢

 

----2014年3月21日14点20

 

----RTOS-CHEERS

 
 
关闭窗口

相关文章