指令系统是计算机硬件的语言系统。
如果我们从软件工程师的角度来讲,CPU 就是一个执行各种计算机指令(Instruction Code)的逻辑机器。这里的计算机指令,就好比一门 CPU 能够听得懂的语言,我们也可以把它叫作机器语言(Machine Language)。
指令由两部分构成:操作码字段、地址码字段。
OP来表示A来表示,不同的地址码位数不同根据一条指令有多少操作数地址,可将该指令称为几操作数指令。
操作数分为被操作数、操作数、操作结果三部分。
A1代表第一个操作数的地址,A2代表第二个操作数的地址,A3用来保存(A1)OP(A2)的计算结果,A4用来指出下一条指令的地址。指令可以明确的支出下一条指令的地址,但是指令长度太大,实际应用中几乎不采用这种指令格式。A1代表第一个操作数的地址,A2代表第二个操作数的地址,A3用来保存(A1)OP(A2)的计算结果,下一条指令直接使用(PC) + 1,也就是下一条指令的地址。指令长度依然很长,执行一次指令要四次访存,取指令一次,取操作数两次,存结构一次;操作数不会被破坏。A1代表第一个操作数的地址,A2代表第二个操作数的地址,A1用来保存(A1)OP(A2)的计算结果,下一条指令直接使用(PC) + 1,也就是下一条指令的地址。执行一次指令仍要四次访存,取指令一次,取操作两次,存结果一次;执行完指令之后,计算结果覆盖掉一个操作数。A1代表第一个操作数的地址,Acc用来保存(Acc)OP(A1)的计算结果,下一条指令直接使用(PC) + 1,也就是下一条指令的地址。执行一次只需要访存两次,取指令一次,去操作一次;被操作数和运算结构都放在累加寄存器中,所以对于被操作数的读取和存入都不需要访存。寻址分为数据寻址(data)和指令寻址(instruction)。
常见的指令可以分成五大类。
下面我们来看看,汇编器是怎么把对应的汇编代码,翻译成为机器码的。
不同的 CPU 有不同的指令集,也就对应着不同的汇编语言和不同的机器码。为了方便你快速理解这个机器码的计算方式,我们选用最简单的 MIPS 指令集,来看看机器码是如何生成的。
MIPS 是一组由 MIPS 技术公司在 80 年代中期设计出来的 CPU 指令集。就在最近,MIPS 公司把整个指令集和芯片架构都完全开源了。
MIPS 的指令是一个 32 位的整数,高 6 位叫操作码(Opcode),也就是代表这条指令具体是一条什么样的指令,剩下的 26 位有三种格式,分别是 R、I 和 J。
R 指令是一般用来做算术和逻辑操作,里面有读取和写入数据的寄存器的地址。如果是逻辑位移操作,后面还有位移操作的位移量,而最后的功能码,则是在前面的操作码不够的时候,扩展操作码表示对应的具体指令的。
I 指令,则通常是用在数据传输、条件分支,以及在运算的时候使用的并非变量还是常数的时候。这个时候,没有了位移量和操作码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或者一个常数。
J 指令就是一个跳转指令,高 6 位之外的 26 位都是一个跳转后的地址。
例如使用 a = 1 + 2 。程序编译过程中,编译器通过分析代码,发现 1 和 2 是数据,于是程序运行时,内存会有个专门的区域来存放这些数据,这个区域就是「数据段」。如下图,数据 1 和 2 的区域位置:
编译器会把a = 1 + 2翻译成 4 条指令,存放到正文段中。如图,这 4 条指令被存放到了 0x200 ~ 0x20c 的区域中:
load 指令将 0x100 地址中的数据 1 装入到寄存器 R0;load 指令将 0x104 地址中的数据 2 装入到寄存器 R1;add 指令将寄存器 R0 和 R1 的数据相加,并把结果存放到寄存器 R2;store 指令将寄存器 R2 中的数据存回数据段中的 0x108 地址中,这个地址也就是变量 a 内存中的地址;上面的例子中,由于是在 32 位 CPU 执行的,因此一条指令是占 32 位大小,所以你会发现每条指令间隔 4 个字节。
而数据的大小是根据你在程序中指定的变量类型,比如 int 类型的数据则占 4 个字节,char 类型的数据则占 1 个字节。