GCC-3——gcc内联汇编

一、gcc内嵌汇编

1. gcc内联汇编格式

__asm_- __volatile__(指令部: 输出部: 输入部: 损坏部)

gcc内联汇编在处理器变量和寄存器上提供了一个模板和一些约束条件:

(1) 在指令部(Assembler Template)中数字前加上%,如%0、%1等,表示需要使用寄存器的样板操作数。若指令部中用到几个不同的操作数,就说明有几个变量需要和寄存器结合。

(2) 输出部(Output Operands) 用于描述在指令部中可以修改的C语言变量以及约束条件。每个输出约束通常以"="或"+"号开头,"="号表示被修饰的操作数只有可写属性,"+"号表示被修饰的操作数具有可读可写属性。然后是一个字母(表示对操作数类型的说明),接着是关于变量结合的约束。输出部可以是空的。

(3) 输入部(Input Operands) 用来描述在指令部只读取的C语言变量及约束条件。输入部描述的参数只有只读属性,不要试图修改输入部参数的内容,因为gcc编译器假定输入部的参数内容在内嵌汇编之前和之后都是一致的。在输入部中不能使用"="号或者"+"号约束条件,否则就会编译报错。输入部可以是空的。

(4) 损坏部(Clobbers) 一般以"memory"结束。"memory"告诉gcc编译器,内联汇编代码改变了内存中的值,强迫编译器在执行该汇编代码前存储所有缓存的值,在执行完汇编代码之后重新加载该值,目的是防止编译乱序。"cc"表示内嵌代码修改了状态寄存器的相关标志位。


2. 例子

(1) 例1:

复制代码
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"表示第2个参数。

(2) 例2:

为了增强代码的可读性,可以使用汇编符号名字来替代%表示的操作数,如下面add函数:

复制代码
int add(int i, int j)
{
    int ret;

    asm volatile(
    "add    %w[result], %w[input_i], %w[input_j]"
    : [result] "=r" (ret)
    : [input_i] "r" (i), [input_j] "r" (j)
    );

    return ret;
}
复制代码

书上Demo编译验证不通过,报错:

$ gcc main.c -o pp
main.c: Assembler messages:
main.c:8: Error: number of operands mismatch for `add'

上述是个简单的gcc内联汇编的例子,主要功能是将i和j的值相加返回结果。先看输出部,表示只定义了一个操作数。"[result]"表示定义了一个汇编符号操作数,符号名为result,它对应"=r"(ret),使用函数中定义的ret变量。在汇编代码中对应%w[result],其中w表示ARM64中的32位通用寄存器。再看输入部,定义了两个操作数,同样使用汇编符号操作数的方式来定义。第一个汇编符号操作数是input_i对应形参i,第二个汇编符号操作数是input_j对应形参j。

 

3. gcc内联汇编操作符和修饰符

操作符/修饰符    说明
=                被修饰的操作数只写
+                被修饰的操作数具有可读可写属性
&                被修饰的操作数只能作为输出

 

4. ARM64特有操作符和修饰符

复制代码
操作符/修饰符    说明
k                SP寄存器
w                浮点寄存器、SIMD、SVE寄存器
Upl                使用P0到P7中任意一个SVE寄存器
Upa                使用P0到P15中任意一个SVE寄存器
Input            整数,常常用于ADD指令
J                整数,常常用于SUB指令
K                整数,常常用于32位逻辑指令
L                整数,常常用于64位逻辑指令
M                整数,常常用于32位的MOV指令
N                整数,常常用于64位的MOV指令
S                绝对符号地址或标签引用
Y                浮点数,其值为0
Z                整数,其值为0
Ush                表示一个符号的PC相对偏移量的高位部分(大于等于12bit的部分),这个PC相对偏移介于0-4GB
Q                表示没有使用偏移量的单一寄存器的内存地址
Ump                一个适用于SI/DI/SF和DF模式下的加载-存储指令的内存地址。
复制代码

 

参考:《奔跑吧Linux内核》第2版,此书不怎么样。

 

二、内核C和汇编互调

1. C调用汇编

复制代码
//arch/arm64/kernel/entry.S
ENTRY(ret_from_fork)
    XXX
ENDPROC(ret_from_fork)

//arm64/process.c
asmlinkage void ret_from_fork(void) asm("ret_from_fork");

int copy_thread_tls() {
    p->thread.cpu_context.pc = (unsigned long)ret_from_fork;
}
复制代码

 

2. 回调调C

复制代码
//sched/core.c
asmlinkage __visible void schedule_tail(struct task_struct *prev)
{
    XXX
}

//include/linux/sched/task.h
extern asmlinkage void schedule_tail(struct task_struct *prev);

//arch/arm64/kernel/entry.S
#include <linux/sched/task.h>
ENTRY(ret_from_fork)
    bl    schedule_tail
ENDPROC(ret_from_fork)
复制代码

 

posted on   Hello-World3  阅读(1253)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
历史上的今天:
2019-06-05 complete完成量——实例分析
2019-06-05 worker线程的创建与使用
2019-06-05 SDIO接口

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示