操作系统 第三章 内存管理

第三章 内存管理

3.1 无存储器抽象

​ 意为物理内存直接面向程序,程序直接访问物理内存。(程序内部的逻辑指令所指向的地址,就是物理内存中实际的地址。)因此这种情况下真个内存中只能同时运行一个程序。

​ 无存储器抽象也出现了几种变体,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iO1BhFbm-1628474261105)(C:\Users\10332\AppData\Roaming\Typora\typora-user-images\image-20210609103226332.png)]

在图(a)中,操作系统位于RAM(随机访问存储器)底部,上面是用户程序,以前被用于大型计算机和小型计算机中,现在很少被使用了在图(b)中,操作系统位于内存顶端的ROM(只读存储器,read only memory),下方是用户程序用在一些掌上电脑和嵌入式系统中在图(c)中,操作系统位于内存底部的RAM中,设备驱动程序位于内存顶端的ROM中,中间存放用户程序,用于早期的个人计算机中,在ROM中的系统部分称为BIOS,基本输入输出系统。

3.1.1 静态重定位

​ 在无存储抽象的情况下在内存中并发的实现两个以上的程序时,会出现严重的错误,如系统的跳转指令仍然按照程序内的逻辑地址跳转,而在实际内存中会跳转至另一个程序的部分。为了避免这种情况,可以使用静态重定位的技术。

静态重定位就是在程序从磁盘装载到内存中时,按照程序在内存中的位置,将程序代码段的起始地址加到每一个代码的地址上,这样能够避免程序之间相互冲突,但是会明显减弱程序运行速度。

3.2 一种存储器抽象:地址空间

​ 之前的无存储器抽象中,操作系统直接将物理地址暴露给进程,这样会带了很大的隐患:1. 程序可能会偶然破坏掉操作系统,2. 多个程序很难同时在一台计算机上运行。因此需要另外的办法。

3.2.1 地址空间的概念

地址空间是一个进程可用于寻址内存的一套地址集合,并且这个地址空间独立于其他进程的地址空间

动态重定位:将每个进程的地址空间映射到物理内存的不同部分。每个CPU配置两个特殊的硬件寄存器,称为基址寄存器界限寄存器,简单来说会将进程的起始物理地址装载到基址寄存器中,将程序长度装载到界限寄存器中,这样在读取指令的时候,指令或者数据的地址会自动加上基址寄存器中的值,实现动态重定位。但是动态重定位的缺点在于每次提取指令的时候都要进行一次运算,运行会变得缓慢。

3.2.2 交换技术

​ 内存是有限的,不可能无限制的装载程序。因此内存中的程序需要不断地交换替代。

​ 第一种方案是交换技术,将一个进程完全的添加到内存中,运行一段时间后再存回磁盘。这样空闲程序大多数时间存储在磁盘中,不会对内存造成负担。

​ 第二种方案是虚拟内存,在下一节详细讨论。

​ 交换技术由图即可理解,即在内存中有空闲就填入。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zFTjCbo1-1628474261107)(C:\Users\10332\AppData\Roaming\Typora\typora-user-images\image-20210610211020188.png)]

​ 交换技术导致了内存中会产生很多碎片,也成为空洞hole(图中阴影部分),分为内碎片和外碎片内碎片是指分配给作业但是没有被利用的空间,外碎片是指碎片太小无法被利用的空间。通过把所有的进程尽可能向下移动,有可能将所有的空闲区融合成一大块区域,这个操作叫做内存紧缩。但是一般不进行这个操作,应为需要耗费大量的CPU时间,需要扫描整个内存。这种分配方式只适用于程序长度不会增长的情况,如果程序大小会发生变化则需要为其预留空间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N73ZN8U6-1628474261109)(C:\Users\10332\AppData\Roaming\Typora\typora-user-images\image-20210610212508114.png)]

3.2.3 空闲内存管理

​ 操作系统必须对内存进行管理。最早的内存管理就是将内存固定的分为几个区域,很显然很简单,但是利用率很低。

3.2.3.1 使用位图的存储管理

​ 使用位图时,内存会按照一定的单位进行分割,几个字节或者几千字节,每一个单元对应的是位图中的一个比特(0或1),0代表空闲,1代表被占用(一般如此,实际情况按照操作系统具体规定。)

