OS-Revision---内存管理

Posted 给个HK.phd读

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OS-Revision---内存管理相关的知识,希望对你有一定的参考价值。

这是OS的最后一次复习了。

程序装入、存储结构

存储结构
存储器的多层结构,这一点在计算机系统的“存储器管理”章节也会涉及到。我们只需要了解概述即可,并不要理解硬件的工作原理。

层级结构呈金字塔形,从上向下依次应为
1.CPU寄存器
2.Cache高速缓存
3.主存储器
4.磁盘缓存(非实际存储介质)
5.固定磁盘
6.可移动存储介质

从上到下的一个趋势就是,造价(成本)越来越便宜、存储容量越来越大、速度越来越慢。其中前四层是属于操作系统的存储管理的范围,所以断电则信息不复存在。而低二层则由设备管理,信息也是被长期保存的。

我们看到的两块缓存都是用于缓和其上下两层访问数据的访问速度不一致的问题的。缓存会根据“局部性原理”装入较多的指令和数据,但此原理并不能%100的确定,因此涉及到缓存的一个命中率问题,不用深究。

与实际存在的高速缓存器不同,磁盘缓存不是一块实际的介质。只是把主存划分出一块空间来存从磁盘读取的数据。

程序装入和链接

其实这一块在CSAPP中也着重讲过,应该是更为详细的,即将发布的ch07的复习。在OS里都只是提及一下概念?

这就是程序的主要处理步骤:
编译成.o文件,即可重定位目标模块。
将所有用到的这些模块,静态库,动态库等进行链接生成可执行程序。最后将此程序段装入内存中。
在CSAPP里我们会详细看到ELF表的格式,链接如何例题rel_text、rel_data等信息进行重定位。

程序的装入过程有几种常见的装入办法:
1.当计算机系统较小时完全可能知道程序驻留在何处,于是编译时可以赋予物理地址让程序直接装入内存。这称为绝对装入:程序中逻辑地址与内存物理地址完全相同。
2.可重定位方式装入:静态映射,在装入时对逻辑地址进行修改。通常我们会使目标模块的起始地址为0,然后其他内容都是相对于这个起始地址而言的。
显然,这适合于多道程序,且我们的计算机系统也是越来越大,我们无法预知程序最后放在哪。
一个简单的图示理解此种方式:

如上图,在我们的目标模块地址空间是从0开始的。但真正装入内存后要加上一个偏移量,当然了,这是一种简单的理解方式,真实情况要复杂一些。
3.动态运行时装入:逻辑地址到物理地址的映射在程序运行时才执行。
我们的可重定位方式,在一次映射后就放在该位置了。这样一来我们的程序就没法移动了,一旦移动得修改物理地址才行。但实际情况就是我们要经常进行对换操作等。


如上图所示,本来我们是用符号来表示程序的。可能是我是JMP到一个函数模块A,但绝对装入后我们直接引用绝对地址。这样一段程序是可以直接运行的。

然后就是程序的链接:
同样有三种方式。
1.静态链接,比如Linux下的.a静态链接库。在装入内存前已经链接成一个整体。
2.装入时动态链接。
一边装入一边链接;
便于修改和更新;
便于实现对目标模块的共享。

3.运行时动态链接
执行过程中,当发现一个被调用模块尚未装入内存时,立即由OS去找到该模块并将之装入内存, 把它链接到调用者模块;
不用的目标模块不会被调入内存和被链接到装入模块;
加快程序的装入过程,而且可节省大量的内存空间。

连续分配存储管理形式

1.单一连续分配。
由一个用户独占用户内存空间,低地址的内存被OS占用。
适用于单用户单任务的OS中。一般需要额外的硬件对系统区(OS)进行保护。
2.固定分区分配。
分为分区大小相等和不等两种方式,概念十分简单,可以看下图:

从上图还可以看到有两张分区表,这是为了便于内存分配设计的。
可以看到分区大小按从小到大排序(但未必都是这样,只是为了说明是有序的),我们装入程序时进行查表即可。
但固定分区易造成浪费,当然也有自己适用的范围,现在用的少。
3.动态分区分配。
为了实现这一功能,需要维护一些数据结构。
一张空闲分区表,包含了分区号、分区首地址及长度。
一个空闲分区链。链表是双向链表,结点的头尾会增加控制信息,比如在尾部会增加一个状态位,还会增加分区大小。当被占用时,值为‘1’。

分配算法

设计合理的分配算法也是我们提高内存利用率的重中之重。
这里的分配算法是基于对动态分区进行的。因为前两种都不需要。

