计算机原理-内存(四)

前面讲了些CPU及CPU与其他部件之间的交互等,我们也知道,对于计算机来说,CPU跟内存是衡量性能的很重要的指标,同时也跟编程性能有莫大关系,所以现在就专门来看看内存里的一些道道~

内存组成

不多说,先上图,好对内存有个直观的印象

图中内存大小为128M,其他是它是由多个内存颗粒组成,而并非单个颗粒就是128M,内存颗粒学名 DRAM芯片。一般的内存都是由多个DRAM芯片组成,进行字长及容量的扩展。比如128M,32位内存,可能是有 8个 16M*4位的单个DRAM芯片组成!这样做应该是工艺成本的制约。篇幅所限,DRAM芯片张啥样及如何进行字长及容量的扩展在其他篇幅中再介绍!

内存地址

字长

计算机在同一时间内处理的一组二进制数称为一个计算机的“字”,而这组二进制数的位数就是“字长”。通常称处理字长为8位数据的CPU叫8位CPU,32位CPU就是在同一时间内处理字长为32位的二进制数据。 所以这里的字并不是我们理解的双字节(Word)而是和硬件相关的一个概念。一般来说计算机的数据线的位数和字长是相同的。这样从内存获取数据后,只需要一次就能把数据全部传送给CPU。

地址总线

前面我们已经介绍过地址总线的功能。地址总线的数量决定了他最大的寻址范围。就目前来说一般地址总线先字长相同。比如32位计算机拥有32为数据线和32为地线,最大寻址范围是4G(0x00000000 ~ 0xFFFFFFFF)。当然也有例外,Intel的8086是16为字长的CPU,采用了16位数据线和20位数据线。

内存编址

内存的大小和它芯片扩展方式有关。比如我们内存模块是采用 16M*8bit的内存颗粒,那么我们使用4个颗粒进行位扩展,成为16M*32bit,使用4个颗粒进行字容量扩展变为64M*32bit。那么我们内存模块使用了8个内存颗粒,实际大小是128MB。

我们需要对这个128M的内存进行编址以便CPU能够使用它,通常我们多种编址方式:

  1. 按字编址:    对于这个128M内存来说,它的寻址范围是32M,而每个内存地址可以存储32bit数据。
  2. 按半字编址:对于这个128M内存来说,它的寻址范围是64M,而每个内存地址可以存储16bit数据。
  3. 按字节编址:对于这个128M内存来说,它的寻址范围是128M,而每个内存地址可以存储8bit数据。

目前的计算机,主要都是采用按字节编址的方式这也是为什么对于32位计算机来说,能使用的最多容量的内存为4GB如果我们按字编地址,能使用的最大内存容量就是16GB了。

内存数据存放

前面我们知道了,内存是按字节编址,每个地址的存储单元可以存放8bit的数据。我们也知道CPU通过内存地址获取一条指令和数据,而他们存在存储单元中。现在就有一个问题。我们的数据和指令不可能刚好是8bit,如果小于8位,没什么问题,顶多是浪费几位(或许按字节编址是为了节省内存空间考虑)。但是当数据或指令的长度大于8bit呢?因为这种情况是很容易出现的,比如一个16bit的Int数据在内存是如何存储的呢?

其实一个简单的办法就是使用多个存储单元来存放数据或指令。比如Int16使用2个内存单元,而Int32使用4个内存单元。当读取数据时,一次读取多个内存单元。于是这里又出现2个问题:

  1. 多个存储单元存储的顺序?
  2. 如何确定要读几个内存单元?
  3. 如何快速寻址?
大端和小端存储
  1. Little-Endian 就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
  2. Big-Endian 就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

需要说明的是,计算机采用大端还是小端存储是CPU来决定的, 我们常用的X86体系的CPU采用小端,一下ARM体系的CPU也是用小端,但有一些CPU却采用大端比如PowerPC、Sun。

存储顺序解决了,那么再来看看如何确定大小,而这也是cpu决定的所以我们先来看看cpu指令。

cpu指令

