组成原理——第二章续
栈的补充
栈在程序的运行过程中起着非常重要的作用。
为了管理栈,我们设置了两个指针:$sp, $fp。$sp记录的是栈最低的部分的地址,$fp记录的是栈最高的部分的地址。在函数执行过程中所需保存的局部变量、参数、返回地址调用的过程叫做过程帧。引入$fp可以达到快速恢复栈的目的,避免了内存泄露的问题。
程序的执行体所占有的内存区域可以分为四大类:
- text-代码段:用来存放指令
- Static data:静态数据
- Dynamic data-动态数据:内部使用的堆,C的malloc(),C++和java的 new 都在堆中。
- Stack-栈:存储局部变量
通常来说,堆和栈共享一个内存区域,这样可以最大限度地利用内存空间。
字符数据-Character Data
字符编码:
- ASCII码:现在已经很少使用,一共可以表示128个字符
- Latin-1: 256个字符,这个是对ASCII码的拓展
- Unicode:三十二位字符集,可以用于表示中文
- UTF-8, UTF-16, 以及一些变长的编码
为了处理这些不同的编码,MIPS准备了以下指令:
完成字符串的复制功能:
以C的代码为例
1 | void strcpy(char x[], char y[]){ |
假设这里传进来的变量是$a0 - x[] 和 $a1 - y[],i是$s0
那么我们可以有下面的代码:
1 | srrcpy: |
三十二位常数 32-bit Constants
结合我们前面学的知识,我们知道,32位mips中,一条语句的长度是32位,这就代表我们无法在一条语句中将一个三十二位数字赋给一个寄存器,mips为我们提供了一种方法,下面看两个语句:
1 | lui $s0, constant |
其中lui代表着将constant数字赋给$s0的高16位,并将$s0的低16位清零。
ori是异或指令,我们都知道0异或x = x ,那么异或指令也可以用做赋值,所以我们在这里可以用ori来达到使用立即数给寄存器低16位赋值的效果。
看不懂可以再结合下面的图看一下。
分支的地址
以beq语句为例
可以看到跳转的地址长度只有十六位,那是不是就表明我们程序的长度仅能在\(2^{16}\)以内呢?
并不,其实跳转的地址是\[Target\_address=PC + offset *4\],这样可以在这个指令前后\(2^{16}\)之内进行转移如果还不够用呢?回想一下还有一个更短的语句,叫做\(j\)。
但是值得注意的是,使用该语句到达的地址也并非address中的地址,而是由PC的高四位和address*4拼接而成的,也就是说:\[Target\_address = [PC_{31},PC_{30},PC_{29},PC_{28},address_{26}...address_{1},address_{0},0,0]\]
编译器可以将跳转的过远的条件转移语句,拆分成条件转移语句和直接转移语句的组合来达到远距离转移的目的。如果还要远,那么可以参考函数跳转的方法,直接使用\(jr , ra\)直接将转移的寄存器的地址赋给$pc,这样就可以实现任意转移,可以实现在\(2^{32}\)次方的范围内进行转移。但是一般情况下代码不会超过\(2^{28}\)。
寻址模式
数据寻址
- 立即数寻址:操作数放在指令里面。
- 寄存器寻址:根据寄存器编号寻址
- 基址寻址:根据初始地址和偏移量进行寻址
指令寻址
- 相对PC寻址:PC+偏移地址
- 无条件跳转:PC高四位和address左移两位拼接
- 直接赋值跳转:使用jar,直接改变pc进行跳转
多进程调度同步机制
1 | ll rt, offset(rs) |
这俩看视频实在是看不懂,我这里参考了这篇博文:理解MIPS指令集中的ll (load linked) 和 sc (store conditional)指令
ll和sc指令是一种在多处理器系统中实现共享内存的原子操作的方法,且不需要为了让一个处理器独占它而锁定它。
意思是,你用ll指令读取一个内存中的数据并存到一个寄存器,然后在寄存器修改(或不)这个值,随后用sc指令将它写入到同样的(原来的)位置。而sc指令只在你修改寄存器中的值的期间,没有任何一个处理器改变它内存中的值 这种情况下,将值写入。它同时需要(的副作用是)设置一个指示状态的变量来表明是否成功写入。(成功为1,失败为0)
当新的值成功地被写入了,那么可以认为这个线程在没有别的线程干涉的情况下完成了(一个值的)读-改-写过程。如果失败了,接下来就取决于程序是要放弃这个操作还是再试一次了,不过至少它(ll&sc)不会生成一个隐性的(不被察觉的)竞争危害。
基于这两条指令的swap
1 | try: |
高级语言的编译、程序的启动机制
总体结构
伪指令
查看下面的语句
1 | move $t0, $t1 |
实际上在mips 指令集中是没有上面的语句的,为了解决这一问题,我们使用了伪指令,也就是说,当遇到这样的伪指令时,将它转化成对应的mips指令,如下:
1 | add $t0, $zero, $t1 |
这里的$at是一个专门给汇编器预留的临时变量。
中是没有上面的语句的,为了解决这一问题,我们使用了伪指令,也就是说,当遇到这样的伪指令时,将它转化成对应的mips指令,如下:
1 | add $t0, $zero, $t1 |
这里的$at是一个专门给汇编器预留的临时变量。