2. MIPS指令格式详解
MIPS指令字长固定为32位(4字节)。
2.1 R型指令 (Register-type)
R型指令通常用于寄存器之间的算术和逻辑操作。
- 格式定义:
| 字段 | op (6) | rs (5) | rt (5) | rd (5) | shamt (5) | funct (6) |
|---|---|---|---|---|---|---|
| 位数 | 31-26 | 25-21 | 20-16 | 15-11 | 10-6 | 5-0 |
| 含义 | 操作码 (opcode) | 第一个源寄存器 | 第二个源寄存器 | 目的寄存器 | 位移量 | 功能码 |
-
特点:
-
op字段对于所有R型指令都为0。 -
真正的操作由
funct字段决定。这种设计允许多个指令共享同一个op码,节约了编码空间。 -
三个操作数都在寄存器中。
-
-
示例:
add $t0, $s1, $s2-
含义:将寄存器
$s1和$s2中的值相加,结果存入寄存器$t0。 -
指令编码 (假设
$t0=8,$s1=17,$s2=18):-
op: 0 (R型指令) -
rs: 17 ($s1) -
rt: 18 ($s2) -
rd: 8 ($t0) -
shamt: 0 (非位移操作) -
funct: 32 (add的功能码)
-
-
二进制表示:
000000 10001 10010 01000 00000 100000
-
2.2 I型指令 (Immediate-type)
I型指令用于包含一个立即数操作数的操作,如立即数算术运算、Load/Store指令、条件分支指令。
- 格式定义:
| 字段 | op (6) | rs (5) | rt (5) | immediate (16) |
|---|---|---|---|---|
| 位数 | 31-26 | 25-21 | 20-16 | 15-0 |
| 含义 | 操作码 | 源寄存器 | 目的/源寄存器 | 16位立即数/地址偏移 |
-
特点:
-
op字段唯一确定指令类型。 -
包含一个16位的立即数字段,其含义根据指令类型而变化。这是I型指令的考查重点。
-
-
示例:
addi $t0, $s1, -100-
含义:将寄存器
$s1中的值与立即数-100相加,结果存入寄存-器$t0。 -
指令编码 (假设
$t0=8,$s1=17,addi的op=8):-
op: 8 (addi) -
rs: 17 ($s1) -
rt: 8 ($t0),在这里作目的寄存器。 -
immediate: -100的16位补码表示(1111111110011100)
-
-
二进制表示:
001000 10001 01000 1111111110011100
-
2.3 J型指令 (Jump-type)
J型指令用于无条件跳转。
- 格式定义:
| 字段 | op (6) | address (26) |
|---|---|---|
| 位数 | 31-26 | 25-0 |
| 含义 | 操作码 | 跳转目标地址的部分编码 |
-
特点:
-
提供了一个26位的地址字段,以实现更大的跳转范围。
-
跳转的目标地址计算方式为:
PC[31:28]拼接address字段,再在末尾补两个0。这是因为MIPS指令是4字节对齐的,指令地址的最低两位总是0,无需存储。所以实际跳转范围是当前PC所在256MB空间内。
-
-
示例:
j 0x1000004-
含义:无条件跳转到地址
0x1000004。 -
指令编码 (
j的op=2):-
op: 2 (j) -
address:0x1000004右移两位得到0x0400001,其26位二进制表示为00010000000000000000000001。
-
-
二进制表示:
000010 00010000000000000000000001
-
3. I型指令的几种重点用法分析
类型一:立即数算术/逻辑运算
-
典型指令:
addi(加立即数),andi(与立即数),ori(或立即数)。 -
指令格式分析:
-
op: 定义具体操作(如addi为8)。 -
rs: 源寄存器。 -
rt: 目的寄存器。这是与R型指令rd字段位置不同但功能相似的易错点。 -
immediate: 16位带符号立即数。在运算前会进行符号扩展(Sign Extension)到32位,然后与rs寄存器的值进行运算。- 例如,
addi $t0, $s1, -1,immediate字段为0xFFFF,扩展后为0xFFFFFFFF。
- 例如,
-
类型二:Load/Store 指令
-
典型指令:
lw(Load Word),sw(Store Word)。 -
指令格式分析:这类指令用于内存和寄存器之间的数据传输,格式为
lw $rt, offset($rs)。-
op: 定义操作(如lw为35,sw为43)。 -
rs: 基址寄存器 (Base Register),提供基地址。 -
rt: 对于lw,是目的寄存器,用于存放从内存读取的数据。对于sw,是源寄存器,其内容将被写入内存。 -
immediate: 16位带符号的地址偏移量 (Offset)。
-
-
操作过程 (以
lw为例):-
取出
rs寄存器的32位值(基地址)。 -
将
immediate字段进行符号扩展到32位。 -
将上述两者相加,得到有效的内存地址 (Effective Address):
EA = (rs) + sign-extend(immediate)。 -
从该内存地址读取一个字(32位)的数据。
-
将读取的数据写入
rt寄存器。
-
-
图示:
lw $t0, 12($sp)
类型三:条件分支指令
-
典型指令:
beq(Branch on Equal),bne(Branch on Not Equal)。 -
指令格式分析:格式为
beq $rs, $rt, label。-
op: 定义操作(如beq为4)。 -
rs: 第一个要比较的源寄存器。 -
rt: 第二个要比较的源寄存器。 -
immediate: 16位带符号的偏移量,但其单位是字 (Word)。
-
-
操作过程 (以
beq为例):-
比较
rs和rt寄存器的值。 -
如果相等,则发生跳转。如果不等,则顺序执行下一条指令
PC = PC + 4。 -
跳转目标地址计算:
Target_Address = PC + 4 + (sign-extend(immediate) * 4)。-
PC + 4:指向分支指令的下一条指令的地址。这被称为分支延迟槽(虽然在MIPS后续流水线中被处理,但考研中计算地址时必须理解这个模型)。 -
immediate左移两位(即乘以4):因为immediate表示的是指令条数(字偏移),而MIPS指令地址按字节寻址且4字节对齐,所以要乘以4转换为字节偏移量。
-
-
-
例题:
假设beq $s0,
s0=$s1。目标标签L1处的指令地址为0x00400044。求immediate字段的值。 -
解答:
-
PC =
0x00400020。 -
PC + 4=0x00400024。 -
目标地址 =
0x00400044。 -
字节偏移量 =
0x00400044 - 0x00400024=0x20(即32字节)。 -
immediate* 4 = 32。 -
所以
immediate= 8。
-
-
4. 常考点分析、历年命题方式与陷阱
-
指令格式识别:给定一个32位的二进制或十六进制代码,要求判断其指令类型(R/I/J),并反汇编成MIPS指令。这是最基本的考法。
- 陷阱:首先看
op字段。op=0是R型,但要结合funct判断具体操作。op不为0,则可能是I型或J型。op=2或3是J型,其余常见为I型。
- 陷阱:首先看
-
I型指令立即数解释:这是选择题和综合题的绝对热点。
-
命题方式:给定一条
lw、sw或beq指令,结合当时的寄存器和内存状态,求操作后的结果,或者计算有效地址、跳转目标地址。 -
陷阱:
-
忘记对16位
immediate进行符号扩展。正数补0,负数补1。 -
混淆
lw/sw和beq中immediate的含义。前者是字节偏移量,后者是字偏移量(需要乘以4)。 -
在计算
beq跳转地址时,忘记基准地址是PC+4而不是PC。
-
-
-
地址计算:
-
J型指令:考查如何从26位
address字段还原出完整的32位目标地址。Target = {PC+4[31:28], address, 00}。 -
寻址方式:
lw/sw属于基址变址寻址。beq属于PC相对寻址。j属于伪直接寻址。考纲要求掌握这些寻址方式的计算过程。
-
-
边界情况与反例:
-
R型指令的
shamt:只有在位移指令(如sll,srl)中shamt字段才有效,对于add、sub等指令,该字段为0。 -
I型指令
rt字段的角色:在addi和lw中,rt是目的寄存器;但在sw和beq中,rt是源寄存器。这是一个非常细微但关键的区别。 -
立即数范围:I型指令的16位立即数表示范围是 -32768 (−215) 到 +32767 (215−1)。对于更大的立即数,需要通过
lui(load upper immediate)指令配合ori指令来加载。例如加载一个32位立即数到$t0:MIPS Assembler
lui $at, 0xABCD # $at = 0xABCD0000 ori $t0, $at, 0x1234 # $t0 = 0xABCD0000 | 0x00001234 = 0xABCD1234这虽然超出了单条指令的范畴,但体现了I型指令立即数范围的局限性,属于大纲内涵的延伸,可能会在综合题中出现。
-
5. MIPS过程调用(函数调用)
5.1 核心思想与考点概述
MIPS过程调用的本质并非由硬件强制规定,而是通过一套**软件约定(Software Convention)来实现的。这套约定规范了调用者(Caller)和被调用者(Callee)如何协作,以正确地传递参数、保存和恢复执行现场、以及返回结果。
包括:
-
控制权转移机制:
jal和jr指令的功能与区别。 -
数据传递规则:通过寄存器和栈传递参数与返回值。
-
现场保存与恢复:理解调用者保存(Caller-saved)和被调用者保存(Callee-saved)寄存器的概念,并熟知栈帧(Stack Frame)的结构和作用。
5.2 寄存器使用约定
理解过程调用的第一步,是背熟MIPS的寄存器使用约定。这是所有后续操作的基础。
| 寄存器 | 编号 | 名称/用途 | 保存策略 |
|---|---|---|---|
$v0 - $v1 |
2 - 3 | 返回值寄存器 (Values) | 被调用者放入,调用者取走 |
$a0 - $a3 |
4 - 7 | 参数寄存器 (Arguments) | 调用者放入,被调用者读取 |
$t0 - $t9 |
8 - 15, 24-25 | 临时寄存器 (Temporaries) | 调用者保存 (Caller-saved) |
$s0 - $s7 |
16 - 23 | 保留寄存器 (Saved) | 被调用者保存 (Callee-saved) |
$sp |
29 | 栈指针 (Stack Pointer) | 指向栈顶,始终有效 |
$fp |
30 | 帧指针 (Frame Pointer) | 指向当前过程活动记录的基地址 |
$ra |
31 | 返回地址 (Return Address) | 存放调用指令的下一条指令地址 |
-
调用者保存 ($t0 - $t9):如果调用者在
jal指令后还想使用这些寄存器中的值,它必须在jal之前自己负责将它们压入自己的栈帧。因为被调用者可以随意使用这些寄存器,无需恢复。 -
**被调用者保存 ($s0 -
s`系列寄存器的值不变。
5.3 过程调用的生命周期
我们将一个完整的过程调用(例如,过程P调用过程Q)分解为六个阶段,并结合你提供的资料进行讲解。
图示:栈的生长方向与栈帧结构
高地址 +-------------------+
| ... |
+-------------------+
| P的参数 (若>4个) | <-- 调用Q前,P压入
P的栈帧 |-------------------|
| P保存的$t寄存器 |
| ... |
+-------------------+ <-- P的$fp
| Q的参数 (若>4个) |
|-------------------|
| 返回地址 ($ra) |
|-------------------|
| 旧的帧指针 ($fp) |
Q的栈帧 |-------------------| <-- Q的新$fp
| Q保存的$s寄存器 |
|-------------------|
| Q的局部变量 |
+-------------------+ <-- Q的$sp (栈顶)
低地址 | ... |
(栈向低地址方向生长)
阶段一:调用者(P)的准备工作
-
参数传递:
-
将前4个参数(或更少)依次放入
$a0, $a1, $a2, $a3寄存器。 -
如果超过4个参数,将第5个及以后的参数从右到左依次压入调用者P的栈顶。
-
-
保存临时寄存器:
-
检查
$t0-$t9中,有哪些值在调用Q结束后仍需使用。将这些寄存器的值压入P的栈中。 -
例如:
sw $t0, -4($sp)sw $t1, -8($sp)addi $sp, $sp, -8
-
阶段二:控制权转移 (jal指令)
调用者P执行jal Q指令。这条J型指令执行两个原子操作:
-
保存返回地址:将
PC+4(即jal的下一条指令地址)存入$ra寄存器。 -
跳转:将PC的值更新为标签Q的地址,开始执行过程Q。
阶段三:被调用者(Q)的“过程头”(Prologue)
这是过程Q的入口部分,完全对应你图片image_db9dbe.png中的步骤。其核心任务是建立自己的栈帧。
-
申请栈帧空间:将
$sp减去一个立即数(该过程所需的栈帧大小framesize)。addi $sp, $sp, -framesize
-
保存
$ra和旧$fp:-
如果Q需要调用其他过程(即Q不是叶过程),那么Q将来会执行自己的
jal指令,这会覆盖$ra。因此必须先把$ra的值保存到Q自己的栈帧中。 -
为了能正确返回到P的栈帧,需要保存P的帧指针
$fp。 -
sw $ra, framesize-4($sp) -
sw $fp, framesize-8($sp)
-
-
保存
$s系列寄存器:-
检查过程Q中使用了哪些
$s0-$s7寄存器。将这些寄存器的原始值(属于P)保存到Q的栈帧中。 -
sw $s0, offset($sp)
-
-
设置新的帧指针
$fp:-
addi $fp, $sp, framesize -
作用:
$fp指向当前栈帧的基地址,在Q的执行过程中保持不变。后续访问局部变量、保存的寄存器都以$fp为基准,使用固定的负偏移量。这比使用随时可能移动的$sp更稳定可靠。
-
阶段四:被调用者(Q)的“过程体”
执行函数的实际代码。此时:
-
可以通过
$a0-$a3或栈上的参数区获取输入参数。 -
可以使用
$t系列寄存器进行计算。 -
可以使用(在保存后)
$s系列寄存器。 -
局部变量(如数组、结构体、发生溢出的变量)通过
$fp的负偏移来访问。例如:lw $t0, -12($fp)。
阶段五:被调用者(Q)的“过程尾”(Epilogue)与返回
这是过程Q的出口部分,任务是撤销自己的栈帧并交还控制权。操作与Prologue严格相反。
-
放置返回值:将计算结果放入
$v0(或$v0,$v1)。 -
恢复
$s系列寄存器:从栈帧中将调用者P的$s寄存器的值恢复。lw $s0, offset($sp)
-
恢复
$ra和旧$fp:-
lw $ra, framesize-4($sp) -
lw $fp, framesize-8($sp)
-
-
释放栈帧空间:将
$sp加上framesize,使其指回调用Q之前的位置。addi $sp, $sp, framesize
-
执行返回:执行
jr $ra指令。这是一条R型指令,它将PC的值设为$ra寄存器中的地址,从而返回到P中jal指令的下一行。
阶段六:调用者(P)的清理工作
控制权返回到P后:
-
获取结果:从
$v0中读取返回值,并根据需要存放到其他寄存器或内存中。 -
恢复临时寄存器和栈:如果阶段一中保存了
$t系列寄存器或在栈上传递了参数,现在需要恢复它们,并调整$sp。
5.4 常考点与陷阱分析
-
寄存器保存责任混淆:
-
陷阱: 题目问“过程Q需要保存哪些寄存器?”,考生误将
$t0选入。正确答案是$s0-$s7中被Q用到的,以及在非叶子情况下必须保存的$ra。 -
关键:
$t是调用者(Caller)操心,$s`是被调用者(Callee)操心。
-
-
叶过程与非叶过程:
-
考点: 一个叶过程(不调用任何其他过程)可以极大地简化调用流程。它不需要在栈上保存
$ra,因为不会有jal覆盖它。如果它也不使用$s寄存器,甚至可以完全不用栈帧。 -
设问方式: “一个MIPS叶过程至少需要执行什么操作?” 或对比叶过程和非叶过程的开销。
-
-
栈指针
$sp移动方向:- 陷阱: MIPS栈向低地址生长。分配栈帧是
sub或addi负数;释放栈帧是add或addi正数。考生容易搞反加减。
- 陷阱: MIPS栈向低地址生长。分配栈帧是
-
jal与jr的区别:-
jal(Jump and Link):J型指令,用于调用。它写入$ra,并执行伪直接寻址跳转。 -
jr(Jump Register):R型指令,用于返回(或函数指针调用)。它读取一个寄存器(通常是$ra)的内容作为目标地址,执行寄存器间接寻址。
-