【计算机组成原理】读书笔记第二期:使用有棱有角的内存

写在开头

  本文继续阅读总结《程序是怎样跑起来的》这本书(作者:矢泽久雄)。上一篇博客介绍了这本书的阅读感受,并对其中的第一章CPU相关的知识进行了总结。详情见:

【计算机组成原理】读书笔记第一期:对程序员来说CPU是什么-CSDN博客

 本文将介绍本书的第四章:熟练使用棱棱角角的内存。主要从物理(硬件)逻辑(软件)角度去分析内存的使用机制。并介绍了使用内存的常见数据结构(数组、栈、队列、链表、二叉查找树)。

内存的结构

  由于处理对象数据是存储在内存和磁盘上的,因此程序必须能自由地使用内存和磁盘。本文将从物理(硬件)逻辑(软件)角度去分析内存的使用机制。

物理(硬件)角度

  内存是由IC(集成电路)组成的电子元件,内存IC中有电源、地址信号、数据信号、控制信号等用于输入输出的大量引脚,通过地址的指定实现数据的读写。总体来讲,内存IC内部有大量可以存储8位数据的地方,通过地址指定这些空间即可进行数据的读写。

内存的分类:

ROM:(Read Only Memory)是一种只能用来读取的内存。

RAM:(Random Access Memory)是可被读取和写入的内存,分为需要经常刷新(refresh)以保存数据的 DRAM(Dynamic RAM),以及不需要刷新电路即能保存数据的 SRAM(Static RAM)。

内存IC的引脚示例如图4-1:

 上图中内存的大小:

地址空间:有A0-A9共10个地址信号引脚,说明可以指定2^10=1024个地址(即0000000000-1111111111)

数据信号:有D0-D7共8个数据信号引脚,表示一次可以输入/输出8位(=1字节)数据。内存在空间中是以字节为单位读写的。

内存大小:1024×1字节=1024B=1KB,即这个内存IC的容量就是1KB

(计算机领域,1K=1024,1k=1000)

  通常情况下,计算机使用的内存IC中会有更多的地址信号引脚,这样就能在一个内存 IC 中存储数十兆字节的数据。因此,只用数个内存 IC,就可以达到GB级别的容量。 数据的读写过程,详见图4-2,a图是写入过程,b图是读取过程。

写入过程:假设要往该内存IC中写入1字节的数据。为了实现该目的,可以给VCC接入+5V,给GND接入 0V 的电源,并使用 A0~A9 的地址信号来指定数据的存储场所,然后再把数据的值输入给 D0~D7 的数据信号, 并把 WR(write = 写入的简写)信号设定成1。执行完这些操作,就可 以在内存IC内部写入数据(图 4-2 (a))了。

读取过程:读出数据时,只需通过 A0~A9 的地址信号指定数据的存储场所, 然后再将 RD(read = 读出的简写)信号设成1即可。执行完这些操 作,指定地址中存储的数据就会被输出到 D0~D7 的数据信号引脚 (图 4-2(b))中。

控制信号:像 WR 和 RD 这样可以让 IC 运行的信号称为控制信号。其中,当 WR 和 RD 同时为0时,写入和读出的操作都无法进行。 

逻辑(软件)角度

  内存的逻辑模型是楼房,从程序员的角度来看,也可以把内存假想成每层都存储着数据的楼房,并不需要过多地关注内存IC的电源和控制信号等。内存为1KB时,表示的是如图 4-3 所示的有1024层的楼房(这里地址的值是从上往下逐渐变大,不过也有与此相反的情况):

数据类型:表示存储的是何种类型的数据。从内存来看,就是占用的内存大小(占有的楼层数)的意思。即使是物理上以1个字节为单位来逐一读写数据的内存,在程序中,通过指定其类型(变量的数据类型等),也能实现以特定字节数为单位来进行读写。 根据程序中所指定的变量的数据类型的 不同,读写的物理内存大小也会随之发生变化,这其实是非常方便的。代码举例如下:

//代码4-1 各种类型的变量
// 定义变量
char a;
short b;
long c;
// 给变量赋值
a = 123;
b = 123;
c = 123;

 这 3 个变量的数据类型分别是表示1字节长度的 char,表示2字节长度的 short,表示4字节长度的 long。虽然同样是数据123,存储时其所占用的内存大小是不一样的。此处假定采用的是将数据低位存储在内存低位地址的低字节序(little endian,intel处理器采用)方式 (图 4-4)。

操作内存的常见数据结构

指针

理解指针的关键在于弄清数据类型的概念。

指针是一种变量,存储着数据的内存地址。通过使用指针,就可以对任意指定地址的数据进行读写。由于Windows 计算机上使用的程序通常都是 32 位(4 字节)的内存地址,指针变量的长度也是 32 位。

指向不同数据类型的指针,如下代码段,char,short,long这些数据类型表示的是从指针存储的地址 中一次能够读写的数据字节数:

//代码4-2 定义指向不同数据类型的指针
char *d; //char 类型的指针 d 的定义
short *e; //short 类型的指针 e 的定义
long *f; //long 类型的指针 f 的定义

