在C语言中,内联汇编(Inline Assembly) 允许我们在C代码中直接嵌入汇编指令, 从而实现对硬件的直接控制、利用特殊指令优化性能、或访问标准C无法直接操作的处理器特性。 尤其在嵌入式系统开发中,内联汇编常用于操作寄存器、中断控制、任务切换、精确延时等场景。 本章将介绍GCC编译器下内联汇编的基本语法、扩展格式以及典型应用。
但内联汇编会降低代码的可移植性和可读性,应仅在必要时使用。
GCC内联汇编使用 asm 或 __asm__ 关键字,有两种形式:基本内联汇编和扩展内联汇编。
只包含汇编指令,没有输入、输出和破坏列表,通常用于执行没有数据交换的简单指令。
#include <stdio.h>
int main() {
// 执行一条空操作指令(NOP)
__asm__("nop");
// 多条指令用分号或换行分隔
__asm__("mov $1, %eax;"
"mov $0, %ebx");
return 0;
}
扩展内联汇编允许指定输入、输出和破坏的寄存器,格式如下:
__asm__ volatile (
"汇编指令模板"
: 输出操作数列表 // 可选
: 输入操作数列表 // 可选
: 破坏列表 // 可选(告知编译器哪些寄存器/内存被修改)
);
示例:交换两个整数变量
#include <stdio.h>
int main() {
int a = 5, b = 10;
__asm__ volatile (
"xchg %0, %1" // 交换 %0 和 %1
: "=r"(a), "=r"(b) // 输出:a和b("="表示只写,"+"表示读写)
: "0"(a), "1"(b) // 输入:使用与输出相同的编号
: // 无破坏寄存器
);
printf("a = %d, b = %d\n", a, b); // a=10, b=5
return 0;
}
"r":通用寄存器"=r":只写输出,寄存器"+r":读写操作数,寄存器"m":内存操作数"i":立即数"0", "1"...:与输出/输入占位符编号匹配| 约束/修饰符 | 含义 | 示例 |
|---|---|---|
r | 任意通用寄存器 | "r"(x) → 将x放入寄存器 |
m | 内存地址 | "m"(x) → 使用x的内存地址 |
i | 立即整数 | "i"(100) → 常数100 |
g | 任意寄存器、内存或立即数 | "g"(x) |
= | 只写输出 | "=r" |
+ | 读写操作数 | "+r" |
& | 早期破坏修饰符 | 表示该输出在输入被读取前就被修改 |
// 使用内存约束示例
int x = 10, y;
__asm__("mov %1, %0" : "=r"(y) : "m"(x));
默认情况下,编译器可能会优化掉或重新排列内联汇编语句。volatile 关键字告诉编译器不要对这段汇编代码进行优化,确保它按原样执行,并保持与其他代码的顺序。
__asm__ volatile("cli"); // 关中断,不能被优化掉
volatile,除非你明确知道该段汇编是纯计算且不需要保持顺序。
告诉编译器汇编代码隐式修改了哪些寄存器或内存,以便编译器进行正确的寄存器分配。
"cc":标志寄存器(条件码)被修改。"memory":内存被修改(不仅仅是列出的操作数)。"eax", "ebx" 等(x86)或 "r0", "r1"(ARM)。// 示例:执行加法,但不使用C变量,破坏eax
__asm__ volatile (
"add %0, %1"
:
: "r"(a), "r"(b)
: "eax"
);
// 使用 "memory" 破坏列表,告知编译器内存内容已改变
__asm__ volatile (
"mov %0, [%1]"
:
: "r"(val), "r"(addr)
: "memory"
);
#define PERIPH_BASE 0x40000000
#define GPIOA_BASE (PERIPH_BASE + 0x20000)
#define GPIOA_ODR (*(volatile unsigned int *)(GPIOA_BASE + 0x14))
// 使用内联汇编设置GPIO输出
void gpio_set(void) {
__asm__ volatile(
"ldr r0, =0x40020014\n"
"ldr r1, [r0]\n"
"orr r1, r1, #(1<<5)\n"
"str r1, [r0]\n"
: : : "r0", "r1", "memory"
);
}
更常用的做法是使用C指针操作,但在某些极端情况下需要特定指令时使用内联汇编。
static inline void disable_interrupts(void) {
__asm__ volatile("cli");
}
static inline void enable_interrupts(void) {
__asm__ volatile("sti");
}
void delay(int count) {
while (count--) {
__asm__ volatile("nop"); // 占用一个指令周期,防止被优化
}
}
unsigned long long rdtsc(void) {
unsigned long long result;
__asm__ volatile("rdtsc" : "=A"(result));
return result;
}
#define DSB() __asm__ volatile("dsb" : : : "memory")
#define ISB() __asm__ volatile("isb" : : : "memory")
利用占位符和约束,可以实现复杂的操作。
// 原子自增(x86 lock前缀)
int atomic_inc(int *ptr) {
int result;
__asm__ volatile(
"lock; xaddl %0, %1"
: "=r"(result), "+m"(*ptr)
: "0"(1)
: "cc"
);
return result;
}
// 位测试并设置(x86)
int test_and_set_bit(int nr, volatile void *addr) {
int old;
__asm__ volatile(
"bts %2, %1\n"
"sbb %0, %0\n"
: "=r"(old), "+m"(*(volatile long *)addr)
: "r"(nr)
: "cc"
);
return old;
}
gcc -S)查看编译器生成的代码,检查寄存器使用和内存访问顺序。
// 使能全局中断(Cortex-M)
static inline void __enable_irq(void) {
__asm__ volatile("cpsie i" : : : "memory");
}
// 禁止全局中断
static inline void __disable_irq(void) {
__asm__ volatile("cpsid i" : : : "memory");
}
// 进入待机模式
static inline void __WFI(void) {
__asm__ volatile("wfi" : : : "memory");
}
// 软件复位(通过AIRCR寄存器,此处使用内联汇编模拟)
static inline void system_reset(void) {
__asm__ volatile(
"ldr r0, =0xE000ED0C\n" // AIRCR 地址
"ldr r1, =0x05FA0001\n" // 写入 KEY + SYSRESETREQ
"str r1, [r0]\n"
: : : "r0", "r1", "memory"
);
}
内联汇编是C语言与底层硬件之间的桥梁,赋予程序员直接控制CPU的能力。 然而,其不可移植性和复杂性意味着应当谨慎使用,并尽量将平台相关代码隔离到单独的文件中。 掌握内联汇编对于嵌入式系统、操作系统内核、驱动程序开发者来说是一项重要技能。