时间轴

2025-09-27

init


参考文档:

内嵌汇编(Inline Assembly Language)在 C 语言中嵌入汇编代码

目的:

  • 优化:针对特定重要代码(time-sensitive 进行优化)
  • C 语言需要访问某些特殊指令来实现特殊功能,比如内存屏障指令

内存汇编两种模式

  • 基础内嵌汇编
  • 扩展内嵌汇编

基础内嵌汇编

格式:

1
asm asm-qualifiers(AssemblerInstructions)
  • asm 关键字:表明这是一个 GNU 扩展
  • 修饰词(qualifiers)

    • volatile:在基础内嵌汇编中通常不需要这个修饰词
    • inline:内联,asm 汇编的代码会尽可能小
  • 汇编代码块(AssemblerInstructions)

    • GCC 编译器把内嵌汇编当成一个字符串

    • GCC 编译不会去解析和分析内嵌汇编

    • 多条汇编指令,需要使用”\n\t“来换行

    • GCC 的优化器,可以移动汇编指令的前后位置。如果你需要保存汇编指令的顺序,最后使用多个内嵌汇编的方式

基础内嵌汇编的例子

扩展内嵌汇编

扩展内嵌汇编

  • 格式
    • asm 关键字:表明这个是一个 GNU 扩展
    • 修饰词(asm-qualifiers)
      • volatile:用来关闭 GCC 优化
      • inline 内联,asm 汇编的代码会尽可能小
      • goto 在内嵌会便利会跳转到 C 语言的标签里

扩展内嵌汇编的例子

  • 输出部:

    用于描述在指令部中可以被修改的 C 语言变量以及约束条件

    • 每个输出约束通常以”=”开头,接着的字母表示对操作数类型的说明,然后是关于变量结合的约束
    • 输出部通常使用”=“或者”+”作为输出约束,其中”=“表示被修饰的操作数只具有可写属性,”+“表示被修饰的操作数只具有可读可写属性
    • 输出部可以是空的
1
"=/+" + 约束修饰符 + 变量
  • 输入部

    用来描述在指令部只能被读取访问的 C 语言变量以及约束条件

    • 输入部描述的参数是只有只读属性,不要试图修改输入部的参数内容,因为 GCC 编译器假定,输入部的参数的内容在内嵌汇编之前和之后都是一致的
    • 在输入部中不能使用”=”或者”+“约束条件,否则编译器会报错
    • 输入部可以是空的
  • 损坏部(Clobbers)

    • “memory”告诉 GCC 编译器内联汇编指令改变了内存中的值,强迫编译器在执行该汇编代码前存储所有缓存中的值,在执行完汇编代码之后重新加载该值,目的是防止编译乱序
    • ”cc”表示内嵌汇编代码修改了状态寄存器相关的标志位
  • 指令部的参数表示

    • %0 对应输出输入部的第一个参数,%1 表示第二个参数

指令部

linux内核中的例子

输出和输入部的约束修饰符

GCC内联操作符和修饰符

输出部和输入部的约束修饰符——通用

输出部和输入部通用的约束修饰符

输出部和输入部的约束修饰符——ARM64

ARM64特有的输出部和输入部约束修饰符

汇编符号名字来代替前缀%

  • %[name] → 引用约束变量

  • %w[name] → 引用 低 32 位寄存器(如 w0, w1

  • %x[name] → 引用 完整 64 位寄存器(如 x0, x1

汇编符号名字代替前缀%

实验 1:实现简单的 memcpy 函数

实验1

实验代码

陷阱与坑

  • GDB 不能单步调试内嵌汇编
  • 输出部和输入部的修饰符不能用错,否则程序会跑错

实验 3:使用内嵌汇编实现 memset 函数

实验3代码

内嵌汇编的高级玩法:和宏结合

  • 技巧 1:使用了 C 语言的#运算符。在带参数的宏中,“#”运算符作为一个预处理运算符,可以把记号转换为字符串

linux内核中的例子

1
2
3
4
5
6
7
8
9
#define ATOMIC_OP(op, asm_op) \
static inline void atomic_##op(int i, atomic_t *v) { \
__asm__ __volatile__( \
asm_op " %1, %0" \
: "+m" (v->counter) \
: "ir" (i)); \
}


可以用这个宏来生成多个原子操作函数:

1
2
ATOMIC_OP(add, "addl")
ATOMIC_OP(sub, "subl")

这会展开为:

1
2
3
4
5
6
7
8
9
10
11
12
13
static inline void atomic_add(int i, atomic_t *v) {
__asm__ __volatile__(
"addl %1, %0"
: "+m" (v->counter)
: "ir" (i));
}

static inline void atomic_sub(int i, atomic_t *v) {
__asm__ __volatile__(
"subl %1, %0"
: "+m" (v->counter)
: "ir" (i));
}

图片中的这个宏使用 #asm_op 是因为 它需要将宏参数 asm_op 转换成字符串字面量,用于字符串拼接或输出。这种语法叫做 宏字符串化(stringification)

asm_op 是宏参数,在宏展开时会替换为你提供的值(比如 "addl")。

它已经是一个字符串,比如你调用:

1
ATOMIC_OP(add, "addl")

宏展开后是:

1
2
3
4
5
__asm__ __volatile__(
"addl %1, %0"
: "+m" (v->counter)
: "ir" (i));

而这样:

1
 ATOMIC_OP(add, addl)

宏展开后是

1
2
3
4
__asm__ __volatile__(
addl "%1, %0"
: "+m" (v->counter)
: "ir" (i));

则不是

##name —— 符号连接(token pasting)运算符

  • 把宏参数和前后的标识符直接拼接成一个新的标识符(不是字符串)。

  • 常用来生成变量名、函数名等。

1
2
3
#define MAKE_FUNC(name) void func_##name(void) {}

MAKE_FUNC(test); // 展开成:void func_test(void) {}

实验 4:使用内嵌汇编与宏的结合

实验4

实验4代码

实验 5:实现读写系统寄存器的宏

实验5

实验5代码

这里用到了GNU C 的语句表达式 (statement expression) 语法:

({ ... }) 是什么

  • 这是 GNU 扩展,不是标准 C。

  • 它让一段代码块既能像语句一样执行,也能返回一个值

  • 语法规则:

    ({ statement1; statement2; ...; expression; })
    代码块里最后的那个 表达式(不加分号)就是返回值。

实验5另一种写法

内嵌汇编:goto

内嵌汇编的 goto 模板,可以跳转到 C 语言的 label 标签里

goto内嵌汇编

  • Goto 模板的输出部必须为空
  • 新增一个 gotolabels 的部,里面列出了 C 语言的 label,是允许跳转的 label

goto内嵌汇编的一个例子

实验 6:goto 模板的内嵌汇编

实验6

实验6代码

%l[label] 是 GCC 内联汇编中 asm goto 语法的特殊写法,表示跳转到 C 代码中的标签 label

详细解释:

  • %l[...] 是告诉编译器这是一个标签符号(label),而不是普通的寄存器或立即数。
  • label 是你在 C 代码里定义的标签名,比如你代码中的 label:
  • asm goto 允许汇编代码通过条件跳转直接跳转到 C 代码中的某个标签,实现条件分支。