目录
1、程序在计算机中到底是如何运行的?
程序是保存在硬盘中的,要载入内存才能运行,CPU也被设计为只能从内存中读取数据和指令。
对于CPU来说,内存仅仅是一个存放指令和数据的地方,并不能在内存中完成计算功能,例如要计算 a = b + c,必须将 a、b、c 都读取到CPU内部才能进行加法运算。为了了解具体的运算过程,我们不妨先来看一下CPU的结构。
CPU是一个复杂的计算机部件,它内部又包含很多小零件,如下图所示:
运算单元
CPU的大脑,负责加减乘除、比较、位移等运算工作,每种运算都有对应的电路支持,速度很快。
寄存器(Register)
CPU内部非常小、非常快速的存储部件,它的容量很有限,对于32位的CPU,每个寄存器一般能存储32位(4个字节)的数据,对于64位的CPU,每个寄存器一般能存储64位(8个字节)的数据。为了完成各种复杂的功能,现代CPU都内置了几十个甚至上百个的寄存器,嵌入式系统功能单一,寄存器数量较少。
我们经常听说多少位的CPU,指的就是寄存器的的位数。现在个人电脑使用的CPU已经进入了64位时代,例如 Intel 的 Core i3、i5、i7 等。
在CPU内部为什么又要设置缓存呢?虽然内存的读取速度已经很快了,但是和CPU比起来,还是有很大差距的,不是一个数量级的,如果每次都从内存中读取数据,会严重拖慢CPU的运行速度,CPU经常处于等待状态,无事可做。在CPU内部设置一个缓存,可以将使用频繁的数据暂时读取到缓存,需要同一地址上的数据时,就不用大老远地再去访问内存,直接从缓存中读取即可。
大家在购买CPU时,也会经常关心缓存容量,例如 Intel Core i7 3770K 的三级缓存为 8MB,二级缓存为 256KB,一级缓存为 32KB。容量越大,CPU越强悍。
CPU指令
要想让CPU工作,必须借助特定的指令,例如 add 用于加法运算,sub 用于除法运算,cmp 用于比较两个数的大小,这称为CPU的指令集(Instruction Set)。我们的C语言代码最终也会编译成一条一条的CPU指令。不同型号的CPU支持的指令集会有所差异,但绝大部分是相同的。
我们以C语言中的加法为例来演示CPU指令的使用。假设有下面的C语言代码:
int a = 0X14, b = 0XAE, c;
c = a + b;
在VS2010 Debug模式下生成的CPU指令为:
mov ptr[a], 0X14
mov ptr[b], 0XAE
mov eax, ptr[a]
add eax, ptr[b]
mov ptr[c], eax
mov 和 add 都是CPU指令:
1) mov 用来将一个数值移动到一个存储位置。这个数值可以是一个常数,也可以在内存或者寄存器上;这个存储位置可以是寄存器或者内存。
第一条指令中,ptr[a]
表示变量 a 的地址,0X14
是一个数值,mov ptr[a], 0X14
表示把数值 0X14 移动到 ptr[a] 指向的内存,也就是给变量 a 赋值。第二条指令与此类似。
第三条指令中,eax
是寄存器的名字,该寄存器常用在加法运算中,用来保存某个加数或运算结果,mov eax, ptr[a]
表示把变量 a 的值移动到寄存器 eax 中。
第五条指令表示把寄存器 eax 的值移动到变量 c 中,此时 exa 中的值为 a、b 相加的和。
2) add 用来将两个数值相加,这两个数值可以在寄存器或者内存中,add 会将相加的结果放在第一个数所在的位置。第四条指令add eax, ptr[b]
表示把 eax 和 ptr[b] 中的数值相加,并把结果放在 eax 中。
2、虚拟内存
在C语言中,指针变量的值就是一个内存地址,&
运算符的作用也是取变量的内存地址,请看下面的代码:
#include <stdio.h>
#include <stdlib.h>
int a = 1, b = 255;
int main(){
int *pa = &a;
printf("pa = %#X, &b = %#X\n", pa, &b);
system("pause");
return 0;
}
代码中的 a、b 是全局变量,它们的内存地址在链接时就已经决定了,以后再也不能改变,该程序无论在何时运行,结果都是一样的。
那么问题来了,如果物理内存中的这两个地址被其他程序占用了怎么办,我们的程序岂不是无法运行了?
幸运的是,这些内存地址都是假的,不是真实的物理内存地址,而是虚拟地址。虚拟地址通过CPU的转换才能对应到物理地址,而且每次程序运行时,操作系统都会重新安排虚拟地址和物理地址的对应关系,哪一段物理内存空闲就使用哪一段。如下图所示:
例如,上面代码中变量 a 的地址是 0X402000,第一次运行时它对应的物理内存地址可能是 0X12ED90AA,第二次运行时可能又对应 0XED90,而我们的程序不需要关心这些,这些繁杂的内存管理工作交给操作系统处理即可。
中间层思想
在计算机中,为了让操作更加直观、易于理解、增强用户体验,开发者经常会使用一件法宝——增加中间层,即使用一种间接的方式来屏蔽复杂的底层细节,只给用户提供简单的接口。虚拟地址是使用中间层的一个典型例子。
实际上,计算机的整个发展过程就是不断引入新的中间层:
- 计算机的早期,程序都是直接运行在硬件之上,自己负责硬件的管理工作;程序员也使用二进制进行编程,需要处理各种边界条件和安全问题。
- 后来人们不能忍受了,于是开发出了操作系统,让它来管理各种硬件,同时发明了汇编语言,减轻程序员的负担。
- 随着软件规模的不断增大,使用汇编语言编程开始变得捉襟见肘,不仅学习成本高,开发效率也很低,于是C语言诞生了。C语言编译器先将C代码翻译为汇编代码,再由汇编器将汇编代码翻译成机器指令。
- 随着计算机的发展,硬件越来越强大,软件越来越复杂,人们又不满足于使用C语言了,于是 C++、Java、C#、PHP 等现代化的编程语言诞生了。
3、虚拟地址空间以及编译模式
所谓虚拟地址空间,就是程序可以使用的虚拟地址的有效范围。虚拟地址和物理地址的映射关系由操作系统决定,相应地,虚拟地址空间的大小也由