sys 汇编
一、ISA 基本概念
- ISA (Instruction Set Architecture) 是处理器架构的一部分。
- ISA 是软件与硬件通信所使用的命令和操作的集合。
- ISA 与微架构不同:微架构是处理器设计方法,用于执行指令集。
- ISA 是内置于处理器微架构中的一组命令,处理器可以直接解码并执行。
二、RISC-V 概述
- RISC-V(读作 “risk-five”)是一个 ISA 标准。
- 基于精简指令集计算(RISC)的开源 ISA。
- 之前有 RISC-I、II、III、IV。
- 大多数 ISA(如 x86、ARM、Power、MIPS、SPARC)受专利保护,RISC-V 是开放的,允许任何人构建兼容计算机并使用相关软件。
- 起源于 2010 年,由 UC Berkeley 的研究人员 Krste Asanovic、David Patterson 及其学生开发。
三、RISC-V 设计目标
- 完全开放的 ISA,自由用于学术和工业。
- 真正的 ISA,适合直接硬件实现,而非仅用于模拟或二进制翻译。
- 避免“过度架构”:
- 不针对特定微架构风格(如微码、硬连线控制、按序、解耦、乱序等)
- 不针对特定实现技术(如全定制、ASIC、FPGA)
- 允许在上述任何风格中高效实现
- RISC-V ISA 包括:
- 小型基础整数 ISA
- 可选的标准扩展
- 可选的自定义扩展
- 支持修订后的 2008 IEEE-754 浮点标准。
四、RISC-V 设计原则
- 通常保持非常简单且可扩展。
- 指令长度可以是短、长或可变。
- 分为多个规范:
- 用户级 ISA 规范(计算指令)
- 压缩 ISA 规范(16位指令)
- 特权 ISA 规范(监管模式指令)
- 更多...
- ISA 支持表示为:RV + 字宽 + 支持的扩展。
- 示例:RV64I 表示 64位 RISC-V,支持 I(整数)指令集。
五、基础指令集与扩展
基础整数 ISA(必须实现)
标准扩展
- M:整数乘除
- A:原子指令
- F:单精度浮点
- D:双精度浮点
- C:压缩指令(16位)
- G = I + M + A + F + D
可选扩展
六、地址空间变体
- RV32:32位地址空间
- RV64:64位地址空间
- 易于子集化/扩展,适合教育/研究
- 示例:RV64IM、RV64IMA、RV64G
七、扩展功能表(摘要)
| 名称 |
功能 |
| RV32I |
基础32位整数指令集,32个寄存器 |
| RV32E |
32位指令集,仅16个寄存器,用于极低端嵌入式 |
| RV64I |
基础64位整数指令集,所有寄存器64位 |
| M |
整数乘除 |
| A |
原子指令(并发处理) |
| F |
单精度浮点,32个32位浮点寄存器 |
| D |
双精度浮点,64位寄存器 |
| Q |
四精度浮点,128位操作 |
| L |
64/128位十进制浮点 |
| C |
压缩指令(16位版本) |
| V |
向量操作(未来) |
| B |
位字段操作(未来) |
| T |
事务内存(未来) |
| P |
打包SIMD指令 |
| RV128I |
128位地址空间(未来) |
八、RV64I 寄存器
- 程序计数器(PC)
- 32个64位整数寄存器(x0–x31)
- x0 始终为 0
- x1 用于保存返回地址
- 32个浮点寄存器(f0–f31)
- 每个可存放单精度或双精度浮点值(64位 IEEE FP)
- 浮点状态寄存器(fsr),用于浮点舍入模式和异常报告
九、ALU 指令示例
| 示例指令 |
名称 |
含义 |
| add x1,x2,x3 |
Add |
Regs[x1] ← Regs[x2] + Regs[x3] |
| addi x1,x2,3 |
Add immediate |
Regs[x1] ← Regs[x2] + 3 |
| lui x1,42 |
Load upper immediate |
Regs[x1] ← 0'32#'42#'0'12 |
| sll x1,x2,5 |
Shift left logical |
Regs[x1] ← Regs[x2] << 5 |
| slt x1,x2,x3 |
Set less than |
if (Regs[x2] < Regs[x3]) Regs[x1] ← 1 else 0 |
十、Load/Store 指令示例(部分)
| 示例指令 |
名称 |
含义 |
| ld x1,80(x2) |
Load doubleword |
64位加载 |
| lw x1,60(x2) |
Load word |
符号扩展32位 |
| lwu x1,60(x2) |
Load word unsigned |
零扩展32位 |
| lb x1,40(x3) |
Load byte |
符号扩展8位 |
| lbu x1,40(x3) |
Load byte unsigned |
零扩展8位 |
| fld f0,50(x2) |
Load FP double |
64位浮点加载 |
| sd x2,400(x3) |
Store double |
64位存储 |
| sw x3,500(x4) |
Store word |
32位存储 |
| fsw f0,40(x3) |
Store FP single |
32位浮点存储 |
| sh x3,502(x2) |
Store half |
16位存储 |
| sb x2,41(x3) |
Store byte |
8位存储 |
注:所有内存引用使用单一寻址模式。
十一、控制转移指令示例
| 示例指令 |
名称 |
含义 |
| jal x1,offset |
Jump and link |
Regs[x1] ← PC+4; PC ← PC+(offset<<1) |
| jalr x1,x2,offset |
Jump and link register |
Regs[x1] ← PC+4; PC ← Regs[x2]+offset |
| beq x3,x4,offset |
Branch equal zero |
if (Regs[x3]==Regs[x4]) PC ← PC+(offset<<1) |
| bgt x3,x4,name |
Branch greater than |
if (Regs[x3]>Regs[x4]) PC ← PC+(offset<<1) |
所有控制指令(除跳转到寄存器地址外)都是 PC 相对寻址。
十二、指令编码格式总结
| 格式 |
字段(位数) |
说明 |
| R-type |
funct7(7), rs2(5), rs1(5), funct3(3), rd(5), opcode(7) |
算术指令 |
| I-type |
imm11:0, rs1(5), funct3(3), rd(5), opcode(7) |
加载/立即数算术 |
| S-type |
imm11:5, rs2(5), rs1(5), funct3(3), imm4:0, opcode(7) |
存储 |
| B-type |
imm12,10:5, rs2(5), rs1(5), funct3(3), imm4:1,11, opcode(7) |
条件分支 |
| J-type |
imm20,10:1,11,19:12, rd(5), opcode(7) |
无条件跳转 |
| U-type |
imm31:12, rd(5), opcode(7) |
上立即数 |
- 基础指令集(RV32)始终为32位固定长度,最低两位为 11₂。
- 所有分支和跳转目标为16位粒度。
总结对照表
| 字段 |
全称 |
作用 |
常见位宽 |
| rd |
Destination |
结果写入哪个寄存器 |
5 位 |
| rs1 |
Source 1 |
第一个操作数来自哪个寄存器 |
5 位 |
| rs2 |
Source 2 |
第二个操作数来自哪个寄存器 |
5 位 |
| imm |
Immediate |
直接给的数值(偏移/常量) |
12/20位 |
| 请给我基本的riscv指令 |
|
|
|
返回[[指令集## 十一、CPU we will be doing(我们将实现的CPU)]]
十三、特权模式
| 级别 |
编码 |
名称 |
缩写 |
| 0 |
00 |
User/Application |
U |
| 1 |
01 |
Supervisor |
S |
| 2 |
10 |
Reserved |
- |
| 3 |
11 |
Machine |
M |
- Machine mode 是最高特权模式,也是唯一必需的模式。
- 更高特权模式可访问低特权模式的所有功能。
- 处理器通常在最低特权模式下执行,中断/异常切换到更高特权模式。
十四、支持的权限模式组合
| 级别数 |
支持模式 |
用途 |
| 1 |
M |
简单嵌入式系统 |
| 2 |
M, U |
安全嵌入式系统 |
| 3 |
M, S, U |
运行类 Unix 操作系统的系统 |
十五、函数调用约定(Calling Convention)
六个阶段:
1. 将参数放在函数可访问的位置
2. 跳转到函数(使用 jal)
3. 获取局部存储资源,保存寄存器
4. 执行函数任务
5. 将结果放回调用者可访问的位置,恢复寄存器,释放存储资源
6. 返回调用点(使用 ret)
十六、栈与栈帧
- 栈:临时保存变量的内存,后进先出(LIFO),可扩展和收缩。
- 栈帧:子例程分配并在退出时释放的私有空间,由帧指针(fp/x8)标识。
十七、寄存器 ABI 名称(部分)
| 寄存器 |
ABI 名称 |
说明 |
保存者 |
| x0 |
zero |
硬连线零 |
- |
| x1 |
ra |
返回地址 |
调用者 |
| x2 |
sp |
栈指针 |
被调用者 |
| x8 |
s0/fp |
保存寄存器/帧指针 |
被调用者 |
| x10-11 |
a0-1 |
函数参数/返回值 |
调用者 |
| x18-27 |
s2-11 |
保存寄存器 |
被调用者 |
十八、函数进入与退出
函数序言(Prologue)
- 调整栈指针(sp)分配栈帧空间
- 保存返回地址(ra)等寄存器
函数尾声(Epilogue)
十九、从源代码到运行程序的过程
步骤(逻辑上):
- 预处理(.c → .i):展开宏、包含文件
- 编译(.i → .s)
- 汇编(.s → .o)
- 链接(多个 .o → 可执行文件)
- 加载(运行)
二十、伪指令(Pseudo-instructions)示例
| 伪指令 |
基础指令 |
含义 |
| nop |
addi x0, x0, 0 |
无操作 |
| neg rd, rs |
sub rd, x0, rs |
取补码 |
| beqz rs, offset |
beq rs, x0, offset |
如果等于零则分支 |
| ret |
jalr x0, x1, 0 |
从子例程返回 |
二十一、汇编器输出:ELF 文件
- 可执行与可链接格式(ELF)
- 两种视图:
- 程序头:运行时使用的段
- 节头:节(section)的集合
可重定位目标文件
- 二进制机器码,但不可执行
- 可能引用外部符号
- 包含符号表
- 每个目标文件有自己的地址空间,地址稍后修复
可执行目标文件
二十二、链接器作用
- 将多个 .o 文件合并为可执行文件
- 重定位每个目标文件的代码和数据段
- 解析未解析的符号
- 记录程序入口点
地址重定位
- 合并同类型段(如 .text, .data)
- 使用重定位信息修复地址
符号解析
二十三、静态链接 vs 动态链接
静态链接
- 所有库代码在运行前链接并加载
- 库较大时浪费内存
- 库更新后仍使用旧版本
动态链接
- 第一次调用时才加载和链接外部函数
- 每次程序运行时链接当前版本
- 多个程序共享同一库代码内存
- 使用 GOT 和 PLT 实现
- 每次调用有少量运行时开销
二十四、静态库
- 一组目标文件的集合(类似 zip 归档)
- 链接器只提取需要的目标文件来解析未定义引用
- 示例:libc.a 包含 printf.o, read.o, exit.o 等
二十五、加载器(Loader)
- 将可执行文件加载到内存中
- 跳转到起始地址
- 初始化寄存器、栈、参数
- 在现代操作系统中,加载是操作系统任务之一
加载静态链接程序步骤:
- 确定所需地址空间大小
- 分配地址空间
- 将程序读入段
- 清零未初始化数据(.bss)
- 创建栈段
- 设置运行时信息(参数、环境变量)
- 启动程序
加载动态链接程序:
- 操作系统启动动态链接器
- 动态链接器启动目标程序
- 处理第一次外部调用
- 将函数复制到内存
- 修改程序调用点指向正确函数
二十六、动态链接的优缺点
- 优点:易于创建和更新,共享库节省内存,使用最新版本
- 缺点:运行时性能开销更高,每次运行需重新解析符号