代码是如何在CPU中执行起来的

1、编译型语言跟解释型语言的区别

计算机是不能够识别高级语言的,所以当我们运行一个高级语言程序的时候,就需要一个“翻译机”来从事把高级语言转变成计算机能读懂的机器语言的过程。这个过程分成两类,第一种是编译,第二种是解释。
编译型语言在程序执行之前,先会通过编译器对程序执行一个编译的过程,把程序转变成机器语言,然后通过链接器对不同文件编译后的文件进行链接,形成一个完整的二进制可执行文件。运行时就不需要翻译,而直接执行就可以了,最典型的例子就是C语言。解释型语言就没有这个编译的过程,而是在程序运行的时候,通过解释器对程序逐行作出解释,然后直接运行,最典型的例子是Ruby,因为编译型语言在程序运行之前就已经对程序做出了“翻译”,运行时就少掉了“翻译”的过程,所以效率比较高。然而,编译型语言的执行依赖于平台,生成的可执行文件不能在其他平台运行,跨平台的性较差。

此外,还有很多编译型也解释型混合的语言。 用Java来举例,Java属于半编译半解释型语言,它将源代码转编译成一种字节码文件(.class),这种字节码文件可以被JVM(JAVA虚拟机)所识别,并对字节码文件进行解释,翻译成机器码并在CPU中运行。所谓一次编译,到处运行,是指在编译和运行中间,多了一个JVM这个抽象层。字节码依赖JVM,而不依赖于平台(操作系统),只要支持JVM,字节码就能运行,不同平台的兼容性,就由JVM来解决,开发者可以专注于开发业务逻辑,这解决了编译语言的跨平台问题。
python类似于Java,也是解释型语言的一种。编译后生成的也是字节码(.pyc)的文件形式,并由Python的字节码解释器去解释执行字节码,较为常见的字节码解释器为CPython。不同点在于,Python的编译并非强制执行的操作,确切来说Python的编译是自动的,通常发生在对某个模块(module)的调用过程中,编译成字节码的可以节省加载模块的时间,以此达到提高效率的目的。编译得到的字节码在python虚拟机中即可解释执行。编译的产物即在python虚拟机中是以PyCodeObject对象的形式存在,而在磁盘中就是我们可见的.pyc文件。

2、CPU是如何执行程序的

python等解释型语言编译成字节码后,由虚拟机将其转换成CPU可直接执行的机器码,机器码也是执行速度最快的代码。编译型语言则是直接编译成可执行文件,这些可执行文件本身就是CPU可识别并执行的机器码,那么CPU是如何执行机器码的呢?

2.1 CPU的组成部件

