《程序是怎么跑起来的》读书笔记(1) -- 关于程序怎么在CPU里旅游

预备知识

1.程序是指令和数据的结合体 C语言的printf("你好")
printf是指令,你好是数据
2.cpu能够直接识别和执行的只有机器语言,什么语言编写的程序都换转换成二进制的0101的机器语言的可执行文件
3.硬盘和磁盘上的程序只有被复制到内存中才能被运行,也就是程序一定运行在内存中

程序运行流程:
程序员编写出的程序-->程序会被编译成机器语言的EXE文件-->运行时,内存中会对EXE文件生成副本-->cpu去解释执行程序

关于CPU:

cpu主要有四个部分组成,各部分由电流信号联通:
* 双核cpu代表一个集成电路里有两个cpu芯片
1.控制器 把内存上的指令、数据等读入寄存器,根据执行结果去控制计算机下一步操作
2.运算器 把内存里拿到的数据去做运算
3.时钟 负责发出cpu开始记时的时钟信号,即使我们常说的主频,2GHz就是时钟信号的频率为2GHz(1GHz=10亿次/秒),频率越高,cpu运行速度更快
4.寄存器 这个是最重要的一个概念,寄存器用来暂存、数据等处理对象,可以将其看作内存的一种。一个CPU里通常由20-100个寄存器,不同的寄存器类别会有差异


关于内存:

通常所说的内存是计算机的主存储器,主存。主存通过控制芯片和cpu相连,主要负责存储指令和数据
主存通常使用DRAM芯片 动态随机存取存储器
主存就是非常多的读写的元素,每个能被读写的字节都会有地址编号。cpu可以直接通过地址访问主存的指令和数据,也可以写入数据。
主存中指令和数据会随着计算机的关机而自动清除

从cpu的角度看程序被解释执行:

控制器根据时钟信号从内存中读取指令和数据,然后解释后交给运算器进行运算 控制器根据结果控制计算机
(这里所谓的控制计算机是指比如数据运算以外的处理,比如内存和磁盘的输入输出、键盘鼠标输入,显示器如何输出等等)


cpu是寄存器的集合体

程序员只需要知道寄存器,因为程序是把寄存器当作对象来描述的
低级语言:机器语言,汇编语言
高级语言:主要指的是类似于人类语言的语法来记述的编程语言的总称,比如java、python、c、c++

汇编:指的是将汇编语言转换成机器语言
编译:指的是将高级语言转换成机器语言,很多语言都会存在编译器
反汇编:将机器语言转化成汇编语言

汇编语言实例:mov eax, dword ptr [ebp-8]

上面进行的操作是把数值从内存复制到eax中,eax是种寄存器
我们可以明白,机器语言级别的程序是通过寄存器来处理的,这也对照了刚才第一句程序员之需要知道寄存器,就可以,仿佛cpu就是寄存器的集合体。

关于寄存器

首先我们要知道cpu不同,寄存器的各种类别的数量也不同,通常说的cpu的好坏也在于这个地方(当然时钟信号决定主频,cpu处理器的个数决定多少个核,这些也都很重要)

cpu中的寄存器的主要种类和功能:

种类 功能 其他理解
累加寄存器 存储执行运算的数据和运算后的数据 只有一个
标志寄存器 存储运算处理后的cpu的状态 重要,只有一个,这个能判断分支循环
程序计数器 存储下一条指令所在内存的地址 只有一个,很重要
指令寄存器 存储指令,cpu内部使用,程序员无法使用该寄存器读写 只有一个,程序员不关心
栈寄存器 存储栈区域的起始地址 只有一个,不关心
基址寄存器 存储数据内存的起始地址 多个
变址寄存器 存储基址寄存器的相对地址 多个
通用寄存器 存储任意数据 多个

寄存器中的程序计数器

从寄存器层面看程序变成指令和数据后如何运行:
程序启动,操作系统把程序往内存里去丢,内存会把指令和数据拿到,然后给他们都分配个地址以便于调用

举个例子:(这个例子简化了,一行程序语言编译成机器语言后会变成多行机器语言,地址离散,即一个数据和指令通常会被存储在多个地址上,但例子简化成一个指令或者一个数据存储在一个地址上)
程序计数器值是读取的程序在内存中的首个指令的地址

cpu程序计数器数值 内存地址 内存地址所存储的内容
0100 0100 指令:将0105地址的数值保存到累加寄存器上
0101 0101 指令:将0106地址的数值保存到通用寄存器上
0102 0102 指令:将累加寄存器和通用寄存器相加
0103 0103 指令:将累加寄存器的值显示在屏幕上
0104 0104 指令:结束程序
0105 数据:123
0106 数据:456

寄存器中的标志寄存器

程序流程其实就三种,顺序执行 条件分支 循环
顺序执行就是利用程序计数器,每执行一个指令,程序计数器加一
但是条件和循环会出现跳转到任意地址,所以会出现新的指令:跳转指令,比如0102的指令说的是如果某某寄存器怎么怎么样,就跳到0105这个地址

这时候标志寄存器就可以使用了,跳转指令执行前,会先调用标志寄存器
对于32位的cpu来说,寄存器有32位,前三位就是做了减法运算去判断大,相同,小三种情况的,其他29位可能判断 溢出(运算结果是否超出寄存器的长度范围)或者奇偶检验等等

程序里的函数是什么情况

首先,函数调用处理也是通过把程序计数器的值设定成函数的存储地址来实现,不同的是,函数调用结束返回后,处理流程需要返回到函数的调用点(就是函数调用指令的下一个地址),不能只返回到入口处,这样,没人知道要调用你

以c语言为例,首先可以这样说,你写了一个函数,这个函数会在内存里有地址,你调用函数的时候,函数地址已知,你可以直接指令说我要调用这个地址,这是可以的。但是如果要返回的时候,我们脑子里也明白你只要返回给我调用的那个那条指令的下一条指令的地址就可以了,但是如果你调用很多次,就不可能去全部记住要给谁,这是根本不可能的,所以call指令和return指令就上场了
函数调用使用call指令,call指令把调用函数后要执行的指令地址存储在栈的主存中,函数执行成功后,return指令会把保存在栈中的地址设定到程序计数器中,这个设计太科学了,服气。。。。

程序一直是在cpu和内存中跑的,内存中程序编译的机器语言会被存在指令区域、数据区域、栈区域等等。返回的目的地的地址就会存在这个栈区域中。


通过地址和索引实现数组

基址寄存器和变址寄存器的使用是在这种情况,一些博客帮助理解
https://www.cnblogs.com/little-YTMM/p/5058354.html
https://www.zhihu.com/question/21641577
首先一些概念,对于内存地址和处理器的交互,有4g=2的32次方内存,你想用处理器访问到每一个地址那就需要一个32位的值作为寻址值,所以32位的cpu一般只能配4g内存
但是64位的处理器可以有36位或者40位的地址总线,这就能支持64g或者1t的内存使用
实际地址=基址寄存器的值+变址寄存器的值(相当于索引)


cpu其实只能做4种指令

数据转送指令:寄存器和内存、内存和内存、寄存器和外围设备之间的数据读写操作
运算指令:累加寄存器执行算术运算、逻辑运算、比较运算、位移运算等
跳转运算:实现条件分支,循环、强制跳转等等
call/return:利用栈内存实现函数的的调用/返回函数调用前的地址

发表评论

电子邮件地址不会被公开。