在linux内核代码中常常用到GCC的内联汇编,GCC内联汇编的格式如下:
__asm__ __volatile__(指令部:输出部:输入部:损坏部:)
GCC内联汇编在处理变量和寄存器的问题上提供了一个模板和一些约束条件。
- 在指令部中数字前加%,如%0,%1等,表示需要使用寄存器的样板操作数。若指令部用到了几个不同的操作数,就说明有几个变量需要和寄存器结合。
- 指令部后面的输出部用于描述在指令部中可以修改的C语言变量以及约束条件,每个输出约束通常以"="或者"+"号开头,然后是一个字母(表示对操作数类型的说明),接着是关于变量结合的约束,输出部可以是空的。"="号表示被修饰的操作数只具有可写属性,"+"号表示被修饰的操作数只具有可读可写属性。
- 输入部用来描述在指令部只能读取的C语言变量以及约束条件。输入部描述的参数只有只读属性,不要试图修改输入的参数内容,因为GCC编译器假定输入部的参数内容在内嵌汇编之前之后都是一致的,在输入部中不能使用"="或者"+"的约束条件,否则编译器会报错。另外,输入部可以是空的。
- 损坏部一般以"memory"约束结束。"memory"告诉GCC编译器,内部汇编代码改变了内存中的值。强迫编译器在执行该汇编代码前存储所有缓存的值。在执行完汇编代码后重新加载该值,目的是防止编译乱序。"CC"表示内嵌代码修改了状态寄存器的相关标志位。
内核中arch_local_irq_save(void)的实现
<arch/arm64/include/asm/irqflags.h>
static inline unsigned long arch_local_irq_save(void)
{
unsigned long flags;
asm volatile(
"mrs %0, daif" //读取PSTAT寄存器中的DAIF域到flags的变量
"msr daifset, #2" //关闭IRQ
: "=r" (flags)
:
:"memory");
return flags;
}
先看输出部,%0操作数对应"=r(flags)",即flags变量,其中"="表示被修饰的操作数的属性是只写,"r"表示使用一个通用寄存器。
接着看输入部,在上述例子中,输入部为空,没有指定参数,最后看损坏部,以"memory"结束。
该函数主要用于把PSTATE寄存器中的DAIF域保存到临时变量flags中,然后关闭IRQ,再输出部和输入部使用%来表示参数的序号,如%0表示第一个参数,%1表示第二个参数,为了增强代码的可读性,可以使用汇编符号的名字来替代以%表示操作数,如下面的add()函数
int add(int i,int j)
{
int res = 0;
asm volatile(
"add %w[result],%w[input_i],%w[input_j]"
:[result] "=r" (res)
:[input_i] "r" (i), [input_j] "r" (j)
);
return res;
}
上述是一个很简单的GCC内联汇编函数的例子,主要功能是把参数i和参数j的值相加最后返回结果,先看输出部,其中只定义一个操作数,"[result]"表示定义一个汇编符号操作数,符号名为result,它对应"=r(res)",使用函数中定义的res变量,在汇编代码中对应%w【result】,其中w表示ARM64中的32位通用寄存器。
在看输入部,其中定义两个操作数,同样使用汇编符号操作数的方式来定义,第一个汇编符号操作数是input_i,对应的函数形参是i,第二个汇编符号操作数是input_j,对应的形参是j.
GCC内联汇编操作数符和修饰符
操作符和修饰符 | 说明 |
= | 被修饰的操作数只写 |
+ | 被修饰的操作数具有可读可写属性 |
& | 被修饰的操作数只能作为输出 |