当前位置:首页 » 编程软件 » 编译器管理的存储器

编译器管理的存储器

发布时间: 2023-06-06 04:23:54

A. 存储器层次结构中的缓存

《深入理解计算机系统》p422

6.1 存储器层次结构中的缓存

一般而言,高速缓存( cache ,读作“ cash ”)是一个小而快速的存储设备,它作为存储在更大、也更慢的设备中的数据对象的缓冲区域。使用高速缓存的过程称为缓存( caching ,读作“ cashing ”)。存储器层次结构的中心思想是,对于每个 k ,位于 k 层的更快更小的存储设备作为位于 k 十1层的更大更慢的存储设备的缓存。换句话说,层次结构中的每一层都缓存来自较低一层的数据对象。例如,本地磁盘作为通过网络从远程磁盘取出的文件(例如 Web 页面)的缓存,主存作为本地磁盘上数据的缓存,依此类推,直到最小的缓存—— CPU 寄存器组。图6-22展示了存储器层次结构中缓存的一般性概念。第 k 十1层的存储器被划分成连续的数据对象组块( chunk ),称为块( block )。每个块都有一个唯一的地址或名字,使之区别于其他的块。块可以是固定大小的(通常是这样的),也可以是可变大小的(例如存储在 Web 服务器上的远程 HTML 文件)。例如,图6-22中第 k 十1层存储器被划分成16个大小固定的块,编号为0~15。

类似地,第 k 层的存储器被划分成较少的块的集合,每个块的大小与 k 十1层的块的大小一样。在任何时刻,第 k 层的缓存包含第 k 十1层块的一个子集的副本。例如,在图6-22中,第 k 层的缓存有4个块的空间,当前包含块4、9、14和3的副本。

数据总是以块大小为传送单元( transfer unit )在第 k 层和第 k +1层之间来回复制的。虽然在层次结构中任何一对相邻的层次之间块大小是固定的,但是其他的层次对之间可以有不同的块大小。例如,在图6-21中,L1和 LO 之间的传送通常使用的是1个字大小的块。L2和L1之间(以及I3和I2之间、L4和I3之间)的传送通常使用的是几十个字节的

块。而L5和L4之间的传送用的是大小为几百或几千字节的块。一般而言,层次结构中较低层(离 CPU 较远)的设备的访问时间较长,因此为了补偿这些较长的访问时间,倾向于使用较大的块。

1. 缓存命中

当程序需要第 k 十1层的某个数据对象 d 时,它首先在当前存储在第 k 层的一个块中查找 d 。如果 d 刚好缓存在第 k 层中,那么就是我们所说的缓存命中( cache hit )。该程序直接从第 k 层读取 d ,根据存储器层次结构的性质,这要比从第 k +1层读取 d 更快。例如,一个有良好时间局部性的程序可以从块14中读出一个数据对象,得到一个对第 k 层的缓存命中。

2. 缓存不命中

另一方面,如果第 k 层中没有缓存数据对象 d ,那么就是我们所说的缓存不命中( cache miss )。当发生缓存不命中时,第 k 层的缓存从第 k 十1层缓存中取出包含 d 的那个块,如果第 k 层的缓存已经满了,可能就会覆盖现存的一个块。

覆盖一个现存的块的过程称为替换( replacing )或驱逐( evicting )这个块。被驱逐的这个块有时也称为牺牲块( victim block )。决定该替换哪个块是由缓存的替换策略( replace — ment policy )来控制的。例如,一个具有随机替换策略的缓存会随机选择一个牺牲块。一个具有最近最少被使用 LRU )替换策略的缓存会选择那个最后被访问的时间距现在最远的块。

在第 k 层缓存从第 k 十1层取出那个块之后,程序就能像前面一样从第 k 层读出 d 了。例如,在图6-22中,在第 k 层中读块12中的一个数据对象,会导致一个缓存不命中,因为块12当前不在第 k 层缓存中。一旦把块12从第 k 十1层复制到第 k 层之后,它就会保持在那里,等待稍后的访问。

3. 缓存不命中的种类

区分不同种类的缓存不命中有时候是很有帮助的。如果第 k 层的缓存是空的,那么对

