初窥计算机指令
初窥计算机指令
在软硬件接口中,CPU 帮我们做了什么事?
从硬件角度来看,CPU就是一个超大规模集成电路,通过电路实现了加法、乘法乃至各种各样的处理逻辑。
从软件上来看,CPU 就是一个执行各种计算机指令的逻辑机器。这里的计算机指令,就好比一门 CPU 能够听得懂的语言,我们也可以把它叫作机器语言(Machine Language)。
不同的 CPU 能够听懂的语言不太一样。比如,我们的个人电脑用的是 Intel 的 CPU,苹果手机用的是 ARM 的 CPU。这两者能听懂的语言就不太一样。类似这样两种 CPU 各自支持的语言,就是两组不同的计算机指令集,英文叫 Instruction Set。
一个计算机程序实际上是由成千上万条指令组成的,但是CPU里不能一直放着所有指令,所以计算机程序平时是存储在存储器中的,这种程序指令存储在存储器里面的计算机,我们就叫作存储程序型计算机(Stored-program Computer)。
从编译到汇编,代码怎么变成机器码?
平时编写的代码,到底是怎么变成一条条计算机指令,最后被 CPU 执行的呢?我们拿一小段真实的 C 语言程序来看看。
1 |
|
要让这段程序在一个 Linux 操作系统上跑起来,我们需要把整个程序翻译成一个汇编语言(ASM,Assembly Language)的程序,这个过程我们一般叫编译(Compile)成汇编代码。
正对汇编代码,我们可以再用汇编器(Assember)翻译成机器码(Machine Code)。这些机器码由“0”和“1”组成的机器语言表示。这一条条机器码,就是一条条的计算机指令。这样一串串的 16 进制数字,就是我们 CPU 能够真正认识的计算机指令。
在一个 Linux 操作系统上,我们可以简单地使用 gcc 和 objdump 这样两条命令,把对应的汇编代码和机器码都打印出来。
可以看到,左边对应的是一条条机器码,右边有一系列的 push、mov、add、pop 等,这些就是对应的汇编代码。
从高级语言到汇编代码,再到机器码,就是一个日常开发程序,最终变成了CPU 可以执行的计算机指令的过程。
常见指令
- 算数类指令。我们的加减乘除,在 CPU 层面,都会变成一条条算术类指令。
- 数据传输类指令。给变量赋值、在内存里读写数据,用的都是数据传输类指令。
- 逻辑类指令。逻辑上的与或非,都是这一类指令。
- 条件分支类指令。比如“if/else”
- 无条件条转指令。写一些大一点的程序,我们常常需要写一些函数或者方法。在调用函数的时候,其实就是发起了一个无条件跳转指令。
<img src=”/img/computer_img/各种指令.webp style=”zoom:67%;)
MIPS 指令集
MIPS 的指令是一个 32 位的整数,高 6 位叫操作码(Opcode),也就是代表这条指令具体是一条什么样的指令,剩下的 26 位有三种格式,分别是 R、I 和 J。
R指令一般用来算术和逻辑操作,里面有读取和写入数据的寄存器的地址。如果是逻辑位移操作,后面还有位移操作的位移量,而最后的功能码,则是在前面的操作码不够的时候,扩展操作码表示对应的具体指令的。
I 指令,则通常是用数据传输、条件分支,以及在运算的时候使用的并非变量还是常数的时候。这个时候,没有了位移量和操作码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或者一个常数。
J 指令就是一个跳转指令,高 6 位之外的 26 位都是一个跳转后的地址。
我以一个简单的加法算术指令 add t0,s1, $s2, 为例,
add $t0,$s2,$s1
对应的 MIPS 指令里 opcode 是 0,rs 代表第一个寄存器 s1 的地址是 17,rt 代表第二个寄存器 s2 的地址是 18,rd 代表目标的临时寄存器 t0 的地址,是 8。因为不是位移操作,所以位移量是 0。把这些数字拼在一起,就变成了一个 MIPS 的加法指令。
为了读起来方便,我们一般把对应的二进制数,用 16 进制表示出来。在这里,也就是 0X02324020。这个数字也就是这条指令对应的机器码。
为了读起来方便,我们一般把对应的二进制数,用 16 进制表示出来。在这里,也就是 0X02324020。这个数字也就是这条指令对应的机器码。
