(*(volatile unsigned int *))详解

在看内核代码时,时常出现(*(volatile unsigned int *))相关定义,把具体宏定义放入其中就是(*(volatile unsigned int *))(0x4e000000),看着好像是定义一指针,该指针指向的内容就是0x4e000000该寄存器的内容。

Google了下相关资料,先看英文的,看看外国人怎么解释的:

Using C, I was trying to assign a variable name to a register address so that my code would be readable. An example of how to do this is as follows:

#define DDRA (*(volatile unsigned char *)(0x22))

This means that if a register or memory location exists at address 0x22, I can use DDRA to read or write to it like so..

DDRA = 0x05

In my C code. The #define looks really cryptic at first. The way to understand this is by breaking it down into pieces.

First of all,

unsigned char 

means we are using a byte-sized memory location. Byte being 8-bits wide.

unsigned char *

means we are declaring a pointer that points to a byte-sized location.

(unsigned char *)(0x22)

means  the byte-sized pointer points to address 0x22. The C compiler will refer to address 0x22 when the variable DDRA is used. The assembly code will end up using 0x22 in Load (LD) and Store (STR) instructions.

(*(unsigned char *)(0x22))

The first asterisk from the left signifies that we want to manipulate the value in address 0x22. * means “the value pointed to by the pointer”.

volatile

volatile forces the compiler to issue a Load or Store anytime DDRA  is accessed as the value may change without the compiler knowing it.

再来看看中文的解释:

使用一个32位处理器,要对一个32位的内存地址进行访问,可以这样定义

#define RAM_ADDR    (*(volatile unsigned long *)0x0000555F)

然后就可以用C语言对这个内存地址进行读写操作了

读:tmp = RAM_ADDR;

写:RAM_ADDR = 0x55;

定义volatile是因为它的值可能会改变,涉及实际操作中各种可能改变的情况。

如果在一个循环操作中需要不停地判断一个内存数据,例如要等待RAM_ADDR的I标志位置位,因为RAM_ADDR也是映射在SRAM空间,为了加快速度,编译器可能会编译出这样的代码:

把RAM_ADDR读取到Register中,然后不停地判断Register相应位。而不会再读取RAM_ADDR,这样当然不行了,因为程序或其他事件(中断等)会改变RAM_ADDR,结果很可能是一个死循环出不来了。

如果定义成volatile型变量,编译的代码是这样的:每次要操作一个变量的时候都从内存中读取一次。

#define rGPACON(*(volatile unsigned long *)0x56000000)

对于不同的计算机体系结构,设备可能是端口映射,也可能是内存映射的。如果系统结构支持独立的IO地址空间,并且是端口映射,就必须使用汇编语言完成对设备的控制,因为C语言并没有提供真正的“端口”的概念。如果是内存映射,那就方便多了。比如像寄存器A(地址假定0x48000000)写入数据0x01:

#define A (*(volatile unsigned long *)0x48000000)

……

A = 0x01;

……

这实际上就是内存映射机制的方便性了。其中volatile关键字是嵌入式系统开发的一个重要特点,用GCC编译时,volatile所指示的寄存器不进行优化!!!

#define rRTCCON    (*(volatile unsigned char *)0x57000043)    //RTC control

嵌入式系统编程,要求程序员能够利用C语言访问固定的内存地址。既然是个地址,那么按照C语言的语法规则,这个表示地址的量应该是指针类型。所以,知道要访问的内存地址后,比如0x57000043,

第一步,要把它强制转换为指针类型,(unsigned char *)0x57000043。volatile(可变的)这个关键字说明这变量可能会被意想不到地改变,这样编译器就不会去假设这个变量的值了。这种“意想不到地改变”,不是由程序去改变,而是由硬件去改变——意想不到。

第二步,对指针变量解引用,就能操作指针所指向的地址的内容了

*(volatile unsigned char *)0x57000043

第三步,小心地把#define宏中的参数用括号括起来,这个一个很好的习惯。

编译器对代码的优化是指:

CPU在执行的过程中,因为访问内存的速度远没有CPU的执行速度快,为了提高效率,引入了高速缓存cache。C编译器在编译时如果不知道变量会被其它外部因素(操作系统、硬件或者其他线程)修改,那么就会对该变量进行标识,即优化。那么这个变量在CPU的执行过程中,就会被放到高速缓存cache去,进而达到对变量的快速访问。在了解了优化的概念后,试想如果我们事先就知道该变量会被外部因素改变,那么我们就在这个变量定义前加上volatile,这样编译器就不会对该变量进行优化。这样该变量在CPU处理过程中,就不会被放到高速cache中。

为什么要让变量在执行的过程中不被放到cache中去呢?

如果变量是被外部因素改变,那么CPU就无法判断出这个变量已经改变,那么程序在执行的过程中如果使用到该变量,还会继续使用cache中的变量,但是这个变量其实已经被改变了,需要到内存地址中更新其内容了。

还有一个原因,在一些寄存器变量或数据端口的使用中,因为寄存器变量本身也是靠cache来处理,为了避免引起错误,也可以使用volatile修饰符。

总而言之,言而总之,使用volatile的目的就是:

让对volatile变量的存取不能缓存到寄存器,每次使用时需要重新存取。

发表评论

电子邮件地址不会被公开。 必填项已用*标注