GCC内联汇编常见陷阱

行是知之始,知是行之成。这篇文章主要讲述GCC内联汇编常见陷阱相关的知识,希望能为你提供帮助。

欢迎关注微信公众号:[奔跑吧linux社区]
本文节选自《奔跑吧Linux内核》第二版卷1第1.5章
在Linux内核代码中常常会使用到GCC内联汇编,GCC内联汇编的格式如下。
__asm__ __volatile__(指令部: 输出部: 输入部: 损坏部)

GCC内联汇编在处理变量和寄存器的问题上提供了一个模板和一些约束条件。
  1. 在指令部(AssemblerTemplate)中数字前加上%,如%0、%1 等,表示需要使用寄存器的样板操作数。若指令部用到了几个不同的操作数,就说明有几个变量需要和寄存器结合。
  2. 指令部后面的输出部(OutputOperands)用于描述在指令部中可以修改的C语言变量以及约束条件。每个输出约束(constraint)通常以“=”或者“+”号开头,然后是一个字母(表示对操作数类型的说明),接着是关于变量结合的约束。输出部可以是空的。“=”号表示被修饰的操作数只具有可写属性,“+”号表示被修饰的操作数只具有可读可写属性。
  3. 输入部(InputOperands)用来描述在指令部只能读取的C语言变量以及约束条件。输入部描述的参数只有只读属性,不要试图修改输入部的参数内容,因为GCC编译器假定输入部的参数内容在内嵌汇编之前和之后都是一致的。在输入部中不能使用“=”或者“+”约束条件,否则编译器会报错。另外,输入部可以是空的。
  4. 损坏部(Clobbers)一般以“memory”结束。“memory”告诉GCC编译器,内联汇编代码改变了内存中的值,强迫编译器在执行该汇编代码前存储所有缓存的值,在执行完汇编代码之后重新加载该值,目的是防止编译乱序。“cc”表示内嵌代码修改了状态寄存器的相关标志位。





下面先看一个简单的例子,即arch_local_irq_save()函数的实现。
< 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变量
"msrdaifset, #2"//关闭IRQ
: "=r" (flags)
:
: "memory");
return flags;

  1. 先看输出部,%0操作数对应"=r" (flags),即flags变量,其中“=”表示被修饰的操作数的属性是只写,“r”表示使用一个通用寄存器。
  2. 接着看输入部,在上述例子中,输入部为空,没有指定参数。
  3. 最后看损坏部,以“memory”结束。
  4. 该函数主要用于把PSTATE寄存器中的DAIF域保存到临时变量flags中,然后关闭IRQ。
在输出部和输入部使用%来表示参数的序号,如%0表示第1个参数,%1表示第2个参数。为了增强代码可读性,可以使用汇编符号名字来替代以%表示的操作数,如下面的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内联汇编操作符和修饰符如表1.23所示。

ARM64架构中特有的操作符和修饰符如表1.24所示。



下面的图是第三季视频课程的部分课件内容。









新书预告
《奔跑吧linux内核》第二版卷1已经上架了。

《奔跑吧linux内核》第二版卷2预计春节后上架!



金色年华,流金岁月,奔二入门篇预计春节后上架!

【GCC内联汇编常见陷阱】

    推荐阅读