​ 分配单元的大小是一个值得探讨的问题:分配的单元过小时,位图会变得非常大,占用内存空间;分配单元过大时,容易造成单元中的空间浪费(一个单元不能分配给两个进程。)

​ 位图需要搜索出连续的几个为0的比特,来满足内存的请求。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DZJmcBVq-1628474261112)(C:\Users\10332\AppData\Roaming\Typora\typora-user-images\image-20210610213844392.png)]

3.2.3.2 使用链表的内存管理

​ 另一种办法是来维护一个记录已经分配内存段的链表。链表中的每一个节点都要包含以下域:一个标记位,用来表示这段空间是空闲的(H)还是被占用的(P);区域的起始地址长度直向下一结点的指针。由于区域在空闲和占用的状态之间的切换,列表个数会变化的非常频繁,因此链表的实现方式,双向链表会比单向链表更加方便。

​ 在使用链表管理的方式下,当新的进程进入内存时,需要沿着链表一路检索,找到足够装载的一整块区域分配给新的进程。在这种情况下又有几种适配算法

  1. 首次适配算法:沿着列表搜索,找到第一个足够装载的区域就分配给新的进程,除非这块区域恰好与新的进程相同大小,否则将该空间的剩余部分开辟成一个新的空闲节点。
  2. 下次适配算法:与首次适配算法相似,唯一的不同点在于搜索到的位置会在此作一个标记,下一次搜索时会从标记位置继续开始搜索。经过仿真程序的证明,下次适配算法的性能略低于首次适配算法。
  3. 最佳适配算法:搜索整个链表,找到能够容纳进程的最小的空闲区。优点是内存利用率高,缺点是运行速度会很慢,以及会比首次适配算法产生更多的内存碎片
  4. 最差适配算法:找出最大的空闲区分配。经过证明浪费的也很多。
  5. 快速适配算法:它维护一个常用大小的空闲区的链表,如第一个项是4KB,第二项是8KB,第三项是12KB。优点在于可以快速找到一个合适的区域存放进程,缺点在于在进程终止时合并相邻块是非常费时间的。
3.2.3.3 伙伴系统(讲义额外内容)

​ 是一种经典的内存分配方案,首先将所有可用的空间看作一块,用2U来表示,新建的进程用S来表示,若2(u-1)<S<2^U,则分配整个块,若更小则按照2的幂次依次减弱,直到找到最合适的块大小。

3.3 虚拟内存

​ 内存交换技术实现方式是将整个进程装载到内存中,在进程大小较小的时候可以实现,但是进程大小如果超过了整个内存大小时,交换技术则无法进行。这时就需要虚拟内存。

​ 虚拟内存的基本思想是:每个程序拥有自己的地址空间,这个空间被分割成很多个块,每一块被称作一个,或者页面。每一页有连续的地址范围。这些页被映射到物理内存,但并不是所有的也都必须在内存中才能运行程序。

3.3.1 分页

​ 分页式存储是虚拟内存的核心,需要用硬件支持(MMU,内存管理单元)。

​ 程序内部产生的地址是虚拟地址,也可以理解为程序内部的逻辑地址,它产生后送往CPU时,会先被MMU映射到物理地址中的某个部分,然后再送往CPU。这是MMU实现的方式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C1KZXAgO-1628474261113)(C:\Users\10332\AppData\Roaming\Typora\typora-user-images\image-20210611184146198.png)]

​ 用户进程自己拥有的地址空间可以是无限大的,这由程序自身决定。用户进程自己的地址空间被划分成大小相等的部分,每一部分就是一页,从0开始编号。物理内存空间同样按照相同大小分成若干的部分,也是从0开始编号,称为物理页面,页帧,内存块。内存以页位单位分配给进程需要的页数,在逻辑上相邻的页在物理内存中不一定相邻。页面尺寸一般为4K到4M。x86一般为4K。

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ynUWld1T-1628474261115)(C:\Users\10332\AppData\Roaming\Typora\typora-user-images\image-20210611184223167.png)]

​ 上图左为进程自身的虚拟地址空间,右边为物理内存。可见并不是所有页都在内存中,同时根据具体的映射规则导致逻辑上相邻的页在物理内存中并不一定相邻。

​ 程序产生的逻辑地址(虚拟地址)是由页号+页内地址组成的,从这个得到MMU的工作原理:

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UZ6HkPWP-1628474261117)(C:\Users\10332\AppData\Roaming\Typora\typora-user-images\image-20210611190557325.png)]