首先我们来看看CPU指令的格式,我们知道CPU质量主要就是告诉CPU做什么事情,所以一条CPU指令一般包含操作码(OP)和操作

操作码字段
   地址码字段

根据一条指令中有几个操作数地址,可将该指令称为几操作数指令或几地址指令。

 操作码
 A1
 A2
 A3

三地址指令: (A1) OP (A2) –> A3

 操作码
 A1
 A2

二地址指令: (A1) OP (A2) –> A1

 操作码
  A1

一地址指令: (AC) OP (A) –> AC   

 操作码

零地址指令

A1为被操作数地址,也称源操作数地址; A2为操作数地址,也称终点操作数地址; A3为存放结果的地址。 同样,A1,A2,A3以是内存中的单元地址,也可以是运算器中通用寄存器的地址。所以就有一个寻址的问题。关于指令寻址后面会介绍。

CPU指令设计是十分复杂的,因为在计算机中都是0和1保存,那计算机如何区分一条指令中的操作数和操作码呢?如何保证指令不会重复呢?这个不是我们讨论的重点,有兴趣的可以看看计算机体系结构的书,里面都会有介绍。从上图来看我们知道CPU的指令长度是变长的。所以CPU并不能确定一条指令需要占用几个内存单元,那么CPU又是如何确定一条指令是否读取完了呢?

3.1.2.2 指令的获取

现在的CPU多数采用可变长指令系统。关键是指令的第一字节。 当CPU读指令时,并不是一下把整个指令读近来,而是先读入指令的第一个字节。指令译码器分析这个字节,就知道这是几字节指令。接着顺序读入后面的字节。每读一个字节,程序计数器PC加一。整个指令读入后,PC就指向下一指令(等于为读下一指令做好了准备)。

Sample1:

view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. MOV AL,00  机器码是1011 0000 0000 0000 

机器码是16位在内存中占用2个字节:

【00000000】 <- 0x0002

【10110000】 <- 0x0001

比如上面这条MOV汇编指令,把立即数00存入AL寄存器。而CPU获取指令过程如下:

  1. 从程序计数器获取当前指令的地址0x0001。
  2. 存储控制器从0x0001中读出整个字节,发送给CPU。PC+1 = 0X0002.
  3. CPU识别出【10110000】表示:操作是MOV AL,并且A2是一个立即数长度为一个字节,所以整个指令的字长为2字节。
  4. CPU从地址0x0002取出指令的最后一个字节
  5. CPU将立即数00存入AL寄存器。

这里的疑问应该是在第3步,CPU是怎么知道是MOV AL 立即数的操作呢?我们在看下面一个列子。

Sample2:

view plaincopyprint?在CODE上查看代码片派生到我的代码片

  1. MOV AL,[0000] 机器码是1010 0000 0000 0000 0000 0000 

这里同样是一条MOV的汇编指令,整个指令需要占用3个字节。

【00000000】 <-0x0003

【00000000】 <- 0x0002

【10100000】 <- 0x0001

我们可以比较一下2条指令第一个字节的区别,发现这里的MOV  AL是1010 0000,而不是Sample1中的1011 000。CPU读取了第一个字节后识别出,操作是MOV AL [D16],表示是一个寄存器间接寻址,A3操作是存放的是一个16位就是地址偏移量(为什么是16位,后面文章会介绍),CPU就判定这条指令长度3个字节。于是从内存0x0002~0x0003读出指令的后2个字节,进行寻址找到真正的数据内存地址,再次通过CPU读入,并完成操作。

从上面我们可以看出一个指令会根据不同的寻址格式,有不同的机器码与之对应。而每个机器码对应的指令的长度都是在CPU设计时就规定好了。8086采用变长指令,指令长度是1-6个字节,后面可以添加8位或16位的偏移量或立即数。 下面的指令格式相比上面2个就更加复杂。

  • 第一个字节的高6位是操作码,W表示传说的数据是字(W=1)还是字节(W=0),D表示数据传输方向D=0数据从寄存器传出,D=1数据传入寄存器。
  • 第二个字节中REG表示寄存器号,3位可以表示8种寄存器,根据第一字节的W,可以表示是8位还是16位寄存器。表3-1中列出了8086寄存器编码表
  • 第二个字节中的MOD和R/M指定了操作数的寻址方式,表3-2列出了8086的编码

