1. 计算机指令是啥?
计算机指令是CPU能直接处理的,它是由一串0或1组成的。那么为什么是0和1呢?因为CPU是一个超大规模的集成电路,通过电路实现加法、乘法等各种运算,0或者1可以对应电路中的低电平或者高电平。从而实现运算功能。
CPU能处理计算机指令组成了计算机指令集(instruction set),然而不同CPU能处理的计算机指令集是不同的,现在指令集见的比较多有两类:
- 一类是CISC:复杂指令集计算,x86架构为代表的(我们的PC);
- 另一类是RISC:精简指令集计算,ARM为代表的(比如我们的手机);
指令集不同的意思比较直观的一点就是比如说在某种指令集中1010代表add,所以这条指令的指令码部分就是1010,在有的指令集里面,0000代表add,所以同样的二进制串在不同的指令集下有不同的解读。那么不同的指令集主要体现在面向的设备、对象、性能不同。
- CISC复杂指令系统就是为了增强原有指令的功能,设置更为复杂的新指令实现部分大量重复的软件功能的硬件化。由于早期的电脑主频低、运行速度慢,为了提高运算速度,不得已将更多的复杂指令加入到指令系统中来提高电脑的处理效率,慢慢形成以桌面电脑为首的复杂指令系统计算机。CISC可以实现高性能CPU设计,但是设计起来就相当麻烦了,要保持庞大硬件设计正确是一件不容易的事情,还要确保性能有所提升,不能开倒车,因此桌面CPU研发时间也慢慢地变长。
- RISC可以说是从CISC中取其精华去其糟粕,简化指令功能,让指令的平均执行周期减少,达到提升计算机工作主频的目的,同时引入大量通用寄存器减少不必要的读写过程,提高子程序执行速度,这样一来程序运行时间缩短并且减少了寻址,提高了编译效率,最终达到高性能目的。
从CISC、RISC的设计思路来看,CISC专注于高性能方向,但也带来了高功耗,而后者专注于做低功耗的嵌入式,而性能不是很强劲。下面附上这两种指令集的差异:
long long ago,编程是采用打孔卡的方式。要写的程序、要处理的数据变成了一条条纸带或者一张张卡片,然后交给计算机去处理。其中打孔和不打孔的分别表示0或者1.
2. 计算机指令的种类
上面我们讲了指令集的分类,而这一节要讲的是同一指令集下,指令又可以分成哪几类。
- 算数指令。加减乘除这些,都会变成一条条算数类指令
- 数据传输类指令。给变量赋值、在内存中读写数据,用的都是数据传输类指令
- 逻辑类指令。逻辑上的与或非,都是这一类指令;
- 条件分支指令。日常我们写的
if/else
,都会变成条件分支类指令; - 无条件指令。调用函数的时候,就是发起一个无跳转指令。
3. 如何生成计算机指令
下面我们拿C语言和汇编语言来举例说明,C语言的程序是如何变成计算机指令的:
// test.c
int main()
{
int a = 1;
int b = 2;
a = a + b;
}
我们使用gcc
来编译上述代码:
$ gcc -g -c test.c
这段程序首先会翻译成一个汇编语言(ASM,Assembly Language)的程序,这个过程叫做编译成汇编代码。针对得到的汇编代码,再使用汇编器(Assembler)翻译成机器码(0或者1组成),这些机器码就是一条条计算机指令。整个过程大致如下所示:
我们使用objdump
来查看一下生成的文件的内容
$ objdump -d -M intel -S test.o
整个文件内容如下:
root@share-virtual-machine:~/c_ws# objdump -d -M intel -S test.o
test.o: 文件格式 elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
int main()
{
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
int a = 1;
4: c7 45 f8 01 00 00 00 mov DWORD PTR [rbp-0x8],0x1
int b = 2;
b: c7 45 fc 02 00 00 00 mov DWORD PTR [rbp-0x4],0x2
a = a + b;
12: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
15: 01 45 f8 add DWORD PTR [rbp-0x8],eax
18: b8 00 00 00 00 mov eax,0x0
}
1d: 5d pop rbp
1e: c3 ret
那么左边的一串数字(16进制)就是一条条计算机指令,右边的push
、mov
等是对应的汇编代码。一条C语言语句可能会对应多条汇编语言,但是汇编语言与计算机指令是一一对应的。因为我们直接看一串01,比如(48 89 e5 (16进制))不知道这个指令是干啥的,但是我们通过mov rbp,rsp
这条汇编指令可以很清楚知道这条指令的作用,而且汇编语言相对计算机指令更容易记忆和使用。虽然汇编语言与计算机指令是一一对应的,但是它两是不能等同的,汇编指令还得经过汇编器才能翻译成计算机指令,汇编语言的出现是为了方便程序员的,面向的是程序员等这类人,而计算机指令面向的是CPU.
3.1. 汇编与计算机指令对应示例
那么汇编器是如何把对应的汇编代码翻译成计算机指令的呢?下面选用最简单的MIPS指令集,来阐述一下这个具体过程。
MIPS 的指令是一个 32 位的整数,高 6 位叫操作码(Opcode),代表这条指令具体要实现的操作是什么,剩下的 26 位有三种格式,分别是 R、I 和 J。
R 指令一般用来做算术和逻辑操作,里面有读取和写入数据的寄存器的地址。如果是逻辑位移操作,后面还有位移操作的位移量,而最后的功能码,则是在前面的操作码不够的时候,扩展操作码表示对应的具体指令的。
I 指令,则通常是用在数据传输、条件分支,以及在运算的时候使用的并非变量还是常数的时候。这个时候,没有了位移量和操作码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或者一个常数。
J 指令就是一个跳转指令, 整个26位表示跳转后的地址。
假如有这么一条汇编指令,那么对应的MIPS指令会是啥呢?
add $t0,$s2,$s1
由于add操作对应的R类型的指令,所以我们采用R指令的格式。操作码字段是0(add对应的操作码是0),rs是第一个寄存器s1的地址(0x11),rt是第二个寄存器s2的地址(0x12),rd是临时寄存器$t0的值(0x08),因为是相加运算所以位移量为0,那么最终拼接起来的值是:000000 10001 10010 01000 00000 100000,那么对应的16进制是0x02324020,这就是我们最终的MIPS指令。
MIPS 是一组由 MIPS 技术公司在 80 年代中期设计出来的 CPU 指令集。最近,MIPS 公司把整个指令集和芯片架构都完全开源了。MIPS相关资料。
本文主要参考