​ 以上图为例:虚拟内存共有64K,共有16个页;实际物理内存共32K,共有八个页。虚拟地址会被分成两部分:页号页内索引。如图中的虚拟地址8196,二进制为001000000000100,由于实际的程序页表共有16页,MMU中就维护一个共有16项的数组,用虚拟地址的前六位来作为页号索引,剩余部分作为在页内的偏移量。MMU通过这样的方式将一个虚拟地址映射为以恶搞物理地址,送到CPU,CPU就可以正确的从内存中读出实际的指令了。

3.3.2 页表

​ 页表是一张表,每个进程各一张,存放在内存中,负责虚拟地址到实际地址的映射。虚拟地址由虚拟号和偏移量组成,虚拟号可以作为页表的索引,索引到具体的页表项。

​ 进程能够占多少内存页,页表中就有多少项。页表中的每一项是一种数据结构,其中包含:

  • 页框号:最重要的,表示这个虚拟地址映射后在哪一个物理内存的页框中。
  • 在/不在:表示这一页虚拟内存中的指令是否在物理内存中,负责触发缺页中断
  • 保护:指出这个页是允许什么类型的访问,比如是否只读、只写、只运行等。
  • 修改位:记录页面是否被修改。
  • 访问位:记录当前页面是否被访问。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yetS0vT3-1628474261118)(C:\Users\10332\AppData\Roaming\Typora\typora-user-images\image-20210611191858450.png)]

​ 页面在磁盘中的地址并不存放在页表中。因为页面若不在内存中则需触发缺页中断,需要陷入操作系统由软件完成,而页表是硬件按位访问的。

3.3.3 加速分页过程

​ 分页式系统中考虑两个主要问题:第一:虚拟内存到物理地址的映射必须要快,否则所有程序运行都会变慢;第二:如果虚拟内存空间非常大,那么页表也非常大。

​ 先考虑第一个问题:

​ 如果将页表全部存入寄存器,那样虽然快,但是代价太过高昂;如果将页表全部存放在内存中,那么查找速度会变慢。

​ 为了解决第一个问题,可以使用caching的思想,可以设置一个小型的硬件设备,将虚拟地址直接映射为物理地址,不必访问页表。这种设备被称为转换检测缓冲区(TLB),又称为相联存储器或者快表

​ 设置TLB的目的是为了加快页表的查找来加快地址的映射,根据的是程序的局部性原理,大多数程序总是对少量的页面进行大量的访问。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-znr7BwVG-1628474261119)(C:\Users\10332\AppData\Roaming\Typora\typora-user-images\image-20210611194943596.png)]

​ 图是快表的一个例子,从左到右分别为:有效位、虚拟页面号、修改位、保护位、页框号。如果虚拟页号对应的页面不在TLB中,会去查找页表,并更新TLB,替换其中一行。

3.3.3.1 软件TLB管理

​ 有几种不同的TLB失效情况:

  1. 软失效:页面不在TLB中,但是在内存中,这种情况下只需要查找页表,然后更新一下TLB即可。
  2. 硬失效:页面不在TLB中,也不再内存中。需要一次磁盘存取更新页表。
  3. 硬失效在实际情况中可能有三种可能:
    • 页面可能在内存中,但是不在页表中。称为次要缺页错误,只需重新调入TLB即可。
    • 页面根本不在内存中,需要重新从磁盘中调入,这种称为严重缺页错误
    • 程序可能访问了一个非法地址,只需报告错误即可。

3.3.4 针对大内存的页表

​ 为了解决上述第二个问题。

3.3.4.1 多级页表

​ 多级页表就是为了解决大进程会产生大页表的情况。多级页表的优势是不需要页表线性存储,同时只需要存储所需要的一部分即可。缺点在于访问页表时速度会变慢。多级页表的本质上是允许页表非线性存放,不能减少总的页表项数

3.3.4.2 倒排页表

​ 每个页表项对应一个页框,而非每个页表项对应一个页。优点在于当虚拟地址空间比物理内存大得多的时候,可以节省大量的空间。劣势在于查表速度缓慢。

3.4 页面置换算法

​ 虚拟内存中,页面在内存与磁盘之间频繁调度,使得页面调度所需时间比程序运行时间还要多,导致程序效率急剧下降,这种现象称为颠簸

