[汇编与C语言关系]5. volatile限定符

  现在研究一下编译器优化会指向转移的指令发出什么震慑,在斯基础及介绍C语言的volatile限定符。首先看下的C程序:

/* artificial device registers */
unsigned char recv;
unsigned char send;
/* memory buffer */
unsigned char buf[3];
int main(void)
{
    buf[0] = recv;
    buf[1] = recv;
    buf[2] = recv;
    send = ~buf[0];
    send = ~buf[1];
    send = ~buf[2];

    return 0;
}

  我们用recv和send这半独全局变量来拟设施寄存器。假设有平台下内存映射I/O,串口发送寄存器和串口接收寄存器位于固定的内存地址,而recv和send这点儿独全局变量也发出固定的内存地址。所以在这事例中我们把它假想成串口接收寄存器和串口发送寄存器。在main函数中,首先从串口接收三个字节存到buf中,然后拿立即三单字节取反,依次从串口发送出。我们查阅这段代码的倒汇编结果:

  图片 1

  movz指令把字节较短的值存到字节较丰富之存储单元中,存储单元的要职用0填充。该令可以生出b(byte)、w(word)、l(long)三种后缀,分别代表单字节、两字节和四字节。比如movzbl
0x804a019,
%eax表示把地址0x804a019处在之一个配节存到eax寄存器中,而eax寄存器是四字节底,高三许节用0填充,而生同样长长的指令mov
%al,0x804a01a中的al寄存器正是eax寄存器的低字节,把此字节存到地址0x804a01a处的一个字节中。可以为此不同之名单独看x86寄存器的低8员、次低8员、低16位还是完全的32位,以eax为条例,al表示低8号,ah代表不好低8各类,ax表示小16各项。如下图所示:

  图片 2

  但如若指定优化增选-O编译,反汇编的结果虽无一致了:

  图片 3

  前三修告句从串口接收三个字节,而编译生成的下令显然不入我们的作用(设备寄存器的情是当转变之内需每次重复于对应之内存地址取值):只有首先长告句从内存地址0x804a019读一个字节到寄存器eax中,然后打寄存器al保存及buf[0],后少条告句就不再由内存地址0x804a019读博,而是直接把寄存器al的价值保存至buf[1]、buf[2]。后三漫长告词把buf中之老三个字节取反重发送到串口,编译生成的下令也非合乎我们的用意:只有最终一久语句把eax的值取反写到内存地址0x804a018了,前片长达形同虚设,根本无弯指令。

  为什么编译器优化的结果会磨也?因为编译器并不知道0x804a018同0x804a019凡装备寄存器的地方,把其当成普通的内存单元了。如果是通常的内存单元,只要程序不失改变写她,它就是无见面更换,可以预先管内存单元里的价读到寄存器缓存起来,以后每次用这个价就径直从寄存器读取,这样效率又胜,我们领略读寄存器远较读内存要快。另一方面,如果对一个一般的内存单元连续召开三赖写操作,只有最终一潮的值会保存到内存单元中,所以前面少不好写操作是多余的,可以优化掉。访问设备寄存器的代码这样优化就蹭了,因为设备寄存器往往有着以下特征:

  • 装备寄存器中的数目不待改写就得好发生变化,每次读上去的价值都或无同等。
  • 连日来数通往设备寄存器中形容多少并无是在举行无用功,而是发生异样含义之。

  用优化增选编译生成的一声令下明显效率还强,但使用不当会出错,为了避免编译器自作聪明,把非拖欠优化的啊优化了,程序员应该醒目告诉编译器哪些内存单首届的走访是休克优化的,在C语言中可以据此
volatile
限定符修饰变量,就是报告编译器,即使以编译时指定了优化增选,每次读之变量仍然使老老实实从内存读取,每次写这变量也一如既往使老老实实写回内存,不能够大概任何手续。我们将代码的启几实施改成为:

  

/* artificial device registers */
volatile unsigned char recv;
volatile unsigned char send;

  然后指定优化增选 -O 编译,查看反汇编的结果:

  图片 4

 

  gcc 的编译优化增选有 -O0 、 -O 、 -O1 、 -O2 、 -O3 、 -Os 几种。
-O0 表示不优化,这是紧缺省之选料项。 -O1 、 -O2 和 -O3
这几只选择一个比较一个优化得重复多,编译时间吧再次丰富。 -O 和 -O1 相同。 -Os
表示为缩小目标代码尺寸要优化。

 

  有了 volatile
限定符,是足以防范编译器优化对装备寄存器的拜会,但是对于生Cache的平台,仅仅这样还不够,还是无法防护Cache优化对设备寄存器的访。在访普通的内存单元时,Cache对程序员是晶莹底,比如执行了
movzbl 0x804a019,%eax 这样同样长指令,我们并不知道 eax
的价是确实打内存地址0x804a019念到的,还是于Cache中读到之,如果Cache已经缓存了此地址的多少就起Cache读,如果Cache没有缓存就于内存读,这些手续都是硬件自动开的,而非是因此命令控制Cache去举行的,程序员写的指令中只有寄存器、内存地址,而从未Cache,程序员甚至无待掌握Cache的存在。同样道理,如果实行了
mov %al,0x804a01a
这样平等条指令,我们并不知道寄存器的值是确实的刻画回内存了,还是就写到了Cache中,以后还由Cache写回内存,即使单独写到了Cache中一经临时无写回内存,下次读0x804a01a此地方时依旧可打Cache中读到上次形容的多寡。然而,在读写设备寄存器时Cache的留存就不容忽视了,如果串口发送和收受寄存器的内存地址被Cache缓存了会有啊问题为?如下图所显示。

  图片 5

  如果串口发送寄存器的地址为Cahce缓存,CPU执行单元对串口发送寄存器做写操作都写到Cache中去矣,串口发送寄存器并没有这得到数码,也尽管非能够即时发送,CPU执行单元程序有的1、2、3叔个字节都见面刻画及Cache中的和一个单元,最后Cache中单单保留了第3独字节,如果此时Cache把数量勾勒回到串口发送寄存器,只能把第3单字节发送出,前片只字节就不见了。与此类似,如果串口接收寄存器的地址为Cache缓存,CPU执行单元在朗诵第1单字节时,Cache会从串口接收寄存器读上来缓存,然而串口接收寄存器后面收的2、3简单单字节Cache并不知道,因为Cache把串口接收寄存器当作普通内存单元,并且信任内存单元中之数据是不见面友善换的,以后每次读串口接收寄存器时,Cache都见面管缓存的第1独字节提供于CPU执行单元。通常,有Cache的阳台还来道对某个一样段落地址范围禁用Cache,一般是以页表中安的,可以设定哪些页面允许Cache缓存,哪些页面不容许Cache缓存,MMU不仅使开地方转换和做客权限检查,也使跟Cache协同工作。
  除了配备寄存器需要为此 volatile
限定之外,当一个全局变量被同样进程遭到之大都单控制流程访问时也只要用
volatile 限定
,比如信号处理函数和多线程。