AIGC标识 2.3 存储管理

操作系统存储管理全解析:从分区到段页式


一、存储器层次结构

计算机存储系统按照速度从快到慢、容量从小到大、成本从高到低,构成如下层次结构:

寄存器 → 高速缓存(Cache) → 主存(内存) → 外存(磁盘等)

CPU 可以直接访问寄存器和高速缓存,通过地址总线访问主存,而外存中的数据必须先调入主存才能被 CPU 使用。存储管理的核心对象就是主存。

📌 生活类比:把存储层次想象成你书桌上的工作空间——

  • 寄存器 = 你手中正拿着的笔(最快、最小)
  • Cache = 桌面上摊开的常用资料(触手可及)
  • 主存 = 身后的书架(需要转身去拿)
  • 外存 = 隔壁仓库(需要走过去搬运,但能存放海量物品)

二、地址重定位

程序在编译后生成的是逻辑地址(又称相对地址、虚地址),而数据实际存放在主存中的物理地址(又称绝对地址)。地址重定位就是将逻辑地址转换为物理地址的过程。

根据转换时机的不同,分为两种方式:

  • 静态重定位:在程序装入主存时一次性完成所有地址转换。程序一旦装入,其在内存中的位置就不能再移动。
  • 动态重定位:在程序执行过程中,由硬件地址变换机构(如基址寄存器)实时完成地址转换。程序在内存中可以移动,支持共享和保护,是现代操作系统的基础技术。

📌 生活类比:

  • 逻辑地址就像"第3排第5个座位"——这是你票面上的相对编号。
  • 物理地址就像"教学楼B栋3楼12号教室第5把椅子"——这是实际位置。
  • 静态重定位:入场前工作人员就把票面编号全部对应到实际座位,入场后对号入座,不能换座。
  • 动态重定位:入场时只告诉你"你属于A区",到了A区门口再看实时显示屏,告诉你今天A区的具体位置在哪。如果A区临时调整了位置,显示屏会更新,你跟着走就行。

三、分区存储管理

分区存储管理是一种连续分配方式,即为每个进程分配一段连续的内存空间,将进程整体装入后再执行。

3.1 三种分区方式

3.1.1 固定分区(静态分区)

操作系统在初始化时就将主存划分为若干个大小固定的分区。作业按大小装配到合适的分区中。

  • 优点:管理简单,无需复杂的分配算法。
  • 缺点:分区大小固定,与作业实际需求往往不一致,产生内部碎片(分区中未被利用的空间)。

📌 举例:假设内存被固定划分为 4 个分区:10K、20K、30K、40K。现在有一个 12K 的作业需要装入,只能放进 20K 的分区,剩下的 8K 就浪费了——这就是内部碎片。

3.1.2 可变分区(动态分区)

主存空间的分区不在初始化时确定,而是在作业请求装入时,根据作业的实际大小动态划分一个恰好够用的分区。

  • 优点:不存在内部碎片,内存利用率比固定分区高。
  • 缺点:随着作业的装入和退出,主存中会产生大量不连续的小空闲块,产生外部碎片。

📌 举例:内存共 100K,依次装入 20K、30K、25K 三个作业后,又退出了中间 30K 的作业,此时内存布局如下:

[已用20K][空闲30K][已用25K][空闲25K]

空闲总量有 55K,但分散成两块。如果此时来了一个 40K 的作业,虽然总空闲够用,但没有一块连续空间能装下它——这就是外部碎片。

3.1.3 可重定位分区(动态重定位分区)

在可变分区的基础上,增加了紧凑(Compaction)功能。当外部碎片导致无法满足新作业的内存请求时,通过移动所有已分配的分区,使其向内存一端集中,从而将分散的小空闲块合并为一个大空闲块。

由于分区移动后物理地址发生了变化,必须依赖动态重定位技术来更新地址映射,因此称为"可重定位分区"。紧凑操作开销较大(需要暂停进程、移动数据、更新地址),通常只在找不到足够大的空闲分区、但空闲分区总和能满足需求时才触发。

📌 举例:继续上面的场景,内存布局为:

[已用20K][空闲30K][已用25K][空闲25K]

现在来了一个 40K 的作业装不下。触发紧凑操作,把所有已分配区域向左靠拢:

[已用20K][已用25K][空闲55K]

两块碎片合并为 55K 的连续空间,40K 的作业就能顺利装入了。代价是移动数据期间需要暂停所有进程。

3.2 可变分区的分配算法

当系统需要为一个作业分配内存时,需要在空闲分区表中查找合适的空闲块。常见的分配算法如下(假设需要分配 9K 空间):

📌 统一场景:假设当前内存中有以下空闲块(按地址排列):

空闲块A:12K(地址 0~11K)
空闲块B:6K (地址 20~25K)
空闲块C:20K(地址 40~59K)
空闲块D:10K(地址 70~79K)

现在需要分配 9K 空间,各算法的处理结果:

算法 策略 选择结果 特点
首次适应法(First Fit) 按地址从低到高,找第一个 ≥ 9K 的空闲块 选中空闲块A(12K),切出 9K,剩余 3K 简单高效,优先利用低地址空间
最佳适应法(Best Fit) 按大小从小到大排序,找最接近的空闲块 选中空闲块D(10K),切出 9K,剩余 1K 找到与需求最接近的空闲块,但容易留下难以利用的微小碎片(1K)
最差适应法(Worst Fit) 按大小从大到小排序,从最大空闲块中切割 选中空闲块C(20K),切出 9K,剩余 11K 分割后剩余空间仍较大(11K),减少微小碎片的产生
循环首次适应法(Next Fit) 与首次适应法类似,但从上次分配结束的位置继续查找 取决于上次分配到了哪里,不会每次都从A开始 使内存利用更均衡,避免低地址部分被过度切割

四、分页存储管理

4.1 引入背景

分区存储管理(无论是固定分区还是可变分区)都属于连续分配方式,要求每个进程占用一段连续的内存空间。这带来两个严重问题:

  1. 碎片问题:固定分区产生内部碎片,可变分区产生外部碎片,即使紧凑技术可以缓解外部碎片,但开销巨大。
  2. 进程大小受限:当进程所需内存大于系统可用连续空间时,无法将整个进程装入内存运行。

为了解决这些问题,引入了分页存储管理——一种离散分配方式。它将进程的逻辑地址空间和物理内存空间都划分为固定大小的块,进程的各个页可以分散存放在不连续的物理块中,从根本上消除了外部碎片。

📌 生活类比:把一本 200 页的书(进程)放进书架(内存)。分区管理要求你必须找到一段能放下整本书的连续空位,找不到就放不了。分页管理则相当于把书拆成一页一页的散页,每页可以夹在书架上任何位置,只要有一页的空位就行——大大降低了"放不下"的概率。

4.2 基本原理

  • 将进程的逻辑地址空间划分为大小相等的页(Page),从 0 开始编号。
  • 将物理内存划分为同样大小的物理块(页框,Frame),从 0 开始编号。
  • 通过页表建立逻辑页号到物理块号的映射关系。

📌 举例:假设页面大小为 1K,一个 4K 的进程被分为 4 页(页号 0、1、2、3)。物理内存有 8 个物理块(块号 0~7)。页表可能如下:

页号 物理块号
0 5
1 2
2 7
3 1

可以看到,进程的 4 个页分散存放在物理块 5、2、7、1 中,并不连续,但通过页表可以正确找到每一页的位置。

4.3 地址结构与转换

逻辑地址由两部分组成(页号在高位,页内偏移在低位):

逻辑地址 = 页号(Page Number) + 页内偏移(Page Offset)
物理地址 = 物理块号(Frame Number) + 页内偏移(Page Offset)

地址转换过程:

  1. 从逻辑地址中分离出页号和页内偏移。
  2. 通过页号查询页表,得到对应的物理块号。
  3. 将物理块号与页内偏移拼接,得到物理地址。

注意:页内偏移在逻辑地址和物理地址中是完全相同的,不需要转换。页号与物理块号之间并非按序对应,必须通过查页表来确定映射关系。

📌 具体计算举例:
假设页面大小为 1K(1024字节),逻辑地址为 3200,页表如下:

页号 物理块号
0 5
1 2
2 7
3 1

第一步:求页号和页内偏移

  • 页号 = 3200 ÷ 1024 = 3(整除)
  • 页内偏移 = 3200 mod 1024 = 128

第二步:查页表

  • 页号 3 → 物理块号 1

第三步:计算物理地址

  • 物理地址 = 1 × 1024 + 128 = 1152

所以逻辑地址 3200 对应的物理地址是 1152。

4.4 优缺点

优点 缺点
消除了外部碎片,内存利用率高 存在页内碎片(最后一页可能装不满)
分配和管理简单 需要额外的硬件支持(页表寄存器、MMU)
支持多道程序设计 增加系统开销(查页表的时间)
在虚拟存储中可能产生抖动(Thrashing)现象

