操作系统在内存管理要完成的目标
- 背景:基于计算机体系结构以及其内存分层体系,CPU,内存和I/O之间的存储速度差距过大,希望os在内存管理要达到的目标是:
- 抽象:逻辑地址空间(是连续的
- 保护:独立地址空间(因为应用程序是多个的
- 共享:访问相同内存
- 虚拟化:耕读的地址空间(可将暂时不访问的数据挪到硬盘先
OS中采用的内存管理方式
- 重定位relocation
- 分段segmentation(一个进程分的空间是连续的空间这是一个限制,于是将程序分成数据,代码,堆栈,当然每一段的内容还是需要连续的,虽然这个要求仍然是高的
- 分页paging (最“小”的单位来构建存储区域
- 虚拟内存virtual memory(逻辑地址空间大于实际的物理地址空间
- 按需分页虚拟内存
- 以上方法都是对硬件高度依赖的,其中mmu就是负责cpu的内存访问请求
地址空间和地址生成
物理地址空间是硬件支持的地址空间,逻辑地址空间是CPU运行的进程看到的地址,这两者之间有转换有映射
逻辑地址如何映射到物理地址?(地址生成过程
- CPU
- ALU: 运算器需要逻辑地址的内存内容
- MMU:内存管理单元进行逻辑地址和物理地址的转换
- CPU控制逻辑:给总线发送物理地址请求
- 内存
- 发送物理地址的内容给CPU
- 操作系统
- 建立逻辑地址LA和物理地址PA的映射(页表
- 设置LA的基址和界限,确保程序间互不打扰
连续内存分配
背景:
在没有其他技术支持的情况下,分配给一个进程的地址空间必须是连续的,为了提高效率,就希望分配的位置可以有适当的选择。又由于是分配连续物理内存区域,当进程在不同时间内调度完之后必然会有内存碎片
- 内存碎片:不能被利用的空闲碎片,分配单元之间未被使用的内存为外部碎片,分配单元内部未被使用的内存为内部碎片
- 操作系统需要维护两个数据结构:1)所有进程的已分配分区 2)空闲分区(empty-block)
动态分区的分配策略
- 最先匹配: 按顺序匹配,找第一个比进程所需内存大的
- 最佳匹配: 空闲分区需要按顺序排序,找最接近的
- 最差匹配: 空闲分区按从大到小拍,选最大的分区
碎片整理
前提是我们已经把内存空间分配给了进程,也确定了位置,但是正在执行的应用进程或者新创建的应用进程需要内存空间,而此时只剩下一些内存空间了。此时我们就可以通过碎片整理来获得更大的可用空间,以满足进程的应用需求
- 碎片紧凑:通过移动分配给进程的内存分区,以合并外部碎片,但问题是如果进程引用的是绝对地址的话是不能随意调动位置的,所以要求所有的程序都是动态可重置的
- 分区对换(交换式碎片整理): 通过抢占并回收出于等待状态进程的分区,即把暂时不用的进程挪到磁盘中
非连续内存分配
背景:
- 非连续内存空间的缺点:
1)分配给程序的物理内存必须连续,比较难达到
2)存在内碎片和外碎片的问题
3)另外,如果一个程序在运行过程中需要的内存大小有变化的话,动态修改内存比较困难
4)综合上述的话就是连续分配内存的利用率较低 - 连续分配内存的设计相对来说可以提高内存的利用效率,增加管理的灵活性
1) 允许共享代码与数据
- 支持动态加载和动态链接
- 实现需要解决的问题
1) 如何实现虚拟地址和物理地址的转换:软件实现or硬件实现
2) 如何选择非连续分配中的内存分块大小:段式存储 and 页式存储
段式存储管理:段地址空间,段访问机制
- 进程的段地址由多个段组成:如主代码段,子模块代码段,公用库代码段,堆栈段,堆数据,初始化数据段,符号表等,即将一个进程以更细粒度和灵活的方式分离与共享,当然,每个粒度即每个小部分之间是连续的
- 由此得到段地址空间的逻辑视图:
- 因此段就表示访问方式和存储数据等属性相同的一段地址空间,一个段对应一个连续的内存块,若干个段组成一个完整进程的逻辑地址空间
- 程序根据段访问机制访问内存需要一个二维的二元组(s段号,addr段内偏移):通过一个段表得到逻辑地址对应的物理地址,段表中有对应段所在物理地址的起始地址和长度限制,段号s是段表的索引
页式存储管理:
背景
页式比段式分配的粒度更小些,这样相对的逻辑地址空间与物理地址空间的转换也更复杂一些
- 页帧 (帧、物理页面,Frame、Page Frame):把物理地址空间划分为大小相同的基本分配单位,2的n次方
- 页面(页,逻辑页面,Page):把逻辑地址空间也划分为相同大小的基本分配单位,帧和页的大小相同
页面到页帧,逻辑地址到物理地址
- 通过下列方法/公式 可以得到每一个存储物理内存中的存储单元所在的位置所对应的帧的二元组表示
- 同理,一个程序的逻辑地址空间被划分为大小相等的页,逻辑地址的页内偏移量等于物理地址的帧内偏移量,逻辑地址也是一个也是一个二元组
- 页寻址机制的实现:这边用到了一个页表,页表实际上就是一个大的数组/hash表,保存了逻辑地址和物理地址之间的映射关系,通过逻辑地址的页号得到物理地址的帧号,又由于这两个地址的偏移相等,所以就可以寻址成功
- 再注意一下呢,页是逻辑地址的,所以页是连续的虚拟内存,而帧是非连续的物理内存,,并且不是所有的页都有对应的帧的
页表:快表,多级页表,反置页表
- 页表是负责逻辑地址到物理地址之间的转换的,how?
- 页表存储在页表基址寄存器中,页表内容有一些标志位,其中第二位resident bit表示这个逻辑地址是否有分配的物理内存,若为0则表示没有,当程序访问这个内存时就会报错,另外两个标志位在后面页面置换中会被用到
- 一个实例:
这边有两个逻辑地址,(4,0)和(3,1023),逻辑地址的页内偏移和物理地址的帧内偏移是一样的,所以页表中不保存偏移,我们只看页帧号,第一个地址的页帧号是4,即100,它的resident bit是0,所以(4,0)在物理地址中没u有对应的内存分配;第二个(3,1023),3是011,resident位是1即存在,通过页表找到页帧号,加上一样的偏移量得到对应的物理地址。值得一提的是,页表是由os维护的
- 页表引起的问题:
1) 因为页表的存在,访问一个内存单元需要2次内存访问,一次用于湖区页表项,第二次才是访问数据
2) 页表有可能会很大,页表的长度等于2^页号位数,这样的开销就会很大。假如,一个64位的机器,如果一页的长度是1024KB。假设页号是n位的,那么页表的长度等于2的n次方,一页是1024KB,所以页内偏移是10位,一个逻辑地址的长度等于计算机位数,也就是64位,因此剩下的54位是留给页号的,明显CPU装不下的一个程序一个页表,n个程序n页页表,那就非常大了。
3)一般用两个解决办法:
- 缓存 caching ------> 快表
- 间接访问 indirection ------> 多级页表
快表TLB:针对第一个需要访问两次内存的解决方法
- TLB(Translation Look-aside Buffer)实际上是CPU的MMU内存单元保存的一段缓存,这段缓存保存的内容是页表的一部分内容——经常访问的那部分页表页。
TLB未命中,叫做TLB miss,这种情况比较少见,因为一页很大,32位系统一页是4K,如果采用局部性原理,那么访问4K次才会遇到一次TLB miiss。
二级页表/多级页表——针对第二个页表空间开销的问题
- 二级页表:
1)在逻辑地址中,页号分成2个部分,p1和p2,p1存放着二级页表的起始地址,p2用来对应物理地址(就是起了之前的P的作用),即P1找二级页表,p2找页,找到地址
2)二级页表的好处:P1对应的位置是flags,假如说有地址的residents bit是0,那么整个二级页表都不用再在内存中保存了,这就减少了很多空间,是一级页表无法实现的 - 多级页表:
同理二级页表,只不过是把页号分成了更多个部分,以此实现多级间接页表,建立页表“树” - 反向页表
传统的页表中是逻辑地址为index去找物理地址value,但是逻辑地址空间增长速度快于物理地址空间,于是考虑将设计一个反向页表,不要让页表与逻辑地址空间的大小相对应,而是让页表与物理地址空间的大小相对应,即index是物理地址,value是逻辑地址,这样能够减少页表尺寸,但是也给映射关系的建立带来了困难
(一)页寄存器
1)基于页寄存器page register的方案
每个帧与一个页寄存器关联,寄存器内容包括:
▲使用位:此帧是否被进程占用
▲占用页号:对用的页号P
▲保护位:访问方式(读/写
—— 页寄存器的优点:页表的大小相对于物理内存来说很小,且页表的大小跟逻辑地址空间的大小无关
—— 缺点:需要的信息对调了,原本是根据页取找帧,现在是根据帧号取找页号,在也寄存器中搜索逻辑地址中的页号是比较困难的一件事儿
2)页寄存器中的地址转换:
▲CPU生成的逻辑地址如何找到对应的物理地址?
· 对逻辑地址进行Hash映射,减少搜索范围(需要解决可能的冲突
▲用快表缓存页表项后的也寄存器搜索步骤:
· 对逻辑地址进行Hash变换
· 在快表中查找对应页表项
· 有冲突时遍历冲突项链表
· 查找失败时,产生异常
(二) 反向页表
反向页表与页寄存器的区别就是反向页表将进程PID也考虑进来了,
举例: