拨开MIPS的神秘面纱🥵
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
)的内容作为目标地址,执行寄存器间接寻址。
-