如早期的 PC 操作系统 MS-DOS,内存中只能有一道用户程序,用户程序独占整个用户区空间。优缺点:
将整个用户空间划分为若干个固定大小的分区,在每个分区中只装入一道作业。两种分配方案:
操作系统需要建立一个分区说明表,来实现各个分区的分配与回收。每个表项对应一个分区,通常按分区大小排列。每个表项包括对应分区的大小、起始地址、状态(是否已分配)。
优缺点:
动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的。动态分区分配没有内部碎片,但是有外部碎片。
操作系统可使用两种方案记录空闲分区的情况:
空闲分区的回收分为四种情况:
把一个新作业装入内存时,须按照一定的动态分区分配算法,从空闲分区表(或空闲分区链)中选出一个分区分配给该作业,不过这些算法的性能都非常差。
首次:找到第一个能放得下的空闲分区
邻近:继续查找
最佳:找到刚好放得下的空闲分区
最坏:找到最大的空闲分区
假设逻辑地址空间为 32 位(4GB),一页为 4KB,物理地址空间为 28 位(256MB),则:
将内存空间分为一个个大小相等的分区(每个分区 4KB),每个分区就是一个“页框”。每个页框有一个编号,即“页框号”。页框号从 0 开始。
物理地址空间中页框和页框号的概念:
物理地址空间的分区如下(228/212 = 216 个页框):
页框号 | 页框大小 |
---|---|
页框号 0 | 4KB |
页框号 1 | 4KB |
页框号 2 | 4KB |
页框号 3 | 4KB |
… | … |
页框号 216-1 | 4KB |
将进程的逻辑地址空间也分为与页框大小相等的一个个部分,每个部分称为一个“页”或“页面” 。每个页面也有一个编号,即“页号”,页号也是从 0 开始。进程的页面与内存的页框有一一对应的关系。
如何理解“一一对应”这个字眼?一个页面可以对应(更准确来讲是“映射”)一个页框,但不可以对应多个页框;但一个页框可以对应多个页面(这里可引申出虚拟映射文件技术)。比如,页号 0 的页面可以对应页框号 6 的页框,页号 1 的页面可以对应页框号 5 的页框,页号 2 的页面可以对应页框号 4 的页框等等;框号 4 的页框可以同时对应页号 2 的页面和页号 8 的页面。
逻辑地址空间中页和页号的概念:
逻辑地址空间的分区如下(232/212 = 220 个页):
页面 | 页面大小 |
---|---|
页号 0 | 4KB |
页号 1 | 4KB |
页号 2 | 4KB |
页号 3 | 4KB |
… | … |
页号 220-1 | 4KB |
逻辑地址结构:
逻辑页号 | 页内地址/页内偏移量 |
---|---|
20b | 12b |
物理地址结构:
物理页号 | 页内地址/页内偏移量 |
---|---|
16b | 12b |
一般公式:
页表寄存器(PTR) 的结构:
页表起始地址 | 页表长度 |
---|---|
记录页表存放的首地址(物理地址) | 记录一个页表能放下多少个页表项(这里存储十进制 220) |
一个页表项的结构(因为物理地址空间有 216 个页,所以页表项的物理页号占 16 位):
(页号) | 物理页号(页框号/块号) |
---|---|
实际不需要记录页号 | 该页实际对应的物理页号(页框号),占 16b |
页表的结构(因为逻辑地址空间有 220 个页,所以页表有 220 个页表项)(举个例子:页号 0 的页面映射页框号 6 的页框,页号 1 的页面映射页框号 5 的页框,页号 2 的页面映射页框号 3 的页框,页号 3 的页面映射页框号 3 的页框,则如下表所示):
(页号) | 物理页号(页框号/块号) |
---|---|
(0) | 16b(存储页框号 6,对应二进制 0000,0000,0000,0110) |
(1) | 16b(存储页框号 5,对应二进制 0000,0000,0000,0101) |
(2) | 16b(存储页框号 3,对应二进制 0000,0000,0000,0011) |
(3) | 16b(存储页框号 3,对应二进制 0000,0000,0000,0011) |
… | … |
(220-1) | 16b(存储页框号 …) |
一般页表项的长度都会凑成字节的整数倍。这个例子刚好能凑成 2B,所以这个页表大小为 220*2B = 221B = 2MB。因为一个页是 4KB,所以这样一个页表需要占据 2MB/4KB = 211KB/22KB = 29 = 128 页。页数有点多了,如何解决这个问题呢?接下来采用二级页表就能解决这个问题。
快表,又称相联寄存器,是一种访问速度比内存快很多的高速缓存(TLB不是内存!),用来存放最近访问的页表项的副本,可以加速地址变换的速度。与此对应,内存中的页表常称为慢表。
TLB 的结构跟页表差不多,只是 TLB 需要多存储“页号”。
页号 | 物理页号(页框号/块号) |
---|---|
2 | xxx(16b) |
4 | xxx(16b) |
15 | xxx(16b) |
… | … |
(1)根据逻辑地址计算出页号、页内偏移量:
页号 = 逻辑地址 / 页面大小
,页内偏移量 = 逻辑地址 % 页面大小
页号 = 逻辑地址高 x 位
,页内偏移量 = 逻辑地址剩余位
(2)判断页号是否越界:
页号 ≥ 页表长度
,发生越界中断,立即终止;若页号 < 页表长度
,则没有越界(3)查询页表:通过 PTR 的页表访问内存,起始地址找到页号对应的页表项,确定页面存放的页框号(内存块号)
(4)用页框号(内存块号)和页内偏移量得到物理地址:
物理地址 = 页框号 * 页面大小 + 页内偏移量
物理地址 = 页框号 拼接 页内偏移量
(把逻辑地址的高 x 位地址换成页框号即可得到物理地址)(5)访问目标内存单元
(1)根据逻辑地址计算出页号、页内偏移量:
页号 = 逻辑地址 / 页面大小
,页内偏移量 = 逻辑地址 % 页面大小
页号 = 逻辑地址高 x 位
,页内偏移量 = 逻辑地址剩余位
(2)判断页号是否越界:
页号 ≥ 页表长度
,发生越界中断,立即终止;若页号 < 页表长度
,则没有越界(3)查询快表:若在 TLB 中找到页号对应的页表项,则可确定页面存放的页框号(内存块号),直接到第(5)步;若没有找到,则进行第(4)步
(4)查询页表:通过 PTR 的页表起始地址访问内存,找到页号对应的页表项,确定页面存放的页框号(内存块号)
(5)用页框号(内存块号)和页内偏移量得到物理地址:
物理地址 = 页框号 * 页面大小 + 页内偏移量
物理地址 = 页框号 拼接 页内偏移量
(把逻辑地址的高 x 位地址换成页框号即可得到物理地址)(6)访问目标内存单元
假设逻辑地址空间为 32 位(4GB),一页为 4KB,物理地址空间为 28 位(256MB),所以逻辑地址空间被分为 232/212 = 220 个页,物理地址空间被分为 228/212 = 216 个页框。
现继续假设规定页表项长度为 8b = 1B(一般来说,不同级页表的页表项长度都是固定的),所以一个页能存储 4KB/1B = 4K = 212 个页表项。而又因为逻辑地址空间被分为 220 个页,即总共需要 220 个页表项,从而进一步计算 220/212 = 2(向上取整),分页系统需要分两级。
怎么分呢?可以这样分:212 个页表项给页目录表存储,那么每个二级页表就存储 28 个页表项;也可以这样:28 个页表项给页目录表存储,那么每个二级页表就存储 212 个页表项;还可以有其他方案,总之指数部分加起来等于 20 就行了。这里采用第一种方案。
逻辑地址结构:
顶级逻辑页号 | 二级逻辑页号 | 页内地址/页内偏移量 |
---|---|---|
12b | 8b | 12b |
物理地址结构:
顶级物理页号 | 二级物理页号 | 页内地址/页内偏移量 |
---|---|---|
8b | 8b | 12b |
页目录表寄存器(PDBR) 的结构:
页目录表起始地址 | 页表长度 |
---|---|
记录页目录表存放的首地址(物理地址) | 记录一个页表能放下多少个页目录表项 |
一个页目录表项的结构:
(页号) | 物理页号(页框号/块号) |
---|---|
实际不需要记录页号 | 记录页表存放的页框号(物理块号),占据 8b |
页目录表的结构:
(页号) | 物理页号(页框号/块号) |
---|---|
(0) | 8b |
(1) | 8b |
(2) | 8b |
(3) | 8b |
… | … |
(212-1) | 8b |
所以这个页目录表大小为 212*1B = 212B = 4KB。因为一个页是 4KB,所以这样一个页表需要占据 1 页。
由以上页目录表可以看出,二级页表一共有 212 个。
一个页表项的结构:
(页号) | 物理页号(页框号/块号) |
---|---|
实际不需要记录页号 | 该页实际对应的物理页号(页框号),占据 8b |
页表的结构:
(页号) | 物理页号(页框号/块号) |
---|---|
(0) | 8b |
(1) | 8b |
(2) | 8b |
(3) | 8b |
… | … |
(28-1) | 8b |
这个页表大小为 28*1B = 256B。因为一共有 28 个页表,所以这些二级页表总共占据 28*256B = 216B = 26KB = 64KB。因为一个页是 4KB,所以这些二级页表需要占据 64/4 = 16 页,再加上页目录表占据 1 页,总共 17 页,跟一级页表的存储方式相比节省了大部分空间。
(1)按照地址结构将逻辑地址拆分成三部分
(2)从 PDBR 中读出页目录表始址,再根据顶级页号查页目录表(访存 1 次),找到对应二级页表在内存中的存放位置
(3)根据二级页号查二级页表(访存 2 次),找到最终想访问的内存块号
(4)结合页内偏移量得到物理地址
(5)访问目标内存单元(访存 3 次)
一个页表项的结构:
(页号) | 物理页号(页框号/块号) | 状态位 | 访问字段 | 修改位(脏位) | 外存地址 |
---|---|---|---|---|---|
实际不需要记录页号 | 该页实际对应的物理页号(页框号) | 指示该页是否调入内存,供程序访问时参考 | 记录该页在一段时间内被访问的次数 | 指示该页在调入内存后是否被修改过,以确定页面置换时是否写回外存 | 存储该页在外存的地址,供调入该页时使用 |
页表的结构:
(页号) | 物理页号(页框号/块号) | 状态位 | 访问字段 | 修改位(脏位) | 外存地址 |
---|---|---|---|---|---|
(0) | … | ||||
(1) | … | ||||
(2) | … | ||||
(3) | … | ||||
… | … | ||||
(…) | … |
TLB 的结构跟页表差不多,只是 TLB 需要多存储“页号”。
页号 | 物理页号(页框号/块号) | 状态位 | 访问字段 | 修改位(脏位) | 外存地址 |
---|---|---|---|---|---|
1 | … | ||||
7 | … | ||||
2 | … | ||||
9 | … | ||||
… | … |
(1)根据逻辑地址计算出页号、页内偏移量
(2)判断页号是否越界:若判断越界,则发生越界中断,立即终止
(3)查询快表:若在 TLB 中找到页号对应的页表项,则可确定页面存放的页框号(内存块号),直接到第(5)步;若没有找到,则进行第(4)步
(4)查询页表:通过 PTR 的页表起始地址访问内存,若找到页号对应的页表项,则确定页面存放的页框号(内存块号),直接到第(6)步;若没有找到,则进行第(5)步
(5)缺页中断处理:
(5-1)在外存中找到该缺页
(5-2)判断内存是否已满:若是,选择一页换出(还需判断该页是否被修改过,若是,先将其写入外存);若不是,直接进行(5-3)步
(5-3)将该缺页从外存读入内存
(5-4)修改页表,修改 TLB
(6)修改访问位和修改位
(7)用页框号(内存块号)和页内偏移量得到物理地址
(8)访问目标内存单元
请求分页存储管理与基本分页存储管理的主要区别:
在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
【注】虚拟内存需要满足两个条件:
- 虚拟内存的实际容量 ≤ 内存容量 + 外存容量
- 虚拟内存的最大容量 ≤ 计算机的地址位数能容纳的最大容量
段:进程的地址空间按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从 0 开始编址。每个段在内存中占据连续空间,但各段之间可以不相邻。
以实际例子说明分段存储管理的结构,某个进程的地址空间划分段如下:
段号 0 |
---|
程序段 4KB |
段号 1 |
---|
数据段 1KB |
段号 2 |
---|
堆栈段 2KB |
段号 3 |
---|
数据段 2KB |
物理内存存放各个段的位置如下:
地址 | 内容 |
---|---|
… | … |
40K-70K | (段号 0)程序段 4KB |
… | … |
100K-102K | (段号 3)数据段 2KB |
… | … |
159K-160K | (段号 1)数据段 1KB |
… | … |
214K-216K | (段号 2)堆栈段 2KB |
… | … |
段表寄存器的结构:
段表起始地址 | 段表长度 |
---|---|
记录段表存放的首地址(物理地址) | 记录一个段表能放下多少个段表项 |
一个段表项的结构:
(段号) | 段长 | 段基址 |
---|---|---|
实际不需要记录段号 | 记录该段最大可以多长 | 记录该段的首地址(物理地址) |
段表的结构(可对照物理地址空间):
(段号) | 段长 | 段基址 |
---|---|---|
(0) | 4KB | 40K |
(1) | 1KB | 159K |
(2) | 2KB | 214K |
(3) | 2KB | 100K |
… | … | … |
逻辑地址结构:
(若要访问程序段的地址 8B 处,假设逻辑地址是 13 位,给出例子如下)
段号 | 段内偏移量 |
---|---|
0 | 0000,0000,1000 |
物理地址结构:
(继续上面的例子,则通过查段表,可得到物理地址如下)
段的首地址 | 段内偏移量 |
---|---|
40K | 0000,0000,1000 |
(1)根据逻辑地址得到段号、段内偏移量:高位为段号,低位为段内偏移量
(2)判断段号是否越界:
段号 ≥ 段表长度
,发生越界中断,立即终止;若段号 < 段表长度
,则没有越界(3)查询段表:通过段表寄存器访问所指向的段表,找到段号对应的段表项,确定段基址
(4)用段基址和段内偏移量得到物理地址:
物理地址 = 段基址 + 段内偏移量
物理地址 = 段基址 拼接 段内偏移量
(把逻辑地址的高 x 位地址换成段基址即可得到物理地址)(5)访问目标内存单元
项目 | 分页 | 分段 |
---|---|---|
目的 | 系统管理的需要,提高内存利用率 | 用户的需求,用户编程时需要显式地给出段名 |
长度 | 页的大小固定且由系统决定 | 段的长度却不固定,决定于用户编写的程序 |
地址空间 | 进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址 | 进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址 |
碎片 | 只有内部碎片 | 只有外部碎片 |
假设某进程最多可以分 256 个段,页面大小为 4KB,页表有 210 个页表项。
将进程按逻辑模块分段,再将各段分页(各段均从 0 页开始),再将内存空间分为大小相同的内存块,将各页面分别装入各内存块中。假设逻辑地址空间如下:
段号 0(程序段 10KB) |
---|
0 号页(4KB) |
1 号页(4KB) |
2 号页(2KB) |
段号 1(数据段 6KB) |
---|
0 号页(4KB) |
1 号页(2KB) |
段号 2(堆栈段 2KB) |
---|
0 号页(2KB) |
假设各个段及其页表映射到物理地址空间的位置如下:
页框 | 内容 |
---|---|
页框号 0 | 程序段(1) 4KB |
页框号 1 | 无 |
页框号 2 | 程序段(3) 2KB |
页框号 3 | 无 |
… | … |
页框号 7 | 数据段(2) 2KB |
页框号 8 | 程序段(2) 4KB |
页框号 9 | 数据段(1) 4KB |
… | … |
页框号 15 | 堆栈段 2KB |
… | … |
页框号 25 | 程序段的页表 4KB |
页框号 26 | 数据段的页表 4KB |
页框号 27 | 堆栈段的页表 4KB |
… | … |
每个进程只有一张段表,根据上面的映射情况,段表中存储如下:
(段号) | 页表长度 | 页表存放块号 |
---|---|---|
(0) | 3 | 25 |
(1) | 2 | 26 |
(2) | 1 | 27 |
… | … | … |
每个段都有自己的页表,根据上面的映射情况,这三个段的页表中存储如下:
(页号) | 页框号 |
---|---|
(0) | 0 |
(1) | 8 |
(2) | 2 |
… | … |
(页号) | 页框号 |
---|---|
(0) | 9 |
(1) | 7 |
… | … |
(页号) | 页框号 |
---|---|
(0) | 15 |
… | … |
(假设某进程最多可以分 256 个段,页面大小为 4KB,页表有 210 个页表项)
逻辑地址结构:由段号、页号、页内地址(页内偏移量)组成。
段号 | 页号 | 页内偏移量 |
---|---|---|
8b | 10b | 12b |
物理地址结构:
(假设访问程序段的 9KB 处,则对应 2 号页,2 号页框)
物理页号 | 页内偏移量 |
---|---|
2*4K=8K | 12b |
(1)根据逻辑地址得到段号、页号、页内偏移量
【注】也可引入快表机构,用段号和页号作为查询快表的关键字。若快表命中则仅需一次访存
(2)判断段号是否越界:若段号 ≥ 段表长度
,发生越界中断,立即终止;若段号 < 段表长度
,则没有越界
(3)查询段表:通过段表寄存器,找到段表,找到对应的段表项,得到页表存放块号(即存放页表的起始页号)(第一次访存)
(4)检查页号是否越界:若页号 ≥ 页表长度
,发生越界中断,立即终止;若页号 < 页表长度
,则没有越界
(5)根据页表存放块号、页号查询页表,找到对应页表项:页表项地址 = 存放页表的起始页号*页面大小 + 页号*页表项长度
(第二次访存)
(6)根据内存块号、页内偏移量得到最终的物理地址:内存块号 拼接 页内偏移量
(7)访问目标内存单元(第三次访存)