存储模型
-
在文章开始前, 我们巩固几个概念
- 装载: 程序运行时, 操作系统会将程序在磁盘中对应的可执行文件load到内存中
- 多道程序设计模型: 允许多个程序同时进入
- 每个进程都有自己的地址空间, 在进程创建的时候, 操作系统就已经创建好了, 且进程间不能相互访问各自的地址空间
- 进程地址空间只有装载到物理内存中才能执行
-
存储模型解决的问题就是如何合理的分配装载在物理内存中的进程
地址重定位
- 相关术语: 地址转换, 地址映射, 地址翻译
- 为什么需要地址重定位?
- 进程分为用户地址和内核地址, 其中用户地址是装载程序,数据堆栈的主要部分
- 进程中的地址不是真实的物理地址
- 在进程运行前, 我们并不知道进程对应的真实物理地址是什么
- 逻辑地址(虚拟地址): 用户程序经过汇编后形成的代码对应的地址,这个地址不是物理地址
- 物理地址: 内存存储单元的地址, 可以直接寻址
- 地址重定位: 所以将进程的逻辑地址转化为CPU可以直接访问的地址的过程就是地址重定位
静态重定位
- 概念: 就是当程序加载到物理内存时, 一次性计算好物理内存地址, 之后不能改变,否则需要重新计算分配
动态重定位
- 概念: 就是当程序加载到物理内存时, 按照指令的执行顺序, 边执行边计算(较为常用)
- MMU: 内存管理单元
- 实现步骤: CPU拿到逻辑地址->MMU转换->物理地址->存取指令(数据)
物理内存管理
空闲内存管理
- 物理内存等长划分: 采用位图
- 不等长划分: 采用空闲区表或者链表
内存分配算法
- 首次适配: 找到第一个满足要求的空闲区
- 下次适配: 从上次找到的位置接着找
- 最佳适配: 遍历空闲表, 找到满足进程要求的最小空闲区
- 最差适配: 总是分配能满足进程要求的最大空闲区
内存回收算法
- 进程回收之后, 内存是否存在更大内存区, 存在则合并, 修改空闲区表
伙伴系统
- Linux内核底层内存管理采用的方案
- 算法思想:
- 假设进程所需内存空间为n , 找出满足n<= 2^n里面n的最小值, 那么所分配的空间就是取2的n次幂。如果不满足, 就按照2的n次幂一直划分. 之所以称为伙伴, 就是一直把一块内存空间一分为二。
- 当进程释放空间时, 只有伙伴之间才能合并内存
内存管理方案
- 单一连续区
- 一段时间内只有一个进程在内存中
- 固定分区
- 将内存分割为若干区域, 大小可相同可不同, 每个分区只能装载一个进程
- 可变分区
- 进程要多大分多大, 听起来蛮吊的, 但是会造成外碎片, 最终导致内存利用率下降
- 碎片解决方案
- 移动程序的地址, 将碎片拼接起来, 但是要考虑进程移动的开销和时机
页式存储方案
- 这个比较重要, 单独列个标题
- 核心思想
- 将用户地址空间分页,称为page
- 同时将物理内存按照相同大小分为相等的区域, 称为页框(page frame)
- 分配规则: 按照页为单位, 分配进程需要的页
ps. 逻辑上相邻的页, 物理上未必相邻
- 页表: 非常重要的数据结构, 感觉就是一个字典
- 页表项: 页表中的每一行, 记录了进程逻辑页和物理页框的对应关系
- 地址转换过程
- CPU拿到逻辑地址
- 将逻辑地址分为页号和偏移量(操作系统完成)
- 页号去查页表,拿到页框号
- 将查询到的页框号和逻辑偏移量拼接得到物理地址
- 缺点: 容易产生内碎片(最后一条或者几条指令被分配了一页, 多出的剩余空间)
段式存储方案
- 核心思想
- 用户地址空间: 按照程序的逻辑分为不同的段
- 内存空间: 动态划分不同的区域,每个段之间可以不连续, 但是段内必须连续
- 转换过程: 和页式存储基本一样
段页式存储方案
- 其实很简单, 就是先通过段表找到对应的段, 然后再把这个段想象成一个完整的分页程序, 再按照页式存储方案走就行了。
- 寻址方式: 通过段号查段表->段表的某一项包含了页表->页表中按照页号查到对应的页内地址->页内地址和页内偏移量结合就是真实的物理地址
参考
[1]操作系统原理