这里没必要也无法更详细介绍CPU指令的,只需要知道,CPU指令中已经定义了指令的长度,不会出现混乱读取内存单元的现象。有兴趣的可以查看引用中的连接。

那么我们如何进行快速寻址呢?

内存对齐

存储器一个cell是8bit,进行位扩展使他和字长还有数据线位数是相同,那么一次就能传送CPU可以处理最多的数据。而前面我们说过目前是按字节编址可能是因为一个cell是8bit,所以一次内存操作读取的数据就是和字长相同。

也正是因为和存储器扩展有关(参考1.2.1的图),每个DRAM位扩展芯片使用相同RAS。如果需要跨行访问,那么需要传递2次RAS。所以以32位CPU为例,CPU只能对0,4,8,16这样的地址进行寻址。而很多32位CPU禁掉了地址线中的低2位A0,A1,这样他们的地址必须是4的倍数,否则会发送错误。

如上图,当计算机数据线为32位时,一次读入4个地址范围的数据。当一个int变量存放在0-3的地址中时,CPU一次就内存操作就可以取得int变量的值。但是如果int变量存放在1-4的地址中呢? 根据上面条件2的解释,这个时候CPU需要进行2次内存访问,第一次读取0-4的数据,并且只保存1-3的内容,第二次访问读取4-7的数据并且只保存4的数据,然后将1-4组合起来。如下图:

所以内存对齐不但可以解决不同CPU的兼容性问题,还能减少内存访问次数,提高效率。

如何内存对齐

http://blog.csdn.net/cc_net/article/details/2908600

计算机原理(一)

大学毕业作也快两年了,回顾下,其实大学很多课程都是非常基础,对IT工作者而言其实是非常有作用的,特别是当要学得更加深入的时候,再去回顾下基础的东西是非常有必要的,所以就记录下回顾下计算机原理 的心得体会!

计算机之父

计算机发展史也不必刻意去多写了,我们都说冯诺依曼是计算机之父,其实图灵大大才是教父!

计算机组成

我们的计算机核心就是 :存储程式,然后执行!

下图就是一个基本结构流程:输入,存储,执行,输出。

对我们写代码的来说,比较重要的是存储与执行,因为这涉及到程序的性能如IO,计算速度等,我们最关心的就是CPU与内存了!程序数据是存储在硬盘里面,而如果要执行,一般来说我们需要把程序启动,把执行指令加载到内存中(数据交互更快)缓存着,然后CPU执行内存中的指令运算得出结果输出!

指令集

我们都知道,计算机是只能识别0跟1的(不通电OR通电),但是如果只是使用 0跟1进行编程,OMG,我想我们都要晕了,所以就出现了指令,我们装机买CPU的时候,都会听到指令集这么个说法,指令集跟牛逼啥的,其实就对应了我们这里说的0和1的一些组合,每个指令集可以然CPU执行一个(或者一组【牛逼的指令集】)操作,越牛逼的指令集在相同硬件条件下,能够让计算机速度更快!

汇编语言

二进制指令集虽然可以让CPU识别并执行,但是作为人类,还是太难懂了,所以就出现汇编语言,他用符号来映射具体的指令集,可读性进一步提升!那么把汇编语言转化为指令集,就需要一个辅助工具,汇编器

高级语言

计算机技术要普及,汇编语言显然还是太晦涩难懂,并且跟具体的硬件还有太多耦合,不同CPU可能只能识别不同的指令集,这样就需要写不同的汇编程序去实现,为了生产力的发展,于是不断出现各种高级语言,如C,C++,JAVA,C#…,从前面我们知道,高级语言也是不能被计算机识别的,所以我们就要编译器将高级语言转换成汇编程序,不同CPU硬件,我们就用不同编译器进行转换成不同的汇编程序,而不需要写不同的程序,编译器帮我们把硬件的差异屏蔽!

