2017-2018-1 20155201 《信息安全系统设计基础》第十三周学习总结
教材学习内容总结
第六章 存储器层次结构
6.1 存储技术
6.2 局部性
6.3 存储器层次结构
6.4 高速缓存存储器
6.5 编写高速缓存友好的代码
6.7 小结
存储器系统是一个具有不同容量、成本和访问时间的存储设备的层次结构。存储器层次结构是可行的,是因为与下一个更低层次的存储设备相比来说,一个编写良好的程序倾向于更频繁的访问某一个层次上的存储设备。靠近CPU的小的、快速的高速缓存存储器(cache memory)作为一部分存储在相对慢速的主存储器(mainmemory,简称主存)中的数据和 指令的缓冲区域。主存暂时存放存储在容量较大的、慢速磁盘上的数据,而这些磁盘常常又作为存储在通过网络连接的其他机器的磁盘或磁带上的数据的缓冲区域。
6.1 存储技术
-
随机访问存储器(Random-AccessMem)分两类:静态RAM(SRAM)和动态RAM(DRAM)。
-
SRAM将每个位存储在一个双稳态的(bitable)存储器单元里。每个单元是用一个六晶体管电路来实现的。
-
DRAM将每个位存储为对一个电容的充电。DRAM存储器可以制造得非常密集——每个单元由一个电容和一个访问晶体管组成。
-
如果断电,DRAM和SRAM会丢失它们的信息,从这个意义上说,它们是易失的(volatile)。
-
ROM以能够被重编程的次数和它们进行重编程所用的机制被区分为:可编程ROM (PROM),可擦写可编程ROM(EPROM),和电子可擦除PROM(EEPROM)。闪存:闪存是基于EEPROM的非易失性存储器,是一种较为普遍的非易失性存储器。
-
数据流通过称为总线的共享电子电路在处理器和DRAM主存之间来来回回。每次CPU与主存之间的数据都是由总线事务来完成的,总线事务包括读事务和写事务。
-
-
磁盘是用来保存大量数据的存储设备,能比基于RAM的存储器存更多的数据,但是读取数据的速度却远远低于基于RAM的存储器。
-
磁盘是由盘片构成的。磁盘是由盘片(platter)构成的。每个盘片有两面或者称为表面(surface),表面覆盖着磁性记录材料。盘片中央有一个可以旋转的主轴(spindle),它使得盘片以固定的旋转速率 (rotational rate)旋转;每个盘片上是由一组成为磁道的同心圆组成的。每个磁道被划分为一组扇区。扇区之间存在间隙。磁盘通常包含一个或多个这样的盘片,并封装在一个密封的容器内。整个装置称为磁盘驱动器,我们简称磁盘。
-
磁盘容量的计算:
-
磁盘以扇区大小的块来读写数据。对扇区的访问时间(access time)主要有三个部分:寻道时间(seek time)、旋转时间(rotational time)和传送时间(transfer time)。
-
当CPU从磁盘读数据时的步骤:
- CPU通过命令、逻辑块号和目的内存地址写到与磁盘相关联的内存映射地址,发起一个读
- 磁盘控制读扇区,并执行到主存的DMA
- 当DMA传送完成时,磁盘控制器用中断的方式通知CPU
-
固态硬盘(SSD)是一种基于闪存的存储技术。一个SSD包由一个或多个闪存芯片和闪存翻译层组成,闪存芯片替代传统旋转磁盘中的机械驱动器,而闪存翻译是一个硬件/固件设备,扮演与磁盘控制器相同的角色,将对逻辑块的请求翻译成对底层物理设备的访问。
-
6.2 局部性
一个编写良好的计算机程序常常具有良好的局部性(locality)。也就是说,它们倾向于引用邻近于其他最近引用过的数据项的数据项,或者最近引用过的数据项本身。这种倾向性,被称为局部性原理(principle of locality)。
- 局部性通常有两种不同的形式:时间局部性(temporal locality)和空间局部性(spatial locality)。
- 重复引用同一个变量的程序有良好的时间局部性。
- 一个连续向量中,每隔k个元素进行访问,就被称为步长为k的引用模式。对于具有步长为k的引用模式的程序,步长越小,空间局部性越好。具有步长为1的引用模式的程序有很好的空间局部性。在存储器中以大步长跳来跳去的程序空间局部性会很差。
- 对于取指令来说,循环有好的时间和空间局部性。循环体越小,循环迭代次数越多,局部性越好。
6.3 存储器层次结构
一般而言,从高层往底层走,存储设备变得更慢、更便宜和更大。
-
一般而言,高速缓存是一个小而快速的存储设备,它作为存储在更大也更慢的存储设备中的数据对象的缓冲区域。使用高速缓存的过程称为缓存。 数据块总是以块大小为传送单位(transfer unit)在第k层和第k+1层之间来回拷贝。
- 缓存命中:当程序需要第k+1层的某个数据对象d时,它首先在当前存储在k层的一个块中查找d。如果d刚好缓存在k层,那么就“命中”。
- 缓存不命中:如果k层没有缓存数据对象d,那么就“不命中”。当发生缓存不命中后,第k层的缓存从第k+1层缓存中取出包含d的那个块。
- 冷不命中
- 冲突不命中
6.4 高速缓存存储器
早期计算机系统的存储结构只有三层:CPU寄存器、DRAM主存储器和磁盘存储。不过,由于CPU和主存之间逐渐增大的距离,系统设计者被迫在CPU寄存器文件和主存之间插入了一个小的SRAM高速缓存存储器,称为L1高速缓存。随着CPU和主存之间的性能差距不断增大,系统设计者在L1高速缓存更大的高速缓存,称为L2高速缓存。有些现代计算机还包括一个更大的高速缓存,称为L3缓存。
-
一般而言,高速缓存的结构可以用元组(S,E,B,m)来描述。高速缓存的大小(或容量)C指的是所有块的大小的和。标记位和有效位不包括在内。因此,C=S * E * B。
-
根据E(每个组的高速缓存行数)高速缓存被分为不同的类:
- 直接映射高速缓存
- 每个组只有一行(E=1)的高速缓存称为直接映射高速缓存(direct-mapped cache)。
- 组选择
- 行匹配
- 字选择
- 行替换
- 组相联高速缓存:直接映射高速缓存中冲突不命中造成的问题是源于每个组只有一行这个限制,组相联高速缓存放松了这条限制,所以每个组都保存多余一个的高速缓存行。
- 全相联高速缓存:一个全相联高速缓存是由一个包含所有高速缓存行的组(即E=C/B)组成的。
- 直接映射高速缓存
-
有关写的问题
- 设我们要写一个已经缓存了的字w写命中(write hit)。在高速 缓存更新了它的w的拷贝之后,想要更新w在层次结构中紧接着低一层中的拷贝,可以有多种选择:
- 直写(write-through):立即将w的高速缓存块写回到紧接着的低一层中。
- 写回(write-back),尽可能地推迟存储器更新,只有当替换算法要驱逐更新过的块时,才把它写到紧接着的低一层中。
- 另一个问题是如何处理写不命中。
- 写分配(write-allocate),加载相应的低一 层中的块到高速缓存中,然后更新这个高速缓存块。写分配试图利用写的空间局部性,但是缺点是每次不命中都会导致一个块从低一层传送到高速缓存。
- 非写分配(not-write-allocate),避开高速缓存,直接把这个字写到低一层中。直写高速缓存通常是非写分配的。写回高速缓存通常是写分配的。
- 一般而言,高速缓存越往下层,越可能采用写回而不是直写。
- 设我们要写一个已经缓存了的字w写命中(write hit)。在高速 缓存更新了它的w的拷贝之后,想要更新w在层次结构中紧接着低一层中的拷贝,可以有多种选择:
-
实际上,高速缓存既可以保存数据也可以保存指令。只保存指令的高速缓存称为i-cache,只保存程序数据的高速缓存称为d-cache。记保存数据又保存指令的高速缓存称为统一的高速缓存(unified cache)。
6.5 编写高速缓存友好的代码
一般高速缓存友好的基本方法:
- 让常见的情况运行的快。程序通常把大部分时间都花在少量的核心函数上,而这些函数通常把时间都花在了少量循环上。所以集中注意力在核心函数的少量循环上,而忽略其他部分。
- 在每一个循环内部缓存不命中的数量最小。
6.6 综合:高速缓存对程序性能的影响
- 存储器山:存储器系统的性能不是一个数字就能简单描述,所以引入“山”的概念,这座时间和空间局部性的山,其上升高度差别可以超过一个数量级。
- 在程序中利用局部性:
- 集中关注程序的内循环上,大部分计算和存储器访问都发生在这里。
- 通过按照数据对象存储在存储器中的顺序、以步长为1的来读数据,从而使得你程序中的 空间局部性最大。
- 一旦从存储器中读入了一个数据对象,就尽可能多地使用它,从而使得程序中的时间局部性最大。
教材习题解答:
以下我会列出对我来说耗费时间较长的习题,套公式计算的题就简写了。
习题6.1
本题的要点是理解DRAM芯片的组成原理:
传统的DRAM芯片单元被分成d个超单元,每个超单元由w个DRAM单元组成。一个dxW的DRAM总共存储了dw位信息。一个16x8的DRAM芯片组织如下图所示:
x前面的数字代表超单元的数量, 其分割应趋向于正方形,才能使得访问第(i, j)个地址时,访问的地址位数趋于最少。
br和bc是寻址时需要的位数,4=22,16=24以此类推,完成填表。
习题6.2
磁盘容量=(字节数/扇区) * (平均盘区数/磁道) * (磁道数/表面) * (表面数/盘片) * (盘片数/磁盘)
计算磁盘容量:2个盘片,10000个柱面,每条磁道平均400个扇区,每个扇区有512个字节。
(注:每个盘片有2个表面,柱面是所有盘片表面上到主轴中心的距离相等的磁道的集合。)
磁盘容量=(512) * (400) * (10000) * (2) * (2)=8 192 000 000字节=8.192 GB
习题6.3
一个有如下参数的磁盘:旋转速率:15000 RPM,T(avg seek)=8ms,每条磁道的平均扇区数:500
平均旋转时间:T(avg rotation)=1/2 * T(max rotation)
=1/2 * (60sec/15000 RPM) * (1000ms/sec)
=2 ms
平均传送时间:T(avg transfer)=(60s/15000 RPM) * (1/500) * (1000ms/sec)=0.008 ms
T(access)=T(avg seek) + T(avg rotation) + T(avg transfer) = 8 + 2 + 0.008=10 ms
习题6.4
首先,T(avg seek)= 5 ms, T(max rotation) = 6 ms, T(avg rotation) = 3 ms
A:总时间为T(avg seek) + T(avg rotation) + 2 * T(max rotation) = 5 + 3 + 12= 20 ms
B:总时间为(T(avg seek) + T(avg rotation) ) * 2000 = =16 000 ms
习题6.5
A:(10 ^ 9 * 128) * (1/470) * (1/(86400 * 365)) 约为8年
B:(10 ^ 9 * 128) * (1/303) * (1/(86400 * 365)) 约为13年
C:(10 ^ 9 * 128) * (1/20000) * (1/365)约为17535年
习题6.7
需要保证最内层循环对应数组的最细元素。
int sumarray3d(int a[N][N][N]){
int i,j,k,sum=0;
for(i=0;i<N;i++)
for(j=0;j<N;j++)
for(k=0;k<N;k++)
sum+=a[i][j][k];
return sum;
}
习题6.8
clear1函数以步长为1的引用模式访问数组,空间局部性最好。
习题6.9
其实也是套公式计算
C = B * E * S
b = log2(B)
s = log2(S)
t = m - (s + b)
习题6.10
命中率3/4
习题6.11
A:每个连续的数组片由2 ^ t个块组成。 数组头2 ^ t个连续的块都会映射到组0,接下来的映射到1.
B:对于直接映射高速缓存(S, E, B, m)=(512, 1, 32, 32),数组的头2 ^ 18个块会被映射到组0,接下来的映射到组1。
习题6.12
习题6.13
地址:0x0E34
A. 地址格式:
B.内存引用:
习题6.14
地址:0x0DD5
A. 地址格式:
B.内存引用:
习题6.15
地址:0x01FF4
A. 地址格式:
B.内存引用:
习题6.16
组3包含一个有效行,标记为0x32,组中只有一个有效行,4个地址会命中。
在组3中命中的4个十六进制地址是:
0x064C
0x064D
0x064E
0x064F
习题6.17
注意:每个高速缓存行只包含数组的一个行,高速缓存正好只够保存一个数组。对于所有的i,src和dst的行i映射到同一个高速缓存行。
对dst[0][0]写会驱逐当我们读src[0][0]时加载进来的那一行。当读src[0][1]是,会有一个不命中。
当高速缓存为32字节时,他足够容纳两个数组。所有的不命中都是开始时的;冷不命中。
习题6.18
A:读的总数:512
B:缓存不命中的读总数:256
C:不命中率:256/512=1/2
习题6.19
这个高速缓存只能保存数组的一半。所以,按照列顺序来扫描数组的第二部分时会驱逐扫描第一部分时加载进来的那些行。
A:读的总数:512
B:缓存不命中的读总数:256
C:不命中率:256/512=1/2
D:如果高速缓存足够大,那么它能够保存整个数组,所有的不命中都会是开始时的冷不命中,而不命中率1/4。
习题6.20
该循环步长为1,因此所有的不命中都是最开始时的冷不命中。
A:读的总数:512
B:缓存不命中的读总数:128
C:不命中率:128/512=1/4
D:冷不命中不可避免,命中率不变。
习题6.21
周期:2670 / 6500 * 8 = 3.2周期
4周期
教材学习中的问题和解决过程
- 问题1:高速缓存中行、组和块有什么区别?
- 问题1解决方案:
块是一个固定大小的信息包,在高速缓存和主存之间来回传送。
行是高速缓存中存储块以及其他信息的容器。
组是一个或多个行的集合。
在直接映射高速缓存中,组和行确实是等价的。在相联高速缓存中,组和行有各自的概念。
代码调试中的问题和解决过程
- 问题1:对于程序的空间局部性如何理解?
- 问题1解决方案:先看一个一维数组的示例:
int sum1(int array[N])
{
int i, sum = 0;
for(i = 0; i < N; i++)
sum += array[i];
return sum;
}
在这个程序中,变量sum
和i
在每次循环时就被引用一次,因此对sum
和i
来说,有较好的时间局部性。
对变量array
来说,它是一个int
类型数组,循环时按顺序访问array
,因为一个数组在内存中是占用连续的内存空间。因而的较好的空间局部性。
这个程序具有良好的空间和时间局部性。
再看一个二维数组的例子:
int sum2(int array[M][N])
{
int i, j, sum = 0;
for(i = 0; i < M; i++){
for(j = 0; j < N; j++)
sum += array[j][i];
}
return sum;
}
这个相比之前的程序局部性就不好了,尤其是空间局部性。
当M=2,N=3时,
访问这个数组的时候,从array[0][0]开始,array[1][0]->array[2][0]->array[0][1]->array[1][1]…
数组在内存中是按行顺序来存放的,array[0][0]的地址假设为0x0,array[0][1]地址应该是0x4,array[0][2]地址0x8……这样的程序对array[0][0]、array[0][1]元素的访问顺序就变成了1、3、5、2、4、6,步长变成了3而不是最优的1。但这个程序是可以变成步长为1的最优程序的,只需要修改遍历的顺序,使之按照行来扫描:
int sum2(int array[M][N])
{
int i, j, sum = 0;
for(i = 0; i < M; i++){
for(j = 0; j < N; j++)
sum += array[i][j];
}
return sum;
}
代码托管
上周考试错题总结
mount -t nfs -o nolock 192.168.0.56:/root/share /host
,其中的IP是()的IP
A. Windows 宿主机
B. Ubuntu虚拟机
C. ARM实验箱
D. 以上都不对
【答案】B
本来选了A,后来翻了翻自己的博客,发现提到了宿主机之类的,然后果断改了选项,就错了。。跟宿主机IP应该是毫无关系的,nfs系统是把Ubuntu虚拟机的一个目录映射到ARM实验箱,跟Windows机无关。
- 实验1中Windows宿主机,Ubuntu虚拟机,ARM实验箱三者IP要在同一网段,操作中是根据()的IP来确定网段的。
A. Windows 宿主机
B. Ubuntu虚拟机
C. ARM实验箱
D. 以上都可以
【答案】C
本题我选了个D。。ARM实验箱的IP修改要重新烧录Linux系统,Windows 宿主机,Ubuntu虚拟机的IP比较容易修改,所以我们在超级终端中查看ARM实验箱的IP,之后把Windows 宿主机,Ubuntu虚拟机的IP设置成和ARM实验箱同一个网段。
结对及互评
本周结对学习情况
- 20155313
-
结对学习内容
-
- 直接映射高速缓存的示例:
-
(S,E,B,m)=(4,1,2,4),即4个组,每个组1行,每个块2个字节,地址是4位。这里假设每个字都是单字节,每次都读1个字。
初始时,高速缓存是空的:
a)读地址0的字。因为组0的有效位是0,缓存不命中,所以高速缓存从存储器取出块0,,并存储在组0中。然后高速缓存返回取出的高速缓存行的m[0]。
b)读地址1的字。高速缓存命中。
c)度地址13的字。
d)读地址8的字。
e)读地址0的字。又发生缓存不命中,因为前面引用地址8时,刚好替换了块8。
具体的图示意在伙伴的博客里。
从伙伴的总结里学习到的:最近一段时间操作系统课程也讲了计算机中的内存使用,借此学习一下:
- 操作系统采用虚拟内存的方式管理存储器有很多好处:
- 虚拟内存管理可以控制物理内存的访问权限。
- 虚拟内存管理最主要的作用是让每个进程有独立的地址空间。所谓独立的地址空间是指,不同进程中的同一个VA被MMU映射到不同的PA,并且在某一个进程中访问任何地址都不可能访问到另外一个进程的数据,这样使得任何一个进程由于执行错误指令或恶意代码导致的非法内存访问都不会意外改写其它进程的数据,不会影响其它进程的运行,从而保证整个系统的稳定性。另一方面,每个进程都认为自己独占整个虚拟地址空间,这样链接器和加载器的实现会比较容易,不必考虑各进程的地址范围是否冲突。
- 方便了共享库的实现。采用共享库的目的在于节省内存(物理内存)。VA到PA的映射会给分配和释放内存带来方便,物理地址不连续的几块内存可以映射成虚拟地址连续的一块内存。
- “增加”了程序可以使用的内存空间。一个系统如果同时运行着很多进程,为各进程分配的内存之和可能会大于实际可用的物理内存,虚拟内存管理使得这种情况下各进程仍然能够正常运行。当所访问的页面不在内存中时再将其加载进来,无空闲页面的时候还可能需要采取一定的置换算法将某个页面换出。这种机制的可行性得益于伟大的“局部性原理”。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 195/195 | 1/1 | 10/10 | |
第三周 | 314/706 | 1/2 | 15/25 | |
第五周 | 254/960 | 2/4 | 10/35 | |
第七周 | 24/1759 | 2/6 | 15/50 | |
第九周 | 1207/2966 | 2/8 | 15/65 | |
第十一周 | 1207/2966 | 2/10 | 15/65 | |
第十三周 | 419/3385 | 3/13 | 16/81 |
-
计划学习时间:12小时
-
实际学习时间:16小时