操作系统学习笔记2--内存虚拟化

1 内存虚拟化的目标:透明、高效、隔离保护

        虚拟内存系统负责为程序提供一个巨大的、稀疏的、私有的地址空间的假象,其中保存了程序的所有指令和数据。操作系统在专门硬件的帮助下,通过每一个虚拟内存的索引,将其转换为物理地址,物理内存根据获得的物理地址去获取所需的信息。操作系统会同时对许多进程执行此操作,并且确保程序之间互相不会受到影响,也不会影响操作系统。

        虚拟内存系统的一个主要目标是透明(transparency)。操作系统实现虚拟内存的方式,应该让运行的程序看不见。

        虚拟内存的另一个目标是效率(efficiency)。操作系统应该追求虚拟化尽可能高效(efficient),包括时间上(即不会使程序运行得更慢)和空间上(即不需要太多额外的内存来支持虚拟化)。在实现高效率虚拟化时,操作系统将不得不依靠硬件支持,包括TLB这样的硬件功能。

        虚拟内存第三个目标是保护(protection)。操作系统应确保进程受到保护(protect),不会受其他进程影响,操作系统本身也不会受进程影响。因此,保护让我们能够在进程之间提供隔离(isolation)的特性,每个进程都应该在自己的独立环境中运行,避免其他进程的影响。

        实际上,作为用户级程序的程序员,可以看到的任何地址都是虚拟地址。只有操作系统,通过精妙的虚拟化内存技术,知道这些指令和数据所在的物理内存的位置。所以永远不要忘记:如果你在一个程序中打印出一个地址,那就是一个虚拟的地址。虚拟地址只是提供地址如何在内存中分布的假象,只有操作系统(和硬件)才知道物理地址。

2 内存操作API

        运行一个程序的时候需遇到两种内存的分配:栈内存和堆内存。栈内存的申请释放由编译器隐式完成,所以也叫自动内存。堆内存的申请和释放操作都由程序员显式地完成。

2.1 malloc()

        void* malloc(size_t size);传入要申请的堆空间的大小,成功就返回一个指向新申请空间的指针,失败就返回NULL。

        malloc通过两个系统函数 brk() 和 mmap() 完成内存分配,当申请的内存小于128K时,由brk()在堆中分配内存;当申请的内存大于128K时,由mmap()在堆栈之间的自由存储区分配一块内存。第一次访问刚分配的虚拟内存时,会发生缺页中断,由操作系统完成物理内存分配及映射关系。

2.2 free()

        该函数接受一个参数,即一个由malloc()返回的指针。分配区域的大小不需用户传入,必须由内存分配库本身记录追踪。

        由brk()分配的内存不会交还操作系统得到真正的释放,会被交由malloc内存池管理,方便下次申请内存;由mmap()分配的内存会被交还操作系统,得到真正的释放。

3 内存虚拟化的机制--地址转换

        如何高效、灵活地虚拟化内存呢?

        利用一种通用技术,有时被称为基于硬件的地址转换(hardware-based address translation),简称为地址转换(address translation)。它可以看成是受限直接执行这种一般方法的补充。利用地址转换,硬件对每次内存访问进行处理(即指令获取、数据读取或写入),将指令中的虚拟(virtual)地址转换为数据实际存储的物理(physical)地址。

        当然,仅仅依靠硬件不足以实现虚拟内存,因为它只是提供了底层机制来提高效率。操作系统必须在关键的位置介入,设置好硬件,以便完成正确的地址转换。因此它必须管理内存(manage memory),记录被占用和空闲的内存位置,并明智而谨慎地介入,保持对内存使用的控制。

        基于硬件的动态重定位:在动态重定位的过程中,一个基址寄存器将虚拟地址转换为物理地址,一个界限寄存器确保这个地址在进程地址空间的范围内。

        遗憾的是,这个简单的动态重定位技术有效率低下的问题。

4 分段:泛化的基址/界限

        在MMU中引入不止一个基址和界限寄存器对,而是给地址空间内的每个逻辑段(segment)一对。一个段只是地址空间里的一个连续定长的区域,分段的机制使得操作系统能够将不同的段放到不同的物理内存区域,从而避免了虚拟地址空间中的未使用部分占用物理内存。

外部碎片:物理内存中充满了许多空闲空间的小洞,而很难分配给新的段,或扩大已有的段。

内部碎片:如果分配程序给出的内存块超出请求的大小,在这种块中超出请求的空间就被认为是内部碎片。

        外部碎片的一种解决方案是紧凑(compact)物理内存,重新安排原有的段。但是,内存紧凑成本很高,因为拷贝段是内存密集型的,一般会占用大量的处理器时间。另一种更简单的做法是利用空闲列表管理算法,试图保留大的内存块用于分配。相关的算法有成百上千种,包括传统的最优匹配(best-fit,从空闲链表中找最接近需要分配空间的空闲块返回)、最坏匹配(worst-fit)、首次匹配(first-fit)以及伙伴算法(buddy algorithm)等更复杂的算法。但是,无论算法多么精妙,都无法完全消除外部碎片,好的算法只是试图减小它。

5 空闲空间管理free-space management)

        在堆上管理空闲空间的数据结构通常称为空闲列表(free list)。该结构包含了管理内存区域中所有空闲块的引用。

5.1 最优匹配(best fit)

        先遍历整个空闲列表,找到和请求大小一样或更大的空闲块,然后返回这组候选者中最小的一块。