任何数据对象的访问都会不命中。一个空的缓存有时被称为冷缓存( cold cache ),此类不命中称为强制性不命中( compulsory miss )或冷不命中( cold miss )。冷不命中很重要,因为它们通常是短暂的事件,不会在反复访问存储器使得缓存暖身( warmed up )之后的稳定状态中出现。

只要发生了不命中,第 k 层的缓存就必须执行某个放置策略( placement policy ),确定把它从第 k 十1层中取出的块放在哪里。最灵活的替换策略是允许来自第 k +1层的任何块放在第 k 层的任何块中。对于存储器层次结构中高层的缓存(靠近 CPU ),它们是用硬件来实现的,而且速度是最优的,这个策略实现起来通常很昂贵,因为随机地放置块,定位起来代价很高。

因此,硬件缓存通常使用的是更严格的放置策略,这个策略将第 k 十1层的某个块限制放置在第 k 层块的一个小的子集中(有时只是一个块)。例如,在图6-22中,我们可以确定第 k 十1层的块 i 必须放置在第 k 层的块( i mod 4)中。例如,第 k 十1层的块0、4、8和12会映射到第 k 层的块0;块1、5、9和13会映射到块1;依此类推。注意,图6-22中的示例缓存使用的就是这个策略。

这种限制性的放置策略会引起一种不命中,称为冲突不命中( conflict miss ),在这种情况中,缓存足够大,能够保存被引用的数据对象,但是因为这些对象会映射到同一个缓存块,缓存会一直不命中。例如,在图6-22中,如果程序请求块0,然后块8,然后块0,然后块8,依此类推,在第 k 层的缓存中,对这两个块的每次引用都会不命中,即使这个缓存总共可以容纳4个块。

程序通常是按照一系列阶段(如循环)来运行的,每个阶段访问缓存块的某个相对稳定不变的集合。例如,一个嵌套循环可能会反复地访问同一个数组的元素。这个块的集合称为这个阶段的工作集( working set )。当工作集的大小超过缓存的大小时,缓存会经历容量不命中( capacity miss )。换句话说就是,缓存太小了,不能处理这个工作集。

4. 缓存管理

正如我们提到过的,存储器层次结构的本质是,每一层存储设备都是较低一层的缓存。在每一层上,某种形式的逻辑必须管理缓存。这里,我们的意思是指某个东西要将缓存划分成块,在不同的层之间传送块,判定是命中还是不命中,并处理它们。管理缓存的逻辑可以是硬件、软件,或是两者的结合。

例如,编译器管理寄存器文件,缓存层次结构的最高层。它决定当发生不命中时何时发射加载,以及确定哪个寄存器来存放数据。L1、L2和L3层的缓存完全是由内置在缓存中的硬件逻辑来管理的。在一个有虚拟内存的系统中, DRAM 主存作为存储在磁盘上的数据块的缓存,是由操作系统软件和 CPU 上的地址翻译硬件共同管理的。对于一个具有像 AFS 这样的分布式文件系统的机器来说,本地磁盘作为缓存,它是由运行在本地机器上的 AFS 客户端进程管理的。在大多数时候,缓存都是自动运行的,不需要程序采取特殊的或显式的行动。

6.3.2 存储器层次结构概念小结

概括来说,基于缓存的存储器层次结构行之有效,是因为较慢的存储设备比较快的存储设备更便宜,还因为程序倾向于展示局部性:

1)利用时间局部性: 由于时间局部性,同一数据对象可能会被多次使用。一旦一个数据对象在第一次不命中时被复制到缓存中,我们就会期望后面对该目标有一系列的访问命中。因为缓存比低一层的存储设备更快,对后面的命中的服务会比最开始的不命中快很多。

2)利用空间局部性: 块通常包含有多个数据对象。由于空间局部性,我们会期望后面对该块中其他对象的访问能够补偿不命中后复制该块的花费。现代系统中到处都使用了缓存。正如从图6-23中能够看到的那样, CPU 芯片、操作系统、分布式文件系统中和万维网上都使用了缓存。各种各样硬件和软件的组合构成和管理着缓存。注意,图6-23中有大量我们还未涉及的术语和缩写。在此我们包括这些术语和缩写是为了说明缓存是多么的普遍。

