程序在计算机底层是如何运行的?
程序通常是我们所编写的代码块,这些代码块主要存储在磁盘空间内部,当需要运作的时候首先需要将其加载到内存当中,然后翻译成一条条机器指令,交给cpu去逐条执行。
假设我们需要执行一段代码实现0+1+2+...+100的求和。大致流程为:
public int count(){ int sum=0; for(int i=0;i<=100;i++){ sum += i; } return sum; } 复制代码
1.cpu处于空闲状态,发送指令将程序加载到内存中。
2.内存中程序代码被翻译为一行行指令,并且将开头指令的地址存储到程序计数器中。
3.控制器将每行机器指令读入,然后交给alu(逻辑运算单元)执行。
4.执行过程中的临时变量(例如1,2,3这些)会被存放在通用寄存器中,循环中的一些累加变量(例如for(int i =1;i<=100;i++)里面的i)会被存储到累加寄存器中,每一次for循环的结尾都会执行一次jump指令重新进入for循环的开头,直到满足相关条件才会跳出for循环。
5.最后计算出来的结果会被存放到通用寄存器中。
如果我们在for循环的里面还需要执行一些比较类型的操作,那么还可能会使用到一些标志寄存器。(这是因为计算机底层对数据的比较是通过做差之后计算得出,得出结果是+ 或者 - 通常会被存放在标志寄存器中)
如果在for循环里面涉及到其它相关的函数调用,则会触发一条叫做call的指令,和return的指令。(call负责将被调用函数的地址压入栈中,同时更新程序计数器地址,return指令则是在函数执行结束之后将原先对函数发起调用的主函数地址压入栈中同时更新程序计数器的地址)。
编译
将高级语言转换为计算机可以识别的机器语言,这部分任务主要教给编译器去做,不同的语言的编译过程不同。例如Java,需要先转换为class文件然后让JVM去将class文件翻译为汇编指令,再转换为机器代码。有些语言例如C++,则是直接将C++的代码转换为汇编指令,再转换为机器代码。
编译执行和解释执行
简单理解编译执行就是一次性将代码全部翻译为本地代码,执行效率比较高,具体语言的代表有:Java,C++。
解释执行通常是执行一行代码则将对应的代码翻译为本地代码,虽然编译的成本低,但是运行过程中的成本较高。
内存与磁盘
在程序执行的过程中,难免会需要涉及到内存和磁盘之间的拷贝问题。
这里有两个概念我们需要了解下:
磁盘缓存
这是一种早期操作系统在设计上的思路,当某些数据从磁盘加载出来之后会被存放到内存中,当二次访问的时候就可以直接从内存中提取,而不是从磁盘中加载
虚拟内存
将部分磁盘用于存储内存数据,通常可以用于处于等待cpu状态中的内存数据,借助这项技术,我们可以尝试将一块128gb的磁盘中分出32gb的空间给计算机存储内存数据。(注意企业一般不会用这种技术,因为它非常卡顿)
内存和磁盘之间的数据拷贝常用到分段式和分页式的两种模式进行管理,由于虚拟内存在实际应用中数据拷贝存在一定的性能瓶颈,所以很容易导致操作系统出现卡顿的情况,因此不推荐使用。
磁盘的组成部分
磁盘的内部主要划分为例盘道和簇,各个磁道上又划分为了多个区间,它们统称为扇区。一块磁盘可以想象为一个圆形,然后将这个圆划分为多个同心圆,不同同心圆的间隔部分就是磁道。
存储的基本单位我们称之为簇,通常1簇可以是1kb也可以是0.5kb,在磁盘中一个扇区内可以有多个簇组成。 一份文件的实际大小为簇的成倍,一份文件不可能占用非成倍大小的簇,这是因为在数据删除的时候会按照簇为基本单位进行移除,如果文件的大小占用了0.5簇,剩余0.5簇用于存储其他文件数据,那么在删除过程中就会影响到其他数据的完整性 。
如何压缩我们的文件?
rle算法
例如AAAABBCC会被压缩为A4B2C2。但是这种算法存在不足点:例如ABCD会被压缩为A1B1C1D1,反而体积更大了。
哈夫曼编码算法
比较成熟且高效。
如何实现随机数
线性同余算法
公式:Ri+1=((Ri * a )+ b) % c,abc分别代表了不同的随机种子,Ri表示第i个随机数。如果随机种子是固定的话,产生的只能是周期性随机数,如果希望有完全随机的话,可以将任意种子变成时间戳即可。
最后我在文末贴上一张思维导图,希望对有需要的读者能有帮助: