asm volatile ( " instructions "

2020-09-10  本文已影响0人  Nothing_655f

asm volatile ( " instructions " ) 语法

格式说明

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

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

每项概念说明:
1.__asm__ 是GCC关键字asm的宏定义:

#define __asm__ asm  

__asm__ 或asm用来声明一个内联汇编表达式,任何内联汇编表达式都是以它开头,必不可少。
2.Instruction list是汇编指令序列,可以为空比如:asm volatile(""); 或 asm (""),都是完全正当的内联汇编表达式,只不过这两条语句没有什么意义。
但是如:asm ("":::"memory"),就有意义,它向GCC 声明:“内存作了改动”,GCC 在编译的时候,会将此因素考虑进去。
当Instruction list中有多条指令时,可以将多条指令放在一对引号中,用;或\n将它们分开,如过一条指令放一对引号中,可以每条指令一行。
即:
(1)每条指令都必须被双引号括起来
(2)两条指令必须用换行或分号分开。如:

 asm volatile("crc32 %%ebx, %%eax\n\t"  : "=a" (initval) : "b" (data), "a" (initval));
  1. __volatile__是GCC 关键字volatile 的宏定义
#define __volatile__ volatile  

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

  1. Output 用来指定内联汇编语句的输出

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

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")

主要是保证程序的执行遵循顺序一致性。

文档介绍

The format of basic inline assembly is very much straight forward. Its basic form is
asm("assembly code");
e.g.

asm("movl %ecx %eax");  /*cp ecx to eax*/
__asm__("movl %eax, %ebx\n\t"
          "movl $56, %esi\n\t"
          "movl %ecx, $label(%edx,%ebx,$4)\n\t"
          "movb%ah, (%ebx)");

In basic inline assembly, we had only instructions. In extended assembly, we can also specify the operands. It allows us to specify the input registers, output registers and a list of clobbered registers. It is not mandatory to specify the registers to use, we can leave that head ache to GCC and that probably fit into GCC’s optimization scheme better. Anyway the basic format is:

asm ( assembler template
          : output operands                  /* optional */
          : input operands                   /* optional */
          : list of clobbered registers      /* optional */
          );

The assembler template consists of assembly instructions. Each operand is described by an operand-constraint string followed by the C expression in parentheses. A colon separates the assembler template from the first output operand and another separates the last output operand from the first input, if any. Commas separate the operands within each group. The total number of operands is limited to ten or to the maximum number of operands in any instruction pattern in the machine description, whichever is greater.

If there are no output operands but there are input operands, you must place two consecutive colons surrounding the place where the output operands would go.
example:

int a=10, b;
asm ("movl %1, %%eax; 
      movl %%eax, %0;"
      :"=r"(b)        /* output */
      :"r"(a)         /* input */
      :"%eax"         /* clobbered register */
     );

Here what we did is we made the value of ’b’ equal to that of ’a’ using assembly instructions. Some points of interest are:
• "b" is the output operand, referred to by %0 and "a" is the input operand, referred to by %1.
• "r" is a constraint on the operands. We’ll see constraints in detail later. For the time being, "r" says to GCC to use any register for storing the operands. output operand constraint should have a constraint modifier "=". And this modifier says that it is the output operand and is write-only.
• There are two %’s prefixed to the register name. This helps GCC to distinguish between the operands and registers. operands have a single % as prefix.
• The clobbered register %eax after the third colon tells GCC that the value of %eax is to be modified inside "asm", so GCC won’t use this register to store any other value.
When the execution of "asm" is complete, "b" will reflect the updated value, as it is specified as an output operand. In other words, the change made to "b" inside "asm" is supposed to be reflected outside the "asm".

If you are familiar with kernel sources or some beautiful code like that, you must have seen many functions declared asvolatileor__volatile__which follows anasmor__asm__. I mentioned earlier about the keywordsasmand__asm__. So what is thisvolatile?
If our assembly statement must execute where we put it, (i.e. must not be moved out of a loop as an optimization), put the keywordvolatileafter asm and before the ()’s. So to keep it from moving, deleting and all, we declare it as
asm volatile ( ... : ... : ... : ...);
Use__volatile__when we have to be verymuch careful.
If our assembly is just for doing some calculations and doesn’t have any side effects, it’s better not to use the keywordvolatile. Avoiding it helps gcc in optimizing the code and making it more beautiful.

The NEON register bank consists of 32 64-bit registers. If both Advanced SIMD and
VFPv3 are implemented, they share this register bank. In this case, VFPv3 is
implemented in the VFPv3-D32 form that supports 32 double-precision floating-point
registers. This integration simplifies implementing context switching support, because
the same routines that save and restore VFP context also save and restore NEON
context
The NEON unit can view the same register bank as:
• sixteen 128-bit quadword registers, Q0-Q15
• thirty-two 64-bit doubleword registers, D0-D31.

常用ASM指令介绍

BIC:ARM指令,对某些位,清零。先取反再相与。
asm("BIC r2, r2, #0x1f"); ##对R2的低5位清零。
ORR:ARM指令,逻辑或
asm("ORR r2, r2, #0x10"); ##R2逻辑与0x10。
MRS:asm("MRS r2, CPSR"); ##将CPSR的值加载到R2中。
MSR:asm("MSR CPSR, r2"); ##将R2的值加载到CPSR中。
asm(" SWI 0x0"); ##跳转到软件中断函数,并转换为SVC模式。
LDR:{条件}目的寄存器 存储器寄存器。将存储地址所指的4个字节数据传送到寄存器,其中寻址方式会有很多种。
asm("ldr r0,=0xddeeaabb"); ##在这里ldr是一个伪指令,相当于move指令。
asm("ldr r0, [r1]"); ##将存储器地址为r1的一个字的数据加载到r0中。
MRC与MCR:在处理器寄存器与协处理器寄存器之间交换数据。
MRC {cond} coproc, opcode1, Rd, CRn, CRm {,opcode2}
asm("mrc p15,0,r0,c1,c1,0"); ##在CRn, CRm均为c1, opcode均为0时,表示SCR
(Secure Configuration Register),表示将c1的值赋值给r0.
asm("mcr p15,0,r0,c1,c1,0"); ##将r0的值赋值给c1.
asm("mcr p15,0,r0,c12,c0,0"); ##将r0的值赋值给c12,此时c12表示VBAR,
Vector Base Address Register,存放异常时的入口地址。
SMC:(Secure Monitor Call). asm(" SMC 0x0"); ##将ARM core切换到Trust Zone模式。
asm("mov r2, r2, lsr, #4"); ##r2左移4位后,加载到r2中。
asm("str r3, [sp, #-4]"); ##将r3中的数据加载到堆栈中,堆栈顶指针向前移4.
asm("ldmia r0!, {r3-r10}"); ##ldmia多寄存器加载指令,将r0地址中的数据加载到r3中,且每次r0增加4.
!表示,最终r0的值会改变。

asm("SVC 0x0"); ##core转为Supervisor模式。

上一篇下一篇

猜你喜欢

热点阅读