7.5.5 cache 的一致性问题

第 3 版 · OCR 辅助 · 原书检索 · 文字用于定位,公式与图表请回原页核验 · 本次导入快照

##### 7.5.5 cache 的一致性问题
因为cache中的内容是主存块副本,当对 cache中的内容进行更新时,就存在 cache 和
主存如何保持一致的问题。除此之外,以下情况也会出现 cache一致性问题。
(1)当多个设备都允许访问主存时。例如,像磁盘这类高速1/O设备可通过DMA方
式直接读写主存,如果 cache 中的内容被CPU修改而主存块没有更新的话,则从主存传送
到I/O设备的内容就无效;若I/O设备修改了主存块的内容,则对应cache行中的内容就
无效。
(2)当多个CPU都带有各自的 cache而共享主存时。在多CPU系统中,若某个 CPU
修改了自身cache中的内容,则对应的主存块和其他CPU中对应的 cache行的内容都变为
无效。
解决 cache一致性问题的关键是处理好写操作,通常有两种基本的写操作方式。
1.全写法
全写法(write through)的基本做法是:写操作时,若写命中,则同时写cache 和主存;若
写不命中,则有以下两种处理方式。
(1)写分配法(write allocate)。先在主存块中更新相应存储单元,然后分配一个 cache
行,将更新后的主存块装入到分配的cache行中。这种方式可以充分利用空间局部性,但每
次写不命中都要从主存读一个块到 cache中,增加了读主存块的开销。
(2)非写分配法(not write allocate)。仅更新主存单元而不装入主存块到 cache中。这
种方式可以减少读入主存块的时间,但没有很好地利用空间局部性。
由此可见,全写法实际上采用的是对主存块信息及其所有副本信息全都直接同步更新
的做法,因此,该方式通常也被称为通写法或直写法,也有教材称之为写直达法。
显然,采用全写法使得 cache和主存的一致性能得到充分保证。但是,这种方法会大大
增加写操作的开销。例如,假定一次写主存需要100个CPU时钟周期,那么10??存数指
令就使得CPI增加了100×10?0个时钟。
为了减少写主存的开销,通常在 cache和主存之间加一个写缓冲(write buffer)。在
CPU写cache的同时,也将信息写入写缓冲,然后由存储控制器将写缓冲中的内容写入主
存。写缓冲是一个 FIFO 队列,只有少量空间,在写操作频率不是很高的情况下,因为 CPU
只需将信息写入快速的写缓冲而无须写入慢速的主存,因而效果较好。但是,如果写操作频
繁发生,则会使写缓冲饱和而发生阻塞。
2.回写法
回写法(write back)的基本做法是:当CPU执行写操作时,若写命中,则信息只被写入
cache 而不被写入主存;若写不命中,则在 cache中分配一行,将主存块调入该 cache行中并
更新相应单元的内容。因此,该方式下在写不命中时,通常采用写分配法进行写操作。
由此可见,该方式实际上采用的是回头再写或最后一次性写的做法,因此通常被称为回
写法或一次性写方式,也有教材称之为写回法。
在CPU执行写操作时,回写法不会更新主存单元,只有当cache行中的主存块被替换
第7章
247


<!-- page: 265 -->