FF首次适应算法
按顺序选择第一个满足要求的内存区域。
优点:保留高地址的大部分空间**(因为每次都是从第一块找)
缺点:低地址存在很多小片无法利用的空间,查找时间长
(因为每次都是从第一块找)**。

循环首次适应算法
显然改进了刚刚的缺点。但因为极有可能需求小的分配到很大就是缺点。
在上一次找到空闲分区的下一个分区开始寻找,找到第一个满足要求的内存区。
优点:空闲分区在内存中分布均匀,查找时间短。
缺点:缺乏大的内存空间。

最佳适应算法
在所有空闲分区中查找与程序大小最相近的空闲分区。
为了加快速度,可以把空闲分区从小到大排列。
优点:提高内存使用率,保留大空闲去。
缺点:仍然会存在小片无法利用的空闲分区。

可以发现都是比较简单的,都是初等数学的问题,小例题:

红色的是我们分配的空间,绿色的就是碎片。很容易,不用多说。

我们的内存分配也有一个流程图:
m.size是需求空间,u.size就是某一分区的大小。比较疑惑的可能就是两者相减小于等于size。实际上size是一个不可再分割的区域,就是一块较小的碎片吧。
如果比这个值都小就干脆把整个分区给程序了,否则的话仅仅是分配需求空间给程序,剩下的还可以供其他使用!


这是内存的分配的流程图以及一个准则,还有很重要的关于内存的就是回收了。针对此我们需要讨论几种情况:

如第一张图,我们要回收的空间和前一块空闲去相连。则无需在空闲表设置新表项保存回收区,仅修改F1的大小即可。

第二图也是进行合并,只不过与下面的空闲区合并。此时注意的就是首地址要改为回收区的首地址。

第三张图将三区合并,采用F1首地址,并且取消F2的表项。

第四张图就是最普通的了,添加新表项,取回收区首地址,大小就是回收区大小。

分页管理

首先理解对换的含义。
对换:把内存中暂时不能运行的进程或者暂时不用的程序和数据,调出到外存上,以便腾出足够的内存空间,再把已具备运行条件的进程或进程所需要的程序和数据,调入内存。

有两种类型:
整体对换。以进程为单位。
部分对换。以段或者页为单位。
我们通常将磁盘空间划分为对换空间和文件空间。由于对换出去的内容只是短暂存在磁盘上,可能立马又会被调入内存空间。所以对此空间进行管理的目标是提高访问速度,先不考虑存储空间的效率问题。

因此,我们采用连续分配方式。
然后就是理解换入换出的原因和一些基本的操作:

对换其实还是一个比较耗时的过程,虽然存在但随着现在计算机内存和CPU效率的提高用的较少。只有当较多进程发生“缺页”现象时,才启动对换程序。

分页式存储管理

我们上边谈连续分配会产生碎片,即使采用“紧凑”的方法也是比较麻烦的。比较好的思路实际上是引入非连续的分配。

所以就有了我们的“页”、“段”的概念。
我们的页是操作系统分配内存的最小单位,但其只是一个逻辑划分。
同时我们会把内存划分为很多“物理块”,页大小会去适配物理块大小。这样一来一个程序的某一“页”就可以找任意物理块进行放入。
我们再来看看,这个逻辑和物理概念上的对应关系:

下面就是页面的地址结构。
如下图所示:
32bit的前12bit是位移量,也就是页内地址,大小是4KB。
而剩余的高位是页号,不难算出可以表示1M的页号。
地址计算方式也很简单,就是除法的取整和取模运算。

我们会把所有的页整合成一张页表,页表的每一项纪录是页号和物理块号。实现的就是一个映射的功能:

页表的数据存放在内存中。页表的起始地址和长度会记录在PCB中,但最后要做一个地址转换,要把页表的起址和长度送入页表寄存器。
地址转换做的事情如下:
地址变换过程
1.进程访问某个逻辑地址时,分页地址机构自动将逻辑地址分为页号和页内地址
2.页号大于页表长度,越界错误
3.根据页号计算位置,从页表中查物理块号
4.页内地址直接对应块内地址
5.通过物理块号和块内地址得到物理地址
6.根据物理地址读取数据

例题:
先求出页号 = int(4832 / 2048) = 2
再求出偏移量 = 4832 - 4096 = 736
对应一下2号页号是25块号。
所以最后的物理地址 = 25 × 2KB + 736B = 103136

