基本分段存储管理 (Segmentation)
我们来深入探讨另一种重要的非连续分配方式——分段存储管理 (Segmentation)。如果说分页是基于物理尺寸的“一刀切”,那么分段就是基于程序逻辑的“智能组合”。
为了理解分段,我们使用一个全新的比喻:你是一位剧作家,正在创作一部多幕话剧剧本。
- 进程:整部话剧的完整剧本。
- 逻辑段:剧本中逻辑上独立的组成部分,如**“第一幕”、“第二幕”、“演员列表”、“道具清单”**等。
- 内存:一个巨大的、有多层搁板的文件柜,用来存放剧本的各个部分。
- 分段存储:一种整理剧本手稿的方法。
1. 分段的概念:按“逻辑功能”组织剧本
在分页管理中,我们会不分青红皂白地把整部剧本按固定页数(如每10页)切成一沓一沓。这种方法很机械,可能导致“第一幕”的结尾和“第二幕”的开头被分在同一沓纸里。
分段管理则完全不同,它完全尊重剧本的内在逻辑结构:
- “第一幕” 的所有手稿,整理成一沓,形成“第一幕段”。
- “第二幕” 的所有手稿,整理成另一沓,形成“第二幕段”。
- “演员列表” 和 “道具清单” 也各自形成独立的段。
核心特点:
- 逻辑单位:每个“段”都是一个逻辑上完整的单元。比如一个完整的函数、一个数组、一个代码块或数据区。
- 大小不一:“第一幕段”可能很长,“演员列表段”可能就一页纸。每个段的长度是不固定的,取决于其逻辑内容的大小。
- 程序员可见:作为剧作家(程序员),你非常清楚地知道剧本有“第一幕”、“第二幕”等几个部分,你在创作和组织时,就是以这些段名为单位来思考的。
- 离散存放:“第一幕段”可以放在文件柜的顶层,“第二幕段”可以放在底层。每个段在文件柜里各自占据一块连续的空间,但段与段之间可以不相邻。
2. 逻辑地址结构:从“剧本第几页”到“第几幕的第几行”
现在,导演(CPU)想要排练剧本里的某句台词,他对助理(地址变换机构)下达指令的方式也变了。他不会说“我要剧本的第5000行”,而是说:“我要 ‘第二幕’(段号) 的第325行 (段内地址)”。
所以,分段管理的逻辑地址是二维的:
逻辑地址 = (段号 S, 段内地址/偏移量 W)
- 段号 (S):指定是哪个逻辑段(比如“第一幕”、“道具清单”)。
- 段内地址 (W):指定在该段内的偏移位置(比如第几行、第几个字节)。
3. 段表:剧本的“内容索引卡”
为了能快速找到每个幕的剧本手稿被放在了文件柜的哪个位置,我们在文件柜的抽屉里放了一套索引卡片(段表, Segment Table)。
- 段表的作用:记录每个逻辑段到物理内存位置的映射。
- 段表项的构成:每一张索引卡片(段表项)都对应一个段(如“第一幕”),上面记录了三条关键信息:
- 段号:这张卡片是关于哪个段的(通常是隐含的,作为数组下标)。
- 段长 (Limit):这个段有多厚/多长(有多少行/字节)。这是必须的,因为每个段大小不一,需要用它来检查访问是否越界。
- 段基址 (Base):这个段在文件柜里是从哪个位置开始放的(在内存中的起始物理地址)。
4. 地址变换过程:图书管理员的“两步安全检查”
当导演(CPU)需要某句台词时,图书管理员(地址变换机构)会进行一次非常严谨的查找,包含两次重要的安全检查。
导演发出指令:“我要找段号为S
,段内地址为W
的内容!”
-
第一步:检查段号是否合法(是否存在这一幕?)
- 管理员先查看段号
S
,然后对比段表长度寄存器 (STLR)(记录了总共有多少个段/幕)。 - 如果
S >= 段表长度
,说明导演在找一个不存在的幕(比如“第五幕”),立刻“报警”(地址越界中断)。
- 管理员先查看段号
-
第二步:查索引卡,找段的位置和长度
- 段号合法后,管理员根据段表起始地址寄存器 (STBR) 和段号
S
,计算出对应索引卡片(段表项)的位置 (STBR + S * 段表项大小
),第一次访问内存,取出这张卡片。 - 从卡片上读出两个信息:段长
L
和 段基址B
。
- 段号合法后,管理员根据段表起始地址寄存器 (STBR) 和段号
-
第三步:检查段内地址是否合法(台词是否超出了这一幕的范围?)
- 这是第二次安全检查。管理员会检查导演想要的行号
W
是否超过了这一幕的总长度L
。 - 如果
W >= 段长 L
,说明想找的台词超出了这一幕的剧本范围,同样“报警”(地址越界中断)。
- 这是第二次安全检查。管理员会检查导演想要的行号
-
第四步:计算最终物理地址
- 所有检查都通过后,管理员用该幕剧本的起始位置
B
和想要的行号W
相加,得到最终在文件柜里的物理位置。 物理地址 = 段基址 B + 段内地址 W
- 然后第二次访问内存,从这个位置取出那句台词交给导演。
- 所有检查都通过后,管理员用该幕剧本的起始位置
5. 分段 vs. 分页:一场深刻的哲学对决
这是操作系统面试和考试中永恒的经典问题。
对比维度 | 分页 (Paging) | 分段 (Segmentation) | 比喻 |
---|---|---|---|
单位性质 | 物理单位 | 逻辑单位 | 机械地每10页切一沓 vs. 按幕/章/节划分 |
划分依据 | 系统固定大小 | 程序自身逻辑结构 | 机器说了算 vs. 程序员/编译器说了算 |
大小 | 固定 (由系统决定) | 不固定 (由逻辑段决定) | 每沓纸页数一样 vs. 每幕剧本长度不同 |
对用户可见性 | 透明 (不可见) | 可见 | 用户感觉不到分页 vs. 用户需按段名组织程序 |
地址空间 | 一维 | 二维 | 只需要一个线性地址 vs. 需要(段号,段内地址) |
碎片 | 内部碎片 (最后一页不满) | 外部碎片 (段间的小空闲区) | 最后一沓纸的空白页 vs. 文件柜搁板上的零散空隙 |
共享与保护 | 不方便,粒度粗 | 方便,粒度细 | 共享一沓无意义的纸 vs. 共享整个“演员列表”段 |
核心区别:
- 分页的目的是为了提高内存利用率,满足操作系统自身的管理需求。它对用户是完全透明的,用户感知不到自己的程序被“肢解”了。分页是面向机器的。
- 分段的目的是为了更好地满足用户的编程需求。它使得程序可以按逻辑模块组织,便于分别编译、共享和保护。用户在编程时,是以“段”为单位来思考的。分段是面向用户/程序员的。
必会题与详解
题目一:在分段存储管理中,地址变换过程需要进行两次合法性检查,请问是哪两次?它们分别是为了防止什么问题?
答案详解:
需要进行以下两次合法性检查:
-
段号越界检查:将逻辑地址中的段号
S
与段表长度寄存器 (STLR) 中的值进行比较。- 目的:是为了防止进程访问一个不存在的逻辑段。比如一个进程只定义了3个段(段号0,1,2),但它试图访问段号为3的段,这个检查就会捕获到这个非法访问。
-
段内地址越界检查:将逻辑地址中的段内地址
W
与从段表中查出的该段的**段长L
**进行比较。- 目的:是为了防止进程访问超出其自身逻辑段边界的内存区域。比如一个函数(段)只有100个字节长,但程序试图访问该段的第101个字节,这个检查就会捕获到这个错误,从而保护了其他段或进程的内存空间不被破坏。
题目二:为什么说分段管理比分页管理更有利于实现程序的共享和保护?
答案详解:
因为段是信息的逻辑单位,而页是物理单位。
-
共享 (Sharing):
- 在分段系统中,一个逻辑上完整的单位,如一个函数库、一个公用数据区,可以被定义为一个独立的段。当多个进程需要共享这个函数库时,只需要在它们各自的段表中,都增加一个指向这个物理内存段的段表项即可。这种共享是有意义的、清晰的。
- 而在分页系统中,一个逻辑上完整的函数可能会被切割到两个或多个页面中。要共享这个函数,就需要共享多个毫无逻辑关联的页面,管理起来非常困难和不直观。
-
保护 (Protection):
- 由于段是逻辑单位,我们可以很方便地对不同的段赋予不同的访问权限。例如,可以把代码段设置为“只读”,数据段设置为“可读可写”,而另一个敏感数据段则设置为“仅限内核访问”。这种基于逻辑意义的保护非常自然和有效。
- 而在分页系统中,对一页内存设置保护权限,但这页内存里可能既包含了一部分代码,又包含了一部分数据,很难进行精细化的、有意义的权限控制。
题目三:一个分段系统的逻辑地址由16位段号和16位段内地址组成。请问,一个进程最多可以有多少个段?每个段的最大长度是多少?
答案详解:
-
最大段数:
- 段号由16位二进制数表示。
- 16位可以表示
2^16
个不同的值(从0到2^16 - 1
)。 - 因此,一个进程最多可以拥有 2^16 个段,即 65536 个段。
-
每段的最大长度:
- 段内地址由16位二进制数表示。
- 16位可以表示
2^16
个不同的偏移量(从0到2^16 - 1
)。 - 假设系统是按字节编址的,那么每个段的最大长度就是 2^16 字节(B)。
2^16 B = 2^6 * 2^10 B = 64 * 1 KB = 64 KB
。- 因此,每个段的最大长度是 64KB。