一、程序执行的基本过程
程序就是一条条指令,因此程序的执行过程就是cpu把每一条指令一步一步的执行起来.
1、cpu读取程序计数器里的数值,这里面存储的是即将执行的指令的地址,接着cpu的控制单元操作地址总线指定需要访问的内存地址,接着通知内存设备准备好数据,准备好的数据通过数据总线将数据传输到指令寄存器里
2、程序计数器开始自增(一般是在取值完成后自增,流水线架构)表示指向下一条指令的地址,这个自增的大小由cpu的字长来决定,比如是32位cpu,指令是4字节,需要4个内存地址存放,程序计数器的值就会增加4,同理64位就增加8
3、cpu分析指令寄存器里面的指令,确定指令的参数和类型,如果是计算机指令,就把指令交给逻辑运算单元运算,如果是存储类型的指令就交给控制单元执行
简单的来说就是先读取程序计数器里面的指令地址进行取址,接着对应内存的地址里面的数据通过数据总线传输到cpu的指令寄存器里执行,程序计数器开始自增,开始读取下一条指令,这样循环直到程序执行结束,这个不断循环的过程被称为 CPU 的指令周期。
二、a=1+2的基本执行过程
cpu是不认识a=1+2这个字符串的,需要将程序翻译成汇编语言的程序,接着将汇编代码转换位机器语言(二进制语言,由0和1组成),具体的过程是经历过预处理,编译,汇编,链接4个阶段,后面我会写一篇来讲讲我的理解
现在我会写一些a=1+2在32位cpu下的执行过程
在程序编译过程中,编译器通过分析代码,发现1和2是数据,在程序运行起来时会有专门的区域来存储数据,这个区域就是数据段
数据 1 被存放到 0x200 位置,数据 2 被存放到 0x204 位置
编译器会把a=1+2翻译成 4 条指令,存放到正文段中
这里注意:数据和指令的存放位置是分开的,数据存放在数据段中,指令存放在正文段中
如上图所示
0x100 的内容是 load 指令将 0x200 地址中的数据 1 装入到寄存器 R0
0x104 的内容是 load 指令将 0x204 地址中的数据 2 装入到寄存器 R1
0x108 的内容是 add 指令将寄存器R0和R1的数据相加,并把结果放在寄存器R2里面
0x10c 的内容是 store 指令将寄存器R2的数据存放到数据段中的0x208 地址里,这个地址也就是变量 a 内存中的地址
编译完成后,具体执行程序的时候,程序计数器会被设置为 0x100 地址,然后依次执行这 4 条指令
这里面会发现,在32位cpu中,一条指令的占位是32位,也就是4字节,所以指令将的地址间距就是4个字节,而数据段中的地址是根据定义的数据类型来确定间隔的,int是4字节,char是一个字节
三、指令
上面的例子实际是简易的汇编代码,目的是为了方便理解指令的具体内容,实际上的指令内容是一串二进制的机器码,每条指令都对应相应的机器码。cpu通过机器码来解析指令内容,不同的 CPU 有不同的指令集,也就是对应着不同的汇编语言和不同的机器码。
现代大多数 CPU 都使用来流水线的方式来执行指令,所谓的流水线就是把一个任务拆分成多个小任务,于是一条指令通常分为 4 个阶段,称为 4 级流水线.
1、cpu通过程序计数器来读取指令地址,这个部分称为 Fetch(取得指令)
2、cpu对指令解码,这个部分称为 Decode(指令译码)
3、cpu执行指令,这个部分称为 Execution(执行指令)
4、cpu将计算结果存回寄存器或者将寄存器的值存入内存,这个部分称为 Store(数据回写)
简答来说就是取址,翻译指令,执行指令,将结果存入,这四个步骤称为指令周期,CPU 的工作就是一个周期接着一个周期
1、指令的类别
指令从功能角度划分,可以分为 5 大类:
1、数据传输类型的指令,比如 store/load,执行指令是寄存器与内存之间数据传输的指令,mov是是将一个内存地址的数据移动到另一个内存地址的指令
2、运算类型的指令,比如加减乘除、位运算、比较大小等等,它们最多只能处理两个寄存器中的数据
3、跳转类型的指令,通过修改程序计数器的值来达到跳转执行指令的过程,比如编程中常见的if/else,case/switch,函数调用等。
4、信号类型的指令,比如发生中断的指令 trap
5、闲置类型的指令,比如指令 nop,执行后 CPU 会空转一个周期
2、指令的执行速度
CPU 的硬件参数都会有GHz这个参数,GHz(吉赫兹)是衡量 CPU(中央处理器)运算速度的重要硬件参数单位,它本质上表示的是 CPU 的时钟频率,比如一个 1 GHz 的 CPU,指的是时钟频率是 1 G,代表1秒会产生 1G 次数的脉冲信号,每一次脉冲信号高低电平的转换就是一个周期,称为时钟周期。因此,时钟频率越高,理论上 CPU 每秒能完成的操作次数越多,运算速度越快。
这里要注意,一个时钟周期是不一定能执行完一条指令的,大多数指令不能在一个时钟周期完成,通常需要若干个时钟周期。不同的指令需要的时钟周期是不同的,加法和乘法都对应着一条 CPU 指令,但是乘法需要的时钟周期就要比加法多。
程序执行的时候,耗费的 CPU 时间少就说明程序是快的,对于程序的 CPU 执行时间
对于 CPU 时钟周期数我们可以进一步拆解成:指令数 x 每条指令的平均时钟周期数(CPI)
因此,
1、指令数,表示执行程序所需要多少条指令,以及哪些指令。这个层面是基本靠编译器来优化
2、每条指令的平均时钟周期数 CPI,表示一条指令需要多少个时钟周期数
3、时钟周期时间,表示计算机主频,取决于计算机硬件。
假设:
程序指令总数 = 10 亿条,CPI = 2(每条指令平均消耗 2 个时钟周期);
CPU 频率 = 2GHz(时钟周期时间 = 0.5ns)。 计算过程:
CPU 时钟周期数 = 指令总数 × CPI = 10 亿 × 2 = 20 亿个周期;
CPU 执行时间 = 20 亿 × 0.5ns = 10 亿 ns = 1 秒。