📌 页内碎片举例:页面大小 1K,一个 3.5K 的进程需要 4 页。前 3 页装满(3K),第 4 页只装了 0.5K,剩下 0.5K 浪费——这就是页内碎片。它无法避免,但通常很小(平均半页)。

4.5 快表(TLB)

由于每次访存都需要先查页表再访问数据,相当于两次访存,严重影响性能。为此引入了快表(Translation Lookaside Buffer, TLB)来加速地址转换。

快表是一块小容量的相联存储器(Content-Addressable Memory, CAM),集成在 MMU 中,按内容并行查找,速度极快。它存放的是当前访问最频繁的少数活动页面的页表项副本。

地址转换流程(带快表):

  1. CPU 发出逻辑地址,将页号送入快表并行查找。
  2. 快表命中:直接取出物理块号,拼接页内偏移得到物理地址,只需一次访存取数据。
  3. 快表未命中:访问主存中的页表(慢表)获取物理块号,同时将该页表项写入快表,再访问数据,需要两次访存。

📌 生活类比:快表就像你手机通讯录里的常用联系人。打电话时,如果对方在常用联系人里(快表命中),一点就拨出去了;如果不在(快表未命中),就得打开完整通讯录慢慢翻找(查慢表),找到后再拨号,而且下次还会把这个人加到常用联系人里。

由于程序的局部性原理,快表的命中率通常可达 90% 以上,大幅提升了地址转换效率。


五、页面置换算法

当采用请求分页的虚拟存储管理时,如果发生缺页且内存已满,需要选择一个页面换出到外存,腾出空间给新页面。常见的置换算法如下:

📌 统一场景:假设物理内存只有 3 个页框,页面访问序列为:1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5

5.1 最优算法(OPT)

  • 原理:选择未来最长时间内不再被访问的页面予以淘汰。
  • 特点:这是一种理论上的理想算法,需要预知未来的访问序列,实际中无法实现。它的作用是作为性能评价的基准,用来衡量其他算法的优劣。

📌 举例:当前内存中有页面 1、2、3,需要装入页面 4。查看未来访问序列 1, 2, 5, 1, 2, 3, 4, 5:

  • 页面 1:未来第 1 个就会被访问
  • 页面 2:未来第 2 个会被访问
  • 页面 3:未来第 6 个才会被访问(最晚)

OPT 选择淘汰页面 3,因为它在未来最长时间内不会被用到。

5.2 先进先出算法(FIFO)

  • 原理:淘汰最早进入内存的页面(先进先出)。
  • 特点:实现简单,但效率较低。可能淘汰正在频繁使用的页面。
  • Belady 异常:FIFO 可能出现一种反常现象——分配的物理块数增多,缺页率反而升高。这是 FIFO 特有的缺陷。

📌 举例(Belady 异常):访问序列 1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5

  • 分配 3 个页框时,FIFO 缺页 9 次
  • 分配 4 个页框时,FIFO 缺页 10 次

页框多了反而缺页更多!这就是 Belady 异常,FIFO 的"反直觉"缺陷。

5.3 最近最少使用算法(LRU)

  • 原理:淘汰最近最久未被访问的页面。根据局部性原理,过去很少使用的页面,将来大概率也不会被使用。
  • 特点:性能优于 FIFO,接近 OPT,是实践中广泛采用的算法。LRU 属于栈算法,不会产生 Belady 异常。但在极端工作集剧烈变化等场景下,仍有可能出现抖动,只是概率远低于 FIFO。
  • 实现:需要硬件支持,可通过寄存器或栈来记录页面的访问时间。

📌 举例:当前内存中有页面 1、2、3,需要装入页面 4。查看过去的访问历史(从近到远):3, 2, 1

  • 页面 3:最近刚访问过
  • 页面 2:倒数第 2 个访问的
  • 页面 1:最久没访问

LRU 选择淘汰页面 1,因为它最久没被使用。

5.4 淘汰原则

在实际系统中,页面淘汰通常遵循以下优先级:

  1. 优先淘汰最近未被访问的页面。
  2. 在都未被访问的页面中,优先淘汰未被修改的页面(因为未修改的页面换出时无需写回外存,节省 I/O 开销)。

📌 生活类比:想象你的书桌(内存)只能放 3 本书。你不断需要新书:

  • OPT(上帝视角):扔掉未来最久不会翻的书——但你无法预知未来。
  • FIFO:扔掉最早放上桌的书——不管它现在是否还在用。
  • LRU:扔掉最久没翻过的书——根据过去的习惯推测未来,最实用。

六、段式存储管理

6.1 基本原理