CPU主要由4个部分组成:

  1. 算术逻辑单元ALU(Arithmetic Logic Unit)
    ALU是运算器的核心。它是以全加器为基础,辅之以移位寄存器及相应控制逻辑组合而成的电路,在控制信号的作用下可完成加、减、乘、除四则运算和各种逻辑运算。
  2. 寄存器组 RS(Register Set或Registers)
    RS实质上是CPU中暂时存放数据的地方,里面保存着那些等待处理的数据,或已经处理过的数据,CPU访问寄存器所用的时间要比访问内存的时间短。采用寄存器,可以减少CPU访问内存的次数,从而提高了CPU的工作速度。但因为受到芯片面积和集成度所限,寄存器组的容量不可能很大。寄存器组可分为专用寄存器和通用寄存器。专用寄存器的作用是固定的,分别寄存相应的数据。而通用寄存器用途广泛并可由程序员规定其用途。通用寄存器的数目因微处理器而异。
  3. 控制单元(Control Unit)
    正如工厂的物流分配部门,控制单元是整个CPU的指挥控制中心,由指令寄存器IR(Instruction Register)、指令译码器ID(Instruction Decoder)和操作控制器OC(Operation Controller)三个部件组成,对协调整个电脑有序工作极为重要。它根据用户预先编好的程序,依次从存储器中取出各条指令,放在指令寄存器IR中,通过指令译码(分析)确定应该进行什么操作,然后通过操作控制器OC,按确定的时序,向相应的部件发出微操作控制信号。操作控制器OC中主要包括节拍脉冲发生器、控制矩阵、时钟脉冲发生器、复位电路和启停电路等控制逻辑。
  4. 总线(Bus)
    就像工厂中各部位之间的联系渠道,总线实际上是一组导线,是各种公共信号线的集合,用于作为电脑中所有各组成部分传输信息共同使用的“公路”。直接和CPU相连的总线可称为局部总线。其中包括:数据总线DB(Data Bus)、地址总线AB(Address Bus) 、控制总线CB(Control Bus)。其中,数据总线用来传输数据信息;地址总线用于传送CPU发出的地址信息;控制总线用来传送控制信号、时序信号和状态信息等。

    2.2 机器码在CPU中的执行过程

    几乎所有的冯·诺伊曼型计算机的CPU,其工作都可以分为5个阶段:取指令、指令译码、执行指令、访存取数、结果写回。
  • 取指令阶段
    取指令(Instruction Fetch,IF)阶段是将一条指令从主存中取到指令寄存器的过程。
  • 指令译码阶段
    取出指令后,计算机立即进入指令译码(Instruction Decode,ID)阶段。在指令译码阶段,指令译码器按照预定的指令格式,对取回的指令进行拆分和解释,识别区分出不同的指令类别以及各种获取操作数的方法。在组合逻辑控制的计算机中,指令译码器对不同的指令操作码产生不同的控制电位,以形成不同的微操作序列;在微程序控制的计算机中,指令译码器用指令操作码来找到执行该指令的微程序的入口,并从此入口开始执行。
  • 执行指令阶段
    在取指令和指令译码阶段之后,接着进入执行指令(Execute,EX)阶段。此阶段的任务是完成指令所规定的各种操作,具体实现指令的功能。为此,CPU的不同部分被连接起来,以执行所需的操作。例如,如果要求完成一个加法运算,算术逻辑单元ALU将被连接到一组输入和一组输出,输入端提供需要相加的数值,输出端将含有最后的运算结果。
  • 访存取数阶段
    根据指令需要,有可能要访问主存,读取操作数,这样就进入了访存取数(Memory,MEM)阶段。此阶段的任务是:根据指令地址码,得到操作数在主存中的地址,并从主存中读取该操作数用于运算。
  • 结果写回阶段
    作为最后一个阶段,结果写回(Writeback,WB)阶段把执行指令阶段的运行结果数据“写回”到某种存储形式:结果数据经常被写到CPU的内部寄存器中,以便被后续的指令快速地存取;在有些情况下,结果数据也可被写入相对较慢、但较廉价且容量较大的主存。许多指令还会改变程序状态字寄存器中标志位的状态,这些标志位标识着不同的操作结果,可被用来影响程序的动作。

在指令执行完毕、结果数据写回之后,若无意外事件(如结果溢出等)发生,计算机就接着从程序计数器PC中取得下一条指令地址,开始新一轮的循环,下一个指令周期将顺序取出下一条指令。
通常情况下,一条指令可以包含按明确顺序执行的许多操作指令,CPU的工作就是执行这些指令,完成一条指令后,CPU的控制单元又将告诉指令读取器从内存中读取下一条指令来执行。这个过程不断快速地重复,快速地执行一条又一条指令。我们很容易想到,在处理这么多指令和数据的同时,由于数据转移时差和CPU处理时差,肯定会出现混乱处理的情况。为了保证每个操作准时发生,CPU需要一个时钟,时钟控制着CPU所执行的每一个动作。时钟就像一个节拍器,它不停地发出脉冲,决定CPU的步调和处理时间,这就是我们所熟悉的CPU的标称速度,也称为主频。主频数值越高,表明CPU的工作速度越快。

2.3 常见的机器码指令

常见的指令可以分为五大类:

  1. 第一类是算术类指令。我们的加减乘除,在CPU层面,都会变成一条条算术类指令。
  2. 第二类是数据传输类指令。给变量赋值,在内存里读写数据,用的都是数据传输类指令。
  3. 第三类是逻辑类指令。逻辑上的与或非,都是这一类指令。
  4. 第四类是条件分支类指令。日常我们写的“if/else”,其实都是条件分支类指令。
  5. 最后一类是无条件跳转指令。
    指令类型 实例指令 示例汇编代码 含义 注释
    算术类指令 add add $s1,$s2,$s3 $s1=$s2+$s3 将寄存器s2和s3中的数相加后放到寄存器s1中
    逻辑类指令 or or $s1,$s2,$s3 $s1=$s2 $s3
    数据传输指令 load word load $s1,10($s2) load $s1,10($s2) $s1=memory[$s2+10]
    条件分支指令 branch on equal beq $s1,$s2, 10 if($s1 == $s2) go to PC+10 如果寄存器s1和s2中的值相等,从程序计数器往后跳10
    无条件跳转指令 jump j 1000 go to 1000 跳转到目标地址为1000的位置