指针的数据类型表示一次可以在内存中读写的长度。假设 d,e,f的值都是100。在这种情况下,使用d 时就能够从编号100的地址中读写1个字节的数据,使用e时就是2个字节(100 地址和101地址)的数据,使用f时就是4个字节(100地址~103地址)的数据。 如图4-5:

数组 

 数组是指多个同样数据类型的数据在内存中连续排列的形式。作为数组元素的各个数据会通过连续的编号被区分开来,这个编号称为索引(index)。数组和内存的物理构造是一样的。特别是 1 字节类型的数组,它和内存的物理构造完全一致。

//代码4-3 各种类型的数组定义
char g[100]; //char 类型数组 g 的定义
short h[100]; //short 类型数组 h 的定义
long i[100]; //long 类型数组 i 的定义

  数组的定义中所指定的数据类型,也表示一次能够读写的内存大小。char,short,long分别每次以1,2,4个字节为单位进行内存的读写操作。

 数组是使用内存的基本,其他使用内存的数据结构都以数组为基础。

栈、队列和环形缓冲区

 栈和队列,都可以不通过指定地址和索引来对数组的元素进行读写。需要临时保存计算过程中的数据、连接在计算机上的设备或者输入输出的数据时,都可以通过这些方法来使用内存。在对内存数据进行读写时,栈用的是LIFO(Last Input First Out,后入先出)方式,而队列用的则是FIFO(First Input First Out,先入先出)方式。如果我们在内存中预留出栈和队列所需要的空间,并确定好写入和读出的顺序, 就不用再指定地址和索引了。

入栈(写入数据)和出栈(读取数据)的代码示意如下4-4:

//代码4-4 入栈和出栈,用函数示意。(隐去函数内部实现)
// 往栈中写入数据
Push(123); // 写入 123
Push(456); // 写入 456
Push(789); // 写入 789
// 从栈中读出数据
j = Pop(); // 读出 789
k = Pop(); // 读出 456
l = Pop(); // 读出 123

代码运行过程如下,注意体会数据在栈中的先进后出:

在程序中实现临时保存数据,或需要暂时舍弃当前的数据,随后再原貌还原时,会使用。 

入列(写入数据)和出列(读取数据)的代码示意如下4-5: 

//代码4-5,入列和出列,用函数示意。(隐去函数内部实现)
// 往队列中写入数据
EnQueue(123); // 写入 123
EnQueue(456); // 写入 456
EnQueue(789); // 写入 789
// 从队列中读出数据
m = DeQueue(); // 读出 123
n = DeQueue(); // 读出 456
o = DeQueue(); // 读出 789

代码运行过程如下,注意体会数据在队列中的先进先出:

  队列这一方式也称为排队 ,当我们需要处理通讯中发送的数据时,或由同时运行的多个程序所发送过来的数据时,会用到队列这种数据结构存储数据。

  队列一般是以环状缓冲区(ring buffer)的方式来实现的,假设我们要用有6个元素的数组来实现一个队列。这时可以从数组的起始位置开始有序地存储数据,然后再按照存储时的顺序把数据读出。在数组的末尾写入数据后,后一个数据就会被写入数组的起始位置(此时数据已经被读出所以该位置是空的)。这样,数组的末尾就和开头连接了起来,数据的写入和读出也就循环起来了(图 4-9)。

链表 

链表:在数组的各个元素中,除了数据的值之外,通过为其附带下一个元素的索引,即可实现链表。数据的值和下一个元素的索引组合在 一起,就构成了数组的一个元素。由于链表末尾的元素没有后续的数据,因此就需要用别的值(在这里是-1)来填充(链表示意见图4-10)。

使用链表的好处:通过使用链表,可以更加高效地对数组数据(元素)进行追加和删除处理。

 通过使用链表,可以更加高效地对数组数据(元素)进行追加和删除处理(无需移动数组中元素的内存位置)。 链表中对元素的删除如图4-11,图中p[2]虽然在物理内存中依然存在,但在链表的逻辑中已经被删除了。

 链表的追加示意图见4-12,将追加的元素写在物理上p[2]的位置,逻辑上插入到p[4]之后,p[5]之前。

 如果不使用链表数组,那么中途删除或追加元素时(尤其是实际环境中的元素非常多),其后的元素就必须要全部移动,这非常耗费计算资源。

二叉查找树

二叉查找树:指在链表的基础上往数组中追加元素时,考虑到数据的大小关系,将其分成左右两个方向的表现形式。

使用二叉查找树的好处:方便进行数据的查找。由数组顺序查找的时间复杂度O(n)最低可降低至O(logn)(平衡二叉查找树的情况,数的左子树和右子树越平衡,则时间复杂度越低)。二叉查找树示意如下:

具体的实现也是通过类似链表的形式,为内存数组中的每个元素添加左右元素:

 结尾

  本文总结了内存的物理结构和逻辑结构。介绍了数据类型是如何操作内存的。数组是内存最直接的使用方法,是高效使用内存的基础,其他数据结构的衍生都是为了处理数据的方便,结构在本质上都是对数组的变形。

  这篇文章就总结到这里吧,下一篇可能重点总结磁盘相关的内容。除此之外还会进一步更新红队打靶的解析和渗透测试相关的技术分享,恳请希望读者们多多支持。

猜你喜欢

转载自blog.csdn.net/Bossfrank/article/details/132826506