007.GNU C内联汇编杂谈|千篇笔记实现嵌入式全栈/裸机篇

内联汇编就像一把尖刀,它允许我们在C的高级抽象和CPU底层硬件之间自由穿梭~

1. 什么时候用到?

嵌入式开发,经常有这样的场景:

  • 访问CPU特殊寄存器, 比如通过CPSR切换工作模式,通过CP15配置协处理器(MMU、缓存等)

  • 通过nop指令精准延时

  • 中断的入口封装、现场保护等

  • 特殊硬件指令比如swi

这个时候,在C/C++编程中,就需要使用到内联汇编去处理这样的场景。

2. 核心语法

C 复制代码
__asm__ volatile (
    "汇编指令模板\n\t"   /* 第1部分:你要执行的指令 */
    : 输出操作数列表     /* 第2部分:C 变量 <- 寄存器 (由内向外写) */
    : 输入操作数列表     /* 第3部分:寄存器 <- C 变量 (由外向内读) */
    : 破坏描述部(Clobber)/* 第4部分:告诉编译器你弄脏了哪些东西 */
);

举例1,

C 复制代码
unsigned long cpsr, out, in = 1;
__asm__ volatile (
    // 读取cpsr
    "mrs %0, cpsr\n\t"
    // 禁用IRQ/FIQ, 切换至IRQ模式
    "msr cpsr_c, #0b11010010\n\t"
    // 设置IRQ模式的SP
    "ldr sp, =0x40001000\n\t"
    // 恢复原来模式和原始中断状态
    "msr cpsr, %0\n\t"
    // 纯凑数
    "mov %1, %2\n\t"
    : "=&r" (cpsr), "=r" (out)
    : "r" (in)
    : "sp", "cc", "memory"
);

举例2,

C 复制代码
void restore_irq_mask(unsigned long old_cpsr)
{
    __asm__ volatile (
        "msr cpsr, %0\n\t"
        : 
        : "r" (old_cpsr)
        : "cc", "memory"
    );
}

2.1 汇编指令模板

就是ARM汇编指令, 但是操作数变成了占位符(%0, %1, %2...)

多天指令之间用\n\t分割

占位符对应输出和输入列表中出现的顺序, 参见举例1中的占位符用法

2.2 操作数约束

上述举例中在输出和输入部分有这样的符号组合,有些是修饰符(只用于输出部分),有些是约束字符

C 复制代码
: "=&r" (cpsr), "=r" (out)
: "r" (in)
约束符号 说明
"r" 让编译器随便挑一个通用寄存器(R0-R12),把变量放进去
"m" 内存操作数。告诉编译器这个变量在内存里,不要放进寄存器
"I" 立即数
修饰符 说明
"=" 只写(Write-only)。表示这条汇编会把新值写入这个 C 变量,变量之前的值不重要了
"+" 读写(Read-write)。表示这条汇编既会先读后写这个 C 变量(此时变量即是输入数又是输出数)
"&" 早占(Earlyclobber)。这是一个高级且救命的修饰符!它告诉编译器:"在这个汇编块执行完之前,这个输出寄存器就已经被写入了。所以,千万不要把任何输入变量分配到同一个物理寄存器上,否则输入数据会被提前覆盖!"比如在上述举例中"=&r" (cpsr),cpsr如果使用了编译器自动分配的寄存器r2,那么"r" (in)就不会分配给r2,以免r2被提前写入发生值改变,让in失效

2.3 破坏描述部

编译器可能会聪明过头,悄悄把很多C变量还存在寄存器中。如果汇编中修改了某些寄存器又不告诉编译器,那么执行就可能出现问题。

那么就需要用一些口令告诉编译器你修改了什么部分。

破坏描述符 说明
"cc" 告诉编译器,这段汇编修改了 CPSR 的标志位(N, Z, C, V),比如你用了 adds 或 cmp 指令。编译器之后就不会依赖之前的条件判断了
"memory" 内存屏障。告诉编译器,这段汇编偷偷修改了内存。编译器会强制将缓存在寄存器里的所有变量重新写回内存,并在汇编执行后重新从内存读取,防止读到脏数据
具体寄存器名(如 "r1", "lr") 如果你在汇编里硬编码(写死)使用了某个物理寄存器,必须在这里声明,编译器就会在执行汇编前把那个寄存器压栈保护起来
相关推荐
忡黑梨9 小时前
eNSP_DHCP配置
c语言·网络·c++·python·算法·网络安全·智能路由器
D4c-lovetrain9 小时前
Linux个人心得28(k8s实战)
linux·运维·kubernetes
淼淼爱喝水9 小时前
openEuler 环境下 Ansible Playbook 实战:批量创建用户并修改 Shell 属性
linux·运维·服务器·openeuler·playbook
莎士比亚的文学花园9 小时前
Linux驱动开发(2)——驱动编程
linux·运维·驱动开发
YaBingSec9 小时前
玄机网络安全靶场:Jackson-databind 反序列化漏洞(CVE-2017-7525)
linux·网络·笔记·安全·web安全
计算机安禾9 小时前
【Linux从入门到精通】第30篇:综合案例:编写一个Linux系统体检脚本
linux·运维·服务器
海的预约9 小时前
Bootloader应用分析
linux·运维·服务器
时空未宇10 小时前
海鸥派顺利运行YOLO11S
linux·运维·服务器
半壶清水10 小时前
ubuntu中部署开源交换机模拟器bmv2详细步骤
linux·运维·网络·网络协议·tcp/ip·ubuntu
j_xxx404_10 小时前
Linux:深入解析ELF文件结构
linux·运维·服务器