C语言与ARM汇编混编

简介:

 __asm__ __volatile__内嵌汇编用法简述 在阅读C/C++原码时经常会遇到内联汇编的情况,下面简要介绍下__asm__ __volatile__内嵌汇编用法。

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

 
  1. __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 系统结构上关闭中断的操作
 
  1. int disable_interrupts (void
  2.         { 
  3.                 unsigned long old,temp; 
  4.                 __asm__ __volatile__("mrs %0, cpsr\n" 
  5.                                 "orr %1, %0, #0x80\n" 
  6.                                 "msr cpsr_c, %1" 
  7.                                 : "=r" (old), "=r" (temp) 
  8.                                 : 
  9.                                 : "memory"); 
  10.                 return (old & 0x80) == 0; 
  11.         } 

 

3. __volatile__

__volatile__ GCC 关键字 volatile 的宏定义

#define __volatile__ volatile

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

4 Output

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

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

 
  1. static unsigned long read_p15_c1 (void
  2.         { 
  3.                 unsigned long value; 
  4.                 __asm__ __volatile__( 
  5.                                 "mrc p15, 0, %0, c1, c0, 0 @ read control reg\n" 
  6.                                 : "=r" (value) @编译器选择一个R*寄存器 
  7.                                 : 
  8.                                 : "memory"); 
  9.         #ifdef MMU_DEBUG 
  10.                 printf ("p15/c1 is = %08lx\n", value); 
  11.         #endif 
  12.                 return value; 
  13.         } 
 

5 Input

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

号分隔)

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

 
  1. static void write_p15_c1 (unsigned long value) 
  2.         { 
  3.         #ifdef MMU_DEBUG 
  4.                 printf ("write %08lx to p15/c1\n", value); 
  5.         #endif 
  6.                 __asm__ __volatile__( 
  7.                                 "mcr p15, 0, %0, c1, c0, 0 @ write it back\n" 
  8.                                 : 
  9.                                 : "r" (value) @编译器选择一个R*寄存器 
  10.                                 : "memory"); 
  11.                 read_p15_c1 (); 
  12.         } 
 

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声明内存发生了变化,而内存发生变

化带来的影响并不止这一点。 

例如:

 
  1. int main(int __argc, char* __argv[]) 
  2.         { 
  3.         int* __p = (int*)__argc; 
  4.         (*__p) = 9999; 
  5.         __asm__("":::"memory"); 
  6.         if((*__p) == 9999) 
  7.         return 5; 
  8.         return (*__p); 
  9.         } 
 

本例中,如果没有那条内联汇编语句,那个if语句的判断条件就完全是一句废话。GCC在优化时会意识到这一

点,而直接只生成return 5的汇编代码,而不会再生成if语句的相关代码,而不会生成return(*__p)的相关代码。

但你加上了这条内联汇编语句,它除了声明内存变化之外,什么都没有做。但GCC此时就不能简单的认为它不需

要判断都知道 (*__p)一定与9999相等,它只有老老实实生成这条if语句的汇编代码,一起相关的两个return语句

相关代码。

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

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

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

理器有关的






本文转自 驿落黄昏 51CTO博客,原文链接: http://blog.51cto.com/yiluohuanghun/1060105 ,如需转载请自行联系原作者

相关文章
|
4月前
|
存储 机器学习/深度学习 编译器
ARM汇编快速入门
ARM汇编快速入门
124 0
|
4月前
|
编译器 Linux C语言
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)(上)
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)
|
3月前
|
存储 缓存 Linux
C语言编译过程——预处理、编译汇编和链接详解
C语言编译过程——预处理、编译汇编和链接详解
|
3月前
|
存储 NoSQL 编译器
C语言的本质(二):汇编与C
C语言的本质(二):汇编与C
25 0
|
4月前
|
编译器 C语言
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)(下)
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)
|
8月前
|
编译器 C语言
ARM与C语言的混合编程【嵌入式系统】
ARM与C语言的混合编程【嵌入式系统】
82 0
|
11月前
|
C语言
进阶C语言 第七章-------《程序的编译(预处理操作)+链接》 (预编译、编译、汇编、#define、条件编译,#include的包含)知识点+完整思维导图+基本练习题+深入细节+通俗易懂建议收藏(三)
进阶C语言 第七章-------《程序的编译(预处理操作)+链接》 (预编译、编译、汇编、#define、条件编译,#include的包含)知识点+完整思维导图+基本练习题+深入细节+通俗易懂建议收藏(三)
|
11月前
|
编译器 C语言
进阶C语言 第七章-------《程序的编译(预处理操作)+链接》 (预编译、编译、汇编、#define、条件编译,#include的包含)知识点+完整思维导图+基本练习题+深入细节+通俗易懂建议收藏(二)
进阶C语言 第七章-------《程序的编译(预处理操作)+链接》 (预编译、编译、汇编、#define、条件编译,#include的包含)知识点+完整思维导图+基本练习题+深入细节+通俗易懂建议收藏(二)
|
11月前
|
存储 自然语言处理 程序员
进阶C语言 第七章-------《程序的编译(预处理操作)+链接》 (预编译、编译、汇编、#define、条件编译,#include的包含)知识点+完整思维导图+基本练习题+深入细节+通俗易懂建议收藏(一)
进阶C语言 第七章-------《程序的编译(预处理操作)+链接》 (预编译、编译、汇编、#define、条件编译,#include的包含)知识点+完整思维导图+基本练习题+深入细节+通俗易懂建议收藏(一)
|
11月前
|
存储 移动开发 前端开发
linux内核1-GNU汇编入门_X86-64&ARM(下)
linux内核1-GNU汇编入门_X86-64&ARM(下)