广告

原创 __asm__ __volatile__ 嵌入式内嵌汇编语法解构

2015-1-22 16:50 635 0 分类: MCU/ 嵌入式 文集: BootLoader学习

带有C/C++表达式的内联汇编格式为:

  __asm__ __volatile__("Instruction List" :Output: Input : Clobber/Modify);

 

其中每项的概念及功能用法描述如下:

  1、__asm__

  __asm__是GCC 关键字asm 的宏定义:

  #define __asm__ asm

  __asm__或asm 用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的。

  

    2、Instruction List

   Instruction List 是汇编指令序列。它可以是空的,比如:__asm__ __volatile__(""); 或__asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。但并非所有Instruction List 为空的内联汇编表达式都是没有意义的。

    比如:__asm__ ("":::"memory");

  就非 常有意义,它向GCC 声明:“内存作了改动”,GCC 在编译的时候,会将此因素考虑进去。 当在"Instruction List"中有多条指令的时候,可以在一对引号中列出全部指令,也可以将一条 或几条指令放在一对引号中,所有指令放在多对引号中。如果是前者,可以将每一条指令放在一行,如果要将多条指令放在一行,则必须用分号(;)或换行符 (\n)将它们分开. 综上述:(1)每条指令都必须被双引号括起来(2)两条指令必须用换行或分号分开。

  例如: 在ARM系统结构上关闭中断的操作

  int disable_interrupts (void)

  {

  unsigned long old,temp;

  __asm__ __volatile__("mrs %0, cpsr\n"

  "orr %1, %0, #0x80\n"

  "msr cpsr_c, %1"

  : "=r" (old), "=r" (temp)

  :

  : "memory");

  return (old & 0x80) == 0;

  }

 

3. __volatile__

  __volatile__是GCC 关键字volatile 的宏定义

  #define __volatile__ volatile

  __volatile__或volatile 是可选的。如果用了它,则是向GCC 声明不允许对该内联汇编优化,否则当 使用了优化选项(-O)进行编译时,GCC 将会根据自己的判断决定是否将这个内联汇编表达式中的指令优化掉。

 

4、Output

  Output 用来指定当前内联汇编语句的输出

  例如:从arm协处理器p15中读出C1值

  static unsigned long read_p15_c1 (void)

  {

  unsigned long value;

  __asm__ __volatile__(

  "mrc p15, 0, %0, c1, c0, 0 @ readcontrolreg\n"

  : "=r" (value) @编译器选择一个R*寄存器

  :

  : "memory");

  #ifdef MMU_DEBUG

  printf ("p15/c1 is = %08lx\n", value);

  #endif

  return value;

  }

 

5、Input

  Input 域的内容用来指定当前内联汇编语句的输入Output和Input中,格式为形如“constraint”(variable)的列表(逗号分隔)

  例如:向arm协处理器p15中写入C1值

  static void write_p15_c1 (unsigned long value)

  {

  #ifdef MMU_DEBUG

  printf ("write %08lx to p15/c1\n", value);

  #endif

  __asm__ __volatile__(

  "mcr p15, 0, %0, c1, c0, 0 @ write itback\n"

  :

  : "r" (value) @编译器选择一个R*寄存器

  : "memory");

  read_p15_c1 ();

  }

 

