第五章:内联汇编
涉及内核源码: , ,
楔子
林小源理解了寄存器,但他发现了一个矛盾。
内核是用 C 写的,但有些操作——比如读取控制寄存器、发起系统调用、执行原子操作——C 语言无法直接完成。它们需要特定的汇编指令。
但内核不能全是汇编。汇编难写、难读、难维护。
怎么办?
答案是内联汇编——在 C 代码中嵌入汇编指令。
他第一次看到 asm volatile("csrr %0, satp" : "=r"(val)) 这行代码时,完全不知道它在说什么。但当他理解了约束("=r"、"+r"、"i")的含义后,他意识到这是一种极其精妙的设计——C 编译器负责寄存器分配和代码布局,程序员负责指定关键的汇编指令。
两者配合,既高效又安全。
但"安全"这个词,只在你写对了的时候成立。
一、基本语法
林小源在工坊的尽头找到了一扇暗门。
门后是一间狭小的密室,空气中弥漫着金属和臭氧的味道。密室的中央坐着一个老者——不是编译器大殿里那个灰袍老者,而是一个更苍老、更沉默的存在。他的左半边身体是 C 语言的语法结构,右半边身体是汇编指令的二进制流。两种截然不同的语言在他身上交汇,却奇异地和谐。
"你是谁?"林小源问。
老者抬起头,声音像两种语言同时在说话:"我是内联汇编。C 与汇编之间的桥梁。"
他伸出左手——那只手由 C 语言的花括号和分号组成:"C 语言是内核的骨架。它可读、可维护、可移植。但有些事它做不到——读取控制寄存器、执行原子操作、发起系统调用。"
又伸出右手——那只手由汇编指令的二进制编码组成:"汇编是机器的母语。它能做任何事——但它难写、难读、难维护。内核不能全是汇编。"
然后他合起双手:"所以有了我。在 C 代码中嵌入汇编指令。编译器负责寄存器分配和代码布局,你负责指定关键的汇编指令。两者配合,既高效又安全。"
"怎么用?"
老者在空中写了两行字:
asm("汇编指令");
asm volatile("汇编指令"); /* volatile: 防止编译器优化掉 */" 告诉编译器:这条汇编有副作用,不能省略,不能移动顺序。没有 ,编译器可能觉得这条指令'没用',直接删掉。内核中的汇编几乎都用 。"
二、带约束的内联汇编
老者从桌上拿起一张泛黄的羊皮纸,上面写着完整的内联汇编语法:
asm volatile(
"汇编模板"
: 输出操作数 (可选)
: 输入操作数 (可选)
: 破坏列表 (可选)
);"汇编模板是你要执行的指令。操作数用 %0、%1、%2 引用——编译器会把它们替换成实际的寄存器或内存地址。约束告诉编译器:这个操作数应该放在哪里。"
林小源看着那些约束符号,一头雾水。
老者指着羊皮纸上的注释:""r" — 通用寄存器。编译器选一个空闲的寄存器存放这个操作数。"=r" — 输出操作数,只写。"+r" — 同一个寄存器既输入又输出。"i" — 编译期立即数。"m" — 内存操作数。"
他在空中演示了一个例子:
int x;
asm volatile("li %0, 42" : "=r"(x));"li %0, 42 — 把立即数 42 加载到 %0。约束 "=r" 告诉编译器:%0 是一个输出操作数,放在通用寄存器里。编译器会选一个空闲寄存器,生成 li reg, 42,然后把 的值赋给 x。"
又演示了一个:
int a = 10, b = 20, result;
asm volatile(
"addw %0, %1, %2"
: "=r"(result)
: "r"(a), "r"(b)
);"addw %0, %1, %2 — 把 %1 和 %2 相加,结果存到 %0。%1 是 a,%2 是 b,%0 是 。编译器负责把 a 和 b 放到寄存器里,再把结果写回 。"
"如果我想让输入和输出用同一个寄存器呢?"
老者露出赞许的表情:""+r"。比如 addi %0, %0, 1——自增。%0 既是输入又是输出,必须是同一个寄存器。"
三、内核中的常用内联汇编
老者从密室的墙壁上取下三个锦囊,一一展开。
第一个锦囊里写着 rdcycle:
static inline uint64_t read_cycle(void) {
uint64_t val;
asm volatile("rdcycle %0" : "=r"(val));
return val;
}"rdcycle 读取 CPU 的周期计数器。从上电开始,CPU 每执行一个时钟周期,这个计数器就加一。用它可以精确测量代码的执行时间——两个 rdcycle 的差值就是消耗的周期数。"
第二个锦囊里写着 :
static inline uint64_t read_time(void) {
uint64_t val;
asm volatile("rdtime %0" : "=r"(val));
return val;
}" 读取时间寄存器。和 rdcycle 不同,它不是 CPU 的周期数,而是经过校准的纳秒级时间戳。用于需要真实时间的场景。"
第三个锦囊里写着 :
static inline void mb(void) {
asm volatile("fence rw, rw" ::: "memory");
}"内存屏障。fence rw, rw 告诉 CPU:在这条指令之前的所有读写操作,必须在之后的任何读写操作开始之前完成。没有乱序,没有重排。"
林小源看着那三个锦囊。一个测量时间,一个读取时钟,一个强制顺序。它们看似简单,却是内核中最基础的原语——没有它们,性能测量、定时器、并发控制都无从谈起。
"破坏列表里的 "memory" 是什么?"林小源问。
"告诉编译器:这条指令会修改内存。编译器不能假设内存中的值在这条指令前后不变——它必须重新从内存读取,而不是用缓存在寄存器中的旧值。"
四、带内存操作数的汇编
老者从密室的地板下取出一个铁箱。箱子里装着三件武器——每一件都散发着危险的气息。
第一件是一把双刃剑,剑柄上刻着 lr.w / sc.w:
static inline int atomic_cmpxchg(int *ptr, int old_val, int new_val) {
int prev, tmp;
asm volatile(
"0: lr.w %0, (%2)\n"
" bne %0, %3, 1f\n"
" sc.w %1, %4, (%2)\n"
" bnez %1, 0b\n"
"1:"
: "=&r"(prev), "=&r"(tmp)
: "r"(ptr), "r"(old_val), "r"(new_val)
: "memory"
);
return prev;
}"Load-Reserved / Store-Conditional,"老者的声音低沉,"这是 RISC-V 实现比较并交换的基础。lr.w 从内存读取一个值,同时在那个地址上设置一个'预约'。sc.w 尝试写入——如果预约还在(没有其他 CPU 修改过那个地址),写入成功;否则写入失败,整个操作从头重试。"
"如果另一个 CPU 在 lr 和 sc 之间修改了那个地址?"
"sc.w 失败,返回非零值。代码跳回 0: 重新开始。这就是'条件'的含义——只有在没有竞争的情况下,写入才会成功。"
第二件武器是一柄重锤,锤面上刻着 amoadd.w:
static inline void atomic_add(int *ptr, int val) {
asm volatile(
"amoadd.w zero, %1, (%0)"
:
: "r"(ptr), "r"(val)
: "memory"
);
}"Atomic Memory Operation — Add。一条指令完成读取、加法、写回。在多核系统中,这保证了原子性——不会有另一个 CPU 在中间插一脚。"
第三件武器是一面盾牌,盾面上刻着 amoswap.w:
static inline int xchg(int *ptr, int new_val) {
int old;
asm volatile(
"amoswap.w %0, %2, (%1)"
: "=r"(old)
: "r"(ptr), "r"(new_val)
: "memory"
);
return old;
}"Atomic Memory Operation — Swap。读取旧值的同时写入新值,一条指令完成。自旋锁的实现就依赖这个——用 原子地把锁的状态从'未锁定'改为'已锁定'。"
林小源看着那三件武器。每一件都是多核世界的基石。没有它们,并发就是一句空话。
"Linux 内核把这些指令封装成通用接口,"老者说,"、、——上层代码不需要知道底层用的是 lr/sc 还是 amo*。架构不同,接口相同。"
五、系统调用的汇编实现
老者最后带着林小源来到密室最深处的一面石壁前。石壁上刻着一个完整的系统调用流程——从用户态到内核态,再从内核态返回。
static long my_write(int fd, const void *buf, size_t count) {
register long a0 asm("a0") = fd;
register long a1 asm("a1") = (long)buf;
register long a2 asm("a2") = (long)count;
register long a7 asm("a7") = SYS_write;
asm volatile(
"ecall"
: "+r"(a0)
: "r"(a1), "r"(a2), "r"(a7)
: "memory"
);
return a0;
}"这就是手动发起系统调用,"老者指着石壁,"register long a0 asm("a0") 告诉编译器:把变量 a0 固定在寄存器 a0 上。然后 触发陷入。返回值也放在 a0 中——用 "+r" 约束表示它既是输入又是输出。"
林小源看着石壁上的代码。每一步都精确到寄存器——a7 存系统调用号,a0 到 a2 存参数, 触发陷入,a0 接收返回值。没有 libc 的包装,没有额外的开销——从用户态到内核态,只有这一条路。
"这就是 的本质,"老者说,"RISC-V 用户态进入内核的门户。每次用户态程序调用 read()、write()、,最终都会通过 进入内核。只不过 libc 帮你把参数放好了,你感受不到它的存在。"
老者转身走向密室的出口。他的半边 C 身体和半边汇编身体在门口的光线中融为一体。
"内联汇编是最后的手段,"他的声音从远处传来,"能用 C 解决的问题,不要用汇编。但有些事——读取 CSR、执行原子操作、发起系统调用——C 语言无能为力。那时候,你就需要我。"
"但记住——我是一把没有剑鞘的剑。用对了,削铁如泥。用错了,伤的是你自己。"
道藏笔记
内核启示
指令是 RISC-V 用户态进入内核的门户。 每次用户态程序调用 read()、write()、,最终都会通过 进入内核。
RISC-V 的系统调用入口在 附近展开:硬件从 U 态陷入 S 态,内核保存用户上下文,依据 a7 中的调用号查表分派,最后恢复寄存器并返回用户态。
原子操作同样体现了架构差异。RISC-V 用 lr/sc 和 amo* 指令表达原子读改写;内核把它们封装在统一的 atomic_*()、 和锁原语后面,让上层代码不用关心具体指令。
代码典籍
int x;
asm volatile("li %0, 42" : "=r"(x));
int a = 10, b = 20, result;
asm volatile(
"addw %0, %1, %2"
: "=r"(result)
: "r"(a), "r"(b)
);
int val = 100;
asm volatile(
"addi %0, %0, 1"
: "+r"(val)
);#include <stdio.h>
/*
* 完整语法:
* asm volatile(
* "汇编模板"
* : 输出操作数 (可选)
* : 输入操作数 (可选)
* : 破坏列表 (可选)
* );
*
* RISC-V 常用约束:
* "r" — 通用寄存器
* "f" — 浮点寄存器
* "i" — 编译期立即数
* "m" — 内存操作数
* "0" — 与第 0 个操作数使用同一位置
* "+r" — 同一个寄存器既输入又输出
*/
int main() {
printf("=== RISC-V 内联汇编基础 ===\n\n");
int x;
#if defined(__riscv)
asm volatile("li %0, 42" : "=r"(x));
#else
x = 42;
#endif
printf("li → x = %d\n\n", x);
int a = 10, b = 20, result;
#if defined(__riscv)
asm volatile(
"addw %0, %1, %2"
: "=r"(result)
: "r"(a), "r"(b)
);
#else
result = a + b;
#endif
printf("addw: %d + %d = %d\n\n", a, b, result);
int val = 100;
#if defined(__riscv)
asm volatile(
"addi %0, %0, 1"
: "+r"(val)
);
#else
val += 1;
#endif
printf("addi: 100 -> %d\n\n", val);
unsigned long cycle;
#if defined(__riscv)
asm volatile("rdcycle %0" : "=r"(cycle));
#else
cycle = 0;
#endif
printf("rdcycle = %lu\n", cycle);
printf("注意: satp/sstatus 等特权 CSR 只能在内核态读取\n");
return 0;
}static inline uint64_t read_cycle(void) {
uint64_t val;
asm volatile("rdcycle %0" : "=r"(val));
return val;
}
static inline uint64_t read_time(void) {
uint64_t val;
asm volatile("rdtime %0" : "=r"(val));
return val;
}
static inline void mb(void) {
asm volatile("fence rw, rw" ::: "memory");
}
static inline void wmb(void) {
asm volatile("fence w, w" ::: "memory");
}
static inline void rmb(void) {
asm volatile("fence r, r" ::: "memory");
}#include <stdio.h>
#include <stdint.h>
static inline uint64_t read_cycle(void) {
#if defined(__riscv)
uint64_t val;
asm volatile("rdcycle %0" : "=r"(val));
return val;
#else
static uint64_t fake_cycle;
return fake_cycle += 1000;
#endif
}
static inline uint64_t read_time(void) {
#if defined(__riscv)
uint64_t val;
asm volatile("rdtime %0" : "=r"(val));
return val;
#else
static uint64_t fake_time;
return fake_time += 100;
#endif
}
static inline void mb(void) {
#if defined(__riscv)
asm volatile("fence rw, rw" ::: "memory");
#else
__sync_synchronize();
#endif
}
static inline void wmb(void) {
#if defined(__riscv)
asm volatile("fence w, w" ::: "memory");
#else
__sync_synchronize();
#endif
}
static inline void rmb(void) {
#if defined(__riscv)
asm volatile("fence r, r" ::: "memory");
#else
__sync_synchronize();
#endif
}
int main() {
printf("=== RISC-V 内核内联汇编实战 ===\n\n");
uint64_t t1 = read_cycle();
volatile int sum = 0;
for (int i = 0; i < 100000; i++) sum += i;
uint64_t t2 = read_cycle();
printf("cycle 起点: %llu\n", (unsigned long long)t1);
printf("cycle 终点: %llu\n", (unsigned long long)t2);
printf("循环消耗: %llu cycles\n\n", (unsigned long long)(t2 - t1));
printf("time CSR: %llu\n\n", (unsigned long long)read_time());
mb();
printf("RISC-V 屏障:\n");
printf(" fence rw, rw — 全屏障\n");
printf(" fence r, r — 读屏障\n");
printf(" fence w, w — 写屏障\n\n");
printf("特权 CSR 示例(这里只展示,不在用户态执行):\n");
printf(" csrr %%0, satp — 页表根寄存器\n");
printf(" csrr %%0, sstatus — S 态状态寄存器\n");
return 0;
}/* RISC-V LR/SC 实现的比较并交换 */
static inline int atomic_cmpxchg(int *ptr, int old_val, int new_val) {
int prev, tmp;
asm volatile(
"0: lr.w %0, (%2)\n"
" bne %0, %3, 1f\n"
" sc.w %1, %4, (%2)\n"
" bnez %1, 0b\n"
"1:"
: "=&r"(prev), "=&r"(tmp)
: "r"(ptr), "r"(old_val), "r"(new_val)
: "memory"
);
return prev;
}
static inline void atomic_add(int *ptr, int val) {
asm volatile(
"amoadd.w zero, %1, (%0)"
:
: "r"(ptr), "r"(val)
: "memory"
);
}
static inline int xchg(int *ptr, int new_val) {
int old;
asm volatile(
"amoswap.w %0, %2, (%1)"
: "=r"(old)
: "r"(ptr), "r"(new_val)
: "memory"
);
return old;
}#include <stdio.h>
#include <stdint.h>
/* RISC-V LR/SC 实现的比较并交换 */
static inline int atomic_cmpxchg(int *ptr, int old_val, int new_val) {
#if defined(__riscv)
int prev, tmp;
asm volatile(
"0: lr.w %0, (%2)\n"
" bne %0, %3, 1f\n"
" sc.w %1, %4, (%2)\n"
" bnez %1, 0b\n"
"1:"
: "=&r"(prev), "=&r"(tmp)
: "r"(ptr), "r"(old_val), "r"(new_val)
: "memory"
);
return prev;
#else
int expected = old_val;
__atomic_compare_exchange_n(ptr, &expected, new_val, 0,
__ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
return expected;
#endif
}
static inline void atomic_add(int *ptr, int val) {
#if defined(__riscv)
asm volatile(
"amoadd.w zero, %1, (%0)"
:
: "r"(ptr), "r"(val)
: "memory"
);
#else
__atomic_fetch_add(ptr, val, __ATOMIC_SEQ_CST);
#endif
}
static inline int xchg(int *ptr, int new_val) {
#if defined(__riscv)
int old;
asm volatile(
"amoswap.w %0, %2, (%1)"
: "=r"(old)
: "r"(ptr), "r"(new_val)
: "memory"
);
return old;
#else
return __atomic_exchange_n(ptr, new_val, __ATOMIC_SEQ_CST);
#endif
}
int main() {
printf("=== RISC-V 原子操作 ===\n\n");
int val = 100;
printf("CAS(&val, 100, 200):\n");
int old = atomic_cmpxchg(&val, 100, 200);
printf(" 返回旧值: %d, val 现在: %d\n\n", old, val);
printf("CAS(&val, 100, 300) — 期望不匹配:\n");
old = atomic_cmpxchg(&val, 100, 300);
printf(" 返回旧值: %d (不等于 100,CAS 失败)\n", old);
printf(" val 仍然是: %d\n\n", val);
val = 0;
for (int i = 0; i < 10; i++) {
atomic_add(&val, 1);
}
printf("10 次 amoadd.w -> val = %d\n\n", val);
val = 42;
int old_val = xchg(&val, 99);
printf("amoswap.w: 旧值 = %d, 新值 = %d\n", old_val, val);
return 0;
}static long my_write(int fd, const void *buf, size_t count) {
register long a0 asm("a0") = fd;
register long a1 asm("a1") = (long)buf;
register long a2 asm("a2") = (long)count;
register long a7 asm("a7") = SYS_write;
asm volatile(
"ecall"
: "+r"(a0)
: "r"(a1), "r"(a2), "r"(a7)
: "memory"
);
return a0;
}
static long my_getpid(void) {
register long a0 asm("a0") = 0;
register long a7 asm("a7") = SYS_getpid;
asm volatile(
"ecall"
: "+r"(a0)
: "r"(a7)
: "memory"
);
return a0;
}#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
/*
* Linux RISC-V 64:
* a7 = 系统调用号
* a0-a5 = 参数
* ecall = 进入内核
* a0 = 返回值
*/
static long my_write(int fd, const void *buf, size_t count) {
#if defined(__riscv)
register long a0 asm("a0") = fd;
register long a1 asm("a1") = (long)buf;
register long a2 asm("a2") = (long)count;
register long a7 asm("a7") = SYS_write;
asm volatile(
"ecall"
: "+r"(a0)
: "r"(a1), "r"(a2), "r"(a7)
: "memory"
);
return a0;
#else
return write(fd, buf, count);
#endif
}
static long my_getpid(void) {
#if defined(__riscv)
register long a0 asm("a0") = 0;
register long a7 asm("a7") = SYS_getpid;
asm volatile(
"ecall"
: "+r"(a0)
: "r"(a7)
: "memory"
);
return a0;
#else
return getpid();
#endif
}
int main() {
printf("=== RISC-V 手动系统调用 ===\n\n");
const char *msg = "Hello from RISC-V inline assembly!\n";
long len = my_write(1, msg, 35);
printf("write 返回: %ld\n\n", len);
long pid = my_getpid();
printf("getpid (汇编): %ld\n", pid);
printf("getpid (libc): %d\n", getpid());
printf("\necall 约定:\n");
printf(" a7: 系统调用号\n");
printf(" a0-a5: 参数\n");
printf(" a0: 返回值,负值表示 -errno\n");
return 0;
}内联汇编试炼
RISC-V 从用户态陷入内核态发起系统调用的指令是什么?