volatile和变量模型
学习tinyrisc-v项目运行程序时出现Time Out的输出。对比测试锁定在了volatile关键字上。发现像示例一样声明变量时加上volatile就解决了。之前也接触过volatile的相关资料,当时似懂非懂,借这次机会捣鼓一下。
以例程里的计时器中断文件举例:/tests/example/timer_int/main.c
注:在同目录的Makefile
文件下可以看到这个参数,预定义了SIMULATION
,只会编译#ifdef SIMULATION
中的代码
Volatile 优化了什么?
我们仅仅修改count
变量声明,一次保留volatile
,一次去掉,分别编译,对比生成的反汇编文件timer_int.dump
程序出现了超时问题(Time Out),因此我们重点关注循环的条件判断观察是否导致死循环——也就是源文件main.c
的 if(count == 2)
语句
条件判断在汇编里对应的是条件分支指令,也就是反编译中出现的 bne。
bne(branch not equal)作用是对比两个寄存器的值,如果不等即跳转到指定的程序位置。
两次bne的区别是跳转的位置不同,去掉关键字后是跳转到9c
——本行,而保留是跳转到98
——上一行。我们来看看这98
标签的程序。
98
是:lw a3,0(a5)
lw(Load Word),从内存中取数据到寄存器之中
从a5(基地址,Base)+0(偏移量,offset)的地址对应的内存中,取出字放在a3寄存器内。a5是0x10000<<12(左移12位来自lui
,Load Upper Immediate),是编译器为变量分配的基地址,而0(a5)则存放着count变量
也就是说,每次进行循环判断时,保留volatile会重新从内存中取出count的值放入寄存器中进行比较,而去掉后则不会更新寄存器进行比较,所以导致寄存器中的值始终不变,进入死循环
为什么会优化?
处理器能对寄存器之中的值进行运算处理,所以储存在寄存器中的变量运算最快。
flowchart LR a[处理器]<--> b[寄存器]
但为了控制数据传输的开支,寄存器的数量是有限的,若变量数量超出寄存器数量,编译器就会将一部分变量储存在内存之中。
处理器不能直接对内存中的值进行操作,因此如果要对内存中的值进行处理,要先把内存的值放在寄存器之中。
flowchart LR a[处理器]<--> b[寄存器]<-->c[内存]
而若两次指令的过程中,编译器判断某个变量的值没有发生变化,便会省去从内存中取值的操作,使用上一次的值进行计算。
那么volatile的作用是什么呢?
C17标准中对于voliate的说明:
volatile类型的变量可能受某些执行或副作用修改,使用volatile将严格使用abstract machine(定义也在C17中)的规则评估(操作它的行为)。
count变量的更新是在中断之中执行的,在编译阶段中无法“察觉”到这个更新,于是编译器认为count变量没有发生变化,进行了优化。使用volatile关键字则可以强制每次使用count从内存中取值。
还有一些搞不懂的地方
反汇编指令中出现了mv a5,a5,这个指令有什么作用?能去掉吗?