目录

计算机体系结构 | 计算机指令

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专注于高性能方向,但也带来了高功耗,而后者专注于做低功耗的嵌入式,而性能不是很强劲。下面附上这两种指令集的差异:

./image/3_1_CISC_RISC.png

long long ago,编程是采用打孔卡的方式。要写的程序、要处理的数据变成了一条条纸带或者一张张卡片,然后交给计算机去处理。其中打孔和不打孔的分别表示0或者1../image/3_0.jpg

2. 计算机指令的种类

上面我们讲了指令集的分类,而这一节要讲的是同一指令集下,指令又可以分成哪几类。

  1. 算数指令。加减乘除这些,都会变成一条条算数类指令
  2. 数据传输类指令。给变量赋值、在内存中读写数据,用的都是数据传输类指令
  3. 逻辑类指令。逻辑上的与或非,都是这一类指令;
  4. 条件分支指令。日常我们写的if/else,都会变成条件分支类指令;
  5. 无条件指令。调用函数的时候,就是发起一个无跳转指令。

./image/3_2.jpeg

3. 如何生成计算机指令

下面我们拿C语言和汇编语言来举例说明,C语言的程序是如何变成计算机指令的:

1
2
3
4
5
6
7
// test.c
int main()
{
  int a = 1; 
  int b = 2;
  a = a + b;
}

我们使用gcc来编译上述代码:

1
$ gcc -g -c test.c

这段程序首先会翻译成一个汇编语言(ASM,Assembly Language)的程序,这个过程叫做编译成汇编代码。针对得到的汇编代码,再使用汇编器(Assembler)翻译成机器码(0或者1组成),这些机器码就是一条条计算机指令。整个过程大致如下所示:

./image/3_3.png

我们使用objdump来查看一下生成的文件的内容

1
$ objdump -d -M intel -S test.o

整个文件内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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进制)就是一条条计算机指令,右边的pushmov等是对应的汇编代码。一条C语言语句可能会对应多条汇编语言,但是汇编语言与计算机指令是一一对应的。因为我们直接看一串01,比如(48 89 e5 (16进制))不知道这个指令是干啥的,但是我们通过mov rbp,rsp这条汇编指令可以很清楚知道这条指令的作用,而且汇编语言相对计算机指令更容易记忆和使用。虽然汇编语言与计算机指令是一一对应的,但是它两是不能等同的,汇编指令还得经过汇编器才能翻译成计算机指令,汇编语言的出现是为了方便程序员的,面向的是程序员等这类人,而计算机指令面向的是CPU.

3.1. 汇编与计算机指令对应示例

那么汇编器是如何把对应的汇编代码翻译成计算机指令的呢?下面选用最简单的MIPS指令集,来阐述一下这个具体过程。

./image/3_4.jpeg

MIPS 的指令是一个 32 位的整数,高 6 位叫操作码(Opcode),代表这条指令具体要实现的操作是什么,剩下的 26 位有三种格式,分别是 R、I 和 J。

R 指令一般用来做算术和逻辑操作,里面有读取和写入数据的寄存器的地址。如果是逻辑位移操作,后面还有位移操作的位移量,而最后的功能码,则是在前面的操作码不够的时候,扩展操作码表示对应的具体指令的。

I 指令,则通常是用在数据传输、条件分支,以及在运算的时候使用的并非变量还是常数的时候。这个时候,没有了位移量和操作码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或者一个常数。

J 指令就是一个跳转指令, 整个26位表示跳转后的地址。

假如有这么一条汇编指令,那么对应的MIPS指令会是啥呢?

1
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相关资料

本文主要参考

1.CPU架构有多少种?X86与ARM有哪些不同之处?看完这篇你就懂了