6.、Clobber/Modify

   有时候,你想通知GCC当前内联汇编语句可能会对某些寄存器或内存进行修改,希望GCC在编译时能够将这一点考虑进去。那么你就可以在 Clobber/Modify域声明这些寄存器或内存。这种情况一般发生在一个寄存器出现在"Instruction List",但却不是由Input/Output操作表达式所指定的,也不是在一些Input/Output操作表达式使用"r"约束时由GCC 为其选择的,同时此寄存器被"Instruction List"中的指令修改,而这个寄存器只是供当前内联汇编临时使用的情况。

  例如:

  __asm__ ("mov R0, #0x34" : : : "R0");

  寄存器R0出现在"Instruction List中",并且被mov指令修改,但却未被任何Input/Output操作表达式指定,所以你需要在Clobber/Modify域指定"R0",以让GCC知道这一点。

   因为你在Input/Output操作表达式所指定的寄存器,或当你为一些Input/Output操作表达式使用"r"约束,让GCC为你选择一个寄 存器时,GCC对这些寄存器是非常清楚的——它知道这些寄存器是被修改的,你根本不需要在Clobber/Modify域再声明它们。但除此之外,GCC 对剩下的寄存器中哪些会被当前的内联汇编修改一无所知。所以如果你真的在当前内联汇编指令中修改了它们,那么就最好在Clobber/Modify 中声 明它们,让GCC针对这些寄存器做相应的处理。否则有可能会造成寄存器的不一致,从而造成程序执行错误。

  如果一个内联汇编语句的 Clobber/Modify域存在"memory",那么GCC会保证在此内联汇编之前,如果某个内存的内容被装入了寄存器,那么在这个内联汇编之后, 如果需要使用这个内存处的内容,就会直接到这个内存处重新读取,而不是使用被存放在寄存器中的拷贝。因为这个时候寄存器中的拷贝已经很可能和内存处的内容 不一致了。

  这只是使用"memory"时,GCC会保证做到的一点,但这并不是全部。因为使用"memory"是向GCC声明内存发生了变化,而内存发生变化带来的影响并不止这一点。

  例如:

  int main(int __argc, char* __argv[])

  {

  int* __p = (int*)__argc;

  (*__p) = 9999;

  __asm__("":::"memory");

  if((*__p) == 9999)

  return 5;

  return (*__p);

  }

   本例中,如果没有那条内联汇编语句,那个if语句的判断条件就完全是一句废话。GCC在优化时会意识到这一点,而直接只生成return 5的汇编代码,而不会再生成if语句的相关代码,而不会生成return (*__p)的相关代码。但你加上了这条内联汇编语句,它除了声明内存变化之外,什么都没有做。但GCC此时就不能简单的认为它不需要判断都知道 (*__p)一定与9999相等,它只有老老实实生成这条if语句的汇编代码,一起相关的两个return语句相关代码。

  另外在linux内核中内存屏障也是基于它实现的include/asm/system.h中

  # define barrier() _asm__volatile_("": : :"memory")

  主要是保证程序的执行遵循顺序一致性。有的时候你写代码的顺序,不一定是最终执行的顺序,这个是处理器有关的。

广告

文章评论 0条评论)

登录后参与讨论
相关推荐阅读
潇洒哥 2015-07-08 16:01
位操作符的使用技巧
在C语言编程中,数据的位是可以操作的最小数据单位,理论上可以用“位运算”来完成所有的运算和操作。一般的位操作是用来控制硬件的,或者做数据变换使用,但是,灵活的位操作可以有效地提高程序运行的效...
潇洒哥 2015-04-04 17:39
finish shell分析之底层usart
rt_thread的finsh shell系统不愧是调试的一项利器,它可以除了完成一般shell的功能外,甚至还可以自定义命令。这个对功能单一的嵌入式系统来说是十分可贵的。在此我并不想对finsh...
潇洒哥 2015-04-01 21:29
__main() 和 main()
因为我们通常在BOOTLOADER中都已做好了比较细致的初始化工作,包括代码的搬运,所以我们最好别再调用库函数__main(),因为__main()作为ADS集成好的库函数,会对系统进行初始化设置...
潇洒哥 2015-03-26 22:54
RT-Thread学习之scons篇--解析rtconfig.py文件
rtconfig.py文件,主要用于指定编译器以及安装路径。除此之外,该文件中定义了大量的变量,这些变量包括编译选项,汇编选项,链接选项。   import os # toolchain...
潇洒哥 2015-03-26 22:53
RT-Thread学习之scons篇--SConsruct脚本文件解析
       scons的构建文件名称是统一的都称为SConstruct。其是scons所接受的编译脚本主文件。当然为了方便目录的组织,也允许在各个目录下面存放SConscript, 然后最上面S...
潇洒哥 2015-03-26 22:53
RT-Thread学习之scons篇--SConcript文件解析
SConscript文件是用来指定哪些文件会加入编译。先来分析下BSP主目录下的SConscript文件: import rtconfig Import('RTT_ROOT') fro...
我要评论
0
0
广告