内存20200330
os被加载到操作系统后,会对整个计算机系统进行管理和控制,首先被管理和控制的就是内存,本章为如何管理物理内存
1 操作系统如何管理内存
1.1 计算机体系结构
计算机体系结构主要包括以下三大块:
- CPU:完成对整个程序或软件的执行的控制
- 内存:放置程序的代码和数据
- 外设:鼠标键盘等,都有各自的功能,来配合整个程序的执行
内存的层次结构:
从上到下CPU可以访问的数据包含:
- 寄存器和cache:os不能直接管理他们,因为他们在CPU内部,他们速度快,容量小,所以放置的数据和指令是有限的
- 主存(物理内存):与寄存器和cache相比,容量大速度慢,os相关的数据和代码放在这,可以存放多个程序同时运行(多道程序),但掉电就没了,所以需要永久保存的数据和代码需要放在硬盘,一加电就可以把数据和程序从硬盘读进来
- 硬盘(虚拟内存):掉电后存储的东西不消失,比主存速度更慢容量更大
所以越靠近CPU速度越快,存储容量越小
我们需要运行程序时数据的存取很快,存储空间很大,操作系统可以帮我们来完成这个需求
操作系统在内存管理方面需重点完成的:
- 抽象:希望应用程序在内存中运行时在os的管理下不需要考虑底层的细节,包括不用考虑物理内存在什么地方,外设在什么地方等等,只需要访问一个连续的地址空间就可以了,这个连续的地址空间一般称为逻辑地址空间
- 保护:内存中可以同时运行多个应用程序,某个程序可以会访问别的正在运行的程序的地址空间,我们需要一个机制来使得每个程序的地址空间相互隔离
- 共享:使得不同进程能够安全可靠有效的进行数据传递
- 虚拟:内存中放了多个程序可能就会出现不够用的情况,可以通过虚拟的方法使得程序有更多的地址空间,把需要使用的数据放到内存中,暂时不需要的放到硬盘上去来实现虚拟,这个过程对应用程序透明,但应用程序还可以使用到要用的数据
在上图os的管理下有4个程序放在内存中,正在占用CPU的是P1,P2、P3、P4处于等待状态
P1 P2 P3放在内存中,均可以轮流有效执行
P4因为需要等待的某事件需要过一会才能发生,且P1、P2、P3占据了大部分内存空间,所以数据没必要放到内存中去,因此把所有数据都放在了磁盘,当满足了某事件后,就可以回到内存去执行
为实现上述过程,就依赖于以下的方法:
1.2 地址空间与地址生成
运行程序需要的地址空间以及地址是如何生成的
1.2.1. 地址空间定义
物理地址空间——硬件支持的地址空间
- 起始地址0,到地址MAXsys
- 包括主存(内存条)和磁盘
- 管理和控制由硬件完成
逻辑地址空间——一个运行的程序所看到的内存范围
- 一维的线性地址空间,有了这个应用程序就可以很好的控制和访问数据
- 起始地址0,到地址MAXprog
所有程序访问的逻辑地址空间最终都要落实到物理地址空间上,在程序中的地址从哪来?
给出的是在程序地址空间中的逻辑地址,然后由os协调放在主存还是硬盘中
1.2.2 地址生成
c程序中函数的位置和变量的名字就是一种逻辑地址
汇编语言已经更贴近机器语言了,但依然是用符号来代表变量和函数的名字,但也比机器语言更易于人阅读
机器语言:起始地址从0开始,会把变量符号名和函数符号名转变为逻辑地址
基于符号的地址空间(程序中的)找到具体逻辑地址(编译器等完成):
- c程序->汇编语言->机器语言->(链接把多个程序变为一个单一的程序).exe file(可执行,放在硬盘中)->载入把放在硬盘的程序放在内存中运行(地址带有偏移量,依旧是逻辑地址)
- 不需要os,全程是应用程序、编译器等等
逻辑地址到物理地址(os完成)
-
CPU方面:
- 1、 运算器需要在逻辑地址的内存内容(ALU发出请求)
- 2、内存管理单元寻找在逻辑地址和物理地址之间的映射(在MMU查找映射,找到物理地址就返回,未找到就会产生一个处理过程,然后去内存中找)
- 3、控制器从总线给内存发送在物理地址的内存内容的请求
-
内存方面
- 4、内存发送物理地址内存的内容给CPU(主存会把内容通过主线传给CPU,CPU拿到这个指令后就可以开始执行了)
-
操作系统方面
- 建立逻辑地址和物理地址间的映射,可以放在主存中,由CPU进行缓存,从而加快映射过程
1.2.3 地址安全检查
操作系统要确保每个程序访问地址不受干扰,确保每个程序访问的地址空间是合法的。每次程序查这个map,看他要访问的逻辑地址是否满足这个限制,满足继续执行,不满足发出一个内存异常,与操作系统再来处理
1.3 连续内存分配:内存碎片与分区的动态分配
1.3.1 内存碎片问题
内存碎片:给一个运行的程序分配内存空间后,会出现一些无法被利用的空间,这些空间就是碎片
- 外部碎片
- 在分配单元
间
的未使用内存
- 在分配单元
- 内部碎片
- 在分配单元
中
的未使用内存(已经分配给了应用程序,但应用程序无法使用这些内存空间)
- 在分配单元
1.3.2 分区的动态分配
简单的内存管理方法:
- 当一个程序准许运行在内存中时(程序从硬盘加载到内存中),分配一个连续的空间
- 分配一个连续的内存区间给运行的程序以访问数据,要给这些数据一些存储空间
首次适配方法
基本原理 & 实现:
- 简单实现
- 需求
- 按地址排序的空闲块列表
- 分配需要寻找一个合适的分区
- 重分配需要检查,看是否自由分区能合并于相邻的空闲分区(若有)
- (从低地址开始找,找到一个合适的就使用,期间还要关注地址空间的回收,若回收区域与相邻区域均为空闲分区,则合并)
如需要400字节的内存,那么第一个空闲块1k就满足需求
优势
- 简单
- 易产生更大空闲块,向着地址空间的结尾(找到一个合适的就行,后面有更大的空间也不会因为被插入了个小的被浪费了)
劣势
- 外部碎片(两块之间的小碎片不易被使用)
- 不确定性(某些情况下会不适用)
最佳适配方法
基本原理 & 实现
-
为了避免分隔大空闲块
-
为了最小化外部碎片产生的尺寸
-
需求:
- 按空闲块尺寸大小排列的空闲块列表
- 分配需要寻找一个合适的分区
- 重分配需要搜索及合并于相邻的空闲分区(若有)
-
优势
- 当大部分小的内存分配时非常有效
- 比较简单
-
劣势
- 把外部碎片拆的比较细,使得将来再利用这些外部碎片的可能性比较小
- 重分配慢
- 易产生很多没用的微小碎片(不怎么好)
最差适配方法
基本原理 & 实现
- 为了避免有太多微小的碎片
- 需求:
- 按尺寸排列的空闲块列表
- 分配很快(获得最大的分区)
- 重分配需要合并于相邻的空闲分区(若有),然后调整空闲块列表
优势
- 假如分配是中等尺寸效果最好(中大型地址空间最好)
劣势
- 重分配慢
- 外部碎片
- 易于破碎大的空闲块,以致大分区无法被分配
- (请求大的地址空间,分配后,再次请求大的就不容易被分配了)
没有一种算法是一直都使用的
接下来就要考虑如何更好的利用这些碎片化空间,使碎片减少或消失
1.4 连续内存分配:压缩式与交换式碎片整理
之前的方法都会产生内碎片和外碎片,有什么办法能够能弥补一下让碎片尽量少呢?碎片少就意味着空闲空间比较完整,大小比较大,便于后续的分配
使用压缩式碎片、交换式碎片方法
1.4.1 压缩式碎片整理
重置程序以合并空洞
要求所有程序是动态可重置
的
当有一个程序需要5个空闲块时,上左图不能满足,这时,就要考虑如何将程序拷贝,使每个程序之间不留内存空闲快,像右图一样
问题:
何时挪?
- 程序运行时不能,因为万一一挪,就有可能导致后续程序请求地址时找不到
- 程序等待某个事情没有占用CPU执时可以挪
开销? - 频繁内存拷贝开销很大,甚至会影响整个系统的执行,纯靠软件完成开销较大
能否改进这种靠软件来挪动运行程序在内存中地址的方法?
1.4.2 交换式碎片整理
运行程序需要更多的内存
抢占等待的程序 & 回收他们的内存
当P3正在运行,P1P2P4没有运行时,P3需要占用更多的空间,而此时,内存空间已经被P1P2P3P4占满了,靠挪空间已无效果,因为已经没有可用的内存空间了。
此时,可以将正在等待的P4程序的数据等拷贝到磁盘中,空出的3个内存块给P3使用。当P4执行时,P3可能就不需要那么多内存了,再把P4拷回来
问题:
把拿个程序拷出去?
什么时候换入换出?(程序较大时开销较大)
连续内存中换入换出的都是大的整个程序快,如何把这些换成小的块来换入换出话需要考虑
虚存管理时会涉及
1.5 非连续内存分配(分段)
1.5.1 为什么需要非连续内存分配
连续内存分配的缺点:
- 分配给一个程序的物理内存是连续的
- 内存利用率较低
- 有外碎片、内碎片的问题
非连续分配的优点:
- 一个程序的物理地址空间是非连续的
- 更好的内存管理和利用:让运行的程序能够更好地做隔离和内存管理
- 允许共享代码与数据(共享库等)
- 支持动态加载和动态链接
非连续分配的缺点:
- 如何建立虚拟地址和物理地址间的转换
- 软件方案:开销大
- 硬件方案:能否和计算机硬件结合,如和CPU里的关于内存管理的硬件组成部分结合在一起,共同完成硬件的非连续内存分配
- 两种硬件方案:分段和分页
1.5.2 分段
程序的分段地址空间
如上图,计算机程序也是由各种各样的段组成的
比如在代码执行方面:有总程序、子程序、共享的库
数据也由各种各样的段组成,如有栈段、堆段、共享数据段
分段:更好的分离和管理
从程序的编写运行来说,逻辑地址空间是连续的
通过分段把逻辑地址分成几个段,然后映射到物理地址上去
物理地址空间是不连续的
中间需要映射机制
更有利于数据的共享和管理
又如:
逻辑地址分段映射到内存不同的段上去
用软件来处理映射开销较大,所以考虑使用硬件
分段寻址方案
虚拟地址空间映射到物理地址空间,物理地址空间由段组成
把逻辑地址分为2块,上半部分是段号,下半部分是段内偏移
通过段号来找到物理地址的段号,使用的是段表,里面存了映射关系,即逻辑地址段号和物理地址段好的对应关系
每个段大小是不一样的,需要了解它的起始地址和长度(这是段表里比较重要的2个信息)
段表的索引(index,哪一项的位置)由段号来决定
查到后的地址和长度限制,CPU查看要访问的是否符合这个限制,不符合,异常,符合,则把起始地址加上偏移量形成物理地址,CPU继续处理
(base和limit的箭头是不是画反了?)
段表由os在寻址之前建立,怎么建立段表与硬件有关
1.6 非连续内存分配(分页)
分段用的是比较少的,主要用分页
分页与分段相同点:
- 分段需要段号和段内偏移
- 分页也要页号和页内偏移
分页与分段区别:分段的段的尺寸是可变的,页的大小是不变的
1.6.1 分页地址空间
- 划分物理内存至固定大小的帧(分成的每一个页)
- 大小是2的幂,如 512,4096,8192
- 划分逻辑地址空间至相同大小的页(页的大小)
- 大小是2的幂,如 512,4096,8192
- 建立方案 转换逻辑地址为物理地址(pages to frames)
- 页表
- MMU(CPU中)/TLB(快表完成对页表的缓存)
物理地址寻址方式:帧(Frame)
物理内存
被分隔成大小相等的帧- 一个内存物理地址是一个二元组(f,o)
- f——帧号(F位,共有2F帧)
- o——帧内偏移(S位,每帧有2S字节)
- 物理地址=2s *f + o
- 地址计算的实例
- 16bit的地址空间,9bit(512byte)大小的页帧,物理地址=(3,6),求?
- 9bit 用来表示帧内偏移,16bit-9bit=7bit用来表示帧号,则F=7,S=9
逻辑地址寻址方式:页
- 一个程序的
逻辑地址空间
被划分为大小相等的页- 页内偏移大小=帧内偏移的大小(每一个页的大小和每一帧的大小同)
- 页号大小可能不等于帧号大小,即 f 可能不等于p
- 一个逻辑地址是一个二元组(p,o)
- p —— 页号(P位,2P个页)
- o —— 页内偏移(S位,每页有2S字节)
- 虚拟地址=2S *p+o
1.6.2 页寻址方案
程序在运行时无论运行指令还是要数据,都需要CPU去寻址知道指令或数据所在的位置
由逻辑地址中的页号作为一个索引去查页表(还需要知道从哪开始查,即页表基地址),查出该索引对应的页表项——帧号,把帧号和偏移量相加,得物理地址
页表由os建立
与段不同的地方:
页的逻辑地址和物理地址中的偏移量是一样的,所以不用像分段还要考虑大小不一致的问题
逻辑地址空间(连续)>物理地址空间(分散)
逻辑地址空间的内容物理地址空间不能全部装下,虚拟内存的时候会讲
逻辑地址连续,映射到物理地址后有可能分散到不同物理内存空间的页帧中,好处是有助于减少碎片
1.6.3 页表
概述
页表其实就是一个大数组
页表结构
- 每个运行的程序都有一个页表
- 属于程序运行状态,会动态变化
- PTBR:页表基址寄存器
- 页表的页表项除了帧号外,还有一些页表项的内容,用于特定的用途
- 比如说,可以表示页表项是否是一个合法的页表项,即逻辑地址的页对应的物理地址的页是否存在,因为逻辑地址空间很大,物理地址空间比它小,那么一部分的逻辑地址就无法对应到物理地址上
- 比如代表这一页读写属性,读过、写过、还是没读也没写过
页表其实是一个大数组,它的索引,即页号,对应的页表项的内容为帧号
过程:
- CPU查出该页表的起始地址
- 通过页号(page index)算出帧号
- 帧号加偏移量得物理地址
上图实例:
逻辑地址:64KB
物理地址:32KB
所以不是所有的逻辑地址都有对应的物理地址,但是逻辑地址和物理地址的页内偏移是一样的,即页大小和帧大小一样,都是1024byte
逻辑地址(4,0)代表页号:4,偏移量:0,去查页表,从下往上数0,1,2,3,4第5个,它的resident bit(驻留位)为0(此逻辑地址在内存中无对应的物理地址)。内存访问异常,os继续处理,非法访问则将运行的程序杀死,(页表在程序运行前就已建立)
逻辑地址(3,1023)代表页号:3,偏移量:1023,去查页表,从下往上数0,1,2,3第4个,它的resident bit(驻留位)为1(此逻辑地址有对应的物理地址),取出帧号4与偏移量1023相加,得物理地址(4,1023)
分页机制的性能问题(速度和空间开销,希望速度快,空间开销小)
-
页表可能非常大
- 64位机器如果每页1024字节,那么一个页表的大小会是多少
- 寻址空间:264
- 一页大小:1024字节=1k=210
- 一个页表的大小:264 /210 =254 (太大了)
- 如果每个程序都有一个页表,那么n个将会有n个页表,很耗空间
- 64位机器如果每页1024字节,那么一个页表的大小会是多少
-
页表很大的话,CPU放不下,就要放在内存,所以就会访问2次内存
- 问题:访问一个内存单元需要2次内存访问
- 一次用于获取页表项(访问页表)
- 一次用于访问数据(访问物理数据)
- 问题:访问一个内存单元需要2次内存访问
-
如何处理?(提示:大部分的计算问题可通过某些方式解决)
- 缓存(Caching):把最常用的内容放到离CPU最近的地方,如Cache,提升访问速度
- 间接(Indirection)访问:把很大的空间拆成很小的空间
TLB(时间解决方法,缓存)
MMU中有TLB
得到的p先去查快表,若有,则取出f直接与o相加;若没有,再去查页表
TLB缺失会不会很大?通常而言,32位系统,1个快表4K,访问4K次才会导致TLB的缺失,通过某种机制使得这种缺失变小,这种机制就是写程序时尽量具有局部性,把平时的访问集中到一个区域
TLBmiss后,对于x86系统cpu是通过硬件来从页表中取出到TLB中,而有的是通过os来实现的
多级的页表(解决空间效率问题)
二级页表
把单一的table分为2块,逻辑地址的p分为p1(对应1级页表页号),p2(对应2级页表页号),使得对大地址的寻址变为n个小的页表的寻址,而不是对很大的页表进行寻址。
首先,对于一级页表,它的起始地址PTBR CPU知道,所以加上p1,得到页表项(存的是2级页表的起始地址),与p2相加,得二级页表的页表项,即f
之后,p2的页表项加上逻辑地址的偏移量得最终的物理地址
处理过程多了一次查找寻址处理(p2),但使得某一些不存在映射关系的页表项就不会占用内存了,如p1指向的某一页表项不存在(逻辑地址映射的在内存中的物理地址不存在导致的内存异常的情况),则不需要在查二级页表,节省了空间
多级页表
时间来换空间
64位可以做成五级页表
查这些页表所耗费的时间变长了,但是所使用的空间变少了
但是时间问题可以使用TLB解决
反向页表
逻辑地址空间越大,对应的页表越多
使得页表大小与逻辑地址无关,而与物理地址有关的方法:反向列表
帧号为索引,页表项为页号
使用页寄存器
问题:怎么根据页号找到页帧号?
基于关联内存(associative memory)的方案
硬件逻辑很复杂
还需要放在CPU内
成本代价导致无法做的很大
基于哈希(hash)查找的方案
以上2者不够实用
哈希函数是数学计算方法,输入一个页号输出帧号,可用软件,也可硬件加速,采用硬件加速
为加快速度,加一个PID,当前运行程序的标识,PID加页号作为输入,算出对应的帧号
问题:
- 一个输入可能会有多个输出,对于hash而言,一个Input会有多个output,要选出哪个是真的帧号
- 做哈希计算,也需要到内存中取数,需要TLB
好处: - 不受制于逻辑地址空间,只跟物理地址空间有关,且较小
- 多级页表每个运行的程序都需要一个页表,而这个整个系统只需要1个页表,和多少个程序也没有关系
代价:
需要高速的哈希运算机制和有效的解决冲突的机制
总结(20220724)
首先是连续内存分配,逻辑地址空间和物理地址空间都是连续的,分区的动态分配有首先适配方法、最佳适配方法、最差适配方法,解决碎片的方法有压缩式和交换式
其次是非连续内存分配,因为连续内存分配连续内存分配的缺点:
1、分配给一个程序的物理内存是连续的
2、内存利用率较低
3、有外碎片、内碎片的问题
所以出现了非连续内存分配,他的物理内存是非连续的,方法有分段和分页,分段的逻辑段和物理段的大小可以不一样,分页的逻辑页和物理帧的大小要一样
为解决页表过大、需要访问2次内存的问题,有软件解决方法(开销大)和硬件解决方法。
硬件解决方法有:
1、页表大小与逻辑地址有关
基于时间的解决方法——TLB(在MMU中),
基于空间的解决方法——多级列表,
2、使得页表大小与逻辑地址无关,而与物理地址有关的方法
反向列表(解决页表过大)