冯诺依曼模型
最重要的是定义计算机基本结构为 5 个部分,分别是运算器、控制器、存储器、输入设备、输出设备,这 5 个部分也被称为冯诺依曼模型。
运算器、控制器是在中央处理器里的,存储器就我们常见的内存,输入输出设备则是计算机外接的设备,比如键盘就是输入设备,显示器就是输出设备。
存储单元和输入输出设备要与中央处理器打交道的话,离不开总线。
内存
在计算机数据存储中,存储数据的基本单位是字节(byte),1 字节等于 8 位(8 bit)。每一个字节都对应一个内存地址。
中央处理器
中央处理器也就是我们常说的 CPU,32 位和 64 位通常称为 CPU 的位宽,代表的是 CPU 一次可以计算(运算)的数据量CPU 。最主要区别在于一次能计算多少字节数据:
32 位 CPU 一次可以计算 4 个字节;
64 位 CPU 一次可以计算 8 个字节;
CPU 内部还有一些组件,常见的有寄存器、控制单元和逻辑运算单元等。其中,控制单元负责控制 CPU 工作,逻辑运算单元负责计算,而寄存器可以分为多种类,每种寄存器的功能又不尽相同。
CPU 中的寄存器主要作用是存储计算时的数据。
常见的寄存器种类:
通用寄存器,用来存放需要进行运算的数据,比如需要进行加和运算的两个数据。
程序计数器,用来存储 CPU 要执行下一条指令「所在的内存地址」,注意不是存储了下一条要执行的指令,此时指令还在内存中,程序计数器只是存储了下一条指令「的地址」。
指令寄存器,用来存放当前正在执行的指令,也就是指令本身,指令被执行完成之前,指令都存储在这里。
总线
总线是用于 CPU 和内存以及其他设备之间的通信,总线可分为 3 种:
地址总线,用于指定 CPU 将要操作的内存地址;
数据总线,用于读写内存的数据;
控制总线,用于发送和接收信号,比如中断、设备复位等信号,CPU 收到信号后自然进行响应,这时也需要控制总线;
当 CPU 要读写内存数据的时候,一般需要通过下面这三个总线:
首先要通过「地址总线」来指定内存的地址;
然后通过「控制总线」控制是读或写命令;
最后通过「数据总线」来传输数据;
输入、输出设备
输入设备向计算机输入数据,计算机经过计算后,把数据输出给输出设备。期间,如果输入设备是键盘,按下按键时是需要和 CPU 进行交互的,这时就需要用到控制总线了。
线路位宽与 CPU 位宽
数据通过操作电压来线路传输,低电压表示 0,高压电压则表示 1。
为了避免低效率的串行传输的方式,线路的位宽最好一次就能访问到所有的内存地址。
CPU 想要操作「内存地址」就需要「地址总线」:
如果地址总线只有 1 条,那每次只能表示 「0 或 1」这两种地址,所以 CPU 能操作的内存地址最大数量为 2(2^1)个(注意,不要理解成同时能操作 2 个内存地址);
如果地址总线有 2 条,那么能表示 00、01、10、11 这四种地址,所以 CPU 能操作的内存地址最大数量为 4(2^2)个。
如果计算的数额不超过 32 位数字的情况下,32 位和 64 位 CPU 之间没什么区别的,只有当计算超过 32 位数字的情况下,64 位的优势才能体现出来。
程序执行的基本过程
程序实际上是一条一条指令,所以程序的运行过程就是把每一条指令一步一步的执行起来,负责执行指令的就是 CPU 了。
![](https://i-blog.csdnimg.cn/blog_migrate/74cea11958e55658f24293d175684b13.png)
一个完整的程序要翻译成汇编语言的程序,这个过程称为编译成汇编代码。
程序编译过程中,编译器通过分析代码,发现 是数据,于是程序运行时,内存会有个专门的区域来存放这些数据,这个区域就是「数据段」。注意,数据和指令是分开区域存放的,存放指令区域的地方称为「正文段」。
![](https://i-blog.csdnimg.cn/blog_migrate/845296d2add0c9cfd1d88f0ae273641c.png)
针对汇编代码,我们还需要用汇编器翻译成机器码,这些机器码由 0 和 1 组成的机器语言,这一条条机器码,就是一条条的计算机指令,CPU 能够祭祀安吉指令的执行程序。
那 CPU 执行程序的过程如下:
第一步,CPU 读取「程序计数器」的值,这个值是指令的内存地址,然后 CPU 的「控制单元」操作「地址总线」指定需要访问的内存地址,接着通知内存设备准备数据,数据准备好后通过「数据总线」将指令数据传给 CPU,CPU 收到内存传来的数据后,将这个指令数据存入到「指令寄存器」。
第二步,「程序计数器」的值自增,表示指向下一条指令。这个自增的大小,由 CPU 的位宽决定,比如 32 位的 CPU,指令是 4 个字节,需要 4 个内存地址存放,因此「程序计数器」的值会自增 4;
第三步,CPU 分析「指令寄存器」中的指令,确定指令的类型和参数,如果是计算类型的指令,就把指令交给「逻辑运算单元」运算;如果是存储类型的指令,则交由「控制单元」执行;
CPU 从程序计数器读取指令、到执行、再到下一条指令,这个过程会不断循环,直到程序执行结束,这个不断循环的过程被称为 CPU 的指令周期。
编译过程
例如a = 1 + 2的程序,编译器会a = 1 + 2 把 翻译成 4 条指令:load 数据1、load 数据 2、add、 store,存放到正文段中。假设这 4 条指令被存放到了 0x100 ~ 0x10c 的区域中:
0x100 的内容是 load 指令将 0x200 地址中的数据 1 装入到寄存器 R0;
0x104 的内容是 load 指令将 0x204 地址中的数据 2 装入到寄存器 R1;
0x108 的内容是 add 指令将寄存器 R0 和 R1 的数据相加,并把结果存放到寄存器 R2;
0x10c 的内容是 store 指令将寄存器 R2 中的数据存回数据段中的 0x208 地址中,这个地址也就是变量 a 内存中的地址;
编译完成后,具体执行程序的时候,程序计数器会被设置为 0x100 地址,然后依次执行这 4 条指令。
指令
指令的内容是一串二进制数字的机器码,每条指令都有对应的机器码,CPU 通过解析机器码来知道指令的内容。
不同的 CPU 有不同的指令集,也就是对应着不同的汇编语言和不同的机器码。例如RISC-V、MIPS、ARM等,详细自己寻找相关内容。在计算机系统基础(五)之RISC-V指令集有简单介绍。
一般指令都有这三种类型的含义:
R 指令,用在算术和逻辑操作,里面有读取和写入数据的寄存器地址。如果是逻辑位移操作,后面还有位移操作的「位移量」,而最后的「功能码」则是再前面的操作码不够的时候,扩展操作码来表示对应的具体指令的;
I 指令,用在数据传输、条件分支等。这个类型的指令,就没有了位移量和功能码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或一个常数;
J 指令,用在跳转,高 6 位之外的 26 位都是一个跳转后的地址
现代大多数 CPU 都使用来流水线的方式来执行指令,所谓的流水线就是把一个任务拆分成多个小任务,于是一条指令通常分为 4 个阶段,称为 4 级流水线,如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/05ed66f068d4336b433dd128d924b826.png)
四个阶段的具体含义:
CPU 通过程序计数器读取对应内存地址的指令,这个部分称为 Fetch(取得指令);
CPU 对指令进行解码,这个部分称为 Decode(指令译码);
CPU 执行指令,这个部分称为 Execution(执行指令);
CPU 将计算结果存回寄存器或者将寄存器的值存入内存,这个部分称为 Store(数据回写);
上面这 4 个阶段,我们称为指令周期(Instrution Cycle),CPU 的工作就是一个周期接着一个周期,周而复始。
![](https://i-blog.csdnimg.cn/blog_migrate/05874bbff7672ba006b4e9214bad8c73.png)
取指令的阶段,我们的指令是存放在存储器里的,实际上,通过程序计数器和指令寄存器取出指令的过程,是由控制器操作的;
指令的译码过程,也是由控制器进行的;
指令执行的过程,无论是进行算术操作、逻辑操作,还是进行数据传输、条件分支操作,都是由算术逻辑单元操作的,也就是由运算器处理的。但是如果是一个简单的无条件地址跳转,则是直接在控制器里面完成的,不需要用到运算器。
指令的类型
指令从功能角度划分,可以分为 5 大类:
数据传输类型的指令,比如 store/load 是寄存器与内存间数据传输的指令,mov 是将一个内存地址的数据移动到另一个内存地址的指令;
运算类型的指令,比如加减乘除、位运算、比较大小等等,它们最多只能处理两个寄存器中的数据;
跳转类型的指令,通过修改程序计数器的值来达到跳转执行指令的过程,比如编程中常见的 if-else、switch-case、函数调用等。
信号类型的指令,比如发生中断的指令 trap;
闲置类型的指令,比如指令 nop,执行后 CPU 会空转一个周期