计算机组成与系统结构(第3版)
248
时,才将该块内容一次性写回主存。这种方式的好处在于减少了写主存的次数,因而大大降
低了主存带宽需求。为了减少写回主存块的开销,每个 cache行设置了一个修改位(dirty
bit,也称“脏位”)。若修改位为1,则说明对应cache行中的主存块被修改过,替换时需要写
回主存;若修改位为0,则说明对应主存块未被修改过,替换时无须写回主存。
由于回写法没有同步更新cache和主存内容,所以存在 cache 和主存内容不一致而带来
的潜在隐患。通常需要其他的同步机制来保证存储信息的一致性。
*7.5.6cache 缺失对总体性能的影响
计算机性能最直接的度量方式就是CPU时间。程序执行的CPU时间应该等于CPU
执行时间和等待主存访问时间之和。当发生 cache缺失时,需要等待主存访问,此时,CPU
处于阻塞状态。因此,CPU时间的计算公式如下:
CPU时间=(CPU执行时钟数+cache缺失引起阻塞的时钟数)×时钟周期
cache 缺失引起阻塞的时钟数=读操作阻塞时钟数+写操作阻塞时钟数
对于写操作,不同写策略下阻塞时钟数的计算方式不同。回写方式下,替换时需要一次性写
回一个块,故会产生一些附加写回阻塞;全写方式下,包括写缺失阻塞和写缓冲阻塞两部分。
假定写回阻塞和写缓冲阻塞忽略不计,则可将读操作和写操作综合考虑,得到如下公式:
cache 缺失引起阻塞的时钟数=程序中访存次数×缺失率×缺失损失
=程序的指令条数×(缺失数/指令)×缺失损失
例7.11 假设计算机中只有一级 cache,并将指令和数据分别存放在 code cache 和 data
cache 中,其缺失率分别为1?4??假定在没有任何访存阻塞时CPI为1,缺失损失为
200个时钟周期,访存指令的使用频度为36每条访存指令存取一次数据,则使用缺失率
为0的 cache时,处理器速度会快多少?
解:假设程序运行共执行了I条指令,每条指令的取指令操作访存一次,则在访问指令
发生缺失的情况下,所用的时钟周期数为I×1?200=2.0×1。
已知访存指令的频度为36故访问数据缺失时所用时钟周期数为I×36?4?
200=2.88×I。
因为在一条指令执行过程中取指令和访问数据总是串行进行的,所以,两者的阻塞时钟
数应该相加,即指令缺失和数据缺失时的总阻塞时钟周期数为2.0×1+2.88×I=4.88×I,
也即平均每条指令要有 4.88个时钟周期处于访存阻塞状态,因此,由于访存阻塞而使得
CPI数从没有访存阻塞时的1增大到1+4.88=5.88。所以,如果 cache 不发生缺失(即缺失
率为0),则处理器速度会快5.88/1=5.88倍。
访存阻塞所用时间占整个执行时间的比例为4.88/5.88≈83??
对上述例子进一步分析,可以得到处理器的性能与 cache 性能之间的依赖关系。分以
下两方面来考虑。
(1)假设上例中没有任何访存阻塞时CPI为2,时钟宽度不变,则访存阻塞使得CPI数
从2增加到2+4.88=6.88。如果 cache不发生缺失,则处理器速度会快6.88/2=3.44倍。
访存阻塞所用时间占整个执行时间的4.88/6.88≈71小于83??
因此,可得出结论:CPI越小,cache 缺失引起的阻塞对系统总体性能的影响越大。
(2)假定上例中时钟频率加倍,CPI不变,则主存速度不太可能改变,故绝对时间不变,


<!-- page: 266 -->

存储器层次结构
所以缺失损失变为400个时钟周期。访问指令缺失和数据缺失时引起的总阻塞时钟数为
(1?400)+36?(4?400)=9.76。因此,访存阻塞使得CPI数从1增大到1+9.76=
10.76,由于时钟频率加倍,相对于原时钟频率,其CPI相当于只有一半。由此可知,时钟频
率快的机器的性能只是较慢机器的5.88/(10.76/2)≈1.1倍。如果没有 cache 缺失,应该
是2倍。
由此可得出结论:CPU时钟频率越高,cache 缺失损失就越大。
由上述两方面的分析结果可知,处理器性能越高,cache 缺失对总体性能的影响就
越大。
·7.5.7 cache 设计应考虑的问题
决定系统访存性能的重要因素之一是 cache 命中率,命中率与关联度有关,同时也和
cache 容量有关。显然,cache 容量越大,命中率就越高。此外,命中率还与主存块大小有一
定的关系。大主存块能很好地利用空间局部性,但是,主存块越大,其缺失损失也越大。因
此,主存块大小必须适中。
除上述提到的这些问题外,设计cache时还要考虑采用单级还是多级 cache,数据 cache
和指令 cache 是分开还是合在一起,主存-总线-cache-CPU 之间采用什么架构等,甚至
DRAM芯片的内部结构、存储器总线的总线事务类型等也都与 cache 设计有关,都会影响
系统总体性能。
1.单级/多级 cache、联合/分离 cache的选择问题
早期采用的是单级片外cache,现在多级片内cache成为主流。目前 cache 都在 CPU 芯
片内,且使用L1和L2 cache,甚至有 L3 cache。通常 L1 cache采用分离 cache,即数据
cache和指令 cache分开设置。L2 和 L3 cache 为联合方式,即数据和指令放在一个
cache 中,
在一个采用两级 cache的系统中,CPU 总是先访问L1 cache,若访问缺失,再从 L2
cache 中找。若 L2 cache包含所请求的信息,则缺失损失为L2 cache的访问时间,这比访问
主存要快得多;若 L2 cache访问缺失,则需从主存取信息并同时送L1 cache和L2 cache,此
时缺失损失较大。
在多级 cache 中,有全局缺失率和局部缺失率两种不同的概念。全局缺失率是指在所
有级 cache中都缺失的访问次数占总访问次数的比例;局部缺失率是指在某级cache中缺失
的访问次数占对该级 cache的总访问次数的比例。例如,对于两级 cache,若 CPU 总的访存
次数为100,在L1 cache命中的次数为94,剩下的6次中在L2 cache命中的次数为5,只有
1次需要访问主存,则全局缺失率为1?1 cache和 L2 cache的局部缺失率分别为6?
16.7??
2.主存-总线-cache 间的连接结构问题
为了计算主存块传送到 cache 所用的时间,必须先了解 CPU 从主存取一块信息到
cache 的过程。从主存读一块数据到 cache,一般包含以下3个阶段。
(1)发送地址和读命令到主存:假定用1个时钟周期。
(2)主存准备好一个数据:假定用10个时钟周期。
(3)从总线传送一个数据:假定用1个时钟周期。
第7章
249


<!-- page: 267 -->

计算机组成与系统结构(第3版)
250
主存、总线和 cache 之间可以有 3种连接方式:①窄形结构,每次按一个字的宽度进行
传送;②宽形结构,每次传送多个字;③多模块交叉存取结构,轮流启动多个存储模块进行读
写,按一个字的宽度进行传送。假定一个主存块有4个字,那么对于这3种结构,其缺失损
失各是多少呢?
图7.35给出了3种方式下主存块传送的过程。图7.35(a)对应窄型结构,连续进行“送
地址-读出-传送”4次,每次传一个字,其缺失损失为4×(1+10+1)=48个时钟周期。
图7.35(b)对应宽度为2个字的宽型结构,连续进行“送地址-读出-传送”两次,每次传两个
字,其缺失损失为2×(1+10+1)=24个时钟周期;假定增加总线条数及总线接口部件,使
得每次可传送4个字,则缺失损失可减为1×(1+10+1)=12个时钟周期。图7.35(c)对应
多模块交叉存取结构,在首地址送出后,每隔一个时钟周期轮流启动一个存储模块,每个模块
都用10个时钟周期准备好一个字,然后送总线进行传送,因此缺失损失为1+1×10+4×1=
15个时钟周期。通过以上分析可知,多模块交叉存取结构的性价比最好。
10
1
第1个字
10
1
第2个字
10
第3个字
1
第4个字
山
10
前2个字
后2个字
10
(a)窄形结构对应的块传送过程
10
第1个字
第2个字
第3个字
第4个字
10
1
10
10
10
(b)宽形结构对应的块传送过程
(c)交叉存储结构对应的传送过程
图7.35 主存块在主存-总线-cache之间的传送过程
3. DRAM结构、总线事务类型与 cache的配合问题
指令执行过程中,若发生 cache 缺失,则要从 DRAM中读取一块信息到 cache。因此,
如何合理设计DRAM结构,如何使存储器总线在一次总线事务中高效地传输一个主存块,
这些都是需要和 cache设计统一考虑的问题。
图7.36所示的存储器总线宽度为128位,即连接在其上的每个内存条一次最多能读出
128位的数据。每个内存条上排列有多个DRAM芯片。可用16个2Mb的DRAM芯片配
置一个 4MB的内存条,每个芯片内有一个512×8的 SRAM行缓冲,16个芯片共 8KB缓
冲。每个芯片每次读写芯片内同行同列的8位,16个芯片共16×8=128位。当CPU访问
一块连续的主存区域(即行地址相同的区域)时,可直接从行缓冲读取,行缓冲用SRAM实
现,速度极快。当cache缺失而要求从主存读一块信息到cache时,只要给定一个首地址,采
用突发传输的方式就可以在一次总线事务中完成一个主存块的传输。特别是当采用 DDR
SDRAM、DDR2 SDRAM或 DDR3 SDRAM芯片时,在芯片内部采用多模块交叉多数据预
取,并在存储器总线上采用时钟上升沿和下降沿各传送一次的方式,使得从主存到 cache的
数据块传送效率更高。