段式存储管理将进程的逻辑地址空间按逻辑意义划分为若干个段(Segment),如主程序段、子程序段、数据段、栈段等。每个段有独立的段名(用段号表示)和段内地址。

与分页不同,段的物理大小不固定,由段的逻辑内容长度决定。

📌 生活类比:分页就像把一本书按固定页数拆成散页(每页一样大),而分段就像按章节拆分——第一章可能 10 页,第二章可能 50 页,每章大小不同,但每章内容自成一体。

6.2 地址结构与转换

段式地址由段号和段内偏移组成:

物理地址 = 段基址 + 段内偏移

段表中的每个表项包含两个关键属性:

  • 段基址(Base):该段在主存中的起始物理地址。
  • 段长(Limit):该段的长度,用于越界保护——段内偏移不能超过段长,否则产生越界错误。

📌 具体计算举例:
假设段表如下:

段号 段基址 段长
0 1000 400
1 3000 200
2 5000 600

逻辑地址为 (段号=1, 段内偏移=150):

  1. 查段表:段号 1 → 段基址 3000,段长 200
  2. 越界检查:150 < 200 ✅ 合法
  3. 物理地址 = 3000 + 150 = 3150

若逻辑地址为 (段号=1, 段内偏移=250):

  1. 查段表:段号 1 → 段长 200
  2. 越界检查:250 > 200 ❌ 越界错误!

6.3 优缺点

优点 缺点
符合程序逻辑结构,便于编程 容易产生外部碎片(段大小不等)
各段独立,修改互不影响 内存利用率相对较低
便于实现段的共享和保护 地址转换开销较大
支持动态链接

七、分页与分段的核心区别

对比维度 分页存储管理 分段存储管理
划分依据 按物理空间划分,对用户透明 按逻辑意义划分,用户可见
大小 每页大小固定 每段大小不等
主要目的 提高内存利用率,消除外部碎片 满足用户编程、共享、保护的需求
碎片类型 仅有页内碎片(内部碎片) 容易产生外部碎片
地址空间 一维地址空间 二维地址空间(段号 + 段内偏移)

📌 一句话总结:分页是系统视角——为了高效利用内存而把空间切碎;分段是用户视角——为了符合程序逻辑而把代码分类。


八、段页式存储管理

8.1 基本原理

段页式存储管理结合了分段和分页的优点:先分段,再对每个段进行分页。

  • 用户程序首先按逻辑结构分为若干段。
  • 每个段内部再按固定大小分为若干页。
  • 物理内存被划分为固定大小的物理块。

地址结构为三维:(段号,段内页号,页内偏移)

📌 生活类比:想象一个图书馆——

  • 段 = 不同类别的书架区(文学区、科学区、历史区……)
  • 页 = 每个书架区内的固定编号格子
  • 物理块 = 仓库里的实际储物格

你要找一本书,先确定它在哪个区(段号),再确定在第几个格子(页号),最后知道它在仓库哪个储物格里(物理块号)。

8.2 地址转换流程

  1. 根据段号查段表,得到该段的页表起始地址。
  2. 根据段内页号查该段的页表,得到物理块号。
  3. 将物理块号与页内偏移拼接,得到物理地址。

📌 具体计算举例:
假设段表如下:

段号 页表起始地址 页表长度
0 500 3
1 800 2

段号 0 的页表(存放在主存 500 起始的位置):

页号 物理块号
0 4
1 7
2 2

逻辑地址为 (段号=0, 页号=1, 页内偏移=200),页面大小 1K:

  1. 查段表:段号 0 → 页表起始地址 500
  2. 查页表:页号 1 → 物理块号 7
  3. 物理地址 = 7 × 1024 + 200 = 7384

8.3 优缺点

优点 缺点
兼具段式和页式的优点 管理复杂度最高
空间浪费小(无外部碎片) 需要两次查表(段表 + 页表),硬件开销大
存储共享和保护容易 地址转换开销增加,影响执行速度
支持动态链接 占用的内存空间(页表等)增加

总结

存储管理技术从简单到复杂,经历了如下演进路径:

固定分区 → 可变分区 → 可重定位分区 → 分页 → 分段 → 段页式
 (连续分配)           (连续分配+紧凑)   (离散分配)

每一次演进都是为了解决前一代方案的痛点:固定分区的内部碎片催生了可变分区,可变分区的外部碎片催生了可重定位分区和分页,而分页缺乏逻辑语义的不足则由分段来弥补,最终段页式将两者融合。理解这条演进线索,就掌握了操作系统存储管理的核心脉络。

posted @ 2026-09-16 10:44  渡solong~  阅读(42)  评论(0)    收藏  举报