现代操作系统::内存管理

内存管理

  • 操作系统中管理分层存储器体系的部分称为存储管理器 (memory manager)。它的任务是有效地管理内存,即记录哪些内存是正在使用的,哪些内存是空闲的,在进程需要时为其分配内存,在进程使用完后释放内存。
  • 无存储器抽象
    • 最简单的存储器抽象就是根本没有抽象。
    • 每一个程序都直接访问物理内存。在这种情况下,要想在内存中同时运行两个程序是不可能的。
    • 在不使用存储器抽象的情况下运行多个程序
      • 操作系统只需要把当前内存中所有内容保存到磁盘文件中,然后把下一个程序读入到内存中再运行即可。只要在某一个时间内存中只有一个程序,那么就不会发生冲突。
      • 在特殊硬件的帮助下,即使没有交换功能,井发地运行多个程序也是可能的。 IBM 360 的早期模型是这样解决的:内存被划分为 2KB 的块,每个块被分配一个 4 位的保护键,保护键存储在 CPU 的特殊寄存器中。一个内存为 1MB 的机器只需要 512 个这样的 4 位寄存器,容量总共为 256 字节。PSW (Program Status Word, 程序状态字)中存有一个 4 位码。一个运行中的进程如果访问保护键与其 PSW 码不同的内存,360 的硬件会捕获到这一事件。因为只有操作系统可以修改保护键,这样就可以防止用户进程之间、用户进程和操作系统之间的互相干扰。引用了绝对物理地址会导致出错。
  • 一种存储器抽象:地址空间
    • 使用绝对物理地址的问题
      • 用户可以访问每个地址,容易造成破坏
      • 运行多个程序很困难
    • 地址空间的概念
      • 需要解决的问题
        • 保护
        • 重定向
      • 地址空间是一个进程可用于寻址内存的一套地址集合。每个进程都有一个自己的地址空间,井且这个地址空间独立于其他进程的地址空间
    • 基址寄存器和界限寄存器
      • 使用动态重定位,简单地把每个进程的地址空间映射到物理内存的不同部分。
      • 经典办法是给每个 CPU 配置两个特殊硬件寄存器,通常叫作基址寄存器和界限寄存器。当使用基址寄存器和界限寄存器时,程序装载到内存中连续的空闲位置且装载期间无须重定位,每次一个进程访问内存,取一条指令,读或写一个数据字,CPU 硬件会在把地址发送到内存总线前,自动把基址值加到进程发出的地址值上。同时,它检查程序提供的地址是否等于或大于界限寄存器里的值。如果访问的地址超过了界限,会产生错误并中止访问。
      • 对基址寄存器和界限寄存器会以一定的方式加以保护,使得只有操作系统可以修改它们。
      • 使用基址寄存器和界限寄存器重定位的缺点是,每次访问内存都需要进行加法和比较运算。比较运算可以做得很快,但是加法运算由于进位传递时间的问题,在没有使用特殊电路的情况下会显得很慢。
    • 交换技术
      • 内存容量优先,多进程存在内存超载的问题。
      • 交换
        • 即把一个进程完整调入内存,使该进程运行一段时间,然后把它存回磁盘。空闲进程主要存储在磁盘上,所以当它们不运行时就不会占用内存。
        • 交换在内存中产生了多个空闲区 (hole, 也称为空洞),通过把所有的进程尽可能向下移动,有可能将这些小的空闲区合成一大块。该技术称为内存紧缩 (memory compaction)。通常不进行这个操作,因为它要耗费大量的 CPU 时间。
        • 进程的数据段可以增长,系统如何分配内存。一种可用的方法是,当换入或移动进程时为它分配一些额外的内存。
    • 空闲内存管理
      • 使用位图的存储管理
        • 使用位图方法时,内存可能被划分成小到几个字或大到几千字节的分配单元。每个分配单元对应于位图中的一位,0 表示空闲, 1 表示占用(或者相反)
        • 分配单元的大小是一个重要的设计因素。分配单元越小,位图越大。
        • 这种方法的主要问题是,在决定把一个占 k 个分配单元的进程调人内存时,存储管理器必须搜索位图,在位图中找出有 k 个连续 0 的串。查找位图中指定长度的连续 0 串是耗时的操作(因为在位图中该串可能跨越字的边界),这是位图的缺点。
      • 使用链表的存储管理
        • 维护一个记录已分配内存段和空闲内存段的链表。其中链表中的一个结点或者包含一个进程,或者是两个进程间的一块空闲区。
        • 分配算法
          • 首次适配
            • 存储管理器沿若段链表进行搜索,直到找到一个足够大的空闲区,除非空闲区大小和要分配的空间大小正好一样,否则将该空闲区分为两部分,一部分供进程使用,另一部分形成新的空闲区。首次适配算法是一种速度很快的算法,因为它尽可能少地搜索链表结点。
          • 下次适配
            • 它的工作方式和首次适配算法法相同,不同点是每次找到合适的空闲区时都记录当时的位置,以便在下次寻找空闲区时从上次结束的地方开始搜索,而不是像首次适配算法那样每次都从头开始。
          • 最佳适配
            • 最佳适配算法搜索整个链表,找出能够容纳进程的最小的空闲区。最佳适配算法试图找出最接近实际需要的空闲区,以最好地匹配请求和可用空闲区,而不是先拆分一个以后可能会用到的大的空闲区。
            • 因为每次调用最佳适配算法时都要搜索整个链表,所以它要比首次适配算法慢。让人感到有点意外的是,它比首次适配算法或下次适配算法浪费更多的内存,因为它会产生大址无用的小空闲区。
          • 最差适配
            • 总是分配最大的可用空闲区,使新的空闲区比较大从而可以继续使用。仿真程序表明最差适配算法也不是一个好主意。
          • 快速适配
            • 它为那些常用大小的空闲区维护单独的链表。
  • 虚拟内存
    • 每个程序拥有自己的地址空间,这个空间被分割成多个块,每一块称作一页或页面(page)。每一页有连续的地址范围。这些页被映射到物理内存,但井不是所有的页都必须在内存中才能运行程序。当程序引用到一部分在物理内存中的地址空间时,由硬件立刻执行必要的映射。当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令。
    • 虚拟内存很适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中。当一个程序等待它的一部分读入内存时,可以把 CPU 交给另一个进程使用.
    • 分页
      • 程序产生的这些地址称为虚拟地址 (virtual address) , 它们构成了一个虚拟地址空间 (virtual address space)。在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上,读写操作使用具有同样地址的物理内存字,而在使用虚拟内存的情况下,虚拟地址不是被直接送到内存总线上,而是被送到内存管理单元 (MemoryManagementUnit, MMU), MMU 把虚拟地址映射为物理内存地址。
      • 虚拟地址空间按照固定大小划分成被称为页面 (page) 的若干单元 。在物理内存中对应的单元称为页框(page frame)。页面和页框的大小通常是一样的,实际系统中的页面大小从 512 字节到 1GB。
      • 缺页中断或缺页错误
        • 操作系统找到一个很少使用的页框且把它的内容写人磁盘(如果它不在磁盘上)。随后把需要访问的页面读到刚才回收的页框中,修改映射关系,然后重新启动引起陷阱的指令
        • 输入的 16 位虚拟地址被分为 4 位的页号和 12 位的偏移址。 4 位的页号可以表示 16 个页面, 12 位的偏移可以为一页内的全部 4096 个字节编址。可用页号作为页表得出对应于该虚拟页面的页框号。如果"在/不在“ 位是 0, 则将引起一个操作系统陷阱。如果该位是 1, 则将在页表中查到的页框号复制到输出寄存器的高 3 位中,再加上输人虚拟地址中的低 12 位偏移址。如此就构成了 15 位的物理地址。输出寄存器的内容随即被作为物理地址送到内存总线。
      • 页表
        • 作为一种最简单的实现,虚拟地址到物理地址的映射可以概括如下:虚拟地址被分成虚拟页号(高位部分)和偏移址(低位部分)两部分
        • 虚拟页号可用作页表的索引,以找到该虚拟页面对应的页表项。由页表项可以找到页框号(如果有的话)。然后把页框号拼接到偏移址的高位端,以替换掉虚拟页号,形成送往内存的物理地址。
        • 页表项的结构是与机器密切相关的,但不同机器的页表项存储的信息都大致相同。
          • 页框号
          • ”在/不在“位
          • 保护位,指出一个页允许什么类型的访问,读写执行
          • 修改位和访问位,
            • 在写入一页时由硬件自动设置修改位。该位在操作系统重新分配页框时是非常有用的。如果一个页面已经被修改过(即它是"脏"的),则必须把它写回磁盘。如果一个页面没有被修改过(即它是“干净”的),则只简单地把它丢弃就可以了,因为它在磁盘上的副本仍然是有效的。这一位有时也被称为脏位 (dirty bit) , 因为它反映了该页面的状态。
            • 不论是读还是写,系统都会在该页面被访问时设置访问位。它的值被用来帮助操作系统在发生缺页中断时选择要被淘汰的页面。不再使用的页面要比正在使用的页面更适合淘汰。
          • 禁止高速缓存位,那些映射到设备寄存器而不是常规内存的页面而言,这个特性是非常重要的。假如操作系统正在紧张地循环等待某个 I/O 设备对它刚发出的命令作出响应,保证硬件是不断地从设备中读取数据而不是访问一个旧的被高速缓存的副本是非常重要的。通过这一位可以禁止高速缓存。具有独立的 I/O 空间而不使用内存映射 I/O 的机器不需要这一位。
      • 加速分页过程
        • 虚拟地址到物理地址的映射必须非常快。
        • 如果虚拟地址空间很大,页表也会很大。
        • 转换检测缓冲区
          • 大多数程序总是对少址的页面进行多次的访问,而不是相反。因此,只有很少的页表项会被反复读取,而其他的页表项很少被访问。解决方案是为计算机设置一个小型的硬件设备,将虚拟地址直接映射到物理地址,而不必再访问页表。这种设备称为转换检测缓冲区(Translation Lookaside Buffer, TLB),有时又称为相联存储器(associate memory)或快表,它通常在 MMU 中,包含少量的表项,在实际中很少会超过 256 个。每个表项记录了一个页面的相关信息,包括虚拟页号、页面的修改位、 保护码(读/写/执行权限)和该页所对应的物理页框。
          • 将一个虚拟地址放入 MMU 中进行转换时,硬件首先通过将该虚拟页号与 TLB 中所有表项同时(即并行)进行匹配,判断虚拟页面是否在其中。如果发现了一个有效的匹配并且要进行的访问操作并不违反保护位,则将页框号直接从 TLB 中取出而不必再访问页表。如果虚拟页号确实是在 TLB 中,但指令试图在一个只读页面上进行写操作,则会产生一个保护错误,就像对页表进行非法访间一样。
          • 如果 MMU 检测到没有有效的匹配项,就会进行正常的页表查询。接着从 TLB 中淘汰一个表项,然后用新找到的页表项代替它。这样,如果这一页面很快被再次访问,第二次访问 TLB 时自然将会命中而不是未命中。当一个表项被清除出 TLB 时,将修改位复制到内存中的页表项,而除了访问位,其他的值不变。当页表项中从页表中装入 TLB 中时,所有的值都来自内存。
        • 软件 TLB 管理
          • 在现代很多机器上,几乎所有的页面管理都是在软件中实现的。在这些机器上,TLB 表项被操作系统显式地装载。当发生 TLB 访问失效时,不再是由 MMU 到页表中查找井取出需要的页表项,而是生成一个 TLB 失效并将问题交给操作系统解决。系统必须先找到该页面,然后从 TLB 中删除一个项,接着装载一个新的项,最后再执行先前出错的指令。当然,所有这一切都必须在有限的几条指令中完成,因为 TLB 失效比缺页中断发生得更加频繁。
          • 如果 TLB 大到(如 64 个表项)可以减少失效率时,TLB 的软件管理就会变得足够有效。这种方法的最主要的好处是获得了一个非常简单的 MMU, 这就在 CPU 芯片上为高速缓存以及其他改善性能的设计腾出了相当大的空间。
          • 软失效
            • 一个页面访问在内存中而不在 TLB 中时,将产生软失效(softmiss)。那么此时所要做的就是更新一下 TLB, 不需要产生磁盘 I/O。典型的处理需要 10~20 个机器指令并花费几纳秒完成操作。
          • 硬失效
            • 页面本身不在内存中(当然也不在 TLB 中)时,将产生硬失效。此刻需要一次磁盘存取以装入该页面,这个过程大 概储要几亳秒。硬失效的处理时间往往是软失效的百万倍。在页表结构中查找相应的映射被称为页表遍历。
          • 几种场景
            • 所需的页面可能就在内存中,但却未记录在该进程的页表里。比如该页面可能已由其他进程从硬盘中调人内存,这种情况下只需要把所需的页面正确映射到页表中,而不用再从硬盘调入。这是一种典型的软失效,称为次要缺页错误
            • 如果需要从硬盘重新调入页面,这就是严重缺页错误。
            • 程序可能访问了一个非法地址,根本不需要向 TLB 中新增映射。此时,操作系统一般会通过报告段错误来终止该程序, 这种缺页属于程序错误
        • 针对大内存的页表
          • 多级页表
            • 引人多级页表的原因是避免把全部页表一直保存在内存中。特别是那些从不需要的页表就不应该保留。
            • 虚拟地址送入 MMU 后分为三个部分 PT1 用于顶级页表寻址,PT2 用于二级页表寻址,偏移量还是偏移量。以此扩展出多级页表
          • 倒排页表
            • 实际内存中的每个页框对应一个表项,而不是每个虚拟页面对应一个表项。例如,对千 64 位虚拟地址,4KB 的页, 4GB 的 RAM, 一个倒排页表仅需要 1 048 576 个表项。表项记录了哪一个(进程,虚拟页面)对定位于该页框。
            • 从虚拟地址到物理地址的转换会变得很困难。它必须搜索整个倒排页表来查找某一个表项(n, p)。此外,该搜索必须对每一个内存访问操作都要执行一次,而不仅仅是在发生缺页中断时执行。每次内存访问操作都要查找一个 256K 的表不是一种使机器快速运行的方法 。
            • 走出这种两难局面的办法是使用 TLB。如果 TLB 能够记录所有频繁使用的页面,地址转换就可能变得像通常的页表一样快。但是,当发生 TLB 失效时,需要用软件搜索整个倒排页表。实现该搜索的一个可行的方法是建立一张散列表,用虚拟地址来散列。当前所有在内存中的具有相同散列值的虚拟页面被链接在一起。如果散列表中的槽数与机器中物理页面数一样多,那么散列表的冲突链的平均长度将会是 1 个表项的长度,这将会大大提高映射速度。一旦页框号被找到,新的(虚拟页号,物理页框号)对就会被装载到 TLB 中。
            • 倒排页表在 64 位机器中很常见,因为在 64 位机器中即使使用了大页面,页表项的数量还是很庞大的。
  • 页面置换算法
    • 最优页面置换算法
      • 不可能实现
      • 在缺页中断发生时,有些页面在内存中,其中有一个页面将很快被访问,其他页面则可能要到 10、100 或 1000 条指令后才会被访问,每个页面都可以用在该页面首次被访问前所要执行的指令数作为标记。
      • 最优页面置换算法规定应该置换标记最大的页面。当缺页中断发生时,操作系统无法知道各个页面下一次 将在什么时候被访问。可以通过收集上一次的运行信息来判断,但是提升效率有限。
    • 最近未使用页面置换算法
      • 为使操作系统能够收集有用的统计信息,在大部分具有虚拟内存的计算机中,系统为每一页面设置了两个状态位。当页面被访问(读或写)时设置 R 位;当页面被写入(即修改)时设置 M 位。这些位包含在每个页表项中.
      • 当启动一个进程时,它的所有页面的两个位都由操作系统设置成 0, R 位被定期地(比如在每次时钟中断时)清零,以区别最近没有被访问的页面和被访问的页面。
      • NRU (Not Recently Used, 最近未使用)算法随机地从没有被访问,没有被修改和没有被访问,已被修改的非空类中挑选一个页面淘汰。
    • 先进先出页面置换算法
      • 由操作系统维护一个所有当前在内存中的页面的链表,最新进入的页面放在表尾,最早进入的页面放在表头。当发生缺页中断时,淘汰表头的页面井把新调入的页面加到表尾。
    • 第二次机会页面置换算法
      • FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问题,对该算法做一个简单的修改: 检查最老页面的 R 位。如果 R 位是 0, 那么这个页面既老又没有被使用,可以立刻置换掉,如果是 1 就将 R 位清 0, 井把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续搜索。
      • 第二次机会算法就是寻找一个在最近的时钟间隔内没有被访问过的页面。
    • 时钟页面置换算法
      • 尽管第二次机会算法是一个比较合理的算法,但它经常要在链表中移动页面,既降低了效率又不是很有必要。 一个更好的办法是把所有的页面都保存在一个类似钟面的环形链表中,一个表针指向最老的页面
      • 当发生缺页中断时,算法首先检查表针指向的页面,如果它的 R 位是 0 就淘汰该页面,并把新的页面插入这个位置,然后把表针前移一个位置,如果 R 位是 1 就清除 R 位并把表针前移一个位置。重复这个过程直到找到了一个 R 位为 0 的页面为止。
    • 最近最少使用页面置换算法
      • 在缺页中断发生时,置换未使用时间最长的页面。
      • 实现 LRU, 需要在内存中维护一个所有页面的链表,最近最多使用的页面在表头,最近最少使用的页面在表尾。困难的是在每次访问内存时都必须要更新整个链表。在链表中找到一个页面,删除它,然后把它移动到表头是一个非常费时的操作,即使使用硬件实现也一样费时
    • 用软件摸拟 LRU
      • 一种可能的方案称为 NFU (NotFrequentlyUsed, 最不常用)算法,该算法将每个页面与一个软件计数器相关联,计数器的初值为 0。每次时钟中断时,由操作系统扫描内存中所有的页面,将每个页面的 R 位(它的值是 0 或 1) 加到它的计数器上。这个计数器大体上跟踪了各个页面被访问的频繁程度。发生缺页中断时,则置换计数器值最小的页面.
    • 工作集页面置换算法
      • 一个进程当前正在使用的页面的集合称为它的工作集
      • 若内存太小而无法容纳下整个工作集,那么进程的运行过程中会产生大量的缺页中断导致运行速度也会变得很缓慢
      • 若每执行几条指令程序就发生一次缺页中断,那么就称这个程序发生了颠簸
      • 不少分页系统都会设法跟踪进程的工作集,以确保在让进程运行以前,它的工作集就已在内存中了。该方法称为工作集模型, 其目的在于大大减少缺页中断率。在进程运行前预先装入其工作集页面也称为预先调页 (prepaging)。
      • 人们很早就发现大多数程序都不是均匀地访问它们的地址空间的,而访问往往是集中于一小部分页面。
      • 一种近似的解法是进程的工作集可以被称为在过去的 r 秒内实际运行时间中它所访问过的页面的集合。
    • 工作集时钟页面置换算法
      • 当缺页中断发生后,需要扫描整个页表才能确定被淘汰的页面,因此基本工作集算法是比较费时的.有一种改进的算法,它基于时钟算法,井且使用了工作集信息,称为 WSClock (工作集时钟)算法
      • 时钟算法一样,所需的数据结构是一个以页框为元素的循环表。最初,该表是空的。当装入第一个页面后,把它加到该表中。随若更多的页面的加入,它们形成一个环。每个表项包含来自基本工作集算法的上次使用时间,以及 R 位(已标明)和 M 位(未标明)。每次缺页中断时,首先检查指针指向的页面。如果 R 位被置为 1 该页面在当前时钟滴答中就被使用过,那么该页面就不适合被淘汰 。然后把该页面的 R 位设为 0, 指针指向下一个页面,井重复该算法。
    • 页面置换算法小结
      • 最优算法 不可实现,但可用作基准
      • NRU (最近未使用)算法
      • FIFO (先进先出)算法
      • 第二次机会算法 比 FIFO 有较大的改善
      • 时钟算法 现实的
      • LRU (最近最少使用)算法 很优秀,但很难实现
      • NFU (最不经常使用)算法 LRU 的相对粗略的近似
      • 老化算法 LRU 的相对粗略的近似
      • 工作集算法 非常近似 LRU 的有效算法
      • 工作集时钟算法 好的有效算法
  • 分页系统中的设计问题
    • 局部分配策略与全局分配策略
      • 三个进程 A、 B、 C 构成了可运行进程的集合。假如 A 发生了缺页中断,页面置换算法在寻找最近最少使用的页面时是只考虑分配给 A 的 6 个页面呢? 还是考虑所有在内存中的页面?
      • 局部算法可以有效地为每个进程分配固定的内存片段。
      • 全局算法在可运行进程之间动态地分配页框,因此分配给各个进程的页框数是随时间变化的.
      • 全局算法在通常情况下工作得比局部算法好,当工作集的大小随进程运行时间发生变化时这种现象更加明显。
    • 负载控制
      • 一旦所有进程的组合工作集超出了内存容量,就可能发生颠簸。
      • 减少竞争内存的进程数的一个好方法是将一部分进程交换到磁盘,并释放他们所占有的所有页面。
      • 决定交换出哪个进程时不光要考虑进程大小和分页率,还要考虑它的特性(如它究竟是 CPU 密集型还是 I/O 密集型)以及其他进程的特性。
    • 页面大小
      • 要确定最佳的页面大小蒂要在几个互相矛盾的因素之间进行权衡。
      • 随便选择一个正文段、数据段或堆栈段很可能不会恰好装满整数个页面,平均的情况下,最后一个页面中有一半是空的。多余的空间就被浪费掉了,这种浪费称为内部碎片 (internal fragmentation)。在内存中有 n 个段、页面大小为 p 字节时,会有 np/2 字节被内部碎片浪费。从这方面考虑,使用小页面更好。
      • 考虑一个程序,它分成 8 个阶段顺序执行,每阶段需要 4KB 内存。如果页面大小是 32KB, 那就必须始终给程序分配 32KB 内存。如果页面大小是 16KB, 它就只需要 16KB。 如果页面大小是 4KB 或更小,那么在任何时刻它只需要 4KB 内存。总的来说,大尺寸页面比小尺寸页面浪费了更多内存 。
      • 另一方面,页面小意味着程序需要更多的页面,这又意味着需要更大的页表。
      • 内存与磁盘之间的传输一般是一次一页,传输中的大部分时间都花在了寻道和旋转延迟上,所以传输一个小页面所用的时间和传输一个大页面基本上是相同
      • 小页面能够更充分地利用 TLB 空间
      • 数学角度分析的结果 4KB 更适合
    • 分离的指令空间和数据空间
      • 早期计算机内存不够用的时候采取的设计,在使用这种设计的计算机中,两种地址空间都可以进行分页,而且互相独立。它们分别有自己的页表,分别完成虚拟页面到物理页框的映射。当硬件进行取指令操作时,它知道要使用指令空间和指令空间页表。
    • 共享页面
      • 那些只读的页面(诸如程序文本)可以共享,但是数据页面则不能共享。
      • 只有实际修改的数据页面需要复制。这种方法称为写时复制,它通过减少复制而提高了性能。
    • 共享库
      • 当一个共享库被装载和使用时,整个库并不是被一次性地读人内存。而是根据需要,以页面为单位装载的,因此没有被调用到的函数是不会披装载到内存中的.
      • 如果共享库中的一个函数因为修正一个 bug 被更新了,那么并不需要重新编译调用了这个函数的程序。旧的二进制文件依然可以正常工作。
    • 内存映射文件
      • 进程可以通过发起一个系统调用,将一个文件映射到其虚拟地址空间的一部分。在多数实现中,在映射共享的页面时不会实际读入页面的内容,而是在访问页面时才会被每次一页地读入,磁盘文件则被当作后备存储。当进程退出或显式地解除文件映射时,所有被改动的页面会被写回到磁盘文件中。
    • 清除策略
      • 通过一个分页守护进程定时把修改过的页写回到磁盘
      • 一种实现清除策略的方法就是使用一个双指针时钟。前指针由分页守护进程控制.当它指向一个脏页面时,就把该页面写回磁盘,前指针向前移动。当它指向一个干净页面时,仅仅指针向前移动. 后指针用于页面置换,就像在标准时钟算法中一样。现在,由于分页守护进程的工作,后指针命中干净页面的概率会增加。
    • 虚拟内存接口
      • 对于一些高级系统而言,程序员可以对内存映射进行控制,并可以通过非常规的方法来增强程序的行为。
      • 过让一个进程把一片内存区域的名称通知另一个进程,而使得第二个进程可以把这片区域映射到它的虚拟地址空间中去。通过两个进程(或者更多)共享同一部分页面,高带宽的共享就成为可能
      • 分布式共享内存:允许网络上的多个进程共享一个页面集合
  • 有关实现的问题
    • 与分页有关的工作
      • 时间点
        • 进程创建
          • 确定程序和数据在初始时有多大,井为它们创建一个页表。
          • 在内存中为页表分配空间并对其进行初始化。当进程被换出时,页表不需要驻留在内存中,但当进程运行时,它必须在内存中。
          • 操作系统要在磁盘交换区中分配空间,以便在一个进程换出时在磁盘上有放置此进程的空间。
          • 操作系统必须把有关页表和磁盘交换区的信息存储在进程表中。
        • 进程执行
          • 为新进程重置 MMU, 刷新 TLB, 以清除以前的进程遗留的痕迹。
          • 新进程的页表成为当前页表
        • 缺页中断
          • 通过读硬件寄存器来确定是哪个虚拟地址造成了缺页中断
          • 计箕蒂要哪个页面,并在磁盘上对该页面进行定位。它必须找到合适的页框来存放新页面,必要时还要置换老的页面,然后把所需的页面读入页框。
          • 回退程序计数器,使程序计数器指向引起缺页中断的指令,并重新执行该指令。
        • 进程终止
          • 操作系统必须释放进程的页表、页面和页面在硬盘上所占用的空间。
          • 如果某些页面是与其他进程共享的,当最后一个使用它们的进程终止的时候,才可以释放内存和磁盘上的页面。
    • 缺页中断处理
      • 硬件陷入内核,在堆栈中保存程序计数器。
      • 启动一个汇编代码例程保存通用寄存器和其他易失的信息,以免被操作系统破坏。
      • 当操作系统发现一个缺页中断时,尝试发现需要哪个虚拟页面
      • 一旦知道了发生缺页中断的虚拟地址,操作系统检查这个地址是否有效,并检查存取与保护是否一致。如果不一致,向进程发出一个信号或杀掉该进程。如果地址有效且没有保护错误发生,系统则检查是否有空闲页框。如果没有空闲页框,执行页面置换算法寻找一个页面来淘汰。
      • 如果选择的页框"脏”了,安排该页写回磁盘,并发生一次上下文切换,挂起产生缺页中断的进程,让其他进程运行直至磁盘传输结束。无论如何,该页框被标记为忙,以免因为其他原因而被其他进程占用。
      • 一且页框“干净”后(无论是立刻还是在写回磁盘后),操作系统查找所需页面在磁盘上的地址,通过磁盘操作将其装入。该页面正在被装入时,产生缺页中断的进程仍然被挂起,井且如果有其他可运行的用户进程,则选择另一个用户进程运行。
      • 当磁盘中断发生时,表明该页已经被装人,页表已经更新可以反映它的位置,页框也被标记为正常状态。
      • 恢复发生缺页中断指令以前的状态,程序计数器重新指向这条指令。
      • 调度引发缺页中断的进程,操作系统返回调用它的汇编语言例程.
      • 该例程恢复寄存器和其他状态信息,返回到用户空间继续执行,就好像缺页中断没有发生过 一样 。
    • 指令备份
      • 当程序访问不在内存中的页面时,引起缺页中断的指令会半途停止并引发操作系统的陷阱 。在操作系统取出所需的页面后,它需要重新启动引起陷阱的指令。
      • 通过使用一个隐藏的内部寄存器。在每条指令执行之前,把程序计数器的内容复制到该寄存器。
    • 锁定内存中的页面
      • 一种解决方法是锁住正在做 I/O 操作的内存中的页面以保证它不会被移出内存。锁住一个页面通常称为在内存中钉住 (pinning)页面.另一种方法是在内核缓冲区中完成所有的 I/O 操作,然后再将数据复制到用户页面。
    • 后备存储
      • 页面被换出时会存放在磁盘上的哪个位置
      • 最简单的算法是在磁盘上设置特殊的交换分区,甚至从文件系统划分一块独立的磁盘。
    • 策略和机制的分离
      • 控制系统复杂度的一种重要方法就是把策略从机制中分离出来。通过使大多数存储管理器作为用户级进程运行,就可以把该原则应用到存储管理中。
  • 分段
    • 对许多问题来说,有两个或多个独立的地址空间可能比只有一个要好得多。
    • 一个直观并且通用的方法是在机器上提供多个互相独立的称为段(segment)的地址空间。每个段由一个从 0 到最大的线性地址序列构成。
    • 段是一个逻辑实体,程序员知道这一点并把它作为一个逻辑实体来使用。一个段可能包括一个过程、一个数组、一个堆栈、一组数值变址,但一般它不会同时包含多种不同类型的内容。
    • 分段也有助于在几个进程之间共享过程和数据。这方面一个常见的例子就是共享库.
    • 不同的段可以有不同种类的保护 。一个过程段可以被指明为只允许执行,从而禁止对它的读出和写入。
    • 分段和分页的实现本质上是不同的: 页面是定长的而段不是。
    • 在系统运行一段时间后内存被划分为许多块,一些块包含着段,一些则成了空闲区,这种现象称为棋盘形碎片或外部碎片
    • 如果一个段比较大,把它整个保存在内存中可能很不方便甚至是不可能的,因此产生了对它进行分页的想法。这样,只有那些真正需要的页面才会被调入内存。
  • 有关内存管理的研究
    • 内存管理中具有重启功能的虚拟机
    • 应用程序向系统提供决策,以指导取哪个物理页来支持虚拟页
    • 云服务器稳定性对页面处理的影响方面