操作系统之内存管理

操作系统之内存管理

物理内存 VS 虚拟内存

物理内存

优点:

  1. 速度快:RAM是CPU可直接访问的高速存储器,访问延迟远低于磁盘
  2. 支持多任务运行:多进程在内存中切换迅速
  3. 可被CPU直接寻址:CPU指令直接对物理地址操作(现代OS都是经过虚拟地址转换的)

缺点:

  1. 容量有限成本高:RAM价格昂贵,不能无限扩展
  2. 数据掉电丢失:断电后内容消失
  3. 程序必须装入内存才能运行:无法直接运行其他存储介质上的程序

虚拟内存

操作系统为每个进程提供一个独立的、统一的、连续的地址空间,通过页表将虚拟地址映射到物理内存或磁盘。

优点:

  1. 扩充有效内存容量:通过磁盘分页,程序不必全部装入内存
  2. 每个进程拥有独立空间:安全隔离,避免相互访问内存
  3. 简化编程模型:程序认为自己拥有完整的连续内存空间

缺点:

  1. 速度比物理内存慢:页缺失需要从磁盘换页,速度远慢于RAM
  2. 内核复杂性提高:需要管理页表、交换、高速缓存一致等
  3. 可能引发抖动:频繁换页导致系统几乎停顿

引入虚拟内存就完全解决物理内存的限制了吗?

不完全解决,而是缓解和抽象

本质原因

虚拟内存通过按需加载和页面置换机制,使得:

  • 程序不需要一次性放入内存
  • 只有正在使用的部分放入内存
  • 不活跃部分换到磁盘中

程序可用的地址空间 ≠ 物理内存大小

解决的问题

  1. 内存不够放程序,通过分页 + 磁盘换出
  2. 进程互相干扰,通过每个进程独立地址空间
  3. 内存碎片,通过页式管理提供连续虚拟空间
  4. 编程困难,屏蔽了物理内存细节

没有完全解决的问题

  1. 磁盘比内存慢,缺页会严重影响性能
  2. 物理内存依旧有限,虚拟内存越大,缺页越频繁
  3. 并不是无限内存,一旦换页密集,性能崩溃

虚拟地址要映射物理地址,需要操作系统提供 内存管理机制

常见的内存管理机制

早期单一连续分配

  1. 只有一个进程运行
  2. 程序整个装入内存
  3. 无虚拟地址概念,直接映射

固定分区分配

物理内存被分成固定大小的分区,每个分区只能装一个进程

  • 简单实现
  • 内部碎片较多
  • 多任务,但不灵活

动态分区分配

内存按需分配不同大小连续空间

典型算法:

  1. First Fit(首次适配):找第一个足够大的空间
  2. Best Fit(最优适配):找最小的足够大的空间
  3. Worst Fit(最差适配):找最大的空间

问题:外部碎片严重,需要紧缩

分页管理(Paging)【现代主流】

虚拟地址和物理地址都划成固定大小的页(Page)/页框(Frame)

  • 解决外部碎片
  • 页表实现地址映射
  • 内存不连续

优点:

  1. 内存利用率高
  2. 支持虚拟内存,按需调页
  3. 支持进程隔离

缺点:

  1. 页表占空间
  2. 多级页表复杂度高
  3. TLB缓存命中影响性能

分段管理(Segmentation)

把内存按逻辑段划分(代码段/数据段/栈段)

  • 地址 = 段号 + 段内偏移
  • 每段大小可变

优点:

  1. 符合程序逻辑
  2. 保护性好

缺点:

  1. 会产生碎片

段页式管理(Segmentation + Paging)

先按段划分,再对段内分页。结合了逻辑结构和无碎片的优点

按需分页(Demand Paging)

配合虚拟内存使用

  1. 程序不用全部装入
  2. 只装用到的页
  3. 缺页中断触发换页机制

核心算法

  1. FIFO(先进先出)
  2. LRU(最近最少使用)
  3. CLOCK / Second-Chance(实用近似LRU)

内存映射文件(Memory-mapped I/O / mmap)

把文件映射到虚拟内存空间

  • 区别于 read/write
  • 虚拟内存管理文件
  • 常见于数据库、共享内存

什么是分页?

把虚拟地址和物理地址都切成固定大小的小块(页 Page / 页框 Frame),让进程不需要连续的物理内存,也不需要一次性全部装入内存。

分页的三条基本规定

  1. 虚拟地址 = 页号 + 页内偏移:CPU访问内存先被拆分
  2. 建立页表(Page Table):记录每页虚拟到物理的映射
  3. 硬件MMU + TLB协助:加速地址转换

虚拟地址是怎么变成物理地址的?

假设:

虚拟地址:32bit

页大小:4KB(2^12)

那么:

高20位:页号

低12位:页内偏移