B. “Keil C51”下如何让编译器优先使用片内“RAM”

C51内存结构深度剖析
在编写应用程序时,定义一个变量,一个数组,或是说一个固定表格,到底存储在什么地方;当定义变量大小超过MCU的内存范围时怎么办;如何控制变量定义不超过存储范围;以及如何定义变量才能使得变量访问速度最快,写出的程序运行效率最高。以下将一一解答。

1 六类关键字(六类存储类型)
data idata xdata pdata code bdata

code: code memory (程序存储器也即只读存储器)用来保存常量或是程序。code memory 采用16位地址线编码,可以是在片内,或是片外,大小被限制在64KB
作用:定义常量,如八段数码表或是编程使用的常,在定义时加上code 或明确指明定义的常量保存到code memory(只读)
使用方法:
char code table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};
此关键字的使用方法等同于const

data data memory (数据存储区)只能用于声明变量,不能用来声明函数,该区域位于片内,采用8位地址线编码,具有最快的存储速度,但是数量被限制在128byte或更少。
使用方法:
unsigned char data fast_variable=0;

idata idata memory(数据存储区)只能用于声明变量,不能用来声明函数. 该区域位于片内,采用8位地址线编码,内存大小被限制在256byte或更少。该区域的低地址区与data memory地址一致;高地址区域是52系列在51系列基础上扩展的并与特殊功能寄存器具有相同地址编码的区域。即:data memory是idata memory的一个子集。

xdata xdata memory 只能用于声明变量,不能用来声明函数,该区域位于MCU
外部,采用16位地址线进行编码,存储大小被限制在64KB以内。
使用方法:
unsigned char xdata count=0;

pdata pdata memory 只能用于声明变量,不能用来声明函数,该区域位于MCU外部,采用8位地址线进行编码。存储大小限制在256byte. 是xdata memory的低256byte。为其子集。
使用方法
unsigned char pdata count=0;

bdata bdata memory 只能用于声明变量,不能用来声明函数。该区域位于8051内部位数据地址。定义的量保存在内部位地址空间,可用位指令直接读写。
使用方法:
unsigned char bdata varab=0

注:有些资料讲,定义字符型变量时,在缺省unsigned 时,字符型变量,默认为无符号,与标准C不同,但我在Keil uVision3中测试的时候发现并非如此。在缺省的情况下默认为有符号。或许在以前的编译器是默认为无符号。所以看到有的资料上面这样讲的时候,要注意一下,不同的编译器或许不同。所以我们在写程序的时候,还是乖乖的把unsigned signed 加上,咱也别偷这个懒。
2函数的参数和局部变量的存储模式
C51 编译器允许采用三种存储器模式:SMALL,COMPACT 和LARGE。一个函数的存储器模式确定了函数的参数的局部变量在内存中的地址空间。处于SMALL模式下的函数参数和局部变量位于8051单片机内部RAM中,处于COMPACT和LARGE模式下的函数参数和局部变量则使用单片机外部RAM。在定义一个函数时可以明确指定该函数的存储器模式。方法是在形参表列的后面加上一存储模式。

示例如下:
#pragma large //此预编译必须放在所有头文前面
int func0(char x,y) small;
char func1(int x) large;
int func2(char x);
注:
上面例子在第一行用了一个预编译命令#pragma 它的意思是告诉c51编译器在对程序进行编译时,按该预编译命令后面给出的编译控制指令LARGE进行编译,即本例程序编译时的默认存储模式为LARGE.随后定义了三个函数,第一个定义为SMALL存储模式,第二个函数定义为LARGE第三个函数未指定,在用C51进行编译时,只有最后一个函数按LARGE存储器模式处理,其它则分别按它们各自指定的存储器模式处理。
本例说明,C51编译器允许采用所谓的存储器混合模式,即允许在一个程序中将一些函数使用一种存储模式,而其它一些则按另一种存储器模式,采用存储器混合模式编程,可以充分利用8051系列单片机中有限的存储器空间,同时还可以加快程序的执行速度。

3绝对地址访问 absacc.h(相当重要)

