计算机的组成与设计——计算机的语言

本文是对《计算机组成与设计》第二章 “指令:计算机的语言” 的理解。以RISC-V指令集为例。

指令是计算机的语言。从汇编代码的角度来看,由操作和操作对象两部分组成。从机器码的角度来看,指令格式有R型,I型等。指令的寻址对象有立即数、寄存器和内存,内存的寻址方式又分为PC相对寻址和基址寻址。

无符号数是直接编码,有符号数使用补码的方式编码。正数的符号扩展是前导位填0,负数的符号扩展是前导位填1。因为RISC-V的指令长度是32位,所以如果要进行大数的运算,就需要用lui指令和其它运算指令把这个大数给拼出来。

非数字以编码的形式存在。ASCII是最初的编码方式,Unicode是通用的编码方式。对于字符串,C语言是在末尾跟一个0,Java则是在首位保存其长度。

操作

算术运算

从第二节可见,C语言里复杂的算术运算也是由若干条算术指令拼接而成的。

数据传送

只有数据传送指令可以访问内存。

逻辑运算

从第六节可见,高级语言的逻辑运算与逻辑运算指令有直接的对应关系。

控制转移

从第七节可见,高级语言的if语句和while语句均是由条件分支指令、无条件分支指令和标签拼接而成的。高级语言的switch语句则是通过跳转表以提高其效率。

jal用于函数调用,它在跳转的同时还把返回地址保存在寄存器中,所谓返回地址就是jal指令的下一条指令。jal是直接跳转,意思是直接跳转到一个地址。

jalr用于函数返回或case语句,它与jal不同的地方在于它跳转的地址是通过寄存器和立即数相加而得的。jalr是间接跳转。

无条件跳转指令和条件分支指令的寻址方式都是PC相对寻址,它们寻址范围是比较小的。jalr结合lui可以实现大范围的寻址,它是基址寻址。

原子操作

原子操作是并行计算的基础。在riscv里是用指令对来实现原子操作的,lr首先在寄存器内保存内存的值,sc再判断内存的值是否改变,如果改变了则表明已经有其它操作发生了,返回非0以表明此操作不是原子的,如果没改变则将某个值写入内存,并返回0以表明此操作是原子的。

操作对象

寄存器

寄存器是珍贵的资源,其数量是有限的。

x1用于函数调用的返回地址。

x2是栈指针寄存器。

x3是全局指针寄存器,用于指向静态变量所在的区域。

x8是帧指针寄存器,它指向的是过程帧,所谓的过程帧就是函数要用到的局部数据的栈片断。由于栈指针可能是随时变化的,有一个帧指针寄存器就可以方便地定位函数用到的局部变量了。

x10~x17这8个寄存器用于传递参数,其中x10和x11也可以用于返回值。

x5x7, X28x31这7个临时寄存器,函数中可以任意使用而不恢复其原值。临时寄存器的存在减少了内存的换入换出,从而提升了效率。

x8x9, x18x27这12个临时寄存器,函数中在使用后必须恢复其原值。

内存

内存可以看做一个大数组,地址相当于数组的下标。

立即数

使用立即数可以加快指令执行速度,降低功耗。

从高级语言到可执行程序

编译器

高级语言经编译器转换为汇编语言。

汇编器

汇编语言经汇编器转换为机器码。它把伪指令转换为实际的机器指令,计算出符号对应的地址,并将机器指令组织成操作系统所能识别的格式(比如EFL)。

链接器

链接器把各个独立的机器码拼接起来,将产生最终的可执行文件。

加载器

加载器把可执行程序加载到内存中,并跳转到该程序的启动例程使其执行。