[汇编与C语言关系]五. 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
0x80肆a01玖,
%eax表示把地址0x80四a01玖处的八个字节存到eax寄存器中,而eax寄存器是4字节的,高叁字节用0填充,而下一条指令mov
%al,0x80四a0一a中的al寄存器正是eax寄存器的低字节,把这些字节存到地址0x80四a01a处的三个字节中。可以用差异的名字单独访问x捌陆寄存器的低8人、次低陆个人、低拾伍位依然完全的叁12位,以eax为例,al表示低陆位,ah代表次低5位,ax表示低1肆位。如下图所示:

  图片 2

  但固然钦定优化增选-O编写翻译,反汇编的结果就不1样了:

  图片 3

  前三条语句从串口接收多个字节,而编译生成的命令显著不相符大家的意图(设备寄存器的剧情是在转变的急需每一次重复从对应的内部存款和储蓄器地址取值):唯有首先条语句从内部存款和储蓄器地址0x80肆a01九读多少个字节到寄存器eax中,然后从寄存器al保存到buf[0],后两条语句就不再从内部存款和储蓄器地址0x804a01九读取,而是径直把寄存器al的值保存到buf[1]、buf[2]。后三条语句把buf中的三个字节取反再发送到串口,编写翻译生成的吩咐也不相符大家的意图:唯有最后一条语句把eax的值取反写到内部存款和储蓄器地址0x804a01八了,前两条形同虚设,根本不转变指令。

  为啥编写翻译器优化的结果会错吧?因为编写翻译器并不知道0x80四a018和0x80四a01九是装备寄存器的地址,把它们当成普通的内部存款和储蓄器单元了。尽管是普普通通的内部存款和储蓄器单元,只要程序不去改写它,它就不会变,可以先把内部存款和储蓄器单元里的值读到寄存器缓存起来,今后每便用到这一个值就直接从寄存器读取,那样效用越来越高,大家了解读寄存器远比读内部存款和储蓄器要快。另1方面,要是对二个平淡无奇的内部存款和储蓄器单元连续做叁回写操作,只有最后一次的值会保存到内存单元中,所以前两回写操作是多余的,能够优化掉。访问设备寄存器的代码那样优化就错了,因为设备寄存器往往有着以下特征:

  • 装备寄存器中的数据不必要改写就能够协调发生变化,每一次读上去的值都只怕不平等。
  • 一连数十次向设施寄存器中写多少并不是在做无用功,而是有特有含义的。

  用优化增选编译生成的吩咐鲜明功能更加高,但使用不当会出错,为了幸免编写翻译器布鼓雷门,把不应该优化的也优化了,程序员应该明显告知编写翻译器哪些内部存款和储蓄器单元的拜会是不能够优化的,在C语言中能够用
volatile
限定符修饰变量,正是报告编写翻译器,固然在编写翻译时钦定了优化增选,每回读这些变量仍旧要老老实实从内存读取,每便写那个变量也依然要诚实写回内部存储器,无法大致任何手续。大家把代码的起来几行改成:

  

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

  然后钦赐优化增选 -O 编写翻译,查看反汇编的结果:

  图片 4

 

  gcc 的编写翻译优化增选有 -O0 、 -O 、 -O一 、 -O2 、 -O3 、 -Os 两种。
-O0 表示不优化,那是缺省的选项。 -O1 、 -O二 和 -O3这多少个选项3个比一个优化得越多,编写翻译时间也更加长。 -O 和 -O1 相同。 -Os
表示为缩短指标代码尺寸而优化。

 

  有了 volatile
限定符,是足以预防编写翻译器优化对设备寄存器的访问,不过对于有Cache的平台,仅仅这样还不够,依旧无法预防Cache优化对设施寄存器的走访。在做客普通的内部存款和储蓄器单元时,Cache对程序员是晶莹剔透的,比如执行了
movzbl 0x804a01九,%eax 那样一条指令,大家并不知道 eax
的值是确实从内部存款和储蓄器地址0x80四a01九读到的,仍然从Cache中读到的,假诺Cache已经缓存了这一个地点的数据就从Cache读,假使Cache未有缓存就从内部存款和储蓄器读,那些步骤都以硬件自动做的,而不是用命令控制Cache去做的,程序员写的吩咐中只有寄存器、内部存款和储蓄器地址,而从未Cache,程序员甚至不须求通晓Cache的留存。同样道理,倘使实施了
mov %al,0x804a0一a
那样一条指令,大家并不知道寄存器的值是真的写回内部存储器了,照旧只写到了Cache中,未来再由Cache写回内部存款和储蓄器,尽管只写到了Cache中而临洋气未写回内部存款和储蓄器,下次读0x80四a0一a以此地点时还能够从Cache中读到上次写的数码。但是,在读写设备寄存器时Cache的留存就不容忽视了,倘若串口发送和收取寄存器的内部存储器地址被Cache缓存了会有如何难题啊?如下图所示。

  图片 5

  固然串口发送寄存器的地点被Cahce缓存,CPU执行单元对串口发送寄存器做写操作都写到Cache中去了,串口发送寄存器并从未应声获取数码,也就不能够马上发送,CPU执行单元程序发生的1、二、叁四个字节都会写到Cache中的同3个单元,最终Cache中只保留了第二个字节,假使此刻Cache把多少写回到串口发送寄存器,只可以把首个字节发送出去,前几个字节就不见了。与此类似,如若串口接收寄存器的地点被Cache缓存,CPU执行单元在读第一个字节时,Cache会从串口接收寄存器读上来缓存,可是串口接收寄存器前边收到的二、3多少个字节Cache并不知道,因为Cache把串口接收寄存器当作普通内部存款和储蓄器单元,并且信任内部存款和储蓄器单元中的数据是不会融洽变的,以往每一次读串口接收寄存器时,Cache都会把缓存的第3个字节提须要CPU执行单元。常常,有Cache的阳台都有点子对某1段地址范围禁止使用Cache,一般是在页表中装置的,能够设定哪些页面允许Cache缓存,哪些页面分裂意Cache缓存,MMU不仅要做地方转换和访问权限检查,也要和Cache协同工作。
  除了装备寄存器供给用 volatile
限定之外,当2个全局变量被同样进度中的几个控制流程访问时也要用
volatile 限定
,比如时域信号处理函数和多线程。