你今天volatile了吗?--准确定位

来源:互联网 发布:阿里云app官方下载 编辑:程序博客网 时间:2024/04/29 12:24
 

相关教程:

 

  • 【C++学习点滴】4月汇总:volatile,数组维度,const修饰符
  • 【C++学习点滴】4月汇总:volatile,数组维度,const修饰符
  • 泛型:volatile——多线程程序员最好的朋友volatile修正符及让你的编...
  • 泛型:volatile:多线程程序员最好的朋友,volatile修正符及让你的编...
  • volatile——多线程程序员最好的朋友volatile
  • 泛型<编程>:volatile——多线程程序员最好的朋友volatile修正...
  • Q&A about volatile & const(Z)
  • Java中的transient、volatile、strictfp关键字使用
  • auto/register/volatile/static/const/mutable
  • 【备忘】volatile关键字的作用

 

 

 

正文

 
在前一篇关于volatile文章《你今天volatile了吗?--慎重使用》(在后面,简称《慎重使用》)中提到,volatile定义的对象其内容可能会忽然的变化。换句话讲,如果你定义了一个volatile对象,就等于你告诉编译器该对象的内容可能会改变,即使代码中没有任何语句去改变该对象。编译器访问非volatile对象和volatile对象的方式很不一样对于前者(经优化后),它先将非volatile对象的内容读到CPU寄存器中,等操作CPU寄存器一段时间后,才最终将CPU寄存器的内容写回volatile对象。然而,对于volatile对象就没有这种优化操作。这时候编译器有些“笨”,代码要求它读取或写入volatile,它就马上如实地去做。前一篇《慎重使用》主要讲述如何明智地正确使用volatile,本篇文章通过一些实际应用进一步阐述volatile在解决嵌入式问题中的一些微妙作用并继续深入探讨使用volatile要注意的一些细节问题。
 
1.构造内存映射的设备寄存器
  许多处理器使用内存映射的I/O设备。这些设备将寄存器映射到普通内存空间的某些固定地址。这些基于内存映射的设备寄存器看起来与一般的数据对象没啥两样。在《慎重使用》中提到ARM Evaluator-7T 的特殊寄存器的定义为:
typedef uint32_t special_register;
在嵌入式应用中,许多设备有时候不仅仅与一个寄存器打交道,有时可能与多个寄存器的集合同时打交道。在Evaluator-7T板子上,串口UART就是一个很好的例子。在这个板子上有两个UART,UART0和UART1。每个UART都由6个特殊寄存器控制。我们可以通过一个数据结构来表示这些寄存器的集合:
 
注意:数据结构UART标识符UART的不同使用方法和位置。
 
typedef struct UARTUART
struct UART
{
  special_register ULCON; 
  special_register UCON;    /*控制*/
  special_register USTAT;     /*状态*/
  special_register UTXBUF;    /*发送缓冲*/
  special_register URXBUF;    /*接收缓冲*/
  special_register UBRDIV;    
};
UART0对应的特殊寄存器被映射到0x03FFD000。我们有两种方法来访问该寄存器,一种是《智用篇》中提到过的宏定义方法:
#define UART0 ((UART *)0x03FFD000)
另一种是通过常量指针:
UART *constUART0  = (UART *) 0x03FFD000;
 
2.使用volatile
  《慎重使用》提到,如果你不希望编译器对你的代码作优化以防止出现你预想不到的情况,那么使用volatile是不二之选。显然,要访问串口的设备寄存器,我们必须要关掉编译器优化。现在,volatile可以大显身手了。我们修改前面的定义为:
#define UART0 ((UARTvolatile *) 0x03FFD000)
或:
UART volatile *const UART0  = (UART *) 0x03FFD000;
如果使用后者(常量指针),就建议做强制转化:
UART volatile *const UART0  = (UARTvolatile *)0x03FFD000;
但这并不是必须。对于任意类型T,c编译器提供T指针到volatile T指针的标准内置转化,就如同T指针到const T指针的转化,整个过程自动完成。另外,将一个对象定义为volatile类型,那么该对象中的所有成员也将成为volatile类型。显然,在UART0前面加volatile类型,不可避免在其它地方也必须要加上volatile
比如,我们有下面的函数实现串口的字符串输出:
void put(char const *s, UART *u);
如果UART0是指向UART对象的volatile指针,那么如下调用会有什么问题呢:
put("hello, world\n", UART0);   
编译出错通不过!因为编译器不会将volatile UART指针转化为UART指针,所以我们能做的就是将其强制转化:/*UART== struct UART*/
put("hello, world\n", (UART *)UART0);/*volatileUART -> UART*/
这个强制转化虽然骗过了编译器,但在运行态(run time)可能会出问题。因为这时编译器将volatile类型UART0当做非volatile类型使用。为了避免这个缺陷,可以这样声明:
void put(char const *s, UARTvolatile *u);

注意:在这里加了volatile之后,在其它相关的地方别忘了也要加上volatile
 
2.准确地构造寄存器
  先看下面对UART0的声明:UARTvolatile*const UART0 = ...;
这种添加volatile的同时还添加const的做法有下面微妙的隐含功能:UART结构本身并不是volatile的,这个声明使得UART0指向一个volatile类型的UART常量对象。然而,其它的串口比如UART1有可能不是定义成volatile类型(有可能将UART1定义成UART类型)。除非系统确实有这样区分的需要,否则这种不一致并不是值得提倡的编程风格。解决这种问题的方法之一就是将volatile引入到UART类型:
typedef struct UART volatile UART;
有些人可能更愿意这么定义:
typedef volatile structUART UART;
但我本人推荐将const/volatile放到类型右侧的定义风格(即前者的风格)。使用上面的定义,我们不用担心哪里是否遗漏了volatile。另外,UART0的定义也修正为:
#define UART0 ((UART *)0x03FFD000)

UART *constUART0  = (UART *) 0x03FFD000;
而put函数也修正为:
void put(char const *s, UART *u);
这时的UART已是volatile类型。如果UART1定义成UART类型,那么显然它也是volatile类型。先打住,假如有人将UART1定义为structUART呢?:
struct UART *constUART1  = (struct UART *) 0x03FF...;
哎呀,没错!我们遗漏了有人可能用struct UART定义UART1的可能,这种定义使得
UART1的访问还是非volatile方式。到此,我们可以看出UART定义为 volatile structUART 并不能防止有人做出不恰当或不统一的定义。所以,想从根本上解决这种不一致的问题,就只能这么定义:
typedef struct   
{   
  special_register ULCON; 
  special_register UCON;     /*控制*/
  special_register USTAT;      /*状态*/
  special_register UTXBUF;     /*发送缓冲*/
  special_register URXBUF;     /*接收缓冲*/
  special_register UBRDIV;
} volatileUART;

这样使得任何使用UART的地方都是volatile类型的。
或:
struct UART
{    
  special_register volatile ULCON;
  special_register volatile UCON;    /*控制*/
  special_register volatile USTAT;     /*状态*/
  special_register volatile UTXBUF;   /*发送缓冲*/
  special_register volatile URXBUF;    /*接收缓冲*/
  special_register volatile UBRDIV;
};/
*UART结构每个成员都是volatile 类型*/
虽然我们用上面的方法解决UART结构类型和structUART类型的不统一,但可以看出special_register不是固有的volatile类型,所以在别的地方,特殊积存器可能不是volatile类型(有人可能不需要用UART来定义寄存器组,他要的只是用special_register定义单个寄存器)。为了从根本上彻底解决这种潜在问题,需要将special_register
作如下定义:
typedef uint32_t volatile special_register;
 
这样一来,不论你定义寄存器组还是单个寄存器都是volatile类型的!
 
总结:本篇文章始终围绕设备寄存器定义,就volatile到底该用在什么地方,该用在什么位置展开深入的分析讨论,最终得到将special_register定义为volatile类型是嵌入式应用中最理想的设计。
 
原创粉丝点击