Skip to content

基本分段

2026 大纲 三(一)4 段式管理

分页解决了内存的问题,却没解决程序员的问题

分页把外碎片消灭得很干净,从内存利用率这个角度看几乎无可挑剔。 但它有一个从头到尾都没考虑过的东西:程序本身是有结构的。

一个程序里有主函数、有子程序、有全局数据、有栈。分页按固定大小 4 KB 一刀切下去, 一个函数可能横跨三个页面,一个页面里可能装着半个函数加半个数组。 于是麻烦就来了——想把某个函数共享给另一个进程,或者给某段数据单独设成只读, 都没法干净地表达,因为"函数"和"数据"这些边界在分页眼里根本不存在。

分段换了个切法:按用户自己的逻辑结构切,一个函数一段、一个数组一段, 每段多长就多长,不强求等大。

这一个改动带来的五项便利,全部源自同一件事——段的边界与程序的逻辑边界重合了。 方便编程(按名字寻址)、信息共享(共享一个逻辑单位)、信息保护(按逻辑单位设权限)、 动态增长(数组变长就改这一段)、动态链接(按段装入),一条都不例外。

所以分段和分页目的根本不同:分页是为了提高内存利用率,是系统的需要; 分段是为了满足编程者,是用户的需要。这句话是两者一切差别的根源—— 包括为什么分段要查两次越界、为什么分段的物理地址是加法而分页要乘、 以及为什么外碎片又回来了。

交互可视化

加载可视化中...

一、分段的五项便利都来自同一件事

分页对用户完全透明,分段却要求用户参与划分,这是因为两者的动力根本不同:

  1. 程序天然由主程序段、子程序段、数据段、栈段构成,每段独立编址、从 0 开始 ⇒ 方便编程,程序员用"段名 + 段内地址"指代位置,不必关心整个程序的线性布局;
  2. 段是完整的逻辑单位,一个函数、一张表就是一个段 ⇒ 共享它只需在两个进程的段表里各放一个表项 ⇒ 信息共享方便
  3. 保护也以逻辑单位为基础("函数 A 只允许执行")⇒ 在段上打一个标志即可 ⇒ 信息保护方便
  4. 数据段的大小事先无法确知、运行中还会增长 ⇒ 段长本来就不固定,让它往后延伸即可 ⇒ 支持动态增长
  5. 动态链接以目标程序(即段)为基本单位 ⇒ 分段天然适合运行时动态链接

还有一处结构性差别:分页的页号是硬件从一个线性地址里切出来的,用户只给一个数;分段的段号与段内偏移是用户分别给出的两个量。所以分页是一维地址空间,分段是二维。

二、段表

进程的各段离散地装入内存中不同的分区,每段占一片连续空间。系统为每个进程建立一张段表,每段占一个表项:

段号段基址(内存起始地址)段长存取控制字段
040K10K只执行
180K20K读/写
2120K15K只读

段表可以放在一组寄存器里以提高变换速度,但实际系统一般把它放在内存中——和页表一样,段表本身也要占内存,也需要一次访存才能读到。

三、地址变换

两次越界检查各自防的东西不同:第一次防的是"段号超范围时算出来的段表项地址落到别的进程的段表甚至别的数据上";第二次防的是"偏移跑出本段",而本段的上界只能查到段表项之后才知道。

三个逻辑地址(含两种越界)逐步变换的完整走查(想看两次检查怎么短路、边界值怎么判时展开)
逻辑地址 = 段号 S + 段内偏移 W

|← 段号 S →|← 段内偏移 W →|

与分页最大的不同:各段长度不固定,所以段内偏移的位数不代表"实际段长",只代表"段长的上界"。

某分段系统当前运行进程的段表长度 TL = 4,段表如下(地址单位为字节):

段号段基址段长
06000800
1120001500
23000600
320000400

(2, 480)—— 合法

  1. 段号越界检查:S=2TL=42<4 ✓。先做这一步是因为段基址和段长都要从段表第 2 项里取,段号非法时这一项根本不属于本进程,后面所有计算都会读到错误数据。
  2. 查段表第 2 项:段基址 =3000,段长 SL=600
  3. 段内越界检查:W=480<600 ✓。这一步不能提前——SL=600 是刚从段表里读出来的,第 1 步时还不知道。
  4. 物理地址 =3000+480=3480。是加法不是乘法:段基址本身就是物理地址,段内偏移直接累加。

(1, 1500)—— 段内越界

段号 1<4 ✓;查得段基址 =12000SL=1500;而 15001500段内越界中断。段内偏移从 0 开始编号,段长 1500 的段合法偏移是 0~1499,偏移等于段长就已经越界

(4, 100)—— 段号越界

S=4TL=444段号越界中断,变换到此终止。合法段号是 0~3。既然连段表项都不该读,就不存在"再检查段内偏移"这回事——两次越界检查是短路的。

四、段的共享与保护

可重入代码(Reentrant Code)又称纯代码(Pure Code),允许多个进程同时访问,绝对不允许它在执行中被修改。现实中的代码难免要用计数器、指针、局部变量,做法是把这些会变的部分拷贝一份到各进程私有的局部数据区,进程只改自己那一份,共享的代码段全程只读。

顺着这条前提就能解释一个常见困惑:"共享数据段"为什么比"共享代码段"麻烦得多——数据本来就是要被写的,一写就不再可重入,必须再叠加同步互斥机制才行。

系统再配一张共享段表做记账,每个共享段占一个表项:

字段作用为什么必须有
段号、段长、内存始址定位这个共享段——
状态(存在)位、外存始址该段是否已在内存、不在时去哪里取共享段也可能被换出
共享进程计数 count当前有多少个进程在共享它某进程释放时不能立即回收,要先 count−1,减到 0 才真正回收物理内存并删除表项
存取控制字段对不同进程赋予不同权限文件主可读写,其他进程可能只读甚至只执行
各共享进程的进程名与在本进程中的段号建立进程视角的映射同一共享段在不同进程中可以有不同的段号

保护的第二层存取控制检查以段为单位,三种权限里"只执行"最容易读漏:它只允许调用该段去执行,不准读,更不允许写。这项检查基于硬件实现,攻击者很难修改存取控制字段;对共享段尤其重要——同一个段对不同进程要赋予不同权限,才能既安全又够用。

保护的第三层环保护机构规定低编号的环特权高:操作系统内核在 0 号环,重要的系统服务占中间环,一般应用程序在外环。两条规则方向相反,原因是:往内拿数据会泄露内核信息,往外调服务则会让内核去执行不可信代码。

五、分页 vs 分段

比较维度分页分段判断依据
目的提高内存利用率,消除外碎片满足用户编程与共享/保护的需要谁提出的需求:系统 / 用户
划分依据系统自动按固定大小划分用户按逻辑结构划分划分时看不看内容的语义
单位大小固定,由硬件决定不固定,由程序内容决定表项里需不需要记长度
表项内容页框号(+ 控制位)段基址 + 段长(+ 控制位)同上
越界检查次数1 次(页号 < 页表长度)2 次(段号 < 段表长度、偏移 < 段长)单位是否等大
物理地址合成页框号 × 页面大小 + 偏移段基址 + 偏移表里存的是块号还是地址
地址空间一维(用户只给一个线性地址)二维(用户给段号和段内偏移两个量)用户要给出几个量
碎片有内碎片,无外碎片无内碎片,有外碎片分配单位是否等大
共享不方便(一个逻辑单位可能跨多页,首尾页还混着别的内容)方便(一个段就是一个逻辑单位,一个表项即可)边界与逻辑边界是否重合
保护只能按页设权限,同一页内属性可能应当不同按段设权限,与逻辑单位天然对齐同上
动态增长不支持(页大小固定,地址空间线性)支持(改段长,必要时搬段并改段基址)单位长度可不可变
对用户透明可见(用户知道段的存在)用户要不要参与划分

动态增长那一行的"不支持"要说清理由,否则只是个孤立结论:分页的页面大小固定,"增长"只能表现为占用更多页面;而进程的逻辑地址空间是一条连续的线性地址,一段往后长就会直接顶到下一段的地址上。 分段则没有这个约束——段长本来就不固定,每段又单独占一片连续区,增长只需改段表项里的段长字段;后面若已被占用,就把整段搬到更大的空闲区并更新段基址,而这一步之所以可行,同样是因为地址变换发生在运行时,搬家只改段表项、不必改代码里的任何地址。

共享那一行值得展开一句:同一个可重入代码段在分页系统里要为每个共享进程建立几十个页表项,而且代码的起止位置若没落在页边界上,首尾两页还会混入保护属性完全不同的内容,共享就无法干净地实现;分段只需要一个段表项判据:共享是否方便,取决于划分依据是逻辑的还是物理的。

考点速记

  1. 分段按用户的逻辑结构划分,动力是满足编程者的需要而非内存利用率——这是它与分页一切差别的根源。五项便利(方便编程、信息共享、信息保护、动态增长、动态链接)都源自"段的边界与逻辑边界重合"。
  2. 地址结构:逻辑地址 = 段号 S + 段内偏移 W,各段长度不固定最大段数=2段号位数每段最大长度=2偏移位数——两者互相挤占同一份地址位,段号多给一位,每段最大长度就减半。
  3. 段表项 = 段基址 + 段长 + 存取控制字段。比页表项多出来的"段长",正是"分段查两次越界"的全部来源。
  4. 两次越界检查的上界来源不同:第一次 S<TL,上界来自段表寄存器;第二次 W<SL,上界来自该段自己的段表项(现查现比,因为每段长度都不一样)。
  5. 顺序不能颠倒,且是短路的SL 就存在段表项里,不先过第一次检查就拿不到它——这是数据依赖不是约定。段号一旦越界立即终止,不存在"再看看偏移"。
  6. 等于就已经越界:编号都从 0 开始,最大合法偏移 = 段长 1、最大合法段号 =TL1大于等于即越界
  7. 物理地址 = 段基址 + W,是加法。段表里存的段基址本身就是物理地址;分页要乘,是因为页表里存的是页框号(第几块)而不是地址。
  8. 段共享三个前提:① 被共享的必须是可重入代码;② 靠共享段表记账,含共享进程计数 count,减到 0 才真正回收;③ 同一共享段在不同进程中可以有不同段号——共享指段基址落到同一片物理内存,不是逻辑段号相同。
  9. 保护分三层:越界检查 → 存取控制检查(由硬件实现)→ 环保护机构。环保护两条规则方向相反:数据只能往外拿,服务只能往内调;低编号环特权高。
  10. 分段有外碎片、无内碎片(与分页正好相反)。

这一节在真题里被考过的形式

题量不大但三道题恰好各考一类,覆盖面很整齐——一道算地址位数、一道走地址变换、一道判共享。

  • 给地址总长和段号位数,问最大段长(2009-27)。地址 32 位、段号占 8 位 ⇒ 段内偏移 24 位 ⇒ 最大段长 224 字节。判据就是速记第二条:段号和偏移瓜分同一份地址位。⚠️ 干扰项 28 是把段号位数当成了偏移位数。
  • 给一张段表,问某个逻辑地址转换的结果(2016-28)。访问段号 2、段内地址 400,而 2 号段段长只有 300 ⇒ 越界异常。⚠️ 这道题把四种可能的结果都摆成了选项(缺段、正常地址、越权、越界),必须按顺序逐关走:先查段号是否越界 → 再查偏移是否越界 → 再查状态位是否在内存 → 最后查权限。本题在第二关就停了,后面的状态和权限根本不用看。顺手算出 4000+400=4400 选 B 是最典型的错法。
  • 判断共享段的四条叙述,选错误的(2019-28)。错项是"段 S 在 P1 和 P2 中应该具有相同的段号"。⚠️ 这就是速记第八条第三点——共享的是内容不是编号。另三条都对:物理内存只保存一份、共享同一个共享段表表项、count 减到 0 才回收。

复习优先级必须拿满,题型固定且都不难。 关键是把地址变换那四关的顺序走熟 (段号越界 → 偏移越界 → 是否在内存 → 权限),以及记住"分段是加法、分页是乘法"的原因。 分页与分段的对比表几乎每年都以某种形式出现在选项里,第一条和第十条要背清楚。

易错:地址变换时跳过越界检查直接算 段基址 + 偏移。必须先查两次越界,越界时不存在"算出来的地址"。

易错:认为偏移等于段长时还合法。编号从 0 起,最大合法偏移 = 段长 − 1,等于就越界。

易错:把两次越界检查的上界搞混。S 比的是段表寄存器里的段表长度,W 比的是该段段表项里的段长。

易错:认为共享段在各进程中必须段号相同。共享的是内容不是编号,段号可以不同。

易错:合成物理地址时对分段也做乘法。分段是段基址 + 偏移,因为段表里存的就是物理地址。

易错:认为分段既无内碎片又无外碎片。分段有外碎片(段长不等、按需切分),只是没有内碎片。

易错:把环保护的两条规则记成同一个方向。数据往外拿、服务往内调

教材出处
  • 汤小丹《计算机操作系统》4.6.1 分段存储管理方式的引入(方便编程、信息共享、信息保护、动态增长、动态链接五项需求),p145–p146
  • 同上 4.6.2 分段系统的基本原理(地址结构:最长 64K 个段、每段最大 64KB;段表;段表寄存器存段表始址与段表长度 TL;两次越界检查;联想存储器),p146–p148
  • 同上 4.6.2「分页和分段的主要区别」(物理单位与逻辑单位、大小固定与否、一维与二维),p148
  • 同上 4.6.3 信息共享(分页共享 editor 需 40 个页表项 vs 分段仅需 1 个段表项;可重入代码与私有局部数据区),p148–p150
  • 同上 5.5.2 分段的共享与保护(共享段表、共享进程计数 count、存取控制字段、越界检查、环保护机构的两条规则),p175–p176

相关知识

基本分页(地址变换)段页式管理同步与互斥的基本概念

真题练习

相关真题(3题)