简单易懂,段页式管理
段页式管理 (Segmented Paging)。这个方案试图“鱼与熊掌兼得”,结合分段和分页的优点,打造一个既方便用户编程,又高效利用内存的终极方案。
我们继续沿用那个创作多幕话剧剧本的比喻,但这次我们引入了更现代化的工具。
- 进程:一部多幕话剧的完整剧本。
- 分段:剧本被划分为**“第一幕”、“第二幕”、“演员表”**等逻辑单元。
- 内存:一本标准规格的活页本和无数可以随意粘贴的便签纸。
- 分页:一种新的整理手稿的方法。
1. 分页与分段的“烦恼”
在引入段页式之前,我们先回顾一下两种方案各自的痛点:
- 纯分段管理的烦恼:
- 比喻:你把“第一幕”的所有手稿(几十页)整理成了一大沓。现在你想把它放进文件柜(内存),就必须找到一个能放下这一大沓纸的、连续的大空间。如果文件柜里只有零散的空位,即使总空间足够,你也放不进去(外部碎片问题)。
- 纯分页管理的烦恼:
- 比喻:你把整部剧本不管三七二十一,每页纸都单独编号,然后随意地插进活页本里。这样做空间利用率很高。但如果你想把“第一幕”整个共享给另一个剧组,或者想把“演员表”设置为“只读”,就非常困难。因为“第一幕”的内容可能散布在几十张不连续的活页纸上,你很难对它进行一个整体的操作(不方便按逻辑模块共享和保护)。
2. 段页式管理的“天才构想”:先分段,再分页
段页式管理的想法非常直接:我们把这两种方法结合起来!
- 第一步:先分段 (按逻辑)
- 作为剧作家(程序员),你还是按照剧本的逻辑结构,把手稿整理成**“第一幕”、“第二幕”**等独立的“段”。
- 第二步:再分页 (按物理)
- 接下来,你不再把每一“段”手稿看成一个整体,而是把每一个段内部的纸张,再按照活页本的标准尺寸,进行分页。
- 比如,“第一幕”有35页内容,就被分成了4个页面(假设每10页一“页”,最后一页有内部碎片)。“演员表”只有2页内容,就只占1个页面。
最终结果:整部话剧(进程)被划分成了若干个逻辑段,而每个逻辑段又由若干个物理页面组成。现在,内存分配的基本单位变成了页面。
如何存放?
- “第一幕”的第0页,可以放在活页本的第5张纸上。
- “第一幕”的第1页,可以放在活页本的第28张纸上。
- “第二幕”的第0页,可以放在活页本的第11张纸上。
- ... ...
- 核心优势:我们再也不需要为“第一幕”这个大段寻找连续空间了!它的所有页面都可以离散地存放在任何空闲的页框中。这就消除了外部碎片。同时,我们又保留了“段”这个逻辑概念,方便进行共享和保护。
3. 地址与表的双重结构
为了支持这种“先找段,再找页”的模式,我们的地址和映射表也变成了双重结构。
逻辑地址结构
逻辑地址 = (段号 S, 页号 P, 页内偏移量 W)
- 段号 S:告诉系统,我要找的是哪个逻辑段(比如“第一幕”)。
- 页号 P:告诉系统,我要找的是这个段里的第几页。
- 页内偏移量 W:告诉系统,我要找的是这一页里的第几个字。
段表与页表
- 段表 (Segment Table):
- 系统为每个进程维护一个段表。
- 段表项里不再是段的物理基址了,因为段是离散存放的。它现在记录的是:该段对应的“页表”被存放在哪里。
- 段表项内容:
段号(隐含), 页表长度, 页表存放的起始地址
。
- 页表 (Page Table): 页表:
- 每个段都有一个自己独立的页表。
- 这个页表的结构和基本分页里的一样,记录了该段内的每个页面到物理块号的映射。
- 页表项内容:
页号(隐含), 物理块号
。
4. 地址变换过程:三步走的“寻宝之旅”
现在,当CPU要访问一个逻辑地址时,地址变换机构(智能秘书)需要进行一次三步查找,这比纯分段或纯分页都更复杂。
第一步:查段表,找页表
- 根据逻辑地址中的段号
S
,查询段表。 - (安全检查:段号是否越界?)
- 从段表项中,得到该段对应的页表的起始地址和页表长度。
- 第一次访存:访问段表。
- 根据逻辑地址中的段号
第二步:查页表,找页面
- 根据上一步得到的页表地址,和逻辑地址中的页号
P
,查询这个特定的页表。 - (安全检查:页号是否越界?
P
是否小于页表长度?) - 从页表项中,得到存放目标数据的那个页面的物理块号
b
。 - 第二次访存:访问页表。
- 根据上一步得到的页表地址,和逻辑地址中的页号
第三步:拼接地址,访问数据
- 将上一步得到的物理块号
b
和逻辑地址中的**页内偏移量W
**组合,形成最终的物理地址。 - 第三次访存:访问真正的目标数据。
- 将上一步得到的物理块号
结论:在没有快表的情况下,段页式管理访问一个逻辑地址,需要三次内存访问,性能开销很大。因此,快表对于段页式系统来说,几乎是必需品。如果快表命中,就能把三次访存减少到一次。
必会题与详解
题目一:段页式存储管理是如何结合分段和分页的优点的?请具体说明。
答案详解:
段页式管理通过“先分段,再分页”的两级策略,巧妙地结合了两种方式的优点:
继承了分段的优点(方便用户,易于共享和保护):
- 它在顶层仍然按照程序的逻辑结构来划分内存,保留了“段”这个逻辑单位。因此,程序员可以方便地对一个逻辑上完整的模块(如一个函数库段、一个数据段)进行整体操作,比如将其设置为“只读”或在多个进程间“共享”。这满足了用户编程和管理的方便性。
利用了分页的优点(提高内存利用率,无外部碎片):
- 它将每个逻辑段进一步划分为固定大小的页面,内存分配以页面为单位。这意味着不再需要为大小不一的段寻找连续的物理内存空间。只要内存中有足够数量的空闲页框(无论是否连续),段就可以被装入。这完全消除了外部碎片,极大地提高了内存的利用率。
题目二:在一个段页式系统中,地址转换需要进行几次内存访问(假设没有快表)?如果引入快表且命中,又需要几次?
答案详解:
没有快表的情况:需要 3次 内存访问。
- 第一次:访问段表,根据段号找到对应段的页表的物理地址。
- 第二次:访问页表,根据页号找到目标页面所在的物理块号。
- 第三次:访问目标数据单元,根据物理块号和页内偏移量组成的物理地址,进行真正的读写操作。
引入快表且命中的情况:只需要 1次 内存访问。
- 快表中直接缓存了“逻辑地址(段号+页号) -> 物理块号”的最终映射结果。当快表命中时,CPU直接从快表获取物理块号,然后与页内偏移量组合成物理地址,直接访问内存中的目标数据。整个地址转换过程在快表中完成,无需访问内存中的段表和页表。
题目三:在段页式管理中,一个进程有多少张段表?有多少张页表?
答案详解:
在一个段页式管理的系统中:
段表:每个进程只有一张段表。这张段表是该进程所有逻辑段的“总目录”,记录了该进程被划分成了哪些段,以及每个段对应的页表在哪里。
页表:一个进程有多少个段,就有多少张页表。每个逻辑段都有其自己独立的一张页表,用于管理该段内部的页面到物理页框的映射。例如,一个进程有代码段、数据段和栈段,那么它就会有三张独立的页表。