<!-- page: 268 -->

存储器层次结构
### 512 列
DRAM 芯片15
DRAM 芯片0
.*
|256Kb×8=2Mb
512行
256Kb×8-2Mb
8位
512×8 SRAM
位<120:127>
512×8 SRAM
位<0:75
行缓冲
存储器总线
位<0:1272
图7.36 内存条中芯片排列示意
*7.5.8cache 结构举例
现代计算机系统中几乎都会使用cache机制,早期 Intel公司在 Pentium 微处理器芯片
内集成了一个代码 cache和一个数据 cache。片内cache采用2路组相联结构,共128组,每
组2行,每行8个双字①,共4×8=32字节,因此每路有4K字节的容量,共8K字节。片内
cache的两路中各有一个目录表,每个表有128个记录项,每个记录项由20位的“标记”和两
位的“状态”组成,共有4种不同状态,用于 cache一致性协议(称为MESI协议②)。当一个
主存块调入 cache 后,就将其32 位地址中的高20位标记填入目录表中对应组(目录0或目
录1)的一个记录项中。片内cache 采用LRU替换策略,每组有一个LRU位,用来表示该
组哪一路中的 cache 行被替换。
Pentium4处理器芯片内集成了一个 L2 cahce和两个 L1 cache。L2 cache 是联合
cache,数据和指令存放在一起,所有从主存获取的指令和数据都先送到L2 cache中。Core
i7则采用了三级 cache结构,如图7.37所示,每个核内有各自私有的L1 cache 和 L2 cache。
其中,L1指令cache和数据 cache 都是32KB数据区,皆为8路组相联,存取时间都是4个
时钟周期;L2 cache是联合cache,共有256KB数据区,8路组相联,存取时间是11个时钟周
期。该多核处理器中还有一个供所有核共享的L3 cache,其数据区大小为8MB,16 路组相
联,存取时间是30~40个时钟周期。Intel Core i7中所有 cache 的块大小都是 64B。
① IA-32 体系结构中字为16位,双字为32位,即4字节。
② MESI协议用于解决 cache一致性问题,将每个 cache行的状态分为更新(modified)、独占(exclusive),共享
(shared),无效(invalid)4种,通过对状态转换进行控制来实现数据的一致性。更新表示在 cache 行中的信息已被修改
过;独占表示在其他 cache中没有副本;共享表示在其他 cache中有副本;无效表示 cache行的信息无效,是空闲行,可存
放新的主存块。
第7章
251


<!-- page: 269 -->

计算机组成与系统结构(第3版)
核0
核3
252
寄存器
L1数据
cache
LI指令
cache
L2联合cache
寄存器
L1数据
cache
L1指令
cache
L2联合cache
L3联合cache
(由所有核共享)
主存储器
图7.37 Intel Core i7 处理器的 cache 结构
7.6虚拟存储器
目前计算机主存主要由DRAM芯片构成,由于技术和成本等原因,主存的存储容量受
到限制,并且各种不同计算机所配置的物理内存容量多半也不相同,而程序设计时人们显然
不希望受到特定计算机的物理内存大小的制约;此外,现代操作系统都支持多道程序运行,
如何让多个程序有效而安全地共享主存是需要解决的另一个问题。
为了解决上述两个问题,在计算机中采用了虚拟存储技术。程序员在一个不受物理内
存空间限制并且比物理内存空间大得多的虚拟的逻辑地址空间中编写程序,就好像每个程
序都独立拥有一个巨大的存储空间一样。程序执行过程中,把当前执行到的一部分程序和
相应的数据调入主存,其他暂不用的部分暂时存放在磁盘上。这种借用外存为程序提供的
很大的虚拟存储空间称为虚拟存储器。
7.6.1虚拟存储器的基本概念
在不采用虚拟存储器的计算机系统中,CPU取指令和存取操作数所用的地址都是主存
物理地址,无须进行地址转换,因而计算机硬件结构比较简单,指令执行速度较快,而实时性
要求较高的嵌入式微控制器大多不采用虚拟存储机制。
目前,在服务器、台式机和笔记本等各类通用计算机系统中都采用虚拟存储器技术。在
这种计算机中,CPU通过存储器管理部件(memory management unit,MMU)将指令中的
逻辑地址(也称虚拟地址或虚地址,简写为VA)转换为主存的物理地址(也称主存地址或实
地址,简写为PA)。在地址转换过程中,MMU会检查是否发生了访问信息不在主存或地址
越界、访问越权或越级等存储保护错。若发现信息不在主存,则由操作系统将数据从外存读
到主存。若发现存储保护错,则由操作系统进行相应的异常处理。由此可以看出,虚拟存储
技术既解决了编程空间受限的问题,又解决了多道程序共享主存带来的安全问题。
图7.38 是具有虚拟存储机制的CPU与主存的连接示意图,从图中可知,CPU执行指
令时所给出的是指令或操作数的虚拟地址,需要通过MMU将虚拟地址转换为主存物理地


<!-- page: 270 -->