高级语言之间的差异

C,C++这种语言,其实是可以跨平台的,但是所写的程序代码,在不同平台(硬件)就需要不同编译器从新编译过才能执行。那么是不是有语言是可以做到写一次代码,编译一次代码就可以到处运行了呢!答案是肯定的,根据前面的一些讲解,你应该猜到了,这中间肯定还要加一层东西吧?答案是肯定的,JVM 虚拟机就是这么个中间的东西,JAVA语言写的代码,是先编译成中间代码的,然后由JVM负责解释执行。因为是在运行的时候解释执行的,所以性能上会有所下降,当然这提高了生产力,所以性能下降算个P啊(当然这只是针对大多数普通程序来说)!当然还有更加牛逼的脚本语言,都不需要编译成中间代码,直接依靠解释器就执行起来了,这又叫脚本语言,JS,大蟒蛇…说实在,也不慢啊!

本来想说计算机原理来着,顺便扯了些语言的东西,我们还是回来看CPU的是如何工作的吧:

CPU

有前面的图我们看到,CPU主要是由运算器与控制器组成的,控制器负责指令的,运算器接收指令进行运算处理,如果就是看上去那么简单,那么就太小看CPU了,实际CPU的组成运算比这个复杂多了,我们来看看Intel奔腾处理器的详细组成:

好晕啊,我来来具体分析下组要部件运算器与控制器的组成:

 控制器由程序计数器、指令寄存器、指令译码器、时序产生器和操作控制器组成。它是计算机指挥系统,完成计算机的指挥工作。尽管不同计算机的控制器结构上有很大的区别,当就其基本功能而言,具有如下功能:

    • 取指令 从内存中取出当前指令,并生成下一条指令在内存中的地址。 
    • 分析指令 指令取出后,控制器还必须具有两种分析的功能。一是对指令进行译码或测试,并产生相应的操作控制信号,以便启动规定的动作。比如一次内存读/写操作,一个算术逻辑运算操作,或一个输入/输出操作。二是分析参与这次操作的各操作数所在的地址,即操作数的有效地址。 
    • 执行指令 控制器还必须具备执行指令的功能,指挥并控制CPU、内存和输入/输出设备之间数据流动的方向,完成指令的各种功能。 
    • 发出各种微操作命令 在指令执行过程中,要求控制器按照操作性质要求,发出各种相应的微操作命令,使相应的部件完成各种功能。 
    • 改变指令的执行顺序 在编程过程中,分支结构、循环结构等非顺序结构的引用可以大大提供编程的工作效率。控制器的这种功能可以根据指令执行后的结果,确定下一步是继续按原程序的顺序执行,还是改变原来的执行顺序,而转去执行其它的指令。 
    • 控制程序和数据的输入与结果输出 这实际也是一个人机对话的设计,通过编写程序,在适当的时候输入数据和输出程序的结果。 
    • 对异常情况和某些请求的处理 当计算机正在执行程序的过程中,发生了一些异常的情况,例如除法出错、溢出中断、键盘中断等。

运算器的组成和功能: 运算器由算术逻辑单元(ALU)、累加寄存器、数据缓冲寄存器和状态条件寄存器组成,它是数据加工处理部件,完成计算机的各种算术和逻辑运算。相对控制器而言,运算器接受控制器的命令而进行动作,即运算器所进行的全部操作都是由控制器发出的控制信号来指挥的,所以它是执行部件。运算器有两个主要功能:

  • 执行所有的算术运算,如加、减、乘、除等基本运算及附加运算;
  • 执行所有的逻辑运算,并进行逻辑测试,如与、或、非、零值测试或两个值的比较等。

总结起来就是如下的一个流程:

光看CPU貌似我们已经了解了基本原理,其实最需要了解的其实是各个模块之间的交互,因为外部之间的IO是最容易产生瓶颈与疑惑的,所以接下来我们得学习下 CPU与内存及其他设备的交互等!