第三章:存储管理¶
存储器的体系结构¶
存储 | 大小 | 延迟 |
---|---|---|
寄存器 | 64位 | 0.5 T (读 + 写) |
高速缓存(cache) | 1M-12M-60M | 2-4/10-20/20-60 T |
主存储器(DRAM) | 128G | 200-300 T |
[非永久]//[永久] | ||
外部存储器(磁盘光盘U盘闪存) | 32T | \(\mathrm{\mu s - ms}\) |
读取过程——应该是逐级的
3.1 单道程序存储管理¶
内存分为两个区域:系统区和用户区。【软件区分的】
每次把一个应用程序装入到用户区运行,由它和操作系统来共享内存。当它运行结束后,操作系统再装入一个新的程序把它覆盖。
优点:简单
缺点:
- 每次只能运行一个程序
- 内存资源的使用效率不高——程序较小时,会浪费大量的内存空间
- OS的保护——应用程序的bug会破坏OS
- 地址空间有限——即为物理内存的大小
3.2 分区存储管理¶
-
内存分为两大区域:系统区、用户区。
-
又把用户区划分为若干分区(partition),一个进程占用一个分区。
固定分区¶
各个用户分区的个数、位置和大小一经确定,就固定不变。
-
分区方法:多个小分区,适量中等分区,少量大分区
-
数据结构:内存分配表
-
内存分配:进入输入队列;按照最先匹配,最佳匹配算法分配
-
内存回收:简单啊
缺点:
- 内碎片:进程所占用分区之内未被利用的空间
- 不够灵活
- 不能预测分区代价
可变分区¶
- 初始时,操作系统会占用内存的一部分,剩余空间为一个完整的大空闲区
- 当一个程序要求装入内存运行时,系统从这个空闲区中划一块分配给它
- 当程序完成后释放所占用的存储区
- 随着一系列的内存分配和回收,原来的一整块大空闲区就形成若干占用区和空闲区相间的布局。
缺点:
- 内碎片减少
- 外碎片增多,即各个占用分区之间难以利用的空闲分区增多;
- 使得内存的分配、回收和管理更为复杂。
可变分区的实现¶
内存管理的数据结构¶
分区链表
记录:
- 分区的状态(占用或空闲)
- 起始地址
- 长度
- ...
内存的分配算法¶
遍历,满足条件,分割
内存的回收方法¶
将相邻的几个空闲分区合并为一个大的空闲分区
碎片问题的解决¶
实例¶
假设堆空间的大小是固定的(一整块内存),请问如何来实现堆空间的管理?
动态申请固定大小的数据结构或缓冲区,如64B、9KB、64KB、256KB、400KB等;
使用malloc/free函数动态申请和释放任意大小的堆空间。
内存中的程序执行¶
进程的地址空间(?)
/* 全局变量,固定地址,其他源文件可见 */
int global_static;
/* 静态全局变量,固定地址,但只在本文件中可见 */
static int file_static;
/* 函数参数:位于栈帧当中,动态创建,动态释放 */
int foo(int auto_param) // 代码
{
/* 静态局部变量,固定地址,只在本函数中可见 */
static int func_static;
/* 普通局部变量,位于栈帧当中,只在本函数可见 */
int auto_i, auto_a[10];
/* 动态申请的内存空间,位于堆当中 */
double *auto_d = malloc(sizeof(double)*5);
return auto_i;
}
内存空间:
-
header(符号表?)——里头到底有什么?
-
text section(代码段)
- data section(静态变量)
- other sections(堆栈)
3.2.6 地址映射¶
物理地址:可以直接寻址
逻辑地址:相对地址,相对首地址来编址
地址映射:逻辑地址转换为物理地址
-
静态地址映射(静态重定位):装入内存时,修改指令代码
-
动态地址映射(动态重定位):程序运行过程中,需要访问内存时进行地址转换(即在逐条执行指令时完成转换)。由硬件地址映射机制完成。
3.2.7 存储保护¶
防止
-
操作系统免受用户程序的破坏:
-
一个用户程序去访问其他用户程序的内存分区
物理限制:
- 增加一个限长寄存器,记录分区长度
3.3 页式和段式存储管理¶
3.3.1 页式存储管理¶
物理页面/页框(page frame):物理内存划分为许多个固定大小的内存块 逻辑页面,或简称页面(page):把逻辑地址空间划分为大小相同的块
页面大小为2n,一般在512字节到64K字节之间
一个进程需要的 n 个逻辑页面可以存储在不连续的 n 个物理页面中
页表:数据结构,逻辑页号 → 物理页号
但是,页表在内存中,不可能为了去取一条指令而花费一次读内存的时间;
所以必须要用硬件加速!
物理页面表:位示图 ~ 空闲物理 page frame 的链表;同时维护总共空闲页数
内存的分配与回收:trivial
地址映射:高位就是页号,低位就是页内偏移量;
页表的具体实现:PCB中设置 PTBR(base), PTLR(length)存储指针, ;都存在内核空间的内存中【1次寄存器+2次内存】
加速【主要是内存访问】:TLB,存储一个类似map的东西,TLB miss 的时候才去内存找
缺点:每个进程都要维护一张页表;必须把程序全部装入内存
3.3.2 段式存储管理¶
存储保护:
- 代码段——只读、可执行
- 数据段——可读写、需写回磁盘
- 栈——运行期间数据
如何跨进程的共享?
对程序的每个逻辑单元(代码、数据和栈等),设立一个完全独立的地址空间,称为“段”。
每个段的内部是一维的线性连续地址,大小可不同;
对于物理内存来说,采用可变分区(动态分区)的管理方法;
当一个程序需要装入内存时,以段为单位进行分配,把每一个段装入到一个内存分区【之前的分区方法】当中,这些内存分区不必是连续的。
用户空间地址:二维,段号+段内偏移
段表:每个进程都有,它给出了每一个段和它所对应的内存分区之间的映射关系【起始地址、长度】→ 寄存器【STBR,STLR】
3.3.3 段页式存储管理¶
先把程序划分为段【内存划分】,然后在段内分页【内存存储】。
- 段表:页表起始地址 & 段长
- 页表:逻辑页面号与物理页面号之间的对应关系。【每一段有一个】
3.4 虚拟存储技术¶
内存不够用的情况,把内存扔到磁盘里面
局部性原理——
只装入部分页面
增页、页面置换
页表项,增加一个驻留位:是否在内存中;一个访问位 :是否被访问过:用于页面置换算法
缺页中断不能加 PC,刚才的指令根本没有执行完成
淘汰页面的算法¶
Info
重点!会考的很细,具体操作之类的,下面都没有写。
最优页面置换:选择未来等待时间最长的
最近最久未用:选择最近最久未使用的那个页面
最近最少使用:选择访问次数最少的那个页面
先进先出置换:选择驻留时间最长的页面
时钟页面置换算法:基本思路(FIFO + 跳过已访问过的页面)
3.4.4 工作集模型¶
证明、定量分析程序的局部性原理
工作集¶
工作集:一个进程当前正在使用的逻辑页面集合,可以用一个二元组 \(W(t,\Delta)\) 来表示, \(t\) 是当前的执行时刻,\(\Delta\) 定长的窗口
工作集大小变化:【快速扩张-快速收缩-稳定期】* 每一个局部性区间
进程固有性质
驻留集¶
实际在内存中的逻辑页面集合
取决于物理页面数目和页面置换算法。
一般我们希望缺一点, |驻留集| < |工作集|
3.4.5 虚拟页式的设计问题¶
- 页面的分配策略
固定分配:驻留集大小固定
可变分配:调整驻留集的大小,可以根据缺页率等等调整
- 页表结构
多级页表
反置页表:根据物理块来维护下标
Windows 的存储管理¶
每个进程的虚拟地址空间大小为 32 位,4G
低 2G 是用户空间,用于存放进程自身的代码和数据
高 2G 是内核空间,虚拟页式存储管理,页面大小为 4 KB
二级页表进行映射
页面置换算法:努力去维持一定量的空页表;算法平时会定期向着各个进程索取页面
Linux 的存储管理¶
虚拟页式存储
四级页表结构