寄存器:
在MIPS中,寄存器直接由硬件构成
$s0 - $s7:(保存寄存器) 映射到数字16-23(10进制数)
$t0 - $t9:(临时寄存器) 8-15
$zero:值恒为0
$a0 - $a3:传递参数的4个参数寄存器
$v0 - $v1:返回值的寄存器
$gp:全局指针
$fp:帧指针
$sp:栈指针
$ra:用于返回起始点的返回地址寄存器
$at:被汇编器保留,用于处理大的常数
存储器:
Memory [0],Memory [4], ...Memory [4294967292]
存储器只能通过数据传输指令访问(lw,sw)。MIPS使用字节编址,连续的字地址相差4。存储器用于保存数据结构,数组和溢出的寄存器。
一个字节(byte)包含8个bit,一个字为4个字节。(寄存器是以bit为最小存储单位而存储器是以字节为最小存储单位)(存储器是8位字节,A[0]是以字存储的,占了四个字节。)
操作数:
数据结构(数组和结构):
这种操作数存储在存储器当中------>存数和取数操作------->寄存器------>算术运算指令
立即数:
立即数也是存储在存储器当中,正常取数会需要取数,为避免麻烦,产生操作数为立即数的指令 addi
最低有效位:最右边 最高有效位:最左边
无符号数和有符号数:有符号数最高有效位代表正负------->有符号数的负数采用的是补码(要掌握补码与原码之间的转换,以及直接从补码转换到10进制)
对于有符号数和无符号数的取数指令是不相同的:
有符号数:取回有符号数后需要使用符号位填充寄存器的所有剩余位(符号拓展)
无符号数:用0填充
字节加载:(把32位的字加载到32位的寄存器中时无需讨论有无符号数----->已经完全填满了,只有在字节加载时讨论才有意义------>涉及到什么去填充空余的寄存器位数)
1.字节加载:lb 将字节看成有符号数,使用符号拓展填充寄存器右侧的24位
2.无符号整数加载:lbu
指令:
R型指令:
6位操作码+5位寄存器(两个)+目标寄存器(5位)(注意这里寄存器的先后位置,目标寄存器在最后)+位移量(5位)+功能码(6位)
算术运算指令:
加法: add $s1, $s2, $s3
-------->机器语言:
op(操作码) rs(源寄存器) rt(源操作码) rd(目标寄存器) shamt(偏移量) funct
000000 10010($s2) 10011($s3) 10001($s1) 00000 100000
减法: sub $s1, $s2, $s3
逻辑操作:
逻辑左移:sll $t0, $s0, 4------->将$s0寄存器里的数向左移四位赋给$t0
------>机器语言:
op rs rt rd shamt funct
000000 00000 10000($s0) 01010($t0) 00100 000000
逻辑右移:srl
按位与:仅当两个操作数均为1时才为1 (and,andi)
按位或:任意一位为1就为1(or,ori)
按位取反:将操作数中1变0,0变1(not)------->为保持三位操作数------->nor(或非)
nor $s1, $s2, $s3 ( 其中$s3为0,相当于对$s2按位取反)
I型指令:
6位操作码+5位寄存器(两个)+16位地址偏移量(这里的偏移量是指字节偏移量----->即A[0]与A[1]之间的偏移量应为4,再转成2进制,即为所需要的机器指令)
这里的地址偏移量需要在进行符号拓展之后与 PC+4相加,原因:为什么MIPS处理器的数据通路中需要符号扩展单元和左移2位?_城外南风起的博客-CSDN博客_符号扩展单元
算术运算指令:
立即数加法: addi $s1, $s2, 20
存取数指令:
取字:lw $s1,20($s2)
存字:sw $s1,20($s2)
取半字:lh $s1,20($s2) (将半个字从内存中取到寄存器)(这里的半字是什么意思?)
取无符号半字:lhu $s1,20($s2)
存半字:sh $s1,20($s2) 将半个字从寄存器中存到内存中)
J型指令:
跳转和链接指令:jal ProcedureAddress (跳转到某个地址的同时将下一条指令的地址保存在寄存器$ra中)
寄存器跳转指令:jr $ra
j 指令与 jr 有什么区别
决策指令:
判断指令:
if-then-else:
beq + 寄存器1 + 寄存器2 + 执行语句L1------>如果寄存器1等于寄存器2则执行语句L1
bne + 寄存器1 + 寄存器2 + 执行语句L1------->不相等则执行语句L1
L1在后文中定义
完整if语句形式:
beq/bne register1, register2, Else
j Exit
Else:+指令
Exit:
case/switch语句:
jr (寄存器跳转指令)
单纯判断大小指令:
slt $s0, $s1, $s2------>如果$s1的值小于$s2的值,则给$s1赋值1,否则赋值0
slti---->立即数 sltiu---->无符号整数
循环指令:
Loop:(进入循环)
一系列的指令,其中必须有Exit的,否则就一直循环
j Loop(再跳到开始的循环指令中)
Exit:
计算机硬件对过程的支持
我的理解是相当于c语言中的函数
使用更多的寄存器
如果对于一个过程,需要使用超过4个参数寄存器和两个返回寄存器------>使用栈(后进先出)
栈指针:指向栈中最新分配的地址,以指示下一个过程放置换出寄存器的位置(以字为单位进行调整)栈指针指的是$sp
压栈:将数据放入栈中
出栈:从栈中移除数据
int leaf_example (int g, int h, int i, int j)
{
int f;
f=(g+h)-(i-j);
return f;
}
利用MIPS写出:
参数变量g,h,i,j对应寄存器$a0,$a1,$a2和$a3,f对应$s0
leaf_example:
addi $sp, $sp, -12 #这里是在栈中建立三个字的空间
sw $t1,8($sp) #这里是让三个寄存器入栈
sw $t0,4($sp)
sw $s0,0($sp) #到这为止是建立栈的过程
add $t1,$a0,$a1
add $t0,$a2,$a3
sub $s0,$t1,$t0
add $v0,$s0,$zero #这一步是将其复制到返回寄存器中
lw $s0,0($sp) #这里注意栈是后进先出的
lw $t0,4($sp)
lw $t1,8(sp)
addi $sp,$sp,12 #这一步是将栈恢复
jr $ra #这里根据跳转寄存器里的地址进行跳转
如果在这个例子中,调用者不希望保存寄存器$t0与$t1的值,则在开始时不需要将其载入栈中
嵌套过程
这里包含调用指令:jal
可能出现问题:由于传递参数的寄存器只有$a0-$a3,则在调用过程A时,若A中也调用了过程B,则可能导致寄存器$a0中的值产生冲突
解决方案:将所有必须保留的寄存器压栈,同时调用者将所有调用后还需要的参数寄存器($a0-$a3)或临时寄存器($t0-$t9)压栈,被调用者将返回地址寄存器($ra)和被调用者使用的保留寄存器($s0-$s7)压栈.
int fact (int n)
{
if (n<1) return (1);
else return (n*fact(n-1));
}
参变量n对应参数寄存器$a0。
fact:
addi $sp,$sp,-8
sw $ra,4($sp) #这里究竟为什么要将$ra压栈?
sw $a0,0($sp)
slti $t0,$a0,1 #判断n是不是小于1,如果是则$t0为1,否则为0
beq $t0,$zero,L1 #若$t0为0,即 n>=1时,转到L1
add $v0,$zero,1
addi $sp,$sp,8 #如果这里调用的不是fact而是其他的,是不是要将参数寄存器等更新一下,那地址寄存器呢,应该怎么调整
jr $ra
L1: addi $a0,$a0,-1
jal fact
lw $a0,0($sp)
lw $ra,4($sp)
addi $sp,$sp,8
mul $v0,$a0,$v0
jr $ra
在栈中为新数据分配空间
问题:在栈中需要存储的数据有可能并不能在寄存器中存储,例如结构体等
帧指针:指向过程帧的第一个字
人机交互(利用字节表示字符)
指令:
字节读取指令:lb $t0,0($sp) 从内存中读取一个字节,并将其放在寄存器最右边的八位
字节存储指令:sb $t0,0($gp) 把寄存器最右边的八位取出来写到内存中
void stropy (char x[], char y[])
{
int i;
i=0;
while ((x[i] =y[i] !='\0')
i+=1;
}
假定数组x和y的基地址在$a0和$a1中,而i在$s0中
stropy:
addi $sp,$sp,-4
sw $s0,0($sp)
add $s0,$zero,$zero
Loop: #这一步进入循环
add $t1,$a1,$s0 #这一步是的到y[i]的地址
lbu $t2,0($t1) #这一步是将y[i]的值放入寄存器$t2中,这里之所以不用lw,是因为读取的是字节
add $t3,$a0,$s0
sb $t2,0($t3)
beq $t2,$zero,L2
addi $s0,$s0,1
jr Loop
L2:
lw $s0,0($sp)
addi $sp,$sp,4
jr $ra
MIPS指令即中还包含读取和存储半字命令: lhu(把半字看成无符号数),sh
MIPS中32位立即数和寻址
立即数
立即数比较长:
读取立即数高位指令:liu,允许后续指令设置常数的低16位
加载32位常量:
0000 0000 0011 1101 0000 1001 0000 0000
lui $s0,61
ori $s0,$s0,2304
分支和跳转中的寻址
问题:条件分支指令只保留了16位用于分支地址
解决办法:程序计算器=寄存器+分支地址