内嵌汇编语法如下:

__asm__( 汇编语句模板
     : 输出部分
     : 输入部分
     : 破坏描述部分)
其中,asm 和 __asm__是完全一样的。共四个部分:汇编语句模板,输出部分,输入部分,破坏描述部分,各部分使用“:”格开,汇编语句模板必不可少,其他三部分可选,如果使用了后面的部分,而前面部分为空,也需要用“:”格开,相应部分内容为空。例如:
__asm__ __volatile__("cli": : :"memory")

一般大家见到的样子是这样的:

asm volatile(
    "msr    daif, %0        // 汇编语句模板
    :                       // 输出部分
    : "r" (flags)           // 输入部分
    : "memory");            // 破坏描述部分

“__asm__”表示后面的代码为内嵌汇编,“asm”是“__asm__”的别名。 “__volatile__”表示编译器不要优化代码,后面的指令保留原样, “volatile”是它的别名。

汇编语句模板

汇编语句模板由汇编语句序列组成,语句之间使用“;”、“\n”或“\n\t”分开。 指令中的操作数可以使用占位符引用C语言变量,操作数占位符最多10个,名称如下:%0,%1…,%9。

举例说明:

#define ATOMIC_OP(op, asm_op)                       \
static inline void atomic_##op(int i, atomic_t *v)          \
{                                   \
    asm volatile("// atomic_" #op "\n"              \
    "1: ldxr    %w0, %2\n"                      \
    "   " #asm_op " %w0, %w0, %w3\n"                \
    "   stxr    %w1, %w0, %2\n"                     \
    "   cbnz    %w1, 1b"                        \
    : "=&r" (result), "=&r" (tmp), "+Q" (v->counter)        \
    : "Ir" (i));                            \
}   

可以看到汇编语句模板有4行,每条汇编都是使用“\n“来分开。指令中的操作数%w0就代表从输出部分第一个数起。比如%w0代表“ =&r (result)“, %w1代表“=&r (tmp)“依次类推。最多到%9

输出部分

输出部分描述输出操作数,不同的操作数描述符之间用逗号格开,每个操作数描述符由限定字符串和C语言变量组成。每个输出操作数的限定字符串必须包含“=”表示他是一个输出操作数。

输入部分

输入部分描述输入操作数,不同的操作数描述符之间使用逗号格开,每个操作数描述符由限定字符串和C语言表达式或者C语言变量组成

破坏描述部分

为何要有破坏描述部分?我们的c代码是gcc来处理的,当遇到嵌入汇编代码的时候,gcc会将这些嵌入式汇编的文本送给gas进行后续处理。这样,gcc需要了解嵌入汇编代码对寄存器的修改情况,否则有可能会造成大麻烦。例如:gcc对c代码进行处理,将某些变量值保存在寄存器中,如果嵌入汇编修改了该寄存器的值,又没有通知gcc的话,那么,gcc会以为寄存器中仍然保存了之前的变量值,因此不会重新加载该变量到寄存器,而是直接使用这个被嵌入式汇编修改的寄存器。这时候,我们唯一能做的就是静静的等待程序的崩溃。

其中常见的就是内存修改通知:
如果一个内联汇编语句的指令列表中的指令对内存进行了修改,或者在此内联汇编出现的地方,内存内容可能发生改变,而被改变的内存地址你没有在其Output操作表达式中使用”m”约束,这种情况下,你需要使用在破坏描述部分使用字符串”memory”向GCC声明:”在这里,内存发生了,或可能发生了改变”;

举例:

asm("msr    daifclr, #8" : : : "memory")

限定字符

以下是常见的限定字符

 r: 表示使用一个通用寄存器,由GCC在%eax/%ax/%al、%ebx/%bx/%bl、%ecx/%cx/%cl、%edx/%dx/%dl中选取一个GCC认为是合适的;
q: 表示使用一个通用寄存器,与r的意义相同;
m: 表示使用内存地址,使用系统支持的任何一种内存方式,不需要借助于寄存器
i: 表示使用一个整数类型的立即数;
F: 表示使用一个浮点类型的立即数;
+: 表示当前输出表达式的属性为可读可写;
=: 当前输出表达式的属性为只写;
&: GCC声明:"GCC不得为任何Input操作表达式分配与此Output操作表达式相同的寄存器;

举例说明

就使用ATOMIC_OPS(add, add)代码举例说明。

ATOMIC_OPS(add, add)
-----------------------
#define ATOMIC_OP(op, asm_op)                       \
static inline void atomic_##op(int i, atomic_t *v)          \
{                                   \
    unsigned long tmp;                      \
    int result;                         \
                                    \
    asm volatile("// atomic_" #op "\n"              \
"1: ldxr    %w0, %2\n"                      \
"   " #asm_op " %w0, %w0, %w3\n"                \
"   stxr    %w1, %w0, %2\n"                     \
"   cbnz    %w1, 1b"                        \
    : "=&r" (result), "=&r" (tmp), "+Q" (v->counter)        \
    : "Ir" (i));                            \
}
---------------------------------------
将宏展开后
-----------------------------------------
static inline void atomic_add(int i, atomic_t *v)           \
{                                   \
    unsigned long tmp;                      \
    int result;                         \
                                    \
    asm volatile("// atomic_add    \n"              \
"1: ldxr    %w0, %2\n"                      \
"   add     %w0, %w0, %w3\n"                \
"   stxr    %w1, %w0, %2\n"                     \
"   cbnz    %w1, 1b"                        \
    : "=&r" (result), "=&r" (tmp), "+Q" (v->counter)        \
    : "Ir" (i));                            \
}

接下来一句一句解释:

"1: ldxr    %w0, %2\n"

ldxr: Load exclusive register (专用的装载寄存器)
简单来说,这句话就将v->counter放入到一个通用的寄存器中。

add     %w0, %w0, %w3\n"    

将通用寄存器中的值+1, 然后在将返回值存放到通用寄存器中。

stxr    %w1, %w0, %2\n" 

stxr : Store exclusive register, returning status
将通用寄存器的值放回到v->counter中,同时将返回结果放入到tmp中

cbnz    %w1, 1b"

判断返回值是否设置成功,如果设置失败再次跳转到标号1继续执行上述操作。