虚拟地址 = 20bit + 12bit

然后:

CPU通过页号查找页表,找到对应的物理页框号

最后计算物理地址:

物理地址 = 物理页框号 + 偏移

举例:

页大小 = 4KB
虚拟地址 = 0x00105ABC

分解地址
4KB = 2^12 → 偏移 12 bit
页号 = 0x00105
偏移 = 0xABC

查页表
假设:虚拟页号 0x00105 → 映射到物理页框 0x00300

拼物理地址
物理地址 = 0x00300ABC = 0x00300 + 0xABC

出现的问题

如果每个进程都有一个页表,而虚拟空间4G,每页4KB就等于1048576个页。每项4B 转换成页表大小 就是 4MB。每个进程都有一份,太大了!

所以就出现了

  1. 多级页表:减少页表占用内存
  2. TLB:缓存页表加速
  3. 反置页表:全局页表公用

引入的硬件:

  1. MMU内存管理单元:负责虚拟地址到物理地址的转换,页表访问和触发缺页异常
  2. TLB(快表):页表缓存,加速映射,TLB未命中就查页表,页表未命中就缺页中断,从磁盘加载

缺页中断的过程:

  1. CPU访问虚拟内存
  2. MMU找不到页映射(先查TLB再查内存页表)
  3. OS捕获异常
  4. 从磁盘加载该页到RAM
  5. 更新页表
  6. 重新执行指令

什么是分段?

分段(Segmentation)是一种把程序的地址空间按逻辑功能划分的内存管理方式

程序运行时,会被划分成多个逻辑段(Segment),每个段长度不一样

含义
代码段(Text Segment)存放程序指令(只读)
数据段(Data Segment)已初始化的全局变量、静态变量
BSS 段未初始化的全局/静态变量
堆段(Heap)动态分配内存(malloc/new)
栈段(Stack)函数调用、局部变量

这些段都是独立管理,独立保护的。分页是按照固定大小分,分段是按照逻辑意义分。

为什么需要分段?

分页解决了物理内存碎片虚拟内存不足问题,但分页没有“逻辑意义”,只是切块。而程序本身有逻辑结构的。分段让内存管理更符合程序逻辑,并可以对每段设置不同权限。

优点:

  1. 按逻辑划分:对应程序结构,方便保护与共享
  2. 每段可以不同大小:没有内部碎片
  3. 可权限控制:代码段不可写,栈自动增长
  4. 支持共享:多个进程共享代码段

缺点:

  1. 外部碎片:段大小不固定,使内存出现空洞
  2. 分配复杂:需要找足够连续的块
  3. 不利于虚拟内存扩展:分段不容易换入换出小块

因此操作系统逐渐以分页为主,分段为辅。

分段思想

虚拟地址 = 段号 + 段内偏移

CPU 访问内存时:

  1. 取段号(Segment Selector)

  2. 找该段的段表(Segment Table)

  3. 获取该段的基址 + 限长(base + limit)

  4. 物理地址 = 基址 + 偏移量

段表和页表的关系类比

分页分段
页表(Page Table)段表(Segment Table)
虚拟页号 + 偏移段号 + 段内偏移
固定大小不固定大小
物理页框号段基址 Base
页大小固定段长度 Limit

段式地址的计算公式

物理地址 = 段基址(Base) + 段内偏移(Offset)

分页:

物理地址 = 页框号(Frame) + 页内偏移

页表告诉:虚拟页 → 物理页框

段表告诉你:段号 → 段基址 Base

多级页表

为了解决页表过大,因此对页表进行了分页,又叫多级页表

核心思想:延迟分配页表,只在需要的虚拟页才建对应页表项

例子

假设 32 位虚拟地址,4KB 页:

虚拟地址:32 bit = [10 bit 一级页表 | 10 bit 二级页表 | 12 bit 页内偏移]

访问过程:

  1. 取虚拟地址高 10 位 → 一级页表索引,一级页表存放二级页表的物理地址
  2. 取中间 10 位 → 二级页表索引,二级页表存放实际物理页框号
  3. 取低 12 位 → 页内偏移
  4. 物理地址 = 页框号 + 页内偏移

TLB硬件加速

每次访问内存都需要多次查询页表(四级页表需要4次内存访问),性能开销巨大。

TLB是什么?

  1. 位于CPU内部的高速缓存
  2. 存储最近使用的虚拟地址到物理地址的映射
  3. 典型容量:64-1024 项

工作流程

1. CPU生成虚拟地址
2. 首先查询TLB
   ├─ TLB命中:直接获取物理地址(几个时钟周期)
   └─ TLB未命中:通过页表查询(几十到几百个时钟周期)
3. 将新的映射加入TLB

