zoukankan      html  css  js  c++  java
  • 操作系统概念学习笔记 15 内存管理(一)

    操作系统概念学习笔记 15

    内存管理(一)


    背景

    内存是现代计算机运行的中心。内存有非常大一组字或字节组成,每一个字或字节都有它们自己的地址。CPU依据程序计数器(PC)的值从内存中提取指令。这些指令可能会引起进一步对特定内存地址的读取和写入。

    一个典型指令运行周期,首先从内存中读取指令。

    接着该指令被解码,且可能须要从内存中读取操作数。在指令对操作数运行后,其结果可能被存回到内存。

    内存单元仅仅看到地址流,而并不直到这些地址是怎样产生的(由指令计数器、索引、间接寻址、实地址等)或它们是什么地址(指令或数据)。

    基本硬件:

    CPU所能直接訪问的存储器仅仅有内存和处理器内的寄存器。机器指令能够用内存地址作为參数。而不能用磁盘地址作为參数。假设数据不在内存中,那么CPU使用前必须先把数据移到内存中。

    CPU内置寄存器通常能够在一个CPU时钟周期内完毕訪问。

    对于寄存器的内容,绝大多数CPU能够在一个时钟周期内解析并运行一个或多个指令,而对于内存就不行。

    完毕内存訪问须要多个CPU时钟周期。因为没有数据以便完毕正在运行的指令,CPU通常须要暂停(stall)。因为内存訪问频繁,这样的情况是难以忍受的。解决方法是在CPU与内存之间添加快速内存。这样的协调速度差异的内存缓冲去,称为快速缓存(cache)

    除了保证訪问物理内存的相对速度之外,还要确保操作系统不会被用户进程所訪问,以及确保用户进程不会被其它用户进程訪问。

    当中一种可能方案为:

    首先确保每一个进程都有独立的内存空间,为此,须要确定进程可訪问的合法地址的范围,并确保进程仅仅能訪问其合法地址。通过基地址寄存器(base register)界限地址寄存器(limit register)能够实现这样的保护。基地址寄存器(base register)含有最小的物理内存地址,界限地址寄存器(limit register)决定了范围的大小。

    比如:假设基地址寄存器为300040而界限寄存器为120900,那么程序能够訪问从300040到420940的全部地址。

    ==**图1**==

    内存空间保护的实现。是通过CPU硬件对用户模式所产生的每一个地址与寄存器的地址进程比較来完毕的。假设訪问了不该訪问的地址。则会陷入到操作系统中。并作为致命错误处理。

    ==**图2**==

    操作系统在内核模式下,能够无限制地訪问操作系统和用户内存。

    因此操作系统能够将用户程序装入用户内存,在出错时输出这些程序,訪问并改动系统调用的參数等。

    地址绑定:

    通常,程序以二进制可运行文件的形式存储在磁盘上。为了运行,程序被调入内存并放入进程空间内。

    依据所使用的内存管理方案,进程在运行时。能够在磁盘和内存之间移动。在磁盘上等待调入内存以便运行的进程形成输入队列(input queue)

    通常的步骤是从输入队列中选取一个进程并装入内存。进程在运行时。会訪问内存中的指令和数据。最后。进程终止,其地址空间将被释放。

    很多系统同意用户进程放在物理地址的任何位置。这样的组合方式会影响用户程序能够使用的地址空间。在绝大多数情况下。用户程序在运行前。会经过好几个步骤,在这些步骤中,地址可能有不同的表示形式,源程序中的地址一般是用符号(如count)来表示,编译器通常将这些符号地址绑定(bind)在可重定位的地址(如:从本模块開始的第14字节)。链接程序或载入程序再将这些可重定位的地址绑定成绝对地址(如74014)。每次绑定都是从一个地址空间到还有一地址空间的映射。

    通常。将指令与数据绑定到内存地址有下面几种情况:

    • 编译时(compile time):假设编译时就知道进程将在内存中的驻留地址。那么就能够生成绝对代码(absolute code)。假设将来開始地址发生变化,那么就必须又一次编译代码。

    • 载入时(load time):当编译时不知道进程将驻留在内存的什么地方,那么编译器就必须生成可重定位代码(reloadable code)。绑定会延迟到载入时才进行。假设開始地址发生变化。仅仅须要又一次载入用户代码已引入改变值。

    • 运行时(execution time):假设进程在运行时能够从一个内存段移到还有一个内存段。那么绑定必须延迟到运行时才发生。

      绝大多数通用计算机操作系统採用这样的方法。

    逻辑地址空间与物理地址空间:

    CPU生成的地址通常称为逻辑地址(logical address)。而内存单元所示地址(即载入到内存地址寄存器(memory-address register)中的地址)通常称为物理地址(physical address)

    编译和载入时的地址绑定方法生成相同的逻辑地址和物理地址。可是。运行时的地址绑定方案导致不同的逻辑地址和物理地址。对于这样的情况,通常称逻辑地址为虚拟地址(virtual address)

    由程序所生成的全部逻辑地址称为逻辑地址空间(logical address space)。与这些逻辑地址相相应的物理地址的集合称为物理地址空间(physical address space)

    运行时从虚拟地址到物理地址的映射由被称为内存管理单元(memory-management unit,MMU)的硬件设备来完毕。有非常多可选择的方法来完毕这样的映射,如使用一个简单的MMU方案来实现这样的映射,这是一种基地址寄存器方案的推广。基地址寄存器在这里称为重定位寄存器(relocation register),用户进程所生成的地址在送交内存之前。都加上重定位寄存器的值。

    假如,基地址为14000,那么用户对地址346的訪问将映射为地址14346。

    用户程序绝对不会看到真正的物理地址。如,程序能够创建一个指向位置346的指针,将他保存在内存中。使用它,与其它地址进行比較等等,全部这些操作都是基于346进行的。用户程序处理逻辑地址时。内存映射硬件将逻辑地址转变为物理地址。所引用的内存地址仅仅有在引用时才最后定位。

    逻辑地址空间绑定到单独的一套物理地址空间。

    动态载入(dynamic loading):

    一个进程的整个程序和数据假设都必须处于物理内存中,则进程的大小受物理内存大小的限制。

    为了获得更好的内存空间使用率。使用动态载入(dynamic loading),即一个子程序仅仅有在调用时才被载入。

    全部的子程序都以可重定位的形式保存在磁盘上。

    主程序装入内存并运行。当一个子程序须要调用另外一个子程序的时候,调用子程序首先检查还有一个子程序是否已经被载入。假设没有,可重定位的链接程序将用来载入所须要的子程序,并更新程序的地址表以反应这一变化。接着控制传递给新载入的子程序。

    动态载入的长处是不用子程序绝不会被载入。假设大多数代码须要用来处理异常情况。如错误处理。那么这样的方法特别实用。对于这样的情况,尽管整体上程序比較大,可是所使用的部分可能小非常多。

    动态载入不须要操作系统提供特别的支持。利用这样的方法来设计程序主要是用户的责任。

    动态链接(dynamically linking)与共享库:

    有的操作系统仅仅支持* 静态链接(static linking)*此时系统语言库的处理与其它目标模块一样,由载入程序合并到二进制程序镜像中。

    动态链接的概念与动态载入类似。仅仅是这里不是将载入延迟到运行时。而是将链接延迟到运行时。这一特点通经常使用于系统库。如语言子程序库。没有这一点,系统上的全部程序都须要一份语言库的副本,这一需求浪费了磁盘空间和内存空间。

    假设有动态链接,二进制镜像中每一个库程序的应用都有一个存根(stub)

    存根是一小段代码,用以指出怎样定位适当的内存驻留的库程序。或假设该程序不在内存中应怎样安装入库。无论怎样,存根会用子程序地址来取代自己,并開始运行子程序。因此,下次再运行该子程序代码时,就能够直接进行。而不会因动态链接产生不论什么开销。採用这样的方案,使用语言库的全部进程仅仅须要一个库代码副本就能够了。

    动态连接也可用于库更新。一个库能够被新的版本号所替代,且使用该库的全部程序会自己主动使用新的版本号。

    没有动态链接。全部这些程序必须又一次链接以便訪问。

    为了不使程序错用新的、不兼容版本号的库。程序和库将包含版本号信息。多个版本号的库都能够装入内存。程序通过版本号信息来确定使用哪个库副本。

    因此,仅仅实用新库编译的程序才会收到新库的不兼容变化影响。在新程序装入之前所链接的其它程序能够继续使用老库。这样的系统也称为共享库。

    与动态载入不同,动态链接通常须要操作系统帮助。假设内存中的进程是彼此保护的。那么仅仅有操作系统才干够检查所需子程序是否在其它进程内存空间内,或是同意多个进程訪问同一内存地址。

    交换

    进程须要在内存中以便运行。进程也能够临时从内存中交换(swap)备份存储(backing store)上,当须要再次运行时在调回到内存中。

    在交换策略的变种被用在基于优先级的调度算法中。

    假设有一个更高优先级的进程且须要服务,内存管理器能够交换出低优先级的进程,以便装入和运行更高优先级的的进程。当高优先级进程运行完后,低优先级进程能够交换回内存继续运行。这样的交换有时候称为滚入(roll in/swap in)滚出(roll out/swap out)

    通常,一个交换出的进程须要交换回它原来所占有的内存空间。这一限制是由地址绑定方式决定的。假设绑定是在汇编时或载入时所定的,那么就不能够移动到不同的位置。假设绑定在运行时才确定,因为物理地址是在运行时才确定。那么进程能够移动到不同的地址空间。

    交换须要备份存储。

    备份存储一般是快速磁盘。

    这必须足够大。以便容纳全部不同用户的内存镜像副本,它也必须提供对这些内存镜像的直接訪问。

    系统有一个就绪队列。它包含在备份存储或内存中等待运行的全部进程。

    当CPU调度程序决定运行进程时,它调用调度程序。

    调度程序检查队列中的下一进程是否在内存中,假设不在内存中且没有空暇内存空间。调度程序讲一个已在内存中的进程交换出去,并换入所须要的进程。然后。它又一次装载寄存器,并将控制转交给所选择的进程。

    交换系统的上下文切换时间比較长。为了有效使用CPU,须要使每一个进程的运行时间比交换时间长。

    注意交换时间主要部分是转移时间。总的转移时间与所交换的内存空间总量成正比。因此假设仅仅需交换真正使用的内存。便能够降低交换时间。

    为有效使用这样的方法,用户须要告诉系统其内存需求情况。因此。具有动态内存需求的进程要通过系统调用(请求内存和释放内存)来通知操作系统其内存需求变化情况。

    交换也受其它因素限制。

    假设要换进的进程,那么必须确保该进程全然处于空暇状态。

    假设I/O异步訪问用户内存的I/O缓冲区,那么该进程就不能被换出。假定因为设备忙,I/O操作在排队等待。假设换出进程P1换入进程P2。那么I/O操作可能试图使用如今已经属于进程P2的内存。

    对于这个问题有两种解决方法:

    • 一是,不能换出有待处理I/O的进程。

    • 二是,I/O操作的运行仅仅能使用操作系统缓冲区。仅当换入进程后。才运行操作系统缓冲与进程内存之间的数据转移。

    交换空间通常作为磁盘的一整块,且独立与文件系统。因此使用就可能非常快。

    通常并不运行交换,但当有很多进程运行且内存空间吃紧时,交换開始启动。假设系统负荷降低,交换就暂停。

    连续内存分配(contiguous memory allocation)

    内存必须容纳操作系统和各种用户进程,因此应该尽可能有效地分配内存的各个部分。

    内存通常分为两个区域:一个用于驻留操作系统,一个用于用户进程。操作系统能够位于低内存或高内存,影响这一决定的主要因素是中断向量的位置。因为中断向量通常位于低内存,因此程序猿通常将操作系统放到低内存。

    通常须要将多个进程同一时候放入内存中。因此须要考虑怎样为输入队列中须要调入内存的进程分配内存空间。

    採用连续内存分配(contiguous memory allocation)时,每一个进程位于一个连续的内存区域。

    内存映射与保护

    通过採用重定位寄存器和界限地址寄存器能够实现保护。

    重定位寄存器含有最小的物理地址值;界限地址寄存器含有逻辑地址的范围值。

    这样每一个逻辑地址必须小于界限地址寄存器。MMU动态第将逻辑地址加上重定位寄存器的值后影射成物理地址。

    映射后的物理地址再送交内存单元。

    这里写图片描写叙述

    当CPU调度器选择一个进程来运行时,作为上下文切换工作的一个部分,调度程序会用正确的值来初始化重定位寄存器和界限地址寄存器,因为CPU所产生的每一地址都须要与寄存器进程核对,所以能够保证操作系统和其它用户程序和数据不受该进程运行所影响。

    重定位寄存器机制为同意操作系统动态改变提供了一个有效方法。如某驱动程序(或其它操作系统服务)不常使用便能够不必在内存中,这类代码有时称为临时(transient)操作系统代码,它们依据须要调入或调出。因此,使用这样的代码能够在程序运行时动态改变操作系统的大小。

    内存分配

    最简单的内存分配方法之中的一个是将内存分为多个固定大小的分区(partition)

    每一个分区仅仅能容纳一个进程。那么多道程序的程度会受分区数限制。假设使用这样的多分区方法(multiple-partition method),当一个分区空暇时,能够输入队列中选择一个进程,以调入到空暇分区。当进程终止时,其分区能够被其它进程所使用。这样的方法如今已不再使用。对于固定分区方案的推广(称为MVT),它主要用于批处理环境。也可用于纯分段内存管理的分时操作系统。

    可变分区(variable-partition)方案中,操作系统有一个表。用于记录那些内存可用和哪些内存已被占用。

    一開始,全部内存都可用于用户进程,因此能够作为一大块可用内存。称为孔(hole),当新进程须要内存时,为该进程查找足够大的孔,假设找到,能够从该孔进程分配所需的内存,孔内未分配的内存可用于下次再用。

    随着进程进入系统,它们将被添加输入队列中。

    操作系统依据调度算法来对输入队列进行排序。

    内存不断地分配给进程,直到下一个进程的内存需求不能满足为止,假设没有足够大的孔来装入进程,操作系统能够等到有足够大的空间。或者往下扫描输入队列以确定是否其它内存需求较小的进程能够被满足。

    通常,一组不同大小的孔分散在内存中。

    当新进程须要内存时,系统为进程查找足够大的孔。假设孔太大。那么就分成两块:一块分配给新进程,还有一块还回到孔集合,当进程终止时。它将释放其内存,改内存将还给孔集合。假设孔与其它孔相邻。那么将这些孔合并为大孔。这时,系统能够检查是否有进程在等待内存空间。新合并的内存空间是否满足等待进程。

    这样的方法是通用动态存储分配问题的一种情况(依据一组空暇孔来分配大小为n的请求)。这个问题有很多解决方法。

    从一组可用孔中选择一个空暇孔的最为经常用法有首次适应(first-fit)(第一个对够大的孔)、最佳适应(best-fit)(最小的最够大的孔)、最差适应(worst-fit)(分配最大的孔)。

    • 首次适应(first-fit):分配第一个足够大的孔,查找能够从头開始。也能够从上次首次适应结束时開始。一旦找到足够大的空暇孔,就能够停止。

    • 最佳适应(best-fit):分配最小的足够大的孔。

      必须查找整个列表,除非列表依照大小排序。这样的方法能够产生最小剩余孔。

    • 最差适应(worst-fit):分配最大的孔,相同必须查找整个列表。除非列表依照大小排序。

      这样的方法能够产生最大剩余孔。

      该孔可能比最佳适应方法产生的最小剩余孔更实用。

    模拟结果显示:首次适应和最佳适应方法在运行时间和利用空间方面都好于最差适应方法。

    首次适应和最佳适应方法在利用空间方面难分伯仲。首次适应方法更快些。

    碎片(fragmentation)

    首次适应和最佳适应算法都有外部碎片问题(external fragmentation)。随着进程装入和移出内存。空暇内存空间被切割为小分段,
    当全部总的空用内存之和能够满足请求,但并不连续时,这就出现了外部碎片问题。

    最坏的情况下。每两个进程之间就有空暇块(或浪费)。假设这些内存是一整块。那么就能够再运行多个进程。

    在首次适应和最佳适应之间的选择可能会影响碎片的量。还有一个影响因素是从空暇块的哪端開始分配。无论使用哪种算法。外部碎片始终是个问题。

    依据内存的总大小和平均进程大小的不同。外部碎片化的重要程度也不同。

    比如。对採用首次适应方法的统计说明,对于首次适应方法无论怎么优化,假定N个可分配块。那么可能有0.5N个块为外部碎片。即1/3内存可能不能使用,这一特性称为50%规则。

    内存碎片能够是内部的。也能够是外部的。假设内存以固定大小的块为单元来分配,进程所分配的内存可能比所要的要大。这两个数字之差称为内部碎片(internal fragmentation)这部分内存在分区内,但又不能使用。

    一种解决外部碎片问题的方法是紧缩(compaction),紧缩的目的是移动内存内容,以便全部空暇空间合并成一整块。可是紧缩并不是总是可能的。假设重定位是静态的。而且在汇编时或装入时进行的,那么就不能紧缩。

    紧缩仅在重定位是动态的并在运行时可採用。假设地址被动态重定位,能够首先移动程序和数据,然后再跟据新基地址的值来改变基地址寄存器。假设採用紧缩。还要评估其开销。最简单的合并算法是简单地将全部进城移到内存的一端。而将全部的孔移到内存的还有一端。以生成一个大的空暇块。

    这样的方案开销较大。

    还有一种解决方法外部碎片问题的方法是同意物理地址为非连续的。这样仅仅要有物理内存就能够为进程分配。这样的方案有两种互补的实现技术:分页和分段。这两种技术也能够合并。

  • 相关阅读:
    揭开Future的神秘面纱——任务取消
    阻塞队列和生产者-消费者模式
    ExecutorService——<T> Future<T> submit(Callable<T> task)
    ExecutorService接口概要
    Executor简介
    使用显式的Lock对象取代synchronized关键字进行同步
    SingleThreadExecutor(单线程执行器)
    后台线程(daemon)
    加入一个线程
    计算机网络的一些英文缩写词
  • 原文地址:https://www.cnblogs.com/lxjshuju/p/7281990.html
Copyright © 2011-2022 走看看