5.2 最差匹配(worst fit)

        最差匹配(与最优匹配相反,它尝试找最大的空闲块,分割并满足用户需求后,将剩余的块加入空闲列表。最差匹配尝试在空闲列表中保留较大的块,而不是向最优匹配那样可能剩下很多难以利用的小块。但是,最差匹配同样需要遍历整个空闲列表。更糟糕的是,大多数研究表明它的表现非常差,导致过量的碎片,同时还有很高的开销。

5.3 首次匹配(first fit)

        找到第一个足够大的块,将请求的空间返回给用户。同样,剩余的空闲空间留给后续请求。

        首次匹配有速度优势(不需要遍历所有空闲块),但有时会让空闲列表开头的部分有很多小块。因此,分配程序如何管理空闲列表的顺序就变得很重要。一种方式是基于地址排序(address-based ordering)。通过保持空闲块按内存地址有序,合并操作会很容易,从而减少了内存碎片。

5.4 下次匹配(next fit)

        不同于首次匹配每次都从列表的开始查找,下次匹配算法多维护一个指针,指向上一次查找结束的位置。其想法是将对空闲空间的查找操作扩散到整个列表中去,避免对列表开头频繁的分割。这种策略的性能与首次匹配很接近,同样避免了遍历查找。

        结论:通过实验证明,首次匹配比最优匹配要好,它们都比最差匹配要好。

6.分页

        分页就是将内存分割成固定大小的单元,每个单元被称为页。相应地,我们把物理内存看成是定长槽块的阵列,叫作页帧(page frame)。每个这样的页帧包含一个虚拟内存页。

        如何通过页来实现虚拟内存,从而避免分段的问题?基本技术是什么?如何让这些技术运行良好,并尽可能减少空间和时间开销?

        为了记录地址空间的每个虚拟页放在物理内存中的位置,操作系统通常为每个进程保存一个数据结构,称为页表(page table)。页表的主要作用是为地址空间的每个虚拟页面保存地址转换(address translation),从而让我们知道每个页在物理内存中的位置。

分页:快速地址转换(TLB)

        使用分页作为核心机制来实现虚拟内存,可能会带来较高的性能开销。想让某些东西更快,操作系统通常需要一些帮助。帮助常常来自操作系统的老朋友:硬件。增加地址转换旁路缓冲存储器(translation-lookaside buffer,TLB),它就是频繁发生的虚拟到物理地址转换的硬件缓存。

        对每次内存访问,硬件先检查TLB,看看其中是否有期望的转换映射,如果有,就完成了转换;如果没有,则需要访问内存中的页表寻找转换映射,并将其添加到TLB中。TLB带来了巨大的性能提升,因此它使得虚拟内存成为可能。

        缓存是计算机系统中最基本的性能改进技术之一,一次又一次地用于让“常见的情况更快”。硬件缓存背后的思想是利用指令和数据引用的局部性(locality)。通常有两种局部性:时间局部性(temporal locality)和空间局部性(spatial locality)。时间局部性是指,最近访问过的指令或数据项可能很快会再次访问。想想循环中的循环变量或指令,它们被多次反复访问。空间局部性是指,当程序访问内存地址x时,可能很快会访问邻近x的内存。想想遍历某种数组,访问一个接一个的元素。

        硬件缓存,无论是指令、数据还是地址转换,都利用了局部性,在小而快的芯片内存储器中保存一份内存副本。处理器可以先检查缓存中是否存在就近的副本,而不是必须访问内存来满足请求。

上下文切换时对TLB的处理

        TLB中包含的虚拟到物理的地址映射只对当前进程有效,对其他进程是没有意义的。所以在发生进程切换时,硬件或操作系统必须注意确保即将运行的进程不要误读了之前进程的地址映射。

        上下文切换的时候清空TLB,这是一个可行的解决方案,进程不会再读到错误的地址映射。但是,有一定开销:每次进程运行,当它访问数据和代码页时,都会触发TLB未命中。如果操作系统频繁地切换进程,这种开销会很高。

        为了减少这种开销,一些系统增加了硬件支持,实现跨上下文切换的TLB共享。比如有的系统在TLB中添加了一个地址空间标识符(Address Space Identifier,ASID)。

TLB替换策略

        一种常见的策略是替换最近最少使用(least-recently-used,LRU)的项。LRU尝试利用内存引用流中的局部性,假定最近没有用过的项,可能是好的换出候选项。另一种典型策略就是随机(random)策略,即随机选择一项换出去。这种策略很简单,并且可以避免一种极端情况。

分页:较小的表

7.内存交换和内存覆盖

        内存交换和覆盖是两种不同的内存管理技术,内存交换适用于不同进程之间,而覆盖则用于同一程序或进程中

        内存交换技术主要在不同进程或作业之间进行,它允许系统在内存空间紧张时,将内存中的某些进程暂时换出到外存,同时将外存中已具备运行条件的进程换入内存。内存交换则是在内存和磁盘之间动态调度整个进程的内存映像,这通常涉及到操作系统层面的进程管理和调度策略。

        内存覆盖技术则是在同一个进程或程序内部进行的管理,它通过将程序分为多个模块,并在需要时将特定模块调入内存,不需要时调出内存,从而节省内存空间。覆盖技术通常将程序分为必须常驻内存的模块和不常用模块,前者放在固定区,后者放在覆盖区,根据需要调入调出。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值