读书人

volatile解决思路

发布时间: 2012-02-05 12:07:14 作者: rapoo

volatile
在调试嵌入式应用时,较难解决的问题之一是瞬时性问题。本文讨论的是此类问题的起因之一:异步访问变量,如由独立线程中的代码或是由中断程序访问。
此种变量必须恰当定义并有足够的保护。其定义必须包含volatile 关键字--它通知编译器这个变量可能会被该执行线程以外的其他地方改变。然后编译器会避免对此变量的某些优化,遵守源代码给出的变量访问方式并保证该变量在每个"顺序点"是稳定的。这样,编译器就不会访问与之前相同的寄存器来获得这个变量的值,而是按照一定的顺序节点进行操作。
为了保护一个共享变量,对它所进行的每一次访问,同时都必须保证不能进行其他的访问,这称之为atomic访问。而在C中对一个变量的读写操作并不能保证其atomic访问。在8位架构上访问32位变量都不是atomic访问,因为任何对象的读或写都需要不止一条指令。即使你检查编译器生成的代码并发现它们还OK,但这并不能保证在改变代码或编译器设置后,还能得到相同的结果。
? C 例子

共享变量的一个典型例子是:中断程序更新从主程序中读到的时间保持变量。如果中断服务程序(ISR)运行时其他中断被屏蔽(通常都是这种情况),则中断函数是atomic的。换句话说就是它在执行过程中不会被中断,这样本次访问就不需要更多保护。
注意下面的ISR仍然需要激活并且初始化,但这并不是这个例子所关心的。
volatile long tick_count = 0;
__interrupt void tick_timer ()
{
tick_count++;
}
另一方面主程序代码必须防止在变量被访问期间发生中断。一个简化的不安全的例子如下:
...
if (tick_count >= next_stop) /* Unsafe! */
{
next_stop += 100;
do_stuff();
}
这个代码不可靠是因为读 tick_count这一操作不是atomic访问。当我们读了tick_count 的一个或几个字节时可能会发生tick_timer中断。然后tick_count在ISR中的值被改变,当我们继续读tick_count时读到的是更新值的剩余字节。我们用来比较的值可能是错的,它是先前的值和更新的值的混合体。在很多情况下把旧值和新值混合并不会造成问题,你可能得到新值或旧值。但在其他情况下,你会得到完全不同的值。例如,tick_count的旧值为0x00FF FFFF,更新值为0x0100 0000,很容易就能看出最终结果会是完全错误的。
为了保护tick_count的访问,我们可以使用一个监视函数:
__monitor long get_tick_count ()
{
return tick_count;
}
...
if (get_tick_count() >= next_stop)
{
next_stop += 100;
do_stuff();
}
监视函数能够在运行时屏蔽中断,在结束时恢复先前的中断状态。

? 一个更短的监视函数和一次C++尝试
监视函数的缺点是在整个函数运行期间中断都是被关闭的。有时候,能够在函数中间关闭中断是很好的。通常可以使用intrinsic函数来实现这种功能;监视函数的缺点是你要记得插入一行代码来关闭中断,在结尾处插入一行来启动中断。在C++中,你可以把这个逻辑嵌入到一个类中--以AVR的IAR Embedded Workbench为例,你可以添加下面的类:
extern "C"
{
#include <inavr.h>
}
class Mutex
{
public:
Mutex ()
{
_state = __save_interrupt();
__disable_interrupt();
}
~Mutex ()
{
__restore_interrupt(_state);
}
private:
unsigned char _state;
};
要在上一个例子中使用Mutex类,你可以在主程序中使用下列代码:
long tick;
{
Mutex m;
tick = tick_count;
}
if (tick >= next_stop)
{
next_stop += 100;
do_stuff();
}
m的构造函数会在程序块的前面插入,保存先前的中断状态并屏蔽中断。然后执行主体程序(读 tick_count),最后析构函数会恢复先前的状态。

? 评论

具体完成的细节有很多中变化,但整体原则是相同的。
对C来说,如果你使用一个没有监视关键字的编译器,你就需要以其他方式来实现相同的功能,可以是与上面Mutex类的例子中相似的intrinsic函数。在ARM的 IAR Embedded Workbench 中,你可以使用 __get_CPSR()来找出中断的旧状态,在 MSP430 的 IAR Embedded Workbench 中可以使用__get_interrupt_state()。
在一些情况下,之前的中断状态是已知的,你只需要enable和disable这个中断。
如果你使用操作系统,那它可以提供多种功能,可以使得你较少的使用如上描述的disable和enable中断。一方面,操作系统会提供一种系统时钟,你只需要正确的使用系统调用,而不需要操作实现细节。另外,大多数的操作系统提供semaphores来保护共享资源。

? 结论

本文的主题是异步访问的变量如何进行适当保护,而关键字 volatile 通常是不够的。
常用解决方案是保证某些操作不被中断。文中描述了几种C和C++中的方法,给出了AVR的 IAR Embedded Workbench的C和C++代码段以及 ARM 和 430 的IAR Embedded Workbench 的相关instrinsic函数的名称。




[解决办法]
*(volatile unsigned int *)Port_IOA_Data=0xcccc;其中Port_IOA_Data是一个地址,在这就是一个内存单元,相当于20H,同时也就相当于一个变量。

读书人网 >C语言

热点推荐