C 中的内联汇编语言
编写汇编代码既困难又无聊!然而,如果你想设置寄存器、读取内存,有时你必须做这些 “脏活”。
幸运的是我们有 GCC 的帮助。GCC 提供了一个关键字 asm
,允许你在 C 代码中嵌入汇编指令。C 语言绝对救了我们的命。
基本 Asm
基本 asm
语句具有以下语法:
asm asm-qualifiers ( Assembler Instructions );
例如,在 PMU 设置 中,我们想读取系统寄存器 PMCR_EL0
,以确保 PMU 是否设置成功。相应的汇编指令是 MRS x0, PMCR_EL0
。在 C 代码中,我们可以写:
asm ("mrs x0, PMCR_EL0");
请注意,asm-qualifiers
被省略了。所有的基本 asm
块都隐式地是 “volatile” 的。
你应该被警告,GCC 不解析汇编指令,对它们的意思甚至是否有效都一无所知。
扩展 Asm
基本 asm
对周围的 C 语法一无所知。例如,如果你想将存储在 C 变量 int_a
中的整数放入寄存器,你不能使用基本 asm
。
使用扩展 asm
,你可以从汇编中读写 C 变量,并从汇编代码执行跳转到 C 标签。扩展 asm
具有以下语法:
asm qualifiers (AssemblerTemplate
: output_constraint (C lvalue)
: input_constraint (C expression)
: Clobbers)
一些汇编指令会产生副作用,我们应该显式地使用限定符 volatile
告诉 GCC 不要优化我们的代码(是的,GCC 真的很聪明,有时我们需要告诉它不要这么聪明)。
约束
约束有点令人困惑。我们只需要知道一个在配置 Ninja 时可能使用的常见约束。
约束 r
标记相关的计算结果可以存储在通用寄存器中,通常用于输入约束。另一方面,=r
意思相同,但是只写寄存器,通常用于输出约束。
例如,以下代码改变系统寄存器中的值。
asm volatile("msr pmintenset_el1, %0" : : "r" ((u64)(0 << 31)));
它标记 C 表达式 (u64) (0<<31)
存储在任何通用寄存器中。运行时,GCC 将分配一个通用寄存器来填充汇编模板中的 %0
,并用表达式的计算结果填充它。
另一个打印存储在系统寄存器中的值的例子。
long long f;
asm("mrs %0, PMCR_EL0" : "=r" (f));
printk(KERN_INFO "PMCR_EL0 = %llu\n", f);
它标记 C 变量 (long long) f
被处理为任何通用寄存器。运行时,GCC 将分配一个通用寄存器来填充汇编模板中的 %0
,并将其与变量 f
绑定。
Clobber 列表
扩展 asm 被设计为将 C 表达式作为输入,并将 C 左值写入输出。但有时我们的汇编代码有副作用,比如我们会改变另一个寄存器中存储的值。
不幸的是,GCC 不知道这种影响,因为它无法理解汇编指令(还记得 GCC 是为了理解 C 代码而设计的吗?)。所以我们必须显式地列出受影响的寄存器,这就是我们有 clobber 列表的原因。
这里是一个例子:
asm( "movl %0,%%eax;\n\tmovl %1,%%ecx;\n\tcall _foo"
: /*no outputs*/
: "g" (from), "g" (to)
: "eax", "ecx"
);
这是一个 x86 的例子,不用担心你无法理解,因为我也不能。我们唯一关心的是我们将 eax
和 ecx
标记为 clobber,所以 GCC 不会依赖这两个寄存器来维护其他东西。
© LICENSED UNDER CC BY-NC-SA 4.0