TLB管理

  • 进程切换时:需要刷新TLB(或使用ASID/PCID标识进程)
  • 页表修改时:需要使TLB失效(通过 invlpg 指令)
  • 命中率:通常可达 95-99%

页面置换算法

最优算法(OPT - Optimal)

原理:选择未来最长时间不会被访问的页面换出

  • 优点:理论上缺页率最低,作为其他算法的性能基准
  • 缺点无法实现(需要预知未来的访问序列)
  • 用途:仅用于理论研究和算法性能评估

先进先出(FIFO)

原理:选择最早进入内存的页面换出,维护一个队列

  • 优点

    • 实现简单,只需维护一个队列
    • 开销小
  • 缺点

    • 性能差,没有考虑页面使用频率
    • 可能换出经常使用的页面
    • 存在Belady异常
  • 适用:很少单独使用,主要用于教学

最近最少使用(LRU - Least Recently Used)

原理:选择最长时间未被访问的页面换出(基于局部性原理)

  • 优点

    • 性能接近最优算法
    • 符合局部性原理,缺页率低
    • 无Belady异常
  • 缺点

    • 开销大:每次访问都需要更新时间戳或调整链表
    • 硬件支持复杂
    • 完整实现成本高
  • 适用:理论性能好,但实际很少严格实现

时钟算法(Clock / 二次机会算法)

原理:LRU的近似实现,使用访问位(Access Bit),页面组织成环形链表

  • 优点

    • 比LRU开销小很多
    • 性能接近LRU
    • 实现简单,只需一个访问位
    • 无Belady异常
  • 缺点

    • 不如LRU精确
    • 最坏情况下需要扫描两遍
  • 适用实际操作系统最常用的算法

改进的时钟算法(Enhanced Clock)

原理:使用访问位(A)和修改位(D)两个标志位

  • 优点

    • 优先换出干净页(无需写回磁盘,更快)
    • 减少磁盘I/O
    • 性能优于基本时钟算法
  • 缺点

    • 比基本时钟算法稍复杂
    • 最坏情况需要4轮扫描
  • 适用:实际系统中广泛使用

最不常用(LFU)

原理:选择访问次数最少的页面换出

  • 优点

    • 考虑了访问频率
    • 对某些访问模式效果好
  • 缺点

    • 早期访问的页面计数器高,即使后来不用也难以换出
    • 需要维护计数器,开销大
    • 性能通常不如LRU
  • 适用:很少使用,缓存系统中偶尔使用

最近未使用(NRU)

原理:使用访问位(R)和修改位(M),将页面分为4类

类0: (R=0, M=0) - 最近未访问,未修改
类1: (R=0, M=1) - 最近未访问,已修改
类2: (R=1, M=0) - 最近访问,未修改
类3: (R=1, M=1) - 最近访问,已修改

策略:

  • 编号最小的非空类中随机选择一个页面换出
  • 定期清除访问位(如每次时钟中断)

优缺点:

  • 优点

    • 实现简单,开销小
    • 性能接近时钟算法
  • 缺点

    • 精度不如LRU
    • 同类中随机选择,可能不够优化
  • 适用:一些简单的操作系统

性能对比:

缺页率

OPT < LRU ≈ Clock ≈ Enhanced Clock < NRU < LFU < FIFO

实现复杂度

FIFO < NRU < Clock < Enhanced Clock < LFU < LRU < OPT(不可实现)

分页和分段管理中内存碎片问题

内存碎片种类

内部碎片:已经分配给进程但无法使用的内存空间。

外部碎片:系统中未分配的内存,但由于不连续而无法满足分配请求。

分页管理中的碎片问题

只有内部碎片,产生原因:

  • 页的大小固定(如4KB)
  • 进程最后一页通常不会刚好用满
  • 虚拟地址连续,但物理地址可以不连续

为什么没有外部碎片:任何空闲页框都可以分配给任何进程,不需要连续的物理空间。

分段管理中的碎片问题

主要是外部碎片,产生的原因:

  • 段的大小不固定(根据逻辑单元决定)
  • 需要连续的物理空间
  • 频繁分配和释放导致内存"碎片化"

解决方案

分页中解决内部碎片:

  1. 减少页面大小
  2. 可变页大小(大页支持)
  3. 接受内部碎片

分段中解决外部碎片:

  1. 内存紧凑
  2. 分配策略优化
  3. 分段+分页(段页式管理)

进程复制内存优化:写时复制(Copy - on Write)

传统直接复制方式

过程:

  1. 分配与父进程相同大小的物理内存
  2. 复制父进程的所有内存内容到子进程
  3. 复制页表
  4. 更新子进程的页表指向新的物理页

问题:

  1. 时间开销大
  2. 内存开销翻倍

写时复制(COW)

核心思想:不立即复制,而是共享内存,只有在写入时才真正复制。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值