MITS6004第三课笔记
课前回顾
简单回顾了上节课讲述的一些risc-v的一些特征,
例如ALU的两类计算,一类是寄存器寄存器计算,还有一类是寄存器与常数的计算。
其次是控制流指令。
等等。。。
寄存器和内存的区别
x2和x3相加存入x1
将x3的值复制到x4
目标寄存器是x5,数据从x3的值加上0,也就是0x14的值0x23。
将x3的数值0x14继续偏移8位也就是0x1c的值0x16填入x6
将x6的值存入,0x3的值0x14,偏移0xc个bit,就是偏移12个bit,到达0x20
处理常数
加上一个常数使用addi, 但是加上超过12bit的内容时就要使用li
load immediate 是立即加载,如果给一个很大的常数,就会变成lui
如果很小就是addi
所以这里的事情就是老师之前并未介绍lui这个指令,也就是load up immediately,这个可以直接将一个大常数加入寄存器的命令。
然后这里的伪指令li,是可以自动分辨长指令和短指令的。
编写简单计算
这里演示了从c语言到机器语言的转换过程,以及实际的代码。
包括将变量放到寄存器,使用寄存器立即指令来 处理小数字的计算,使用li伪指令来处理大的常数。
编写控制条件
首先假设x存储在x10,y存储在x11。
然后使用slt set less than,也就是比较x10和x11,如果x10小于x11,如果是就会把x12变成1,如果不是就是0.
然后beqz,如果x12是零就跳到endif,如果不是就执行sub语句。
如果x10大于等于x11,那么直接到endif。
左右两边的区别就是两条命令和三条命令的区别。
if-else
从图上可以看到,先运行了一个比较,如果执行的是else,那么就直接跳过了if-body。
如果执行的是if-body,那么有一个jump endif 的指令,就会跳过else。
loop
whlie
将判断条件放如寄存器,然后比较判断条件是否执行while,如果不通过则跳出while。如果是真,则会继续跳回while。
因为上面的写法使用了两个控制条件。不是很高效,所以又展示了另一种方式,只使用一种控制条件。
这里的区别就是,没有两个跳转,只有一个bnez在循环中,比上述的方法少了一个跳转。
集成例子讲解
这个是while,if,else的集成例子。
x是x10,y是x11。
bne,检查x不等于y,然后回头执行loop。
函数编写
考虑的事情是函数可以多次调用。
程序入口也就是函数名gcd
零活更多的参数
可以能有一些本地临时变量
return函数值
这里提到的一个问题是函数抽象和调用,如何在别的函数中调用已经编写好的函数。
管理进程寄存器地址
这里引导出来的问题就是,调用程序和被调用程序都是在一个寄存器上的,如何协调两个程序对寄存器的使用是一个复杂点。
第一点调用程序不能依赖与被调程序管理寄存器空间,简单来说就是被调程序的空间可能会被调用程序覆写,所以需要单独存放返回数据。
第二点程序运行时需要可以使用所有的寄存器。
无论是调用者或者是被调用者,其一需要去保存寄存器中需要保存的数据。当被调用函数结束时,再把数据重新读取出来。
实现函数
第一件事时传递参数,然后也需要从被调用程序中获取返回值。
这些都是通过寄存器实现的。
然后一个程序需要可以被多次调用,调用程序可以直接使用无条件跳转到跳转到被调程序。
但是需要返回正确的地址,回到继续执行调用程序。被调程序需要知道使用正确的跳转地址。
总结就是,返回地址必须被保存然后传递给被调程序。
jump and link
这就是上次没讲的jump和link指令。
当我们使用进程调用的时候,就会使用jump and link。
第一步将会把调用程序的地址+4存入ra return address register。
然后就会跳转到地址label,也就是我们的sum程序所在的位置。label就是跳转程序的程序名。
当执行结束之后,会jump ra返回到调用者继续执行运算。
如下图所示,当我们第一次调用jal的时候,ra被写入0x104,然后就跳转到sum的代码位置。
嵌套调用
这里突出的问题就是,如果进行三层调用的话,只有一个ra存储器,所以第三层数据会覆盖之前的地址。
进程存储的需求
进程调用基本的存储需求,传入的参数,返回的地址,返回的结果。
本地存储,那些无法放在寄存器里的变量,存放被覆写的寄存器的值,知道有需要就恢复。
每个进程调用都有它自己的持续的数据被称为进程活动记录。
堆栈stack
需要数据结构来存储活动记录。
遵循先进后出的原则。
堆栈的操作有push,pop
堆栈是位于内存中的,需要一个寄存器来指向它。在risc-v中是x2。
当我们进行push和pop操作时,指针上下移动。指针始终指向堆栈的顶端。
一点提示就是,你可以随时使用堆栈,但是当你使用完成之后需要将它恢复原样。
使用堆栈
进入序列
当我们要使用堆栈的时候,会使用这三条语句,第一条addi移动n位堆栈指针,为什么时-N是因为,堆栈的内存是从低到高的。
众作周知32bit是4bytes。
所以如果要存两个东西,那么指令就是addi sp sp -8。
这样就不需要担心覆写ra和a0。
退出序列
重新将堆栈中的数据读取到寄存器中,然后移动堆栈指针。
调用约定
调用约定强调了寄存器被进程使用的范围和规则。
这张表介绍了cpu的寄存器分类,哪些是被调用者使用的,那些事调用者使用的,以及这个寄存器应该保存的值。