学习tinyrisc-v项目运行程序时出现Time Out的输出。对比测试锁定在了volatile关键字上。发现像示例一样声明变量时加上volatile就解决了。之前也接触过volatile的相关资料,当时似懂非懂,借这次机会捣鼓一下。

以例程里的计时器中断文件举例:/tests/example/timer_int/main.c

main.c
注:在同目录的Makefile文件下可以看到这个参数,预定义了SIMULATION,只会编译#ifdef SIMULATION中的代码

Volatile 优化了什么?

我们仅仅修改count变量声明,一次保留volatile,一次去掉,分别编译,对比生成的反汇编文件timer_int.dump

左侧是去掉volatile,右侧是保留volatile

程序出现了超时问题(Time Out),因此我们重点关注循环的条件判断观察是否导致死循环——也就是源文件main.cif(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的值放入寄存器中进行比较,而去掉后则不会更新寄存器进行比较,所以导致寄存器中的值始终不变,进入死循环

为什么会优化?

处理器能对寄存器之中的值进行运算处理,所以储存在寄存器中的变量运算最快。

但为了控制数据传输的开支,寄存器的数量是有限的,若变量数量超出寄存器数量,编译器就会将一部分变量储存在内存之中。

处理器不能直接对内存中的值进行操作,因此如果要对内存中的值进行处理,要先把内存的值放在寄存器之中。

而若两次指令的过程中,编译器判断某个变量的值没有发生变化,便会省去从内存中取值的操作,使用上一次的值进行计算。

那么volatile的作用是什么呢?

C17标准中对于voliate的说明:

volatile类型的变量可能受某些执行或副作用修改,使用volatile将严格使用abstract machine(定义也在C17中)的规则评估(操作它的行为)。

count变量的更新是在中断之中执行的,在编译阶段中无法“察觉”到这个更新,于是编译器认为count变量没有发生变化,进行了优化。使用volatile关键字则可以强制每次使用count从内存中取值。

还有一些搞不懂的地方

反汇编指令中出现了mv a5,a5,这个指令有什么作用?能去掉吗?