CPU的工作原理

一、CPU的组成部分

1.1、计算机五大部件

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 说起计算机五大件,笔者想起在大学的时候,老师问我们计算机硬件的五大部件有什么,然后听到有同学回答说鼠标,键盘,显示器什么的,把老师逗乐了,说我们计算机系的还这么小白。其实也正常,谁不是从小白慢慢成为技术大牛呢...

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 言归正传,计算机硬件的五大件分为:输入设备,输出设备,运算器,控制器,储存器。输入,输出设备主要就是鼠标,键盘,显示器还有打印机等等。储存器主要分为内存和磁盘,内存是储存程序运行时的指令和数据。本章主要围绕着控制器和运算器来讲,因为它们都是CPU的组成部分。

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 按实际硬件分,CPU内部是由寄存器,控制器,运算器,时钟四个部分组成:

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 寄存器:暂存指令和数据,可以看作是一级内存;

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 控制器:负责把内存上的指令,数据读入寄存器;

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 运算器:负责运算从内存读入寄存器的数据;

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 时钟:负责发出CPU开始计时的时钟信号;

​​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 一个CPU可以有多个寄存器。

1.2、寄存器的分类和功能

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ x86系列32位CPU的寄存器主要分为累加寄存器,标志寄存器,程序计数器,基址寄存器,栈指针寄存器,通用寄存器等;累加寄存器,标志寄存器,程序计数器都只有一个,其他寄存器一般有多个。32位CPU是指寄存器可以存储4个字节。

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 累加寄存器(eax)(这个是运算器):存储执行运算的数据和运算后的数据;

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 标志寄存器:存储运算处理后的CPU的状态;

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 程序计数器(epc)(这个是控制器)的作用是存储下一条指令所在的内存地址;比如程序需要实现123和456相加,并输出结果到显示器。程序开始运行时,程序计数器储存程序初始的内存地址比如是0100,CPU每执行一个指令,程序计数器存储的内存地址就会加1(程序流程分为顺序执行,跳转,循环),这里只拿顺序执行,如下图:

CPU的工作原理

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 基址寄存器(ebp):存储数据存储领域基点的内存地址;

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 栈指针寄存器(esp):存储栈中最高位数据的内存地址;

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 通用寄存器:存储任意数据;

二、程序在CPU的执行过程

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 程序是指令和数据的集合,不管是C、PHP、GO等其他语言写的程序,到最后都是编译成机器语言,就是一系列的指令,程序运行时CPU则依次执行每一行的指令,根据运算结果来控制整个计算机的输入还是输出。程序运行时,是先从把程序在磁盘上复制到内存,然后根据程序计数器指定的内存地址,把程序的指令从内存上读到指定寄存器进行运算。

CPU的工作原理

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ CPU能识别的机器语言,像这种00111110二进制数值,在我们人类看是很难理解的,为了人类能够方便理解机器语言,就发明了助记符,每个助记符都一一对应机器语言,这种助记符对应机器语言的方式就是汇编,汇编语言是直接反应机器语言的程序运行情况。比如助记符LD A , 207,对应的机器码是00111110 11001111。LD A , 207的意思是把数值207写入到A寄存器。LD 是操作码;A,207是操作数。

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 下面用高级语言(比如C、PHP、GO)转换成汇编(低级语言),通过汇编来理解程序的指令在CPU上是怎么运作的:

下面是简单的C程序:

int add_a_and_b(int a, int b) {
   return a + b;
}

int main() {
   return add_a_and_b(2, 3);
}

通过汇编器转换成汇编(省略了汇编器生成的段定义和伪指令,这里只是为了理解程序执行过程):

_add_a_and_b:
         push  ebp
	 mov   ebp,esp
	 mov   eax,dword ptr [ebp+8]  ;[ebp+8]指定栈中储存的数值2,传人eax寄存器
	 add   eax,dword ptr [ebp+12]	;[ebp+12]指定栈中储存的数值3,再跟eax寄存器存储的值相加,结果存入eax
	 pop   ebp
         ret
	
_main:
	 push  ebp
	 mov   ebp,esp
         push  3
         push  2
         call  _add_a_and_b 
         add   esp,8
         pop   ebp
         ret

解释上面汇编代码:

dword ptr 表示从指定内存地址读出4个字节数据;

_main,_add_a_and_b是函数标签;

操作码​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 操作数​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 说明

push​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ebp​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 把ebp寄存器里存储的内存地址存入栈中(32位CPU,占用4个字节)

mov​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ebp,esp​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​​把esp寄存器里存储的内存地址传到ebp寄存器

push​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 3​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​把3放入栈中(占用4个字节)

push​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 2​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​把2放入栈中(占用4个字节)

call​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ _add_a_and_b ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​调用函数

add​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ esp,8​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ esp寄存器存储的内存地址值加8(栈存储数据是从高地址到低地址开始存储,上面加入了3和2,故加8来清理栈中数据)

pop ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ebp ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​读出栈中的数值存入ebp寄存器

ret​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​结束main函数,返回调用源

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 每次调用函数时,都要执行push ebp mov ebp,esp,这两个指令,是因为函数内可能要用到ebp寄存器,所以先把ebp寄存器原来的数值存入栈中,再把当前存储在esp上的栈顶地址传给ebp;函数调用结束后,执行add esp,8 pop ebp来进行占空间清理,再把ebp原来的数值传入ebp寄存器;

​​ ​ ​ ​ ​ ​ ​ ​ ​ ​ 上面的函数调用,函数返回时,程序计数器里存储的地址又回到函数调用的下一个指令,实现的原因是:在进行函数调用前先把函数调用的下一个指令存储到栈中,调用完再把栈上的返回目的地址的内存地址(目的地址的内存是指函数调用完,回到下一个指令的地址。)传入程序计数器存储。上面的程序内存里栈空间是这样的:

CPU的工作原理

总结:计算机的运行机制其实很简单,就是照着程序员写好的程序(源文件),翻译成CPU能理解的二进制指令(本地代码)从上往下逐步的执行。如今的编程从面向过程,到面向对象,这个发展无非是越来越接近人类,从难于理解的机器码,到用英文对应机器码的汇编,再发展到面向对象编程,都是为了增加代码的可读性,可维护性。

上一篇:实用经验 48 函数重载需考虑什么?


下一篇:# esp32使用esp-idf驱动SSD1351(中景园OLED)(一、驱动移植)