MIPS 汇编程序设计
editor:xingjiancui
在学习 MIPS 汇编程序设计之前,必须知道一些关于 MIPS 架构的特性
-
MIPS 一个 Word 占 4 Byte
-
MIPS 结构的每条指令长度都是 1 Word = 32 bit。
寄存器
MIPS 寄存器文件包含 32 个通用寄存器,32 个协处理器1(浮点寄存器)。每个寄存器都是 32 位宽。
通用寄存器
这些寄存器可以用于存储数据和地址。
$0
或$zero
:- 始终为 0。
- 不能被修改。
- 常用于常量 0 的表示。
$1
或$at
:- 保留给汇编器使用。
- 通常用于汇编器临时变量。
$2
到$3
或$v0
到$v1
:- 用于存储函数调用的返回值。
$v0
通常用于返回整数值。$v1
用于返回其他类型的值。$v0
用于存储系统调用号- 在用户交互中,
$v0
用于存储返回的整数类型
$4
到$7
或$a0
到$a3
:- 用于传递函数的前 4 个参数。
$a0
到$a3
分别对应第 1 到第 4 个参数。$a0
通常用于打印整数、字符、字符串- 在用户交互中,
$a0
用于指定读取的字符串存储到的内存地址,并用a1
告诉操作系统允许的存储空间的最大值
$8
到$15
或$t0
到$t7
:- 临时寄存器。
- 用于存储临时数据。
- 在函数调用时不会被保存,因此函数调用后可能会被覆盖。
- 调用者负责保存这些寄存器的内容,如果需要在函数调用后恢复。
$16
到$23
或$s0
到$s7
:- 保存寄存器。
- 用于存储函数中的局部变量。
- 在函数调用时需要保存和恢复。
- 被调用者负责保存这些寄存器的内容,并在函数返回时恢复。
$24
到$25
或$t8
到$t9
:- 临时寄存器。
- 用于存储临时数据。
- 在函数调用时不会被保存,因此函数调用后可能会被覆盖。
$26
到$27
或$k0
到$k1
:- 保留给操作系统内核使用。
- 通常用于中断处理。
$28
或$gp
:- 全局指针。
- 指向全局数据区的中间位置。
$29
或$sp
:- 栈指针。
- 指向当前栈顶。
$30
或$fp
:- 帧指针。
- 指向当前栈帧的基址。
$31
或$ra
:- 返回地址寄存器。
- 存储函数调用的返回地址。
-
$t
和$s
的使用约定何时使用
$t
寄存器- 临时数据:当需要存储临时数据时,使用
$t
寄存器。这些数据通常在函数内部使用,不需要在函数调用时保存。 - 计算中间结果:在进行计算时,使用
$t
寄存器存储中间结果。 - 不需要跨函数调用保存的数据:如果数据不需要在函数调用后恢复,使用
$t
寄存器。
何时使用
$s
寄存器- 局部变量:当需要存储函数中的局部变量时,使用
$s
寄存器。这些变量需要在函数调用时保存。 - 状态信息:当需要存储函数的状态信息时,使用
$s
寄存器。这些信息需要在函数调用时保存。 - 需要跨函数调用保存的数据:如果数据需要在函数调用后恢复,使用
$s
寄存器。
- 临时数据:当需要存储临时数据时,使用
协处理器1(浮点寄存器)
MIPS 架构的浮点寄存器可以用于存储单精度浮点数(32 位)或双精度浮点数(64 位)。其中,单精度浮点数的存储需要一个协处理器1,双精度浮点数的存储需要两个协处理器1
-
$f0
到$f1
:- 用于存储浮点运算的结果。
$f0
通常用于存储单精度结果。$f1
通常用于存储双精度结果。- 在用户交互中,
$f0
用于存储返回的float
类型,$f0 : f1
用于存储返回的double
类型
-
$f2
到$f3
:- 用于存储浮点运算的临时结果。
-
$f4
到$f11
:- 用于存储浮点运算的参数。
- 通常用于传递函数调用的参数。
-
$f12
到$f15
:- 用于存储浮点运算的参数。
- 通常用于传递函数调用的参数。
$f12
通常用于打印单精度和双精度浮点数
-
$f16
到$f19
:- 用于存储浮点运算的临时结果。
-
$f20
到$f31
:- 用于存储浮点运算的临时结果或保存寄存器。
- 在函数调用时需要保存和恢复。
特殊寄存器
- HI 和 LO:
- 用于存储乘法和除法的结果。
HI
存储乘法的高 32 位或除法的余数。LO
存储乘法的低 32 位或除法的商。
数据类型
在MIPS汇编语言中,数据类型主要用于定义和存储数据。以下是对几种数据类型的详细介绍,这几乎涵盖了所有的数据类型:
-
byte:
- 定义:用于存储8位(1字节)的整数。也可用来存储单个字符
- 示例:
.byte 0x41 # 存储字节值0x41 .byte 65 # 存储字节值65
- 用途:常用于存储单个字符或需要较小数据存储的情况。
-
half:
- 定义:用于存储16位(2字节)的整数。
- 示例:
.half 0x1234 # 存储半字值0x1234 .half 4660 # 存储半字值4660
- 用途:用于需要中等大小数据存储的情况。
-
word:
- 定义:用于存储32位(4字节)的整数。
- 示例:
.word 0x12345678 # 存储字值0x12345678 .word 305419896 # 存储字值305419896
- 用途:这是MIPS中最常用的数据类型,用于存储大多数整数数据。
-
ascii:
- 定义:用于存储ASCII字符序列。
- 示例:
.ascii "Hello, World!" # 存储字符串"Hello, World!"
- 用途:用于存储字符数据,但不以NULL字符结尾。
- 注意:被打印的字符序列必须用
" "
包围
-
asciiz:
- 定义:用于存储以NULL字符(0x00)结尾的ASCII字符串。
- 示例:
.asciiz "Hello, World!" # 存储字符串"Hello, World!"并以NULL字符结尾
- 用途:用于存储C风格字符串,即以NULL字符结尾的字符串。
-
float:
- 定义:用于存储32位(4字节)的单精度浮点数。
- 示例:
.float 3.14159 # 存储单精度浮点数3.14159
- 用途:用于存储单精度浮点数,遵循IEEE 754标准。
-
double:
- 定义:用于存储64位(8字节)的双精度浮点数。由于每个浮点寄存器都是32位,每个双精度浮点数需要用两个浮点寄存器来存储
- 示例:
.double 3.141592653589793 # 存储双精度浮点数3.141592653589793
- 用途:用于存储双精度浮点数,遵循IEEE 754标准。
-
space + num(伪指令):
-
定义:用于在数据段中预留一定数量的字节
-
示例:
myString: .space 20 # 存储最大长度为20得字符串 myArray; .space 12 # 存储3个整数
-
系统调用
- 系统调用号都存在 $v0 寄存器中
- 用于打印的整数、字符、字符串存在 $a0 寄存器中
- 用于打印的单精度浮点数、双精度浮点数存在 $f12 寄存器中
系统调用的基本流程:
-
系统调用号:用户程序将系统调用号加载到特定的寄存器中(如 MIPS 中的
$v0
寄存器)。 -
参数传递:用户程序将系统调用的参数加载到特定的寄存器中(如 MIPS 中的
$a0
到$a3
寄存器)。 -
syscall
指令:执行syscall
指令,触发操作系统内核的系统调用处理程序。 -
内核处理:操作系统内核根据系统调用号执行相应的服务,并将结果返回给用户程序
系统调用号
-
打印整数 (
print_int
)- 系统调用号:1
- 示例:
li $v0, 1 # 系统调用号1,表示打印整数 li $a0, 42 # 要打印的整数 syscall # 执行系统调用
-
打印浮点数 (
print_float
)- 系统调用号:2
- 示例:
li $v0, 2 # 系统调用号2,表示打印浮点数 l.s $f12, float_value # 加载浮点数到$f12寄存器 syscall # 执行系统调用
-
打印双精度浮点数 (
print_double
)- 系统调用号:3
- 示例:
li $v0, 3 # 系统调用号3,表示打印双精度浮点数 l.d $f12, double_value # 加载双精度浮点数到$f12寄存器 syscall # 执行系统调用
-
打印字符串 (
print_string
)- 系统调用号:4
- 示例:
li $v0, 4 # 系统调用号4,表示打印字符串 la $a0, message # 加载字符串地址到$a0寄存器 syscall # 执行系统调用
-
打印字符 (
print_char
)- 系统调用号:11
- 示例:
li $v0, 11 # 系统调用号11,表示打印字符 li $a0, 'A' # 要打印的字符 syscall # 执行系统调用
-
读取整数 (
read_int
)- 系统调用号:5
- 存储:返回值存储到
$v0
- 示例:
li $v0, 5 # 系统调用号5,表示读取整数 syscall # 执行系统调用,返回值存储到$v0 move $t0, $v0 # 将读取的整数保存到$t0寄存器
-
读取浮点数 (
read_float
)- 系统调用号:6
- 存储:返回值存储到
$f0
- 示例:
li $v0, 6 # 系统调用号6,表示读取浮点数 syscall # 执行系统调用,返回值存储到$f0
-
读取双精度浮点数 (
read_double
)- 系统调用号:7
- 示例:
li $v0, 7 # 系统调用号7,表示读取双精度浮点数 syscall # 执行系统调用 mov.d $f2, $f0 # 将读取的双精度浮点数保存到$f2寄存器
-
读取字符串 (
read_string
)-
系统调用号:8
-
示例:
li $v0, 8 # 系统调用号8,表示读取字符串 la $a0, buffer # 加载缓冲区地址到$a0寄存器 li $a1, 20 # 设置缓冲区大小 syscall # 执行系统调用
-
-
读取字符 (
read_char
)-
系统调用号:12
-
示例:
li $v0, 12 # 系统调用号12,表示读取字符 syscall # 执行系统调用 move $t0, $v0 # 将读取的字符保存到$t0寄存器
-
-
退出程序 (
exit
)-
系统调用号:10
-
示例:
li $v0, 10 # 系统调用号10,表示退出程序 syscall # 执行系统调用
-
运算指令
- move:用于两个寄存器之间的数据移动
加法
-
add
指令- 全称: Add
-
功能: 将两个寄存器的值相加,并将结果存储在目标寄存器中。
-
操作数: 3个寄存器(目标寄存器、源寄存器1、源寄存器2)。
-
示例:
add $t0, $t1, $t2 # $t0 = $t1 + $t2
-
溢出检测:
add
指令会检测溢出(overflow),如果发生溢出,会触发异常。
-
addu
指令-
全称: Add Unsigned
-
功能: 将两个寄存器的值相加,并将结果存储在目标寄存器中。
-
操作数: 3个寄存器(目标寄存器、源寄存器1、源寄存器2)。
-
示例:
addu $t0, $t1, $t2 # $t0 = $t1 + $t2
-
溢出检测:
addu
指令不会检测溢出,即使发生溢出也不会触发异常。
-
-
addi
指令-
全称: Add Immediate
-
功能: 将一个寄存器的值与一个立即数相加,并将结果存储在目标寄存器中。
-
操作数: 2个寄存器(目标寄存器、源寄存器)和一个立即数。
-
示例:
addi $t0, $t1, 100 # $t0 = $t1 + 100
-
溢出检测:
addi
指令会检测溢出,如果发生溢出,会触发异常。
-
-
addiu
指令-
全称: Add Immediate Unsigned
-
功能: 将一个寄存器的值与一个立即数相加,并将结果存储在目标寄存器中。
-
操作数: 2个寄存器(目标寄存器、源寄存器)和一个立即数。
-
示例:
addiu $t0, $t1, 100 # $t0 = $t1 + 100
-
溢出检测:
addiu
指令不会检测溢出,即使发生溢出也不会触发异常。
-
-
add.s
指令- 全称: Add Single-Precision
- 功能: 将两个单精度浮点数的值相加,并将结果存储在目标寄存器中。
- 操作数: 3个浮点寄存器(目标寄存器、源寄存器1、源寄存器2)。
- 示例:
add.s $f0, $f1, $f2 # $f0 = $f1 + $f2 (单精度浮点加法)
- 解释: 将
$f1
和$f2
中的单精度浮点数相加,结果存储在$f0
中。
-
add.d
指令- 全称: Add Double-Precision
- 功能: 将两个双精度浮点数的值相加,并将结果存储在目标寄存器中。
- 操作数: 3个浮点寄存器(目标寄存器、源寄存器1、源寄存器2)。
- 示例:
add.d $f0, $f2, $f4 # $f0 = $f2 + $f4 (双精度浮点加法)
- 解释: 将
$f2
和$f4
中的双精度浮点数相加,结果存储在$f0
中。
总结:
add
: 用于有符号数的加法,检测溢出。addu
: 用于无符号数的加法,不检测溢出。addi
: 用于有符号数的立即数加法,检测溢出。addiu
: 用于无符号数的立即数加法,不检测溢出。add.s
: 单精度浮点加法,将两个单精度浮点数相加。add.d
: 双精度浮点加法,将两个双精度浮点数相加。