存储管理
早期计算机编程并不需要过多的存储管理
随着计算机和程序越来越复杂,存储管理成为必要
在存储管理中分为连续存储和非连续存储,非连续存储又分为分页存储、分段存储和段页式存储。
存储管理的目的:
确保计算机有足够的内存处理数据
确保程序可以从可用内存中获取一部分内存使用
确保程序可以归还使用后的内存以供其他程序使用
内存分配与回收
内存分配的过程
单一连续分配:
把内存分为系统区(被系统所使用)和用户区
内存空间被划分为若干固定大小的区域,每个分区只提供给一个程序使用,互不干扰
支持多道程序的最简单存储分配方式
根据进程实际需要,动态分配内存空间
相关数据结构(计算机存储、组织数据的方式):
(1)动态分区空闲表数据结构
存储使用情况,是否被使用,是否容量足够
把所有的空闲节点首尾相连,形成一个双向链表
可以把连续的空闲区合并为一个节点来减少节点数量
动态分区相关分配算法:
首次适应算法(FF算法):
主要使用空闲链数据结构,从头部节点开始适配,直到找到容量大小能够满足需求的空闲区
每次从头部开始,使得头部地址空间不断被划分,若头部都已被分配,就需要查找十分靠后的节点才能找到合适的内存
为了改进这种情况,出现了循环适应算法,每次都从上一次检索结束的位置开始查找。
最佳适应算法(BF算法):
最佳适应算法要求空闲区链表按照容量大小排序,
遍历空闲区链表找到最佳合适空闲区,从小到大的遍历,可以减少碎片的产生,避免大材小用。
快速适应算法(QF算法):
快速适应算法要求有多个空闲区链表
每个空闲区链表存储一种容量的空闲区,效率比BF算法更高,可以快速找到适合的内存区域。
内存回收的过程
针对四种不同的情况,有四种回收的方式。
情况① 情况② 情况③ 情况④
情况①:
不需要新建空闲链表节点
只需要把空闲区1的容量增大为空闲区即可
将回收区与空闲区合并
新的空闲区使用回收区的地址
将空闲区1、空闲区2和回收区合并
新的空闲区使用空闲区1的地址
为回收区创建新的空闲节点
插入到相应的空闲区链表中去
段页式存储管理
操作系统管理进程的空间,一般进程的空间在没映射到物理内存时,是在虚拟内存既是辅存中的。
页式存储管理
管理过程:
将进程逻辑空间等分成若干大小的页面,相应的把物理内存空间分成与页面大小的物理块
以页面为单位把进程空间装进物理内存中分散的物理块,把进程空间中的每个逻辑页面放到内存中的物理块里去
内存碎片:
进程空间中页面大小小于物理内存空闲区的大小,造成存在一段无法使用也无法回收的内存区域。
页面大小应该适中,过大难以分配,过小内存碎片过,多页面大小通常是512B~8K。
记录进程逻辑空间与物理空间的映射的表,因为进程的逻辑空间分为的页
面分散在主存不同的分散的物理块中,所以需要页表来记录。
页表中有多少个页面就有多少个页表项。
逻辑地址结构:
由页号P和页内偏移量W(页内地址)两部分组成
相关公式:
逻辑地址A = 逻辑地址的页号P * 页面大小L + 偏移量W(“页内地址”又叫“页内偏移”)
物理地址A = 某程序指定页的块号B * 页面大小L + 偏移量W
偏移量W = 逻辑地址A - 页号P * 页面大小L = 逻辑地址A %(mod) 页面大小L
已知逻辑地址求物理地址:
第一步 : 逻辑地址A / 页面大小L = 逻辑地址的页号P (整除)(商)
第二步 : 逻辑地址A %(mod) 页面大小L = 逻辑地址的页内偏移量W(取余)
第三步 : 生成页表,找出逻辑地址的页面所对应的页框号,一一对应,页号对应块号,从小到大排序。
在实际中,由于虚拟内存一般都会大于实际的内存,所以页面的数量一般会大于或等于字块的数量。
如:
第四步:求出 物理地址 = 某程序指定页的块号 * 页面大小 + 偏移量W
多级页表:
https://blog.csdn.net/forDreamYue/article/details/78887035 多级页表就比一级页表要省空间?
为了解决页表过于占用内存空间而出现的,
由于现代计算机内存较大,可以存在非常多的页面,则页表项会十分的庞大,一个32位的系统,
若规定页面为4KB,则会有2^20个页表项,若每个页表项占用1byte,每个进程仅仅页表就要占
用1M的内存空间,而且每个页表都将会有所有的页表项来对物理内存空间进行映射。如果是在64
位地址的计算机的情况下,如果按照相同的计算方法,一个进程的页表占用的空间一个普通内存根本
放不下。关键是,一个进程,真的会需要一整个虚拟地址空间去存放吗?
解决问题的核心在于,避免把全部页表一直保存在内存中是多级页表的关键所在。特别是那些不需要的页表就不应该保留。
工作原理:
根页表中,每个字块所指向的地址为内存中的一片空间,这一片空间存储的是二级的页表,假设每个
二级页表有1024项,每一项指向的字块才是内存实际使用的内存。每个根页表可以指向多个二级页表。
通过一个顶级页表为真正有用的页表提供索引。
段式存储管理
可以解决也是存储管理中的有一段连续的逻辑分布在多个页面中,在使用时将会大大降低执行效率的问题
与页式存储管理的异同:
段式存储和页式存储都离散地管理了进程的逻辑空间
页是物理单位,段是逻辑单位;
分页是为了合理利用空间,分段是满足用户要求
页大小由硬件固定,段长度可动态变化
页表信息是一维(字块号)的,段表信息是二维(基址和段长)的
实现:
将进程逻辑空间划分成若干段(非等分)
段的长度由连续逻辑的长度决定,如:主函数MAIN、子程序段X、子函数Y等
段表:
段页式存储管理
分页可以有效提高内存利用率(虽然说存在页内碎片);
分段可以更好满足用户需求,逻辑是用户写的;
两者结合,形成段页式存储管理。
实现:
先将逻辑空间按段式管理分成若干段
再把段内空间按页式管理等分成若干
虚拟内存
虚拟内存实际是对物理内存的补充,速度接近于内存,成本接近于辅存。
程序的局部性原理:
局部性原理是指CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个
较小的连续区域中;
根据此原理,程序运行时,无需全部装入内存,装载部分即可,如果访问页不在内存,则发出缺页中断,发起页面置换
从用户层面看,程序拥有很大的空间,即是虚拟内存。
使用虚拟内存的背景:
① 有些进程实际需要的内存很大,超过物理内存的容量;多道程序设计,使得每个进程可用物理内存更加稀缺
不可能无限增加物理内存,物理内存总有不够的时候。
② 虚拟内存是操作系统内存管理的关键技术;使得多道程序运行和大程序运行成为现实;把程序使用内存划分,
可以将部分暂时不使用的内存放置在辅存,只需把要使用的(红色)加载到物理内存中,
如下:
虚拟内存的置换算法
替换策略发生在Cache-主存层次、主存-辅存层次
Cache-主存层次的替换策略主要是为了解决速度问题
主存-辅存层次主要是为了解决容量问题,一般虚拟内存的置换指的是这个层次。
主存页面的替换时机:
高速缓存的替换时机:
Linux的存储管理
内存碎片:
页内碎片:
内部碎片是已经被分配出去(能明确指出属于哪个进程)的内存空间大于请求所需的内存空间,不能被利用的
内存空间就是内部碎片。 空闲区容量大于页面大小,则造成业内碎片。
页外碎片:
外部碎片是指还没有分配出去(不属于任何进程),但是由于大小而无法分配给申请内存空间的新进程的内存空闲块。
操作系统在不断分配空闲区,会根据进程的需要来分配不同的内存空间给进程使用,会造成一些较小的空闲区由于过小而
无法被继续分配,造成页外碎片。
Buddy(伙伴)内存管理算法
Buddy算法是经典的内存管理算法,一种实际在内存中运行的一种内存管理算法,
基于计算机处理二进制的优势具有极高的效率,主要是为了解决内存外碎片的问题,
可以把内存外碎片问题转变为内存内碎片问题,把内存按2的幂次方大小来分配,可以避免出现内存外碎片。
目的是努力让内存分配和相邻内存合并能快速进行。
伙伴系统:
“伙伴”指的是内存的“伙伴”;
一片连续内存的“伙伴”是相邻的另一片大小一样的连续内存。
Buddy实现:
Buddy内存分配原则:
在每次分配内存时,都是默认向上取整为2的幂的大小
如:
70k→128k
129k→256k
666k→1024k
实现过程:
创建一系列空闲块链表,每一种都是2的幂,如容量大小为2^1=1的为一条双向链表,2^2、2^3 ... 以此类推。
例子:
假设存储空间有1M大小(为1M的空闲链表中有一个节点),按Buddy算法分配100k内存,这组空闲链中情形如下图:
1. 100k向上取2的幂=128k
2. 查询是否存在大小为128k的空闲内存块(看看表示128KB空闲区的链表上有没有节点)?
3. 没有!查询是否有256k空闲内存块?
4. 没有!查询是否有512k空闲内存块?
5. 没有!查询是否有1M空闲内存块?
6. 有,摘下1M空闲内存块,分配出去
7. 拆下512k放在512k的空闲链表(由1M=2^10降幂1,得512KB *2,未得到所需的128KB),其余的分配出去
8. 拆下256k放在256k的空闲链表,其余的分配出去
9. 拆下128k放在128k的空闲链表,其余的分配出去
10. 分配完毕(最小只能分配给128KB大小的空闲区,若无则分配更大的空闲区供使用)
Linux交换空间
交换空间(Swap)是磁盘的一个分区, Linux物理内存满时,会把一些内存交换至Swap空间,但是交换空间在磁盘,速度较慢。
Swap空间是初始化系统时配置的。
冷启动内存依赖,很多大型的应用程序,在启动时需要使用大量的内存,但是这些内存在启动后就很少会被使用,可把
这部分置换到交换空间里,以释放更多的物理内存给系统使用。
系统睡眠依赖,当linux需要睡眠时,会把内存中所有的数据保存到swap空间里,下次启动时才重新加载到内存中,这样
可以加快系统的启动速度。
大进程空间依赖,对于一个应用确实需要使用十分大的物理内存空间,当物理内存不足时,把进程需要使用的内容暂时保存
到swap交换空间中。
与虚拟内存对比: