在最小系统中,只关注计算机的内存(Memory)与中央处理器(Central Processing Unit,CPU)。
计算机简化模型
扫描上方二维码可观看知识点讲解视频
内存是一个临时存储设备,是计算机中重要的部件之一,计算机中所有程序的运行都是在内存中进行的。内存也被称为内存储器或主存储器,用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来。
从物理上来说,内存是由一组动态随机存储器(DRAM)芯片组成的;从逻辑上来说,内存是一个线性字节数组,每个字节都有其唯一的地址(数组索引),这些地址从0开始。
系统的内存大小受限于地址编码的长度,例如,某系统的内存地址长度只有2位时,内存地址就只有4个,即00、01、10、11,因此这个系统的最大内存为4B;内存地址长度有4位时,系统的最大内存为16B(如图3-1所示);内存地址长度有10位时,系统的内存最大可以有1KB;而地址空间长度有32位时,系统的最大内存可以有4GB。一般来说,如果系统的内存地址长度有 N 位,那么系统的内存最大就有2 N 字节。
图3-1 地址长度为4位的内存
中央处理器作为计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元。在计算机体系结构中,CPU是对计算机的所有硬件资源(如存储器、输入/输出单元)进行控制调配、执行通用运算的核心硬件单元。计算机系统中所有软件层的操作,最终都将通过指令集映射为CPU的操作。
本章只关注CPU中的两个部件:寄存器(Register)与算术逻辑部件(Arithmetic Logical Unit,ALU)。
· 寄存器:寄存器是CPU内部的存储部件,其存储容量有限,但存储速度非常快,可用来暂存指令、数据和位址。CPU中寄存器的存储位数不像内存一样限定为8位,可以扩展为16位、32位或64位。同时由于寄存器的个数有限(一般是十余个),因此不采用地址编码,而是给每个寄存器起个名字。
· 算术逻辑部件:算术逻辑部件是指能实现多组算术运算与逻辑运算的组合逻辑电路,是中央处理器中的重要组成部分。算术逻辑部件主要是进行二位元算术运算,如加法、减法、乘法等。
图3-2是包含CPU与内存的一个最小系统示例。其中CPU中有四个8位的寄存器(其名字分别为R0、R1、R2、R3),剩余部分用“其他部分”来表示,内存地址宽度为4位,编码为0000~1111,即共有16字节的内存。内存与CPU之间存在一个数据传送的通道,称为总线,同样,在CPU内部也存在传输数据的内部总线,为了简单起见,图中并没有标出这些总线。
图3-2 最小系统示例
假设要在最小系统上执行的代码如下:
首先来看第一行代码,它在C语言中的意义是定义了一个整型变量i,并赋值1。但在执行层面上,计算机在执行这行代码时,所做的操作是在内存中分配一个地址,用于存储该整数,在以后对变量i进行操作时,就是对内存地址中的值进行操作,例如在赋值1的时候,就是将内存地址0000中的数据修改为1。这时最小系统的状态如图3-3所示。
图3-3 执行“int i=1;”语句后的最小系统状态
从执行过程可以看出,定义变量实际上涉及内存分配操作,如果内存已经使用完,那么该操作就无法完成,进而导致程序运行时出错。因此,在定义占用空间特别大的数据类型时,需要特别小心,例如,定义一个大型的整数数组int arr[1000000]在语法上是完全合法的,因此可以顺利编译通过,但在运行时则有可能因为无法分配内存而导致出错。
试一试:在你的计算机上尝试调整数组大小,看看数组多大时运行会出错。
最小系统继续执行“int j=2;”和“int k;”这两个变量定义语句,执行后最小系统状态如图3-4所示。
图3-4 执行“int j=2;”和“int k;”语句后的最小系统状态
在这个执行过程中,有以下两点需要注意。
· 为了简单起见,我们假设三个变量在内存中是连续的,但这并不是强制规定。换而言之,在执行代码时,只需要保证每个变量有一个对应的内存地址即可。
· 变量k没有赋初值,默认为0。这是因为内存是易失性存储器,在断电重启后所有数据都会清零,有些编译器也会为未赋初值的整数变量赋默认值0,但由于变量k所分配的内存空间有可能是回收的空间,因此如果不赋初值,变量k有可能是其他值,因此最好都赋初值0。
对于代码“k=i+j”而言,由于涉及计算,因此CPU需要参与其中。其执行步骤分为以下四步。
1)将内存地址0000中的值(也就是i)送往CPU的某个寄存器中,假设为R0,此时最小系统的状态如图3-5所示。
图3-5 执行“k=i+j;”语句的第一步后的最小系统状态
2)将内存地址0001中的值(也就是j)送往CPU中的另一个寄存器中,假设为R1,此时最小系统的状态如图3-6所示。
图3-6 执行“k=i+j;”语句的第二步后的最小系统状态
3)CPU中的ALU执行加法指令,将两个寄存器中的值相加,将结果放在第二个寄存器中,即R1中存储了两个数的和,此时最小系统的状态如图3-7所示。
图3-7 执行“k=i+j;”语句的第三步后的最小系统状态
4)将R1中的值传送至内存地址0010中,代码执行完成,最小系统的状态如图3-8所示。
图3-8 执行“k=i+j;”语句的第四步后的最小系统状态
下面举一个稍微复杂的例子,代码如下。
第一行代码定义了一个有两个元素的整数数组,其名称为arr,在分配内存时,会分配两个整数空间,由于它们在一个数组内,因此其地址肯定是连续的,这时只需要记录arr的地址编号。假设arr分配的内存地址是0010,则0010中保存了arr[0]的值(即1),0011中保存了arr[1]的值(即2)。
第二行代码定义了一个整数变量,过程与上面相同,假设为k分配的地址是0000,则在分配好后,最小系统的状态如图3-9所示。
图3-9 为变量分配内存后的最小系统状态
下面开始执行第三行代码,这一行代码的功能是将arr[1]与k相加,但对于第一个操作数arr[1],由于在C语言中,[]实际上是一个地址计算操作,即将arr的值(数组首地址)加上[]中的数字,形成新的地址,再去取这个地址中的值,因此第三行代码的执行分为三个阶段。第一个阶段是计算arr[1]在内存中的地址,获得arr[1]的值(第1~4步);第二个阶段是将arr[1]与k相加(第5~6步);第三个阶段是计算arr[0]在内存中的地址,然后将第二阶段获得的结果保存在这个地址中(第7~10步)。具体的描述如下。
1)将arr的值传送到某个寄存器,假设为R0。
2)将[]中的数值1传送到另一个寄存器中,假设为R1。
3)CPU中的ALU执行加法指令,将两个寄存器的值相加,将结果放在R1寄存器中。
4)将R1寄存器的值与读内存指令传送到内存管理单元,将此值的低四位作为内存地址,读取此地址中的数值,并传送到寄存器R2中。第一阶段完成后,最小系统的状态如图3-10所示。
图3-10 第一阶段完成后的最小系统状态
5)将内存地址0000(即变量k)的值传送到寄存器R1中(R1寄存器中之前保存的是arr[1]的地址,已经使用完毕,所以可以覆盖)。
6)CPU中的ALU执行加法指令,将R1与R2两个寄存器的值相加(即计算arr[1]+k的结果),将结果放在R2寄存器中;此时最小系统的状态如图3-11所示。
图3-11 第二阶段完成后的最小系统状态
7)将arr的值传送到寄存器R0中(由于R0中保存的就是arr的值,如果系统足够“聪明”,可以进行优化,省略这一步)。
8)将[]中的数值0传送到寄存器R1中。
9)CPU中的ALU执行加法运算,得到结果并将其保存在R0中(由于是加0操作,如果系统足够“聪明”,可以进行优化,直接将第7~9步简化成一步:直接将arr值传送到寄存器R0中)。
10)将R2(写入内存的内容)与R0(写入内存的地址)的值与写内存命令发送给内存管理单元,在R0的低四位地址(0010,即arr[0])中写入R2的值,至此代码执行完成。这时最小系统的状态如图3-12所示。
图3-12 第三阶段完成后的最小系统状态