一、CPU的组成部分
1.1、计算机五大部件
说起计算机五大件,笔者想起在大学的时候,老师问我们计算机硬件的五大部件有什么,然后听到有同学回答说鼠标,键盘,显示器什么的,把老师逗乐了,说我们计算机系的还这么小白。其实也正常,谁不是从小白慢慢成为技术大牛呢...
言归正传,计算机硬件的五大件分为:输入设备,输出设备,运算器,控制器,储存器。输入,输出设备主要就是鼠标,键盘,显示器还有打印机等等。储存器主要分为内存和磁盘,内存是储存程序运行时的指令和数据。本章主要围绕着控制器和运算器来讲,因为它们都是CPU的组成部分。
按实际硬件分,CPU内部是由寄存器,控制器,运算器,时钟四个部分组成:
寄存器:暂存指令和数据,可以看作是一级内存;
控制器:负责把内存上的指令,数据读入寄存器;
运算器:负责运算从内存读入寄存器的数据;
时钟:负责发出CPU开始计时的时钟信号;
一个CPU可以有多个寄存器。
1.2、寄存器的分类和功能
x86系列32位CPU的寄存器主要分为累加寄存器,标志寄存器,程序计数器,基址寄存器,栈指针寄存器,通用寄存器等;累加寄存器,标志寄存器,程序计数器都只有一个,其他寄存器一般有多个。32位CPU是指寄存器可以存储4个字节。
累加寄存器(eax)(这个是运算器):存储执行运算的数据和运算后的数据;
标志寄存器:存储运算处理后的CPU的状态;
程序计数器(epc)(这个是控制器)的作用是存储下一条指令所在的内存地址;比如程序需要实现123和456相加,并输出结果到显示器。程序开始运行时,程序计数器储存程序初始的内存地址比如是0100,CPU每执行一个指令,程序计数器存储的内存地址就会加1(程序流程分为顺序执行,跳转,循环),这里只拿顺序执行,如下图:
基址寄存器(ebp):存储数据存储领域基点的内存地址;
栈指针寄存器(esp):存储栈中最高位数据的内存地址;
通用寄存器:存储任意数据;
二、程序在CPU的执行过程
程序是指令和数据的集合,不管是C、PHP、GO等其他语言写的程序,到最后都是编译成机器语言,就是一系列的指令,程序运行时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能理解的二进制指令(本地代码)从上往下逐步的执行。如今的编程从面向过程,到面向对象,这个发展无非是越来越接近人类,从难于理解的机器码,到用英文对应机器码的汇编,再发展到面向对象编程,都是为了增加代码的可读性,可维护性。