刚刚的地址变换机制我们要两次访存,第一次查找页表,第二次根据第一次索引到的物理块及偏移量到真正地址上读取数据。
这里我们引入“快表”的概念,当然了,用于加快速度!

带快表的地址变换过程:
1.进程访问某个逻辑地址时,分页地址机构自动将逻辑地址分为页号和页内地址
2.页号大于页表长度,越界错误
3.页号送入快表,查找物理块号
4.若快表找到,送入物理地址寄存器
5.否则在从页表查找物理块号,送入物理地址寄存器
6.页内地址直接对应块内地址
7.通过物理块号和块内地址得到物理地址
8.根据物理地址读取数据

快表看起来流程仿佛也差不多,但实际上快表可以并行查找,将部分页表内容保存在快表,可以大幅提高速度。
但注意我们上面讲了部分,存在命中率问题,如下题:

单级页表有较多的缺陷,比如因为其是一个连续分配的数组,所以一个物理块能放置的表项有限,显然不利于大作业。
所以就有了下面缺陷的讨论:

我们采用的方式就是多级页表,多一个间接索引而已。
先来看的就是两级页表。
两级页表:对页表本身采用分页式管理,对页表本身增加了一层页表管理
两级页表的逻辑地址结构与地址变换
注意:页大小就是页表大小,一张页表只能装在一个页中
一张比较形象的图:

再配合一张图看:

其实就是本来单级情况下,页表项内容直接就是一个页号对应于一个物理块号。但现在不同了!因为我们的定义就是,对页表进行分页存储,所以第一个索引到的就是某一页的页表,第二个才是真实的页表项。

比如可以看这么一道题:
记住页面大小就是和偏移量有关的,所以就是2的n次方,为4KB。
因为一级索引有10位,也就是说可以查到2的9次方张页表。
同时每张页表应该可以存储,2的11次方个页号。
所以共有几个页面的问题就是两者相乘,实际上除了位移量外的其他位全可以拿来表示即可。

然后又回到单级页表先:
这是教会我们直接用二进制完成题目,而不是16进制转10进制,然后还是除法运算。
可以发现我们转换为二进制后,也可以直接得到页号2,就可以映射到物理块。同时再加上偏移量即可。14可以转换为8 + 4 + 2,这样用二进制处理更方便。
最后二进制转回16进制,完毕。

练习三:
偏移量应为2KB,为11bit。0 ~ 10
因为要提供16页的页号,所以页号最多也仅需要4即可,4位。11 ~ 15
所以至少就是15位。
内存空间2KB × 8 = 16KB,因为物理块大小 = 页大小,共8个物理块。

还是一道练习题,有点多。。。

一页可以管理1K个页表项,易知。
36的虚拟地址空间,进行计算236 = 64GB。

1K个表项就是1K页,也就是4KB × 1K = 4MB显然无法保存。

如果采用二级的话,第一个1K搜到的是页表号,每个页表又可以保存1K页表项记录。所以共可以 1K × 1K = 1M的页表项,1M × 4KB = 4GB的内容,还是不够。

三级类似,就是1K × 1K × 1K ×4KB = 4TB = 4096GB,就够了!所以答案三级页表才行!

好了最后一道题……

1.一个页面可以保留256个页表项,这256个页表项可以索引256页,所以逻辑空间就是。 256 × 1KB = 256KB。

2.同上 256 × 256 × 1KB = 1K × 64 × 1KB = 64MB。

3.外层页号。其实就是我们一级索引的整除值。
542783 / (256 × 1024) = 2

而上式取余就是我们接下来处理单级页表的数据。
即18495就是外层页内地址。

18495 % 1024 = 63
所以63就是最后的页内地址。

还是想自己深入了解一下更加形象的二级的道理。

其实就是我们有好多张索引表,一个块放不下,所以就建了一张页表来管理这些页表先。
虽说我们的页表管理的记录是页号和物理块号的映射关系。
但一张页表不正是存放在一页里么?

所以我们的一级索引得到的页号里其实是存储了一张页表!
当然了,单级索引的一级索引就直接得到了块号和偏移量地址。

我们获得了页表的页号但那果然还不够,所以需要一次二级索引将在此页表中存储的页号取出来就是我们的原理了。

按照C的指针理解或许是个不错的方案?

以上是关于OS-Revision---内存管理的主要内容,如果未能解决你的问题,请参考以下文章

OS-Revision-处理机调度

OS-Revision-处理机调度

OS-Revision--文件系统

OS-Revision--文件系统

OS-Revision---进程同步

OS-Revision---进程同步