#define CBYTE ((unsigned char volatile code *) 0)
#define DBYTE ((unsigned char volatile data *) 0)
#define PBYTE ((unsigned char volatile pdata *) 0)
#define XBYTE ((unsigned char volatile xdata *) 0)
功能:CBYTE 寻址 CODE区
DBYTE 寻址 DATA区
PBYTE 寻址 XDATA(低256)区
XBYTE 寻址 XDATA区
例: 如下指令在对外部存储器区域访问地址0x1000
xvar=XBYTE[0x1000];
XBYTE[0x1000]=20;

#define CWORD ((unsigned int volatile code *) 0)
#define DWORD ((unsigned int volatile data *) 0)
#define PWORD ((unsigned int volatile pdata *) 0)
#define XWORD ((unsigned int volatile xdata *) 0)

功能:与前面的一个宏相似,只是它们指定的数据类型为unsigned int .。
通过灵活运用不同的数据类型,所有的8051地址空间都是可以进行访问。

DWORD[0x0004]=0x12F8;
即内部数据存储器中(0x08)=0x12; (0x09)=0xF8

注:用以上八个函数,可以完成对单片机内部任意ROM和RAM进行访问,非常方便。还有一种方法,那就是用指钟,后面会对C51的指针有详细的介绍。

4寄存器变量(register)
为了提高程序的执行效率,C语言允许将一些频率最高的那些变量,定义为能够直接使用硬件寄存器的所谓的寄存器变量。定义一个变量时,在变量类型名前冠以“register” 即将该变量定义成为了寄存器变量。寄存器变量可以认为是一自动变量的一种。有效作用范围也自动变量相同。由于计算机寄存器中寄存器是有限的。不能将所有变量都定义成为寄存器变量,通常在程序中定义寄存器变量时,只是给编译器一个建议,该变量是否真正成为寄存器变量,要由编译器根据实际情况来确定。另一方面,C51编译器能够识别程序中使用频率最高的变量,在可能的情况下,即使程序中并未将该变量定义为寄存器变量,编译器也会自动将其作为寄存器变量处理。被定义的变量是否真正能成为寄存器变量,最终是由编译器决定的。

5内存访问杂谈
1指钟
指钟本身是一个变量,其中存放的内容是变量的地址,也即特定的数据。8051的地址是16位的,所以指针变量本身占用两个存储单元。指针的说明与变量的说明类似,仅在指针名前加上“*”即可。
如 int *int_point; 声明一个整型指针
char *char_point; 声明一个字符型指针
利用指针可以间接存取变量。实现这一点要用到两个特殊运算符
& 取变量地址
* 取指针指向单元的数据

示例一:
int a,b;
int *int_point; //定义一个指向整型变量的指针
a=15;
int_point=&a; //int_point指向 a
*int_point=5; //给int_point指向的变量a 赋值5 等同于a=5;
示例二:
char i,table[6],*char_point;
char_point=table;
for(i=0;i<6;i++)
{
char_point=i;
char_point++;
}
注:
指针可以进行运算,它可以与整数进行加减运算(移动指针)。但要注意,移动指针后,其地址的增减量是随指针类型而异的,如,浮点指针进行自增后,其内部将在原有的基础上加4,而字符指针当进生自增的时候,其内容将加1。原因是浮点数,占4个内存单元,而字符占一个字节。

宏晶科技最新一代STC12C5A360S2系列,每一个单片机出厂时都有全球唯一身份证号码(ID号),用户可以在单片机上电后读取内部RAM单元F1H~F7H的数值,来获取此单片机的唯一身份证号码。使用MOV @Ri 指令来读取。下面介绍C51 获取方法:
char id[7]={0};
char i;
char idata *point;
for(i=0;i<7;i++)
{
id[i]=*point;
point++;
}

(此处只是对指针做一个小的介绍,达到访问内部任何空间的方式,后述有对指针使用的详细介绍)
2对SFR,RAM ,ROM的直接存取
C51提供了一组可以直接对其操作的扩展函数
若源程序中,用#include包含头文件,io51.h 后,就可以在扩展函数中使用特殊功能寄存器的地址名,以增强程序的可读性:

注 此方法对SFR,RAM,ROM的直接存取不建议使用.因为,淡io51.h这个头文件在KEIL中无法打开,可用指针,或是采用absacc.h头文件,

C. as400的存储结构

AS/400 又一特征就是它的存储管理方法。它采用了单级存储( Single-level store )结构对存储器进行管理。它将系统中所有的存储器,即所有主存 (main storage ) 和辅存 (Auxiliary storage ) 看成是一个大的存储器,这个大存储器提供的存储空间相当于机器的寻址能力,从开始的 48 位寻址空间到今天的 64 位寻址空间,近几年内渴望实现 128 位寻址空间。这个诺大的虚地址空间给用户尤其是程序员带来了极大的方便,他们所面对的就是一个盛装对象( Objects )的大容器,只要知道了一个对象的名称和类型就可以访问这个对象,完全不必关心所访问的对象存放在哪个盘上,也不必考虑存储管理等繁琐问题。至于内外存之间信息如何交换以及为提高对象的访问效率而将一个对象的内容分散存放到几个硬盘上,对用户是完全透明的。
事实上,在 MI 上方的任何程序(包括 OS/400 ,应用程序,编译器等)都看不到这个单级存储结构中的实存部分(内存和外存),呈现在他们面前的只是一个偌大的虚存, 每个 AS/400 对象占用这个虚存中的一些虚地址。当用户使用对象的名字和类型访问它时, MI 层上方的程序可以快速地将这个对象的名字映射成它的虚地址。由于 MI 层上方用户编程时,不再区分内存和外存,只是针对这个虚存编写程序,因此可使得编写程序所需的指令数目大大减少。当然,负责系统性能管理的人员用 WRKSYSSTS 命令或 WRKDSKSTS 命令还是可以看到一些关于磁盘的信息的,例如磁盘的使用率,忙闲情况,缺页率( page Fault )等。
对象总是要存储在磁盘上,并且在使用时需将其装入内存的。如何将对象的虚地址映射到磁盘上的实地址或内存的实地址是在 MI 层下方实现的。 AS/400 将内存分为大小相等的一些页面帧 (page frame) ,在 64 位 AS/400 系统中,页面帧的大小为 4096 字节( 4KB ),存储在磁盘上的对象被划分为一个个大小与页面帧相等的页面( page )。一个大的对象由许多页面组成,这些页面在虚存中占用一些连续的地址,但在实际存放时,可能被分散到几个硬盘上,这样做可以提高对对象的读写速度。当处理机要访问的一个对象时,可以将该对象的部分页面从磁盘装入内存的页面帧中, MI 下方的地址转换机构通过查询驻留在内存中的一个页表 (page table) 实现虚实地址转换,如果发现一个虚地址对应的页面不在内存中,就报告页故障 (page fault) ,然后由内存管理程序负责将这个页面从磁盘调入内存。硬件在查询页表时使用的是 Hashing 算法
总之, AS/400 的单级存储结构不仅给编程用户提供了一个相当大的存储空间,同时也将用户从繁琐的存储管理中解放出来,减轻了用户负担,提高了工作效率。 在 OS/400 V4R3 及其以后版本中,已把 AS/400 的单级存储的单一地址空间由原来的内存、硬盘推广到磁带库,系统的存储管理功能可按用户自定义的规则,自动透明地管理分布在磁盘与磁带库中的数据。

热点内容
android改变字体 发布:2025-02-12 09:50:22 浏览:373
如何在本地布置ssh服务器 发布:2025-02-12 09:48:50 浏览:333
本机搭建服务器有用吗 发布:2025-02-12 09:48:14 浏览:234
安卓手机如何打开7x文件 发布:2025-02-12 09:43:02 浏览:485
c语言等号 发布:2025-02-12 09:39:02 浏览:169
ug编程培训要多少钱 发布:2025-02-12 09:38:27 浏览:620
小黄车的密码怎么打开 发布:2025-02-12 09:38:26 浏览:70
存储时4k 发布:2025-02-12 09:33:31 浏览:87
stn数据库 发布:2025-02-12 09:32:31 浏览:602
iossocket编程 发布:2025-02-12 09:32:20 浏览:899