操作系统层面的内存管理
背景:
内存管理的主要操作时把程序装入内存中执行,一般采用虚拟内存的方案
而虚存又是基于分段和分页这两种基本技术,或者这两种中的一种.
页的概念
固定长度的数据块,存储在二级存储器中(如磁盘).数据页可以临时复制到内存中的页框中
页框的概念
内存中固定长度的块,更详细的后面会分析
段的概念
变长数据块,同样是存储在二级存储器中.整个段可以临时复制到内存的一个可用区域中(分段),或者可以将一个段分为许多页,然后将每页单独复制到内存中(分段与分页相结合)
技术 | 说明 | 优点 | 缺点 |
---|---|---|---|
固定分区 | 在系统生成阶段,内存被划分成许多静态分区。进程可以装入大于等于自身大小的分区中 | 实现简单,只需极少的系统开销 | 会产生内存碎片,对内存的利用不够充分;同时活动进程的最大数量是固定的 |
动态分区 | 分区是动态创建的,因而每个进程可装入与自身大小正好相等的分区中 | 没有内存碎片;可以更好的利用内存 | 由于需要压缩外部碎片,处理器利用率低 |
简单分页 | 内存被划分为许多大小相等的页框;每个进程被划分成许多大小与页框相等的页;要装入一个进程,需要把进程包含的所有页都装入内存内不一定连续的某些页框中 | 没有外部碎片 | 有少量内部碎片 |
简单分段 | 每个进程被划分成许多段;要装入一个进程,需要把进程中包含的所有段都装入内存中不一定连续的某些动态分区中 | 没有内部碎片;相对于动态分区,提高了内存利用率,减少了开销 | 存在外部碎片 |
虚拟分页 | 除了不需要装入一个进程中的所有页外,与简单分页一样;非驻留页在以后需要时自动调入内存 | 没有外部碎片;支持更多道数的多道程序设计;巨大的虚拟内存地址空间 | 复杂的内存管理开销 |
虚拟分段 | 除了不需要装入一个进程的所有段外,与简单分段一样;非驻留页在以后需要时自动调入内存 | 没有外部碎片;支持更多道数的多道程序设计;巨大的虚拟内存地址空间;支持保护和共享 | 复杂的内存管理开销 |
内部碎片的概念
比如说我装入一个2M的程序,但分区的大小是4M为单位的,那么我就产生了2M的内部碎片
外部碎片的概念
刚开始是第一个程序分配了一个6M大小的空间,使用完之后将一个程序换出就有6M的剩余空间,第二个程序占用4M,那么同样会产生一个2M大小的空洞,随着时间的推移,这种小的空洞会越来越多,每一个空洞都不足以方向一个小程序的时候,我们内存的利用率也会越来越多
它们的区别?
其实内部碎片更多的是针对于固定分区这种,即一开始是就分配了很多规格大小相同的块,然后小程序往里面放
而外部碎片,更多的是针对动态分区这种,操作系统刚初始化内存的时候,第一次是可以分配到对应大小的内存的,后面才会慢慢产生空洞,而内部碎片,很可能第一次分配的时候就产生出来了.
那么克服碎片的方案就是移动然后压缩
也就是需要移动进程,然后将空洞压缩到一起,当然这是一个很耗时的操作
然后再说一个比较重要的前提
我们为什么会需要虚拟内存
处理器访问一个不再内存中的资源时,会产生一个中断(即缺页中断,这个时候就需要调用I/O功能去从磁盘中加载),而我们知道磁盘I/O是比较慢的,所以怎么来应对进程在执行过程中因为没有装入所需要的进程块而不得不中断,阻塞?
解决思路
1.在内存中保留多个进程(这里我们又知道,内存是有限的,我们不需要一开始就加载每个进程的所有页进内存,如果这样,我们能加载到内存中的进程数就会少很多)
2.进程可以比内存的全部空间还要大(这也是我们需要虚拟内存的原因)
虚拟内存
我们知道虚拟内存对应的就是物理内存,我们访问一个资源的时候一般都是基地址+偏移量(也叫段地址)
物理的就是物理基地址+偏移量
逻辑的就是逻辑基地址+偏移量
那么他们之间就应该有一个映射关系(或者叫对应关系)
而进程中的内存访问都是基于逻辑地址
进程中称为页的块可以分配到内存中称为页框的可用块
逻辑地址的好处
假如现在没有足够的连续页框来保存进程A,这会阻止操作系统加载该进程?答案是不会,那是怎么做的?
每个进程维护一个页表,页表给出该进程中的每页所对应页框的位置.
即逻辑地址会在运行时动态地转换为物理地址.这也意味着一个进程可以被换入或者换出内存,也即进程可以在执行过程的不同时刻占据内存的不同区域
整个访问机制如下
分页系统中的地址转换.png
现在访问的整体流程如下
操作系统页访问流程.pngTLB
全称Translation Lookaside Buffer,转译后备缓存器
它可以看作是页表的高速缓存
此外,页表还可以进一步细分,可以分为根页表和子页表。
分段
采用分页技术,每个进程给划分为相对较小,大小固定的页
采用分段技术,可以使用大小不同的块
分页对用户(程序员)是透明的,它消除了外部碎片,同时它的内存块时固定的,大小相等的,更便于管理,也更容易写出更好的存储管理算法
分段对用户(程序员)是可见的,它的段可以大小不等,所以它具有处理不断增长的数据结构的能力,同时它也支持共享和保护的能力,怎么支持,后面会分析
它的地址转换与分页类似
分段系统中的内存转换.png
关于分段为什么支持共享和保护
一个进程对应一个段表,而每个段表项包含一个长度和一个基地址,因而程序不会不经意地访问超出该段的内存单元
一个段可以被多个进程的段表引用
段页式
段页式系统中的地址转换.png分页和分段都各有长处,所以也可以采用段页式
在段页式系统中,用户的地址空间会被分为许多段,每段上又可以依次划分为许多固定大小的页,页的长度等于内存中页框的大小.
虚拟内存的操作系统策略
读取策略
决定了什么时候将某页读取至内存
1.请求分页:只有当访问到某页中的一个单元时才将该页读取至内存
2.预先分页:一次读取许多连续的页
放置策略
即有多个空闲位置的时候,是怎么去选择的?这里Netty的内存分配应该就是这个思想
最佳适配
选择与要求大小最接近的块
但却是性能最低的,因为它要保证产生的碎片尽可能小,但同时也会产生许多小到无法满足任何内存分配的空洞,进而频繁触发内存压缩
首次适配
从头开始扫描,选择大小足够的第一个可用块
最简单,也最快
下次适配
从上一次放置的位置开始扫描内存,选择下一个大小足够的可用块.
比如说我要分配6M的,那我就先找到上一个已经分配过6M的,然后再找下一个可用块
常常会在内存的末尾分配空间,导致通常位于存储空间末尾的最大空闲存储块很快分裂为小碎片,也会导致更多次数的压缩
伙伴系统
基于固定分区的分配方案限制了活动进程的数量(因为它大小固定),且如果可用分区的大小与进程大小很不匹配,那就会浪费很多内存空间
而基于动态分区的分配方案,维护会比较复杂,同时内存压缩也会降低性能。
所以就有了折中的伙伴系统
定义
伙伴系统的树状表示.png伙伴系统中可用内存块的大小为2k个字,L<=K<=U,其中2L表示分配的最小快的尺寸,2^U表示分配的最大块的尺寸;
释放完后,又可以恢复成一个大的块
后面再讲Netty是怎么分配的
置换策略
在读取一个新页时,应该置换内存中的那一页
基本算法
1.最优
2.最近最少使用(LRU)
3.先进先出(FIFO)
4.时钟
最优
选择置换下次访问距当前时间最长的那些页,但它要求操作系统必须知道将来的事,所以只是理论上的,并没有实现
LRU
选择置换内存中最长时间未被引用的页
可以给每页都添加一个最后一次访问的时间戳,并且在每次访问内存的时候更新整个时间戳.那么我们根据时间戳进行排序,取到最大的那个也就是最长时间未被引用的
FIFO
把分配给进程的页框视为一个循环缓冲区,并且按循环的方式移动页
它隐含的逻辑是置换驻留在内存中时间最长的页,很久以前取入的页,现在可能不会再用到
但如果有一部分数据或者程序在整个生命周期使用频率都很高,那么这些页反而会经常被反复的换入换出.
时钟
每个页都添加一个修改位和使用位
那么每个页框就有4种情况
1.最近未被访问,也未被修改(u=0;m=0)
2.最近被访问,但未被修改(u=1;m=0)
3.最近未被访问,但被修改(u=0;m=1)
4.最近被访问,且被修改(u=1;m=1)
算法执行流程如下:
1.从指针的当前位置开始,扫描页框缓冲区。在这次扫描中,不对使用位做任何修改。选择遇到的第一个页框(u=0;m=0)用于置换
2.若第一步失败,则重写扫描,查找(u=0;m=1)的页框。选择第一个遇到的这种页框用于置换,同时在这一次扫描中,将跳过的每一个页框的使用位置为0
3.若第二部失败,则指针回到最初的位置,且集合中所有的页框的使用位均为0。重复第一步,必要的时候,重复第二步.
小结:查找的就是距今未被修改且最近未被访问的页,这样的页最适合置换
那么它有什么优点?
由于未被修改,它不需要写入辅存。
Linux中的虚拟内存
Linux虚拟内存方案中的地址转换.png由于页比较多,不便于管理,所以Linux采用了三级页表结构
页目录
每个活动进程都有一个页目录,页目录的大小为一页。页目录中的每项指向页中间目录中的一页。每个活动进程的页目录都必须在内存中。
页中间目录:页中间目录可能跨越多个页。页中间目录中的每项指向页表中的一页。
页表:页表也可以跨越多个页。每个页表项指向该进程的一个虚拟页。
页面置换算法
Linux页面回收.png1.访问非激活链表中的一页时,PG_referenced有效位启用
2.页面下次被访问时,将其移动到激活链表。也就是说,页面需要访问两次后才会被声明为激活
3.若第二次访问未很快发生,则重置PG_referenced
4.同样,激活的页面在两次超时之后也需要移动到非激活链表中
Netty的内存分配
image.png具体可见对Netty内存的分析
网友评论