要控制计算机的硬件工作,必须使用它的语言。MIPS指令集是计算机应用中最简单的指令集,弄懂MIPS指令集,可以帮助我们更好的理解计算机的运行原理。
本文大量参考《计算机组成与设计 硬件/软件接口》这本书(提取码为:os0a),如果你看完觉得不过瘾,可以下载原书籍深入学习。本人非科班出身,文章避免不了存在错误之处,发现不对的地方,还望指出,一起进步。
先来看几个简单概念,什么是机器语言?汇编语言?指令?指令集?
机器可以直接识别的语言,就是机器语言,一般是具有一定格式的1和0的组合,上图中指令的'二进制表示行'就是机器语言。MIPS中指令都是32位的。
由于101010...这样的机器语言,人类很难理解和记忆其表示的含义,于是就创造了诸如add $s0, $s1, $s2 这样的人类看起来较为直观的汇编语言,上图中'add行'就是汇编语言。我们用高级程序语言编写的程序,先经过编译器编译成汇编语言,然后再经过汇编器,编译成机器语言。
机器语言和汇编语言可以认为是指令的一体两面。三者含义相似,指令是概括的说法。
指令集,顾名思义,就是一组指令的集合。按照不同规则设计的指令的集合,就是不同的指令集。
一、加减法指令:
例1:将下述C语言中两条赋值语句翻译成MIPS指令。
a = b + c;
d = a - e;
解:在编译过程中,编译器负责将寄存器和程序变量一一对应起来,此处我们假定变量a,b,c,d,e被分别分配到寄存器$s0,$s1,$s2,$s3,$s4,编译后的汇编代码如下(#后为该行汇编代码的注释):
add $s0, $s1, $s2 #将变量b($s1)和c($s2)相加,把它们之和存储在a($s0)中
sub $s3, $s0, $s4 #用变量a($s0)减去e($s4),将它们之差值存储在d($s3)中
针对上述汇编代码,你可能有这样的疑问:
1、为什么要把变量对应到寄存器?
因为MIPS中的算数运算操作只作用于寄存器(可以这样理解:CPU中算术逻辑单元只能对寄存器中的数据进行直接运算),因此变量必须要对应到寄存器,才能进行运算。
2、为什么是$s0,$s1,$s2,$s3,$s4这几个寄存器?
MIPS中有32个寄存器,编号从0到31。寄存器$s0到$s7对应的寄存器号为16到23,这些寄存器主要就是用来进行算数运算操作的。
例2:将下述C语言代码翻译成MIPS指令。
f = (g + h) - (i + j);
解:我们同样假定变量f,g,h,i,j被分别分配到寄存器$s0,$s1,$s2,$s3,$s4,同时,此例中我们还要引入两个临时变量t0(用来存储
g + h 的结果)和t1(用来存储 i + j 的结果),分别被分配到寄存器$t0和$t1,编译后的结果如下:
add $t0, $s1, $s2 #将变量g($s1)和h($s2)相加,把它们的和存储在t0($t0)中
add $t1, $s3, $s4 #将变量 i($s3)和 j($s4)相加, 把它们的和存储在t1($t1)中
sub $s0, $t0, $t1 #用变量t0($t0)减去t1($t1),将它们之差值存储在f($s0)中
你可能还会有这样的疑问:上述指令中的$t0和$t1是哪里来的,为什么要引入这两个寄存器?
答案是:一条MIPS指令只能执行一个运算,所以编译器会将这条稍复杂的C语言代码编译成多条汇编语言指令。
可以认为是多了一个中间环节:
$t0和$t1是MIPS中专门用来存储临时数据的寄存器。这样的寄存器也有8个,分别是$t0到$t7,对应MIPS32个寄存器中的8到15号。
二、数据传送指令:
例3:a是含有100个字的数组(字可以简单理解为指令集中寄存器的位数,本篇文章中,我们默认字是32位,很显然,像数组a这样的数据结构,含有的数据元素甚至超过寄存器的数量,只能存放在存储器中),试编译C语言赋值语句:g = h + a[8];
解:我们假设编译器将寄存器$s1、$s2分别分配给变量g、h,同时假设数组a的起始地址存储在寄存器$s3中。编译后结果如下:
lw $t0, 32($s3) # 将a[8]加载进寄存器$t0中;lw指令是load word的简写,表示将数据从内存加载至寄存器中,($s3) 表示数组a的起始地址,32表示偏移量,就是到起始地址的距离,每个字32位,也就是4个字节,因为内存中计数的基本单位是字节,所以a[8]距离a[0]就是8*4=32个字节,偏移量就是32。
add $s1, $s2,$t0 #将变量 h($s2)和 t0($t0)相加, 把它们的和存储在g($t1)中;
例4:a是含有100个字的数组,试编译C语言赋值语句:a[12] = h + a[8];
解:我们假设编译器将寄存器$s2分配给h,同时假设数组a的起始地址存储在寄存器$s3中。编译后结果如下:
lw $t0, 32($s3) # 将a[8]加载进寄存器$t0中;
add $t0, $s2, $t0 #将变量 h($s2)和 t0($t0)相加, 把它们的和存储在寄存器$t0中;
sw $t0, 48($s3) #将寄存器$t0中的值写入数组a[12]中;sw指令是store word的简称,表示将数据从寄存器存储到内存中。($s3) 表示数组a的起始地址,48表示偏移量,也就是a[12]的在内存中的位置。
最后,摘抄 极客时间徐文浩老师的专栏《深入浅出计算机组成原理》中部分内容,扩展下知识范围:
MIPS 的指令是一个 32 位的整数,高 6 位叫操作码(Opcode),也就是代表这条指令具体是一条什么样的指令,剩下的 26 位有三种格式,分别是 R、I 和 J。
R 指令是一般用来做算术和逻辑操作,里面有读取和写入数据的寄存器的地址。如果是逻辑位移操作,后面还有位移操作的位移量,而最后的功能码,则是在前面的操作码不够的时候,扩展操作码表示对应的具体指令的。
I 指令,则通常是用在数据传输、条件分支,以及在运算的时候使用的并非变量还是常数的时候。这个时候,没有了位移量和操作码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或者一个常数。
J 指令就是一个跳转指令,高 6 位之外的 26 位都是一个跳转后的地址。
后续文章会陆续介绍其他的指令。
看到本文觉得有收获的兄弟,有多余的C币,随便打赏几个,给博主以后下载文章使用。