对于内存映射的I/O端口我们可以像内存读写一样与外设进行交互,但二者之间并不完全等同,请看如下的代码
#define DEVICE_READY 0x01void device_activate(int * _port){ *_port = DEVICE_READY; while (*_port != DEVICE_READY){ ; }}
device_activate函数的作用是激活某一外设,参数_port是外设的控制端口地址,通过向该寄存器的bit0写1的方式来激活它,外设准备好以后,控制端口的bit0将被外设置1,函数正是通过不断的查询该位来判断外设是否被初始化好了。请注意,外设的寄存器并不像内存那样,我们写1进去读出来的也一定是1,这完全取决于外设的行为。
device_activate函数的功能在不使用编译优化选项时是正常的,这可以从如下的反汇编程序看出
void device_activate(int * _port){ 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 48 89 7d f8 mov %rdi,-0x8(%rbp) *_port = DEVICE_READY; 8: 48 8b 45 f8 mov -0x8(%rbp),%rax c: c7 00 01 00 00 00 movl $0x1,(%rax) while (*_port != DEVICE_READY){ 12: 90 nop 13: 48 8b 45 f8 mov -0x8(%rbp),%rax 17: 8b 00 mov (%rax),%eax 19: 83 f8 01 cmp $0x1,%eax 1c: 75 f5 jne 13; }}
但是当使用编译优化选项时,它的功能就不正常了,反汇编如下
void device_activate(int * _port){ *_port = DEVICE_READY; 0: c7 07 01 00 00 00 movl $0x1,(%rdi) while (*_port != DEVICE_READY){ ; }} 6: c3 retq
从汇编代码中可以看出函数中的while语句被优化掉了。这是因为编译器“聪明地”认为:将寄存器的bit0设置为1后读入的值也一定为1,所以那个while语句就是多余的了。为了防止这种情况的发生,我们要告诉编译器这是端口而不是内存,这就需要volatile关键字,使用volatile关键字更改后的代码如下
#define DEVICE_READY 0x01void device_activate(volatile int * _port){ *_port = DEVICE_READY; while (*_port != DEVICE_READY){ ; }}
再一次使用优化选项编译和反汇编的结果如下
void device_activate(volatile int * _port){ *_port = DEVICE_READY; 0: c7 07 01 00 00 00 movl $0x1,(%rdi) 6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) d: 00 00 00 while (*_port != DEVICE_READY){ 10: 8b 07 mov (%rdi),%eax 12: 83 f8 01 cmp $0x1,%eax 15: 75 f9 jne 10; }} 17: f3 c3 repz retq
这次编译器就没有优化掉while语句,所以在编写与外设打交道的程序时要注意运用volatile关键字
注:本文所用程序用例出自 李云的《专业嵌入式软件开发》