3.4.1 最优页面置换算法

​ 将以后不再需要或最远的未来才需要的页面置换出去,这是最优秀的页面置换算法,但是无法实现,因为操作系统无法预知下一步的请求。但是这种算法可以用来当作衡量其他算法的性能。

3.4.2 先进先出置换算法(FIFO)

​ 由操作系统维护一个所有当前在内存中的页面的链表,最新进入的页面放在表尾,最早进入的页面放在表头。当发生缺页中断时,淘汰表头的页面,并将新的页面添加到表尾。很少使用纯粹的FIFO算法。

3.4.3 第二次机会算法

​ 在FIFO算法的基础上新增一处修改:检查最老页面的R位,若为0,则表明这个页面最近没有被访问,故替换该页面。若为1,则将其置为0,并将该页面放置在表尾,更新装入时间为此刻,然后重新搜索表头。

​ 这个算法可能会将所有页面全部置为0,因此肯定会结束。

3.4.4 时钟页面置换算法

​ 是对第二次机会算法的一个更好的实现。将所有链表放置在一个环形链表中,表针指向最老的页面。如果R位是0就淘汰该页面,并把新的页面插入到此位置,然后把表针前移一位。如果R位是1就置0然后前移一位。重复这个过程直到找到一个R位为0的页面。

3.4.5 改进型时钟算法

​ 在时钟行页面置换算法上在进行改进,添加了一个修改位M位,同时考察M和R位,来对页面进行四类的分割:
​ 第0类:R位为0,没有被访问,M位为0,没有被修改。

​ 第1类:R位为0,没有被访问,M位为1,已被修改。

​ 第2类:R位为1,已经被访问,M位为0,没有被修改。

​ 第3类:R位为1,已经被访问,M位为1,已经被修改。

​ 找第0类。若没有重新扫描,并在检查中把R位置0.找不到第0类就找第1类。

3.4.6 最近最少使用算法(LRU)

​ 实现方法1:

​ 设置一个64位计数器C,每条指令执行完后自动加1.每次访问内存后将当前C的值保存到页表项中。一旦发生缺页中断就找到一个值最小的页面。

​ 实现方法2(NRU):

​ 系统周期性的将R位置为0,然后随机从四种类型中的编号最小的类中随机选取一页替换。

3.4.7 最不常用算法(NFU)

​ 每一页对应一个计数器,页表中有访问位R,每次出现时钟中断时就将计数器加上R的值。发生缺页中断时,就将计数器值最小的一页替换。但是缺点在于之前使用的页面会一直换不出去。

​ 解决办法有:老化算法

​ 计数器的增值方式改为:先将计数器左移一位,然后最低位置为R位的值。这种方法是最接近完美算法LRU的。

3.4.8 工作集算法

​ 是一种实现代价非常高的算法。

​ 工作集的定义是:一个进程当前正在使用的页框的集合。工作集w(t,Δ)表示,在时刻t前的Δ秒中,页框的集合。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PsCK1MOL-1628474261120)(C:\Users\10332\AppData\Roaming\Typora\typora-user-images\image-20210613111541668.png)]

​ 作为页面置换算法,工作及算法的基本思路是:找到一个不在工作集中的页面,并将其替换出去

实现方法:定期置0所有页面的R位。每当发生缺页中断时,扫描整个页表。如果R位为1,则把当前时间写入上一次被访问的时间。如果R位为0,则不需要修改上一次被访问的时间。计算生存时间。生存时间= 实际运行时间- 上次被访问时间。如果大于一个阈值,则认为这个页面不在工作集中。反之则在。通过这样的方式**替换第一个检测到的不在工作集中的页面。**替换之后仍要扫描其他页并修改时间。

3.4.9 工作集时钟页面替换算法

​ 是工作集算法的改进型,是一种比较可行的方案。

​ 与时钟算法相同,所需的数据结构是一个以页框为元素的循环表。每个表项包含R位、M位以及上次使用时间。

​ 发生缺页中断时检查指针指向的页面,若R位为1,则置0,检查下一个。若R位为0,且生存时间超过阈值,M位为0没有被修改,则立即替换(相当于时钟算法中的第0类页面)。若R位为0,M位为1,则说明需要修改,此时的操作为指针继续移动检查下一页面,同时调用I/O将这一页写回。如果指针转了一圈回到起始点,那么有两种情况:如果至少进行了一次写操作,则对应的那个页面的M位已经变为0,可以替换;如果没有写操作,则表示所有页面都在工作集中,则随即替换

