计算机执行任何程序,本质上都是在执行机器语言指令(instruction)。每条指令都是一条0-1串。
指令首先要指明执行什么操作,通常用0-1串的前几位来表示,称为操作码。
指令还要指出需要操作的数据来自哪里,操作后的结果数据放回哪里,通常用0-1串中的剩余位来表示,称为操作数或地址码。
大部分操作数都是一个地址编号,告诉CPU从哪里取得数据、向哪里放回数据,所以操作数通常也叫做地址码。
MIPS作为一种RISC指令集,设计力求保证硬件设备的简单性。
在本书的32为MIPS汇编语言(MIPS-32)中,所有指令都是32位长。
MIPS中运算操作的操作数必须来自寄存器(register)或者指令本身。
一种位于CPU、比cache更小更快的存储器,用来暂时存放运算的源数据和结果。
一些寄存器是专用的,如存放执行中指令地址的程序计数器(PC),与此相对应,用于暂时存放运算数据的寄存器称为通用寄存器。
MIPS中一共有32个32位的寄存器,共128B(大部分架构都采用16和32个寄存器)
我们约定:
程序中的变量存放在**保存寄存器(store reg)中: s 0 s0~ s0 s7共8个。运算的临时变量、中间变量存放在临时寄存器(temp-reg)**中; t 0 t0~ t0 t7共8个,还有一个零寄存器,永远存放32位的0,写作$zero。
C赋值语句:c = a + b;
加法指令 add c , a , b; 将a和b中的数据相加,并将结果存放在s中
再次强调:MIPS中运算的操作数必须来自寄存器或者指令本身!
假设变量a,b,c分别存放在寄存器 s 0 , s0, s0,s1,$s2中,这条指令就应当写为
add $a2 , a 0 , a0, a0,a1
加法中两个加数可以对换,但减法不行,,故c = a - b;必须写作
sub $s2 , $s0 , $s1
运算的“原材料”a和b对应的寄存器 s 0 , s0, s0,s1分别称为源操作数1(src1)和源操作数(src2),运算的结果c对应的寄存器$s2称为目的操作数(des)
加减指令的通式:add/sub des ,src1,src2
在i++即i = i + 1;这条赋值语句中,有个确定的常数 1,与其采用额外的步骤将1装入某个寄存器,不如让指令本身包含这个1
假设变量 i 位于寄存器$s0,我们把加法指令的第二个源操作数改为常数1
add $s0 , $s0 , 1
就成了加**立即数(add immediate)**指令
因为add指令中的立即数可以取负数(对立即数取负后相加),因此,MIPS中没有subi指令
当两个源寄存器中,对应的位上同时为1时,与and操作数结果为1
当两个源寄存器中,对应的位上至少有一个为1时,或or操作结果为1
因此,假设
$t0 = 0000 0000 0000 0000 0000 0000 0000 1001
$t1 = 0000 0000 0000 0000 0000 0000 0000 1100
执行下列两条指令后,$t2中的数据分别变为多少?
add $t2 , $t0 , $t1 0000 0000 0000 0000 0000 0000 0000 1000
or $t2 , $t0 , $t1 0000 0000 0000 0000 0000 0000 0000 1101
任何数据与0进行或非nor操作,都会0/1反转
执行下列指令后,$t2中的数据会变为多少?
nor $t2 , $t0 , $zero
比较12和120两个十进制数,通过在最低位的右边添加一个0,变为了10倍
比较11和110两个十进制数,通过在最低位的右边添加一个0,变为了多少倍?1100呢?
**逻辑左移(shift left logic)**指令让寄存器中的数据整体往左移动指定的位数,并在右边空出来的位上补上0
假设$s2 = 0000 0000 0000 0000 0000 0000 0000 0101
逻辑左移两位后,放到寄存器$s0中:
sll $s0 $s2 2
这里的2不是addi指令中的立即数,而是告诉计算机移动几位的位移量(shift amount)
通过这样一句指令:我们实际上完成了x4的运算。
x2 ,x8,x128时,位移量分别为多少呢?
(sll指令当然也可以实现/2操作)
翻译以下C语句
假设result ,a,b,c分别存放在$s3 $s0 $s1 $s2
result = a - 10 + (b + c * 5)
sll $t0 , $s2 , 2
add $t1 , $t0 , $s2
add t 2 , t2, t2,s1,$t1
addi t 3 , t3, t3,s0,-10
add s 0 , s0, s0,t3,$t2
运算指令的操作数必须来自寄存器/指令本身,但是通用寄存器一共只有128B,数组元素却可以占据成千上万个字节,只能存放在内存中
这时,我们把数组第一个元素,a[0]的32位地址,称为数组的基址。放在寄存器中,基址加上要找的元素下标,就组成了这个元素的地址
如果源操作数在内存中,是数组a的5号元素(第六个元素),数组a的基址存放在$s1中。
那么,a[5]的地址就表示为5($s1)
计算机会自动计算$s1中的基址和偏移量5的和,找到a[ 5 ]的地址
将a[ 5 ]从内存传送到寄存器$s0,使用取字指令(load word)
lw s 0 , 5 ( s0 , 5( s0,5(s1)
MIPS的通用寄存器都是32位长
这个长度就是MIPS体系结构的字长,通常代表了参与运算的数据的长度
因此我们约定:整门课程中,1字 = 32bit = 4Byte
a[ 5 ]相对于a[ 0 ],在内存中的距离是5个字,而不是5个字节,又因为内存按字节编址,
即,内存每个字节都有一个特定的编号,所以偏移量应该是5x4 = 20个字节
a[ 5 ]的地址应表示成20($s1)
于是取数指令变为:
lw s 0 , 20 ( s0 , 20( s0,20(s1)
如果,我们要把$t0中的运算结果送回到内存中的a[ 2 ],需要用到存字指令(store word)
sw t 0 , 8 ( t0,8( t0,8(s1)
如果我们需要把数从 t 0 保存到存放某变量的 t0保存到存放某变量的 t0保存到存放某变量的s1中,怎么实现?
MIPS没有专门的寄存器间移动数据的指令,但是通过把源寄存器中的数据加上0再保存到目标寄存器中,可以实现相同的功能。
addi $s1 , $s0 , 0 或 addi $s1 , $s0 , $zero
这个功能可以用move伪指令来代替
move $s1 , $s0
假如我们要把一个常数10装入寄存器$s2,同样可以采用addi指令
addi $s2 , $zero , 10
或者使用取立即数(load immediate)li伪指令
li $s2 , 10
程序设计题是否可以用伪指令根据自己学校情况妥善选择
我们说可以用addi指令向寄存器装载立即数:addi $s2 , $zero , 10;
但是,addi指令中的立即数10只能占用32位指令中的一部分(16位,后面会介绍指令格式)
16位只能表示216即65536个数,寄存器却能容纳232即40多亿个数
二进制转十六进制规则不再赘述
假设我们要向寄存器$s2装载一个32位的立即数:10A2 7FFF(16)
我们必须先用取高位立即数(load upper immediate)指令:将10A2放入$s2的高16位
再用立即数或(immediate or),将32767放入$s2的低16位
lui $s2 , 4258 //十六进制的10A2等于十进制的4258
ori $s2 $s2 , 32767 //7FFF(16) = 32767(10)
这样就分两步把32位立即数装载到了32位的寄存器中
不能用addi代替ori指令,因为如果低16位的最高位是1,那么addi会把它理解为负数
a[ i ] = a[ 0 ] + 100000;
假设数组a的基址位于 s 0 ,变量 i 位于 s0,变量i位于 s0,变量i位于s1
100000(10) = 186A0(16),1(16) = 1(10) , 86A0(16) = 34464
lw t 0 , 0 , 0 ( t0 , 0 , 0( t0,0,0(s0)
lui $t1 , 1
ori $t1 , $t1 , 34464
add $t2 , $t1 , $t0
sll $t3 , $s1 , 2
add $t4 , $s0 , t 3 / / 我们必须算出 a [ i ] 的地址, ∗ ∗ 不能直接写成 t3 //我们必须算出a[ i ]的地址,**不能直接写成 t3//我们必须算出a[i]的地址,∗∗不能直接写成t3($t0) ,前面必须是一个数字**
sw t 2 , 0 ( t2 , 0( t2,0(t4)
计算机和一般计算器的区别在于何处?
**在于决策能力!**即,根据一定的条件选择执行何种运算的能力
最基础的判断条件是相等关系
假设 s 0 = 0 , s0 = 0 , s0=0,s1 = 0, $s2 = 1
相等则分支(branch if equal)指令在两个源操作数寄存器中的值相同时分支
分支以分支标签表示
beq $s0 , $s1 , Label
与此相对应,不相等则分支(branch if not equal)指令在值不同时分支到标签
bne $s0 , $s1 , Label
如果不发生分支,则继续执行内存中相邻的下一条指令
if (i == j) f = g + h;
else f = g - h;
假设f ,g,h,i,j分别存放在 s 0 − s0- s0−s4中
bne $s3 , $s4 , Else
add $s0 , $s1 , $s2
j Exit
Else: sub $s0 , $s1 , $s2
Exit:
结论:判定相等 == 使用bne , 判断不等 != 用beq
除了相等、不等关系,我们还经常比较两个数的大小,MIPS有一条小于则置位(set on less than)指令slt
置位:将一位设置为1;复位:将一位设置为0;
还是假设$s0 = 0 , $s1 = 0 , $s2 = 1
slt $t0 , $s0 , $s2
源操作数1 < 源操作数2吗?Yes!
此时把目的操作数寄存器$t0置位为1
slt $t0 , $s0 , $s1
源操作数1 < 源操作数2吗?No!
此时把目的操作数寄存器$t0复位为0
六种比较条件:== < <= > >= !=
通过slt,beq,bne(严格来说还有小于立即数则置位slti指令,不做讨论)指令的各种组合,我们就能够实现全部六种比较条件,即六种值为真或假的布尔表达式
例如综合练习3我们可以有如下写法:
if (i == j) f = g + h;
else f = g - h;
slt t 0 , i , j / / 当 i < j 时,把 t0 , i , j //当i
beq $t0 , z e r o , E l s e / / 当 zero , Else //当 zero,Else//当t0为0时,执行else后的语句
add f , g ,h //否则顺着执行if后的语句
j Exit //加法完成后退出if-else语句
Else: sub f , g , h //else
Exit:
结论:判定大于>或者小于<用slt和beq , 判定大于等于>=或小于等于<=用slt和bne
对于比大小的四种比较条件,可以使用伪指令
小于则分支 blt 大于则分支 bgt
小于等于则分支 ble 大于等于则分支 bge
while(a[ i ] == k) i++;
假设i, k分别存放在 s 3 和 s3和 s3和s5中,a的基址存放在$s6中
Loop : sll $t0 , $s3 , 2
add $t1 , $s6 , $t0
lw t 2 , 0 ( t2 , 0( t2,0(t1) //此时$t2就是a[i]的值
bne $t2 , $s5 , Exit
addi $s3 , $s3 , 1 //i++
j Loop //回到开头
Exit:
指令中含三个寄存器的运算指令都属于R型(register type)指令
add/sub des,src1,src2
and/or/nor des,src1,src2
slt des , src1 , src2
32位MIPS指令一共分为6个字段
op: operation code 操作码
rs: register source 源操作数寄存器-→ rt: s后面是t,表示第二个源操作数寄存器
rd: register destination 目的寄存器
shamt: shift amount 移位量
funct: function code 功能码
R型指令的操作码op都是6个0,由6位功能码funct 进一步指定执行什么操作
以add指令为例
$t0~ t 7 分别为 8 15 号寄存器 ∗ ∗ ∗ ∗ t7分别为8~15号寄存器** ** t7分别为8 15号寄存器∗∗∗∗s0~ $s7分别为16~23号寄存器
根据我们R型指令的格式可知:op :000000 rs: s 1 r t : s1 rt: s1rt:s1 rd:$t0 shampt:0 funct:32(对应add)
sub指令仅仅是功能码funct字段从32变为了34, sub $s1, $s1, $s0的32位机器码是多少?
000000 10001 10000 10001 00000 100010
需要记忆add、sub指令 的操作码(都是0) 和功能码(分别为32、34)
sll/srl des, src1, shamt
也属于R型指令,因为没有第二个源操作数寄存器, rt被置为0
有两条、“目的reg +源reg+立即数”格式的指令
addi des,src1,i
ori des,src1,i
通过把R型指令中的后三个字段拼接成一个16位的立即数字段,让指令本身包含常数这样的指令属于**|型(immediate type)指令**
以addi指令为,其操作码为8
addi $t1 , $t0 , 15
001000 01000 01001 0000000000001111
其操作码为8,由于rd字段被合并了,现在rt就成了目的寄存器
Iw/sw reg, num(reg)
两条数据传送指令也包含两个寄存器和一个常数
同样属于I型指令
此时,16位立即数字段的含义发生了改变,表示数组元素相对于数组基址的地址偏移量
无论是**|w还是sw指令都是由rs字段表示的寄存器值与address字段相加**,得到存储器单元地址
rt字段表示与存储器单元交换数据的寄存器
Iw、sw指令操作码分别为35和43
Iw t 0 , 8 ( t0, 8( t0,8(s1)
100011 10001 01000 0000000000001000
beq/bne src1,,src2, Label(两个寄存器都是源寄存器)
在这两条条件分支指令中,同样使用了两个寄存器
还有一个分支标签的地址,用16位立即数字段表示(也就变成了Address字段)
也属于|型指令
例如,当i ( s 0 ) 和 j ( s0) 和j ( s0)和j(s1) 相等时分支到地址为10000的标签EIse
beq $s0, $s1, Else
翻译为机器语言为
op 10000 10001 10000(10)
这里的10000实际上并不是EIse标签指向指令的地址,讲寻址方式时再具体说明
lui指令的指令格式不作讨论
五条伪指令本身不是真正的指令,程序运行时会被替换为真正指令,不讨论指令格式
j指令的指令格式稍后讲解
1、指令通常由哪两个部分组成? MIPS-32指令长度均为多少?
操作码 操作数(地址码) 32位
2、8个临时寄存器、8个保存寄存器分别是什么编号?零寄存器存储什么?
t 0 t0~ t0 t7 8~15 s 0 s0~ s0 s7 16~23 0
3、回顾综合练习1~4,掌握运算、数据传送、决策三类汇编指令,注意字和字节的区别.
4、练习上一页PPT中五条指令(add、 sub、 addi、Iw、 sw)汇编语言和机器语言的转化
C语言中的函数(一种典型的过程)是结构化编程的强大工具
函数获取参数、执行运算、返回结果,就好比侦探拿着一份计划书去执行任务,再带来想要的结果
1、主程序(调用者)将参数放在过程(被调用者)可以取用的特定位置**什么位置?**
2、主程序将控制权交给过程
3、过程申请并获得存储资源
4、过程执行
5、过程将结果的值放在主程序可以取用的特定位置**什么位置?**
6、过程把控制权返还给主程序,执行调用过程指令的下一条指令**怎么找到这个位置?**
1、主程序(调用者)将参数放在过程(被调用者)可以取用的特定位置**什么位置?**
4个参数寄存器(argument reg) $a0~ $a3
5、过程将结果的值放在主程序可以取用的特定位置**什么位置?**
2个值寄存器(value reg) v 0 v0~ v0 v1
6、过程把控制权返还给主程序,执行调用过程指令的下一条指令**怎么找到这个位置?**
1个返回地址寄存器(return address reg) $ra
截至目前,学习了保存寄存器、临时寄存器各8个,零寄存器1个,以及这一页的7个
共24个寄存器,占MIPS-32寄存器总数的四分之三
主程序通过什么指令,可以跳转到过程指令,并把下一条指令的地址存入$ra?
j + addi吗?
跳转并链接(jump and link)jal指令可以同时实现两个功能:
①无条件跳转到一个标签
②将下一条指令的地址放入返回地址寄存器$ra
jal Label
jal指令由调用者主程序使用,还是由被调用者过程使用?
主程序
寄存器跳转(jump reg)jr指令可以跳转到某一寄存器存储的32位地址
基本上只和返回地址寄存器搭配
jr $ra
jr指令由调用者主程序使用,还是由被调用者过程使用?
被调用者过程
遇到MIPS翻译C函数的题,这么写,可能有一分:
函数名: jr $ra
void clear(int a[] . int size)
{
for(i = 0 ; i < size ; i++) a[i] = 0;
}
clear:add $t0 $zero $zero // i放在$t0
slt $t1 $t0 $a1
beq $t1 $zero Exit
loop:sll $t2 $t0 2
add $t3 $t2 $a0
sw $zero (0)$t3
addi $t0 $t0 1
slt $t1 $t0 $a1
beq $t1 $zero Exit
j loop
Exit:jr $ra
在过程调用前,主程序往往已经将自己要用的变量放在了保存寄存器中
如果过程要使用保存寄存器,要把主程序已经使用的保存寄存器入栈
栈在内存中以高地址为栈底,低地址为栈顶
即,栈从高地址向低地址“生长’
**栈指针(stack pointer)**永远指向栈顶
入栈时,先把$sp减去待保存的
保存寄存器个数的4倍为什么是4倍?
再用sw将保存寄存器存入栈中(方向从栈底到栈顶)
过程结束时把这些数据出栈、放回保存寄存器,供主程序继续使用
步骤正好相反
int cal(int g , int h , int i , int j)//$a0 $a1 $a2 $a3
{
int f;
f = (g + h) - (i + j);
return f;
}
//假设f存储在$s0中
cal:addi $sp , $sp , -4
sw $s0 0($sp)
add $t0 $a0 $a1
add $t1 $a2 $a3
sub $s0 $t0 $t1
addi $v0 $s0 $zero //move $v0 $s0
lw $s0 0($sp)
addi $sp $sp 4
jr $ra
int cat(int g , int h , int i , int j)//$a0 $a1 $a2 $a3
{
int f;
f = (g + h) - (i + j);
return f;
}
//假设f存储在$s0中
cal:add $t0 $a0 $a1
add $t1 $a2 $a3
sub $v0 $t0 $t1
jr $ra
侦探搞外包、接着雇其他侦探来完成任务,就是嵌套过程调用
int square(int a)
{
int square;
square = a * a;
return square;
}
int sum_of_squares(int a[] , int size)
{
int i = 0;
int sum = 0;
for(i = 0 ; i < size ; i++)
sum = sum + square(a[i]);
return sum;
}
sum_of_squares:addi $sp $sp -4
sw $ra 0($sp) //压栈操作放在循环外提高效率
add $t0 $zero $zero
add $t1 $zero $zero
slt $t3 $t0 $a1
beq $t3 $zer0 Exit
loop:sll $t2 $t0 2
add $t2 $t2 $a0
lw $a2 0($t2) //因为后面要调用square所以直接放参数寄存器里面
jal square
add $t1 $t1 $v0
addi $t0 $t0 1
slt $t3 $t0 $a1
beq $t3 $zer0 Exit
j loop
Exit:move $v0 $t1
lw $ra 0($sp)
addi $sp $sp 4 //弹栈操作放在循环外提高效率
jr $ra
square:mul $v0 $a2 $a2
jr $ra
[综合练习6]我们默认保存寄存器$s0~ $s7存放了主程序的变量需要由过程开始时压栈保存,结束时出栈恢复
[综合练习7]如果一个过程(外层函数)嵌套了其他过程(内层函数)外层函数通过jal修改了返回地址寄存器$ra
$ra指向外层函数jal的下一条指令 ,不再是外层函数的返回地址
栈指针寄存器$sp、栈中的内容(即栈指针以上的栈)也需要由过程保留
在addi栈指针、sw入栈、Iw出栈的过程中即可保存
结论:任何过程须显式地压栈保存即将使用的保存寄存器$s0~ s 7 ( 用哪几个存哪几个 ) ∗ ∗ ∗ ∗ 外层嵌套过程须显式地压栈保存返回地址寄存器 s7 (用哪几个存哪几个)** **外层嵌套过程须显式地压栈保存返回地址寄存器 s7(用哪几个存哪几个)∗∗∗∗外层嵌套过程须显式地压栈保存返回地址寄存器ra
2.8节(68页) 提供了一个递归嵌套调用过程计算阶乘的MIPS程序
2.13节(90页) 提供了一个冒泡排序过程嵌套交换过程的MIPS程序
习题2.27 (114页要素察觉) 考察双层for循环的翻译
习题2.34 (115页) 考察自嵌套调用的多参数过程的翻译
为了标记运行中过程建立的栈,除了栈顶的栈指针$sp还可以加一个帧指针(frame pointer) $fp指向栈底即过程帧的第一个字
f p 和 fp和 fp和sp之间的空间由正在运行的过程使用称为过程帧,也叫活动记录
为了便于寻找位置固定的数据
(主程序使用的变量,以及声明为static的变量,统称静态变量)
使用一个固定指向静态数据区某一位置的全局指针(global pointer) $gp
程序在内存中包含五段,地址从低到高分别为
1)保留段
2)正文段(代码段),保存指令
3)静态数据段 ,保存静态数据
4)动态数据段(堆),从低往高"生长”
5)栈,从高往低“生长”
栈和堆此消彼长,实现了内存空间的高效利用
所有操作数都是寄存器的指令采用寄存器寻址(register addressing)
操作数个数从一个到三个不等
R型指令<=>寄存器寻址
第二部分已经提到过的R型指令有:
1、运算指令: add, sub, and, or, nor 5条三寄存器操作数指令
2、运算指令: sI|、 srl 2条双寄存器操作数指令(rs不使用置为0,使用shamt)
3、决策指令: slt 1条三寄存器操作数指令
第三部分新增R型指令:
4、决策指令: jr 1条单寄存器操作数指令
第三个操作数(第二个源操作数)是常数的指令采用立即数寻址(immediate addressing)
具体包括addi, ori两条指令
其实还包括lui指令,课本没有着重强调其指令格式
两条数据传送指令|w,Sw
将基址寄存器和偏移量相加的内存寻址方式称为基址偏移寻址
可单独称为基址寻址(base addressing)和偏移寻址(displacement addressing)
两条条件分支指令beq, bne
在汇编语言中使用标签来表示分支的目标地址,标签翻译成机器语言其实是个整数
告诉计算机从当前指令的地址出发,到达分支目标地址的距离是多少
程序计数器(program counter, PC)中保存了执行中指令的地址
分支指令中的16位分支地址是一个二 进制补码,可正可负
表示以PC+ 4为基准相加的字地址数目,叫做PC相对寻址(PC-relative addressing)
分支32位地址= PC + 4 +字地址偏移量
J型指令只需要操作码和目标地址两个字段,形式上最为简单
J型指令<=>伪直接寻址,包含j,jal两条
寄存器跳转jr指令是R型指令
直接寻址指的是指令中直接给出32位内存地址,但J型指令地址字段只有26位
因此,执行J型指令时,先将26位字地址左移两位(右侧补0)形成28位字节地址
再和PC的高四位拼接成32位地址
这就是伪直接寻址(pseudodirect addressing)*
PC相对寻址以PC+4为基准,加上一个可正可负的16位补码字地址,寻址范围为
(PC + 4)- 2^17 ~ (PC + 4) + 2^17 -4大约是分支前后各128KB
伪直接寻址用PC中当前指令地址的高四位拼接指令中的26位字地址,寻址范围为
和PC高四位相同的一切地址 一个256MB的地址块
在相近的内存地址中寻址利用了加速大概率事件这一设计思想
要分支到更远距离,可以将beq/bne取反, 下接一条可能绕过的j指令
要跳转到更远距离,可以先将32位地址装载到某临时寄存器,再用jr指令
//修改后
bne $s0 , $s1 , Exit
j L1
Exit:
j L
//修改后
lui $t0 高16
ori $t0 低16
jr $t0
①R型的寄存器寻址:操作数为1个/2个/3个寄存器的数据
②|型的立即数寻址: addi、 lui、 ori三 条立即数指令,其中-个操作数是指令字段中的常数
③I型的基址偏移寻址: |w、 sw两条访存指令
将rs中的基地址和偏移量直接相加,得到偏移地址以lw t 0 , 12 ( t0, 12( t0,12(s0)为例
④I型的PC相对寻址: beq、 bne两条条件分支指令
分支指令中的PC相对地址(可正可负的字偏移量)
左移两位(x4) 形成字节偏移量
再和PC+4中的字节地址相加,形成分支目标地址
以地址为1000 (十进制)的beq reg1, reg2, 4为例
⑤J型的伪直接寻址:将26位字地址左移两位(x4)形成28位字节地址
再和PC (实际上也是PC+4)的高四位拼接成32位跳转目标地址
以PC高四位为1010的i 0000 0000 0000 0000 0000 0000 01为例
编译器将高级语言文件(.c)翻译成汇编语言文件(.asm)
汇编器首先把伪指令替换为等价的真正指令,再将汇编语言翻译成机器语言目标文件(.obj)
链接器把目标文件和静态链接库(.lib)、动态链接库(.dll)拼接成可执行文件(.exe)
加载器把可执行文件放入内存,装载执行
为了让计算机能够处理C的8位的ASCII字符,MIPS提供字节传送指令lb, sb
字符通常理解为无符号数,故取字节常使用取无符号字节lbu指令
同理,为了支持Java的16位Unicode字符,MIPS提供半字传送指令lh, sh, Ihu
当两个程序访问同一个内存单元,且其中存在写操作时,两程序操作的顺序就尤为重要
MIPS提供链接取数ll指令和条件存数sc指令,让程序员能够指定程序操作数据的顺序
ARM和MIPS同为RISC架构,具有优秀的能耗表现,广泛应用于移动端和嵌入式平台
同MIPS相比,ARM的主要区别是通用寄存器更少(16个) 、寻址方式更多(9种)
Intel和AMD主导的x86是一种CISC 架构,指令集十分庞大(2018年约1400条)
x86是一-种本质非常糟糕的架构,最典型的表现是,指令长度从1B到15B不等
由于问世时间恰逢IBM进军PC领域,x86取得了巨大的商业成功,至今占据很大的份额
Intel将x86移动化的尝试屡屡碰壁,苹果将ARM电脑化的实践却高歌猛进
1、为了实现过程调用,我们引入了哪三类寄存器和哪个指令对?
a 0 a0~ a0 a3 v 0 v0~ v0 v1 $ra jal - jr
2、三类寄存器分别存放什么?指令对中的两条指令分别由谁使用,完成什么功能?
参数 返回值 返回地址
jal 主程序 跳转到寄存器指向的地址并把当前指令下一条指令地址放到$ra里面
jr 过程 跳转到寄存器指向的地址
3、当过程要使用保存寄存器时,要进行什么操作?
压栈保存
4、为什么过程内部的变量优先使用临时寄存器?
使用保存寄存器可能会和主程序争夺内存,因此要进行压栈保存出栈恢复操作,为了减少对内存的使用,我们优先选择临时寄存器
5、复习综合练习5~7,熟悉for循环、清零、函数调用等常见的C语句翻译
6、R型指令和型指令分别采用什么寻址方式? |型有哪三种寻址方式?
R:寄存器寻址(充要条件)
I:立即数寻址 基址偏移寻址 PC相对寻址
J型:伪直接寻址(充要条件)
7、PC相对寻址和伪直接寻址为了扩大寻址范围,其地址代表什么单位?
字
8、PC相对寻址和伪直接寻址分别怎样获得标签的真实地址?
PC相对存址的后16位存放的是相对PC + 4的偏移字地址
伪直接寻址是用后26位字地址左移两位的到28位字节地址在与PC高4位相连接得到真实地址
9、复习MIPS汇编指令,对应上每条指令的指令格式和寻址方式
10、运行(程序要经过哪四个步骤?
编译 汇编 链接 装载