存储器层次结构
址才能访问主存,MMU包含在CPU芯片中。图中显示 MMU将一个虚拟地址 5600 转换
为物理地址 4,从而将第4、5、6、7这4个主存单元的数据组成4字节数据送到CPU。图7.38
仅是一个简单示意图,其中并没有考虑 cache 等情况。
CPU芯片
CPU
虚拟地址(VA)
5600
MMU
物理地址(PA)
4
主存储器
0:[
1:[
2:[
3:[
4:
5:
6:
7:
8:
M-I:[
第7章
253
图7.38 具有虚拟存储机制的CPU 和主存的连接
虚拟存储机制(简称虚存机制)由硬件与操作系统共同协作实现,涉及计算机系统的许
多层面,包括操作系统中的许多概念,如进程、存储器管理、虚拟地址空间、缺页处理等。
7.6.2进程的虚拟地址空间
每个高级语言源程序经编译、汇编、链接等处理,生成可执行的二进制机器目标代码时,
最后一步都需要对所有可重定位文件进行链接,其中需要按照应用程序二进制接口
(application binary interface,ABI)规范确定的虚拟地址空间划分(也称存储器映像)进行重
定位,将程序的代码段、数据段等映射到一个统一的虚拟地址空间。
所谓“统一”,是指不同的可执行文件所映射的虚拟地址空间大小一样,地址空间中区域
划分结构也相同。进程是操作系统对处理器中运行的程序的一种抽象,简单来说,进程就是
一个程序的一次执行过程。因此,一个进程一定与以可执行文件方式存放在外存中的一个
用户程序(即应用程序)相对应。可执行文件所映射到的虚拟地址空间,就是进程的虚拟地
址空间映像。
所有进程的虚拟地址空间大小和结构一致,简化了链接器的设计和实现,也简化了程序
的加载过程。虚拟存储管理机制为每个进程提供了一个极大的虚拟地址空间,它是主存和
外存的抽象。虚存机制带来了一个假象,使得每个进程好像都独占地使用主存,并且主存空
间极大。这有3个好处:①每个进程具有一致的虚拟地址空间,从而可以简化存储管理;②
它把主存看成是外存的一个缓存,在主存中仅保存当前活动的程序段和数据区,并根据
需要在外存和主存之间进行信息交换,通过这种方式,使有限的主存空间得到了有效利
用;③每个进程的虚拟地址空间是私有的、独立的,因此,可以保护各自进程不被其他进
程破坏。
下面以Intel架构下 Linux操作系统(对应system V ABI规范)为例,介绍一个进程的
虚拟地址空间映像。图7.39给出了该ABI规范规定的进程虚拟地址空间结构。整个虚拟
地址空间分为两大部分:内核空间和用户空间。所有进程的虚拟地址空间划分是一致的,


<!-- page: 271 -->

计算机组成与系统结构(第3版)
只是在相应的只读区域和可读写数据区域中映射的信息不同而已,分别映射到对应可执行
目标文件中的只读代码段和可读写数据段。
254
对每个进
程都不同
与进程相关的数据结构
(如页表、task和 mm等
结构,系统内核栈等)
物理存储区
对每个进
程都相同
内核代码和数据
用户栈
?p·
共享库
brk-
运行时堆
(用malloc创建)
可读写数据区域
只读代码区域
0x08048000(32 位)
0x00400000(64 位)
未使用
0
图 7.39 进程虚拟地址空间结构
内核空间
用户空间
内核空间用来映射到操作系统内核代码和数据、物理存储区,以及与每个进程相关的系
统级上下文数据结构(如进程标识信息、进程现场信息、页表等进程控制信息以及内核栈
等),其中内核代码和数据区在每个进程的地址空间中都相同。用户程序没有权限访问内核
空间。
用户空间用来映射到用户进程的代码、数据、堆和栈等用户级上下文信息。每个区域都
有相应的起始位置,堆区和栈区相向生长,其中,栈从高地址往低地址生长。
对于IA-32,内核空间在 0xc000 0000 以上的高端地址上,用户栈区从起始位置
0xc000 0000开始向低地址增长;堆栈区中的共享库映射区域从0x40000000 开始向高地址
增长;只读代码区域从0x08048000开始向高地址增长。只读代码区域后面跟着可读写数
据区域,其起始地址通常要求按4KB字节对齐。
对于x86-64,其最开始的只读代码区域从0x0040 0000H 开始,用户空间的最大地址为
0x7fff ffff ffff,通常,共享库映射在0x7fff f000 0000~0x7fff ffff ffff内,从 0x7fff f000 0000
向下是用户运行时栈(runtime stack),一般限定栈大小为8MB,整个用户空间大小为2”字
节(128TB)。内核空间在0x800000000000以上的高端地址上,最大地址为0xffff ffff ffff,
整个内核空间大小也是2”字节(128TB)。


<!-- page: 272 -->

存储器层次结构
7.6.3虚拟存储器的实现
虚拟存储器机制把 DRAM构成的主存看成是外部存储器的缓存,对照前面介绍的
cache 机制(cache是主存的缓存)可知,要实现虚拟存储器机制,也必须考虑交换块的大小
问题、映射问题、替换问题、写一致性问题等。根据对这些问题解决方法的不同,虚拟存储器
分成3种不同类型:分页式、分段式和段页式。
1.分页式虚拟存储器
在分页式虚拟存储系统中,虚拟地址空间被划分成大小相等的页面,外存和主存之间按
页面(page)为单位交换信息。虚拟地址空间中的页称为虚拟页、逻辑页或虚页,简称为VP
(virtual page);主存空间也被划分成同样大小的页框(页帧),有时把页框也称为物理页或
实页,简称为 PF(page frame)或 PP(physical page)。
虚拟存储管理采用“请求分页”的思想,每次访问指令或数据仅将当前需要的页面从硬
盘等外存调入主存某页框中,而进程中其他不活跃的页面保留在硬盘上。当访问某个信息
所在页不在主存时发生缺页异常,此时,从外存将缺失页面装入主存。
虚拟地址空间中有一些“空洞”的没有内容的页面。如图7.39所示,堆区和栈区都是动
态生长的,因而在栈和共享库映射区之间、堆和共享库映射区之间都可能没有内容存在,这
些没有和任何内容相关联的页称为“未分配页”;对于代码和数据等有内容的区域所关联的
页面,称为“已分配页”。在已分配页中又有两类:已调入主存而被缓存在 DRAM中的页面
称为“缓存页”;未调入主存而存在外存上的页称为“未缓存页”。因此,任何时刻一个进程中
的所有页面都被划分成3个不相交的页面集合:未分配页集合、缓存页集合和未缓存页
集合。
在主存和 cache 之间的交换单位为主存块,在外存和主存之间的交换单位为一个页面。
与主存块相比,页面要大得多。因为 DRAM比 SRAM慢10~100 倍,而磁盘等外存比
DRAM大约慢100 000倍,所以进行缺页处理所花的代价要比 cache缺失损失大得多。例
如,根据磁盘的特性,磁盘扇区定位所用时间要比磁盘读写一个数据的时间长大约100 000
倍,即对扇区第一个数据的读写比随后数据的读写要慢100000倍。考虑到缺页代价的巨
大和磁盘访问第一个数据的开销,通常将主存和磁盘之间交换的页的大小设定得比较大,典
型的有4KB、8KB、1MB等,而且有越来越大的趋势。
因为缺页处理代价较大,所以提高命中率是关键,因此,在主存页框和虚拟页之间采用
全相联映射方式。此外,当进行写操作时,由于外存访问速度很慢,所以,不能每次写操作都
同时写DRAM和外存,因而,在处理一致性问题时,采用回写(write back)方式,而不用全写
(write through)方式。
在虚拟存储机制中采用全相联映射,每个虚拟页可以存放到主存任何一个空闲页框中。
因此,与cache一样,必须要有一种方法来建立各个虚拟页与所存放的主存页框号或磁盘上
存储位置之间的关系,通常用页表(page table)来描述这种对应关系。
1)页表
进程中的每个虚拟页在页表中都有一个对应的表项,称为页表项。页表项内容包括该
虚拟页的存放位置、装入位(valid)、修改位(dirty)、使用位、访问权限位和禁止缓存位等,如
第7章
255
图7.40所示。


<!-- page: 273 -->

计算机组成与系统结构(第3版)
256
其中,页表项中的存放位置字段用来建立虚拟页和物理页框之间的映射,用于进行虚拟
地址到物理地址的转换。装人位也称为有效位或存在位,若为1,表示该虚拟页已从外存调
入主存,是一个“缓存页”,此时,存放位置字段指向主存物理页号(即页框号或实页号);若为
0,则表示没有被调入主存,此时,若存放位置字段为null,则说明是一个“未分配页”,否则是
一个“未缓存页”,其存放位置字段给出该虚拟页在磁盘上的起始地址。修改位(也称脏位)
用来说明页面是否被修改过,虚存机制中采用回写策略,利用修改位可判断替换时是否需写
回磁盘。使用位用来说明页面的使用情况,配合替换策略来设置,因此也称替换控制位,例
如,是否最先调入(FIFO位),是否最近最少用(LRU位)等。访问权限位用来说明页面是
可读可写、只读还是只可执行等,用于存储保护。禁止缓存位用来说明页面是否可以装入
cache,通过正确设置该位,可以保证磁盘、主存和 cache 数据的一致性。
图7.40给出的页表示例中,有4个缓存页,VP1、VP2、VP5和VP7;两个未分配页,
VPO和VP4;两个未缓存页,VP3和VP6。
装入位 修改位 使用位 访问权限位 禁止缓存位 存放位置
VPO 0
null
VPI
VP2
VP3
1
1
0
VP4
0
null
VP5
VP6
VP7
1
0
1
页表(主存中)
主存(DRAM)
VPI
VP2
VPS
VP7
磁盘存储器
VP1
VP2
VP3
VP5
VP6
VP7
PPO
PP1
PP2
PP3
图7.40 主存中的页表示例
对于图7.40所示的页表,假如CPU执行一条指令要求访问某个数据,若该数据正好在
虚拟页VP1中,则根据页表得知,VP1对应的装入位为1,该页的信息存放在物理页PPO
中,因此,可通过地址转换部件将虚拟地址转换为物理地址,然后到 PPO 中访问该数据;若
该数据在VP6中,则根据页表得知,VP6对应的装入位为0,表示页面缺失,发生缺页异常,
需要调出操作系统的缺页异常处理程序进行处理。缺页异常处理程序根据页表中VP6 对
应表项的存放位置字段,从磁盘中将所缺失的页面读出,然后找一个空闲的物理页框存放该
页信息。若主存中没有空闲的页框,则还要选择一个页面淘汰出来替换到磁盘上。因为采
用回写策略,所以页面淘汰时,需根据修改位确定是否要写回磁盘。缺页处理过程中需要对
页表进行相应的更新,缺页异常处理结束后,程序回到原来发生缺页的指令继续执行。
对于图7.40所示的页表,虚拟页VP0和VP4是未分配页,但随着进程的动态执行,可
能会使这些未分配页中有了具体的数据。例如,调用malloc函数会使堆区增长,若新增的
堆区正好与VP4对应,则操作系统内核就在磁盘上分配一个存储空间给 VP4,用于存放新
增堆区中的内容,同时,对应VP4的页表项中的存放位置字段被填上该磁盘空间的起始地
址,VP4从未分配页转变为未缓存页。
页表属于进程控制信息,位于虚拟地址空间的内核空间,页表在主存的首地址记录在页


<!-- page: 274 -->

存储器层次结构
表基址寄存器中。页表的项数由虚拟地址空间大小决定,前面提到,虚拟地址空间是一个用
户编程不受其限制的足够大的地址空间。因此,页表项数会有很多,因而会带来页表过大的
问题。例如,在IA-32系统中,虚拟地址为32位,页面大小为4KB,因此,一个进程有2/
212=22°个页面,即每个进程的页表可达22个页表项。每个页表项占32位,因此一个页表
的大小为4MB。显然,这么大的页表全部放在主存中是不合适的。
解决页表过大的方法有很多,可以采用限制大小的一级页表、两级页表或多级页表方
式,也可以采用哈希方式的倒置页表等方法。如何实现主要是 ISA 和操作系统考虑的问
题,在此不再赘述。
2)存储管理总体结构
在虚拟存储系统中,生成可执行文件时会通过可执行文件中的程序头表,将可执行文件
中具有相同访问属性的代码和数据段各自合并形成特定段,如只读代码段、可读可写数据段
等,这些不同的段被映射到虚拟地址空间的不同区域中。
每个用户程序都有各自独立的虚拟地址空间,用户程序以可执行文件方式存在磁盘上。
假定某一时刻用户程序1、2和k都已经被加载到系统中运行,那么,在这一时刻主存中就会
同时有这些用户程序中的代码和相应的数据。CPU在执行某个用户程序时,只知道该程序
中指令和数据在虚拟地址空间中的地址,那么,CPU怎么知道到哪个主存单元去取指令或
访问数据呢?可执行文件中的指令代码和数据都在外部存储器中,如何建立外存(如磁盘)
信息与主存物理地址之间的关联呢?对于上述问题的答案,可以从图7.41给出的存储管理
的总体结构中找到。图7.41描述了在IA-32+Linux平台下的存储管理的总体结构。
可执行目标文件
70000
ELF头
task_struct
页大小:4KB
进程描述符
程序头表
VM虚存
VP#
vm_area_struct
text 节
0xfmr
vm end
虚存空间
.rodata节
vm start
被划分成
04d3
vm prot
虚拟页面
data 节
1014
vm_flags
,bss 节
1
MM 主存PF#
vm end
0
VP#P
PF#
vm_start
0x804c
可读可写