3.4.10 页面替换算法的总结

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CF5TrosF-1628474261120)(C:\Users\10332\AppData\Roaming\Typora\typora-user-images\image-20210613114016820.png)]

3.5 分页系统中的设计问题

3.5.1 页面尺寸问题

​ 页面尺寸过大会导致利用率低,内存碎片过多。页面尺寸过小会导致页表占据非常大的内存空间。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-huIZ5LjK-1628474261121)(C:\Users\10332\AppData\Roaming\Typora\typora-user-images\image-20210613120204540.png)]

3.5.2 程序的编制方法(程序优化)

​ 缺页次数与内存中数据存放的方式以及程序的执行顺序有很大关系。

3.5.3 BELAY现象

​ BELAY现象指:当分配给进程的物理页面数增加后,缺页次数反而增加的现象。

3.5.4 局部分配与全局分配策略

​ 局部算法:每个进程拥有各自固定大小的一部分页框数。

​ 全局算法:所有进程动态的分配页框。

​ 局部算法更容易出现颠簸,但是并非所有进程都适用全局算法。工作集时钟和工作集就不可以。

​ 在使用全局算法时,如何动态确定每个进程所使用的页框数是一个问题。

缺页中断率会随着分配页框数的增加而降低,计算每秒的缺页中断率并设置阈值,可以帮助系统确定动态所需的页框数。

3.5.5 负载控制

​ 简单来说就是当进程增多时就将一部分进程从内存中移出。选择进程移出的时候要考虑进程的特性,分别考虑CPU密集型和I/O密集型。

3.5.6 分离指令和数据空间

​ 方便程序员,提供更大的空间。指令空间和数据空间分别分配独立的页框。

3.5.7 共享页面

​ 共享页面可以分为共享数据页面和指令页面。指令是只读的,因此可以直接共享。数据页面采取写时复制的方式避免出错。

3.5.8 共享库

​ 又称为DLL或动态链接库,是依赖于编译器实现。多个程序共享相同的代码。前提是编译和链接要分开

​ 动态链接特点在于运行时再链接,优点在于可执行文件小,节省空间,有助于系统和软件维护。缺点在于有可能一个库指向不明确。解决方案:编译的时候避免使用绝对地址

3.5.9 内存映射文件

​ 一个系统调用,将一个文件映射到其虚拟地址空间的一部分。通常是共享文件。

3.5.10 清除策略

​ 从进程中收回页框。设计一个分页守护进程,多数时间沉睡,定期唤醒检查内存状态。若空闲页面过少则执行页面置换算法换出空闲页面。

3.6 有关实现的问题

3.6.1 与分页有关的工作

​ 操作系统再进程创建、执行、缺页中断、进程终止时分别有对应的分页工作。

进程创建时:创建一个页表,在磁盘交换区中分配空间。初始化。在进程控制块中更新相应内容。

进程执行时:将进程就绪态转为运行态,重置MMU,刷新TLB,载入页表并调度页面。

进程退出时:释放页面、删除页表。

3.6.2 缺页中断处理

​ 硬件陷入内核,保存上下文环境。调度页面完成后回到用户态,重新执行引起缺页中断的指令

3.6.3 指令备份

3.6.4 锁定

​ 锁定页面后页面不能被换出内存,例如操作系统的核心代码,关键的数据结构、I/O缓冲区,尤其是I/O缓冲区。

3.6.5 后备存储

3.6.6 策略和机制相分离

3.7 分段

化。在进程控制块中更新相应内容。

进程执行时:将进程就绪态转为运行态,重置MMU,刷新TLB,载入页表并调度页面。

进程退出时:释放页面、删除页表。

3.6.2 缺页中断处理

​ 硬件陷入内核,保存上下文环境。调度页面完成后回到用户态,重新执行引起缺页中断的指令

3.6.3 指令备份

3.6.4 锁定

​ 锁定页面后页面不能被换出内存,例如操作系统的核心代码,关键的数据结构、I/O缓冲区,尤其是I/O缓冲区。

3.6.5 后备存储

3.6.6 策略和机制相分离

3.7 分段

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值