对于计算机来讲,最核心的就是「CPU」(Central Processing Unit,中央处理器)
CPU 和其他设备连接,要靠一种叫作「总线」(Bus)的东西,其实就是主板上密密麻麻的集成电路,这些东西组成了 CPU 和其他设备的高速通道。
在这些设备中,最重要的是「内存」(Memory)。因为单靠 CPU 是没办法完成计算任务的,很多复杂的计算任务都需要将中间结果保存下来,然后基于中间结果进行进一步的计算。CPU 本身没办法保存这么多中间结果,这就要依赖内存了。
总线上还有一些其他设备,例如显卡会连接显示器、磁盘控制器会连接硬盘、USB 控制器会连接键盘和鼠标等等。
CPU:包括三个部分,运算单元、数据单元和控制单元。
每个程序都有一个「项目执行计划书」,里面是一行行程序执行的指令。每个进程都有一个程序放在硬盘上,是「二进制」的,再里面就是一行行的指令,会操作一些数据。
进程一旦运行,比如图中两个进程 A 和 B,会有「独立」的内存空间,互相隔离,程序会分别加载到进程 A 和进程 B 的内存空间里面,形成各自的代码段。
程序运行的过程中要操作的数据和产生的计算结果,都会放在数据段里面。
CPU 的控制单元里面,有一个「指令指针寄存器」(IP 寄存器),它里面存放的是下一条指令在内存中的地址。控制单元会「不停」地将代码段的指令拿进来,先放入指令寄存器。
把 CPU 看成是一个非常小的运算机器,为了能够完成复杂的任务,为 CPU 提供了一大堆指令来实现各种功能,这一大堆指令称为指令集(Instructions)。也就是「机器语言」。
❝CPU 只能识别二进制的指令 ❞
但是,二进制代码难以阅读和记忆,又将二进制指令集转换为人类可以识别和记忆的符号 -- 「汇编指令集」
1000100111011000 机器指令
mov ax,bx 汇编指令
CPU 不能直接识别汇编语言,还需要一个「汇编编译器」,其作用是将汇编代码编程成机器代码。
「汇编语言的弊端」
所以, 诸如 C、C++、Java、C#、Python、JavaScript 等高级语言应用而生。
和汇编语言一样,CPU也不能直接识别由高级语言所编写的代码。
根据对高级语言转换的过程中是否生成「机器代码」,把执行高级语言分为
先将源代码通过解析器编译成中间代码,之后「直接」使用解释器解释执行中间代码,然后「直接输出结果」。
并不把整个程序 变成目标码,而是按顺序,读一句,解释一句,执行一句,所以,没给完整程序,它就可以执行了。
例如,浏览器处理网页,网页程序,每下来一句,就可以解释执行一句,不用等整个网页下来后再处理。
先将源代码通过「解析器」编译成中间代码,编译器再将中间代码编译成「机器代码」(编译成的机器代码以二进制文件形式存储), 执行这段程序的时候直接执行二进制文件。
必须给出完整程序,编译器通过几次扫描,翻译,编排,链接,变成exe文件执行。
我们通过针对一段C代码,进行编译执行,来看看CPU是如何执行程序的。(像JS这种解释执行,有另外一套逻辑和实现方式,后期会有介绍)
// test.c
int main (void)
{
int x =1;
int y = 2;
int z = x + y;
return z;
}
CPU 并不能直接执行这段 C 代码,需要对其进行编译,将其转换为二进制的机器码, CPU 才能按照顺序执行编译后的机器码。
通过 GCC 编译器将 C 代码编译成二进制文件。gcc -O0 -o code_prog test.c
。(在Mac环境下,可以通过brew[1]来进行gcc[2]的下载) 随后, 文件夹中生成名为 code_prog 的「可执行程序」。
通过objdump
将编译出来的 code_prog 程序进行反汇编。objdump -d code_prog
编译后的程序是由一堆二进制代码组成的(二进制代码是由一条条指令构成的)
在程序执行之前,程序需要被「装进内存」。CPU 可以通过指定内存地址,从内存中读取数据,或者往内存中写入数据。(内存是一个临时存储数据的设备, 因为断电之后,内存中的数据都会消失)
「内存中的每个存储空间都有其对应的独一无二的地址」。
在内存中,每个存放字节的空间都有其唯一的地址,而且地址是按照顺序排放的
代码被编译成可执行文件, 而可执行文件中包含了二进制的机器码。当二进制代码被加载进了内存后,内存中的每条「二进制代码」便都有了自己对应的地址。
❝CPU时钟周期:取出指令、分析指令、执行指令这三个过程 ❞
CPU 中有一个 PC 寄存器,它保存了将要执行的指令地址。当二进制代码被装载进了内存之后, 系统会将二进制代码中的第一条指令的地址写入到 PC 寄存器中。
到了下一个时钟周期时,CPU 便会根据 PC 寄存器中的地址,从内存中取出指令。
PC 寄存器中的指令取出来之后,将下一条指令的地址更新到 PC 寄存器中,并分析取出的指令。
取出的指令分两部分
要执行这条指令,就要把第一部分交给运算单元,第二部分交给数据单元。
数据单元根据数据的地址,从数据段里读到数据寄存器里,就可以参与运算了。运算单元做完运算,产生的结果会暂存在数据单元的数据寄存器里。最终,会有指令将数据写回内存中的数据段。
如此往复的对内存中的指令,进行取出、分析、执行。直到PC寄存器到达指令的最后。
参考资料:
[1]
brew: https://formulae.brew.sh/formula/gcc#default
[2]
gcc: https://gcc.gnu.org/