Linux内核中的内联汇编

目录

一、概述

二、内联汇编语法

1、内联汇编常规语法说明

①、 asm

② 、asm-qualifiers

③ 、AssemblerTemplate

④、 OutputOperands

⑤、 InputOperands

⑥ 、Clobbers

2、内联汇编中的earlyclobber

3、根据语法编写一个简单的add函数

总结


一、概述

C语言在线编译工具:https://gcc.godbolt.org/

内联汇编是一种在高级编程语言(如C或C++)中直接嵌入汇编代码的技术。这种方法允许程序员在源代码中直接插入汇编指令,从而实现对底层硬件的更直接控制和优化。

  • 内联汇编通常使用asm关键字将汇编代码嵌入到C/C++源代码中。

  • 在GCC编译器中,内联汇编遵循AT&T语法,其中操作数的顺序是源操作数在前,目的操作数在后。

  • 寄存器名称前有%前缀,如%eax

  • 可以使用C/C++变量和函数,并能通过汇编指令直接操作这些变量。

二、内联汇编语法

1、内联汇编常规语法说明

如下图

①、 asm

也可以写作“__asm__”,表示这是一段内联汇编。

② 、asm-qualifiers

有3个取值:volatile、inline、goto。

volatile的意思是易变的、不稳定的,用来告诉编译器不要随便优化这段代码,否则可能出问题。比如汇编指令“mov r0, r0”,它把r0的值复制到r0,并没有实际做什么事情,你的本意可能是用这条指令来延时。编译器看到这指令后,可能就把它去掉了。加上volatile的话,编译器就不会擅自优化。

③ 、AssemblerTemplate

汇编指令,用双引号包含起来,每条指令用“\n”分开,比如:

“mov %0, %1\n” “add %0, %1, %2\n”
④、 OutputOperands

输出操作数,内联汇编执行时,输出的结果保存在哪里。

格式如下,当有多个变量时,用逗号隔开:

[ [asmSymbolicName] ] constraint (cvariablename)

asmSymbolicName是符号名,随便取,也可以不写。

constraint表示约束,有如下常用取值:

constraint

描述

m

memory operand,表示要传入有效的地址,只要CPU能支持该地址,就可以传入

r

register operand,寄存器操作数,使用寄存器来保存这些操作数

i

immediate integer operand,表示可以传入一个立即数

constraint前还可以加上一些修饰字符,比如“=r”、“+r”、“=&r”,含义如下:

constraint Modifier Characters

描述

=

表示内联汇编会修改这个操作数,即:写

+

这个操作数即被读,也被写

&

它是一个earlyclobber操作数

cvariablename:C语言的变量名。

示例1如下:

[result] "=r" (sum)

它的意思是汇编代码中会通过某个寄存器把结果写入sum变量。在汇编代码中可以使用“%[result]”来引用它。

示例2如下:

"=r" (sum)

在汇编代码中可以使用“%0”、“%1”等来引用它。

⑤、 InputOperands

输入操作数,内联汇编执行前,输入的数据保存在哪里。

格式如下,当有多个变量时,用逗号隔开:

[ [asmSymbolicName] ] constraint (cexpression)

asmSymbolicName是符号名,随便取,也可以不写。

constraint表示约束,参考上一小节,跟OutputOperands类似。

cexpression:C语言的表达式。

示例1如下:

[a_val]"r"(a), [b_val]"r"(b)

它的意思变量a、b的值会放入某些寄存器。在汇编代码中可以使用%[a_val]、%[b_val]使用它们。

示例2如下:

"r"(a), "r"(b)

它的意思变量a、b的值会放入某些寄存器。在汇编代码中可以使用%0、%1等使用它们。

⑥ 、Clobbers

在汇编代码中,对于“OutputOperands”所涉及的寄存器、内存,肯定是做了修改。但是汇编代码中,也许要修改的寄存器、内存会更多。比如在计算过程中可能要用到r3保存临时结果,我们必须在“Clobbers”中声明r3会被修改。

下面是一个例子:

: "r0", "r1", "r2", "r3", "r4", "r5", "memory"

我们常用的是有“cc”、“memory”,意义如下:

Clobbers

描述

cc

表示汇编代码会修改“flags register”

memory

表示汇编代码中,除了“InputOperands”和“OutputOperands”中指定的之外,

还会会读、写更多的内存

2、内联汇编中的earlyclobber

OutputOperands的约束中经常可以看到“=&r”,其中的“&”表示earlyclobber,它是最难理解的。有一些输出操作数在汇编代码中早早就被写入了新值A,在这之后,汇编代码才去读取某个输入操作数,这个输出操作数就被称为earlyclobber(早早就被改了)。

这可能会有问题:假设早早写入的新值A,写到了r0寄存器;后面读输入操作数时得到数值B,也可能写入r0寄存器,这新值A就被破坏了。

核心原因就在于输出操作数、输入操作数都用了同一个r0寄存器。为什么要用同一个?因为编译器不知道你是earlyclobber的,它以为是先读入了所有输入操作数,都处理完了,才去写输出操作数的。按这流程,没人来覆盖新值A。

所以,如果汇编代码中某个输出操作数是earlyclobber的,它的constraint就要加上“&”,这就是告诉编译器:给我分配一个单独的寄存器,跟输入操作数用同一个寄存器。

比如现在有这段代码

反汇编如下:

为了避免这种问题,此时只需要在输出结果出加上"&"即可。这是告诉编译器,对于%0操作数它是earlyclobber的,不能跟其他操作数共用寄存器。

3、根据语法编写一个简单的add函数

int  add_asm1(int a, int b)
{
    int res;
    __asm__ volatile (
        "add %0, %1, %2"
        :"=&r"(res)
        :"r"(a), "r"(b)
        :"cc"
    );
    return res;
}

代码实现的就是把第1、2个操作数相加,存入第0个操作数。也就是把a、b相加,存入res。还可以使用另一种写法,在Linux内核中这种用法比较少见。

int  add_asm2(int a, int b)
{
    int res;
    __asm__ volatile (
        "add %[result], %[v1], %[v2]"
        :[result]"=&r"(res)
        :[v1]"r"(a), [v2]"r"(b)
        :"cc"
    );
    return res;
}

总结

文章将C语言中的内联汇编的基本语法做了介绍,并说明一些参数的应用场景和注意事项。最后添加了一个函数例子来理解语法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值