zoukankan      html  css  js  c++  java
  • 操作系统之内存管理

    1.背景

    1.1 基本硬件

    Q1:如何确保每个进程有独立的内存空间,确定进程可访问的合法地址的范围?

    A1:采用基地址寄存器界限地址寄存器。基地址寄存器含有最小的合法物理内存地址,界限地址寄存器决定范围大小。如基地址寄存器为300040,界限地址寄存器为120900,则程序可访问地址范围为300040至420940(含)的所有地址。

    如图所示:

    Q2:如何实现对内存空间的保护?

    A2:通过CPU硬件对用户模式下所产生的每一个地址与寄存器的地址进行比较来完成。如用户模式下执行的程序试图访问操作系统内存或其他用户内存,则会陷入操作系统,作为致命错误处理。见图所示:

    1.2 地址绑定

    Q1:什么是输入队列

    A1:程序以二进制可执行文件的形式存储在磁盘上,为了执行,程序被调入内存并放在进程空间内,根据所使用的内存管理方案,进程在执行时可以在磁盘和内存之间移动,在磁盘上等待调入内存以便执行的进程形成输入队列。通常的步骤是从输入队列选取一个进程并装入内存,进程在执行时,会访问内存中的指令和数据,最后进程终止,其地址空间被释放。

    Q2:什么是地址绑定?

    A2:首先看程序的处理过程,见图:

          源程序中的地址通常是用符号(如count)来表示,编译器通常将这些符号地址绑定(bind)在可重定位的地址(如:从本模块开始的第14字节)。链接程序或加载程序再将这些可重定位的地址绑定成绝对地址(如74014)。每次绑定都是从一个地址空间到另一地址空间的映射。

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

    • 编译时(compile time):若编译时知道进程将在内存中的驻留地址,可以生成绝对代码(absolute code)。如果将来开始地址发生变化,那么就必须重新编译代码。

    • 加载时(load time):当编译时不知道进程将驻留在内存的什么地方,编译器必须生成可重定位代码(reloadable code)。绑定会延迟到加载时才进行。如果开始地址发生变化。只需要重新加载用户代码以引入改变值。

    • 执行时(execution time):如果进程在执行时可以从一个内存段移到另一个内存段,那么绑定必须延迟到执行时才发生。绝大多数通用计算机操作系统采用这种方法。

    1.3 逻辑地址空间与物理地址空间(都是些概念)

    逻辑地址(虚拟地址):CPU生成的地址;

    物理地址:内存单元所看到的地址(即加载到内存地址寄存器(memory-address register)中的地址);

    逻辑地址空间:由程序所生成的所有逻辑地址的集合;

    物理地址空间:与逻辑地址相对应的物理地址的集合;

    Q1:如何在运行时从虚拟地址到物理地址映射?

    A1:靠一个硬件设备——内存管理单元(memory-management unit,MMU)。基地址寄存器在这里称为重定位寄存器(relocation register),用户进程所生成的地址在送交内存之前,都加上重定位寄存器的值。假如,基地址为14000,那么用户对地址346的访问将映射为地址14346。

    如图所示:PS:用户不会看到真正得物理地址。

    1.4 动态加载:

         一个子程序只有在调用时才被加载。所有的子程序都以可重定位的形式保存在磁盘上。主程序装入内存并执行。当一个子程序需要调用另外一个子程序的时候,调用子程序首先检查另一个子程序是否已经被加载。如果没有,可重定位的链接程序将用来加载所需要的子程序,并更新程序的地址表以反应这一变化。接着控制传递给新加载的子程序。

         优点:不会加载不用的子程序。考虑一段程序中含有大量的异常处理代码,但这些异常处理子程序很少需要。

    1.5 动态链接:(看他人的博客)

          Q1:什么是静态链接?

          A1:在链接阶段,将源文件中用到的库函数与汇编生成的目标文件.o合并生成可执行文件。(可执行文件中包含函数库的一份拷贝)。该可执行文件可能会比较大。优点:方便程序移植,因为可执行程序与库函数再无关系。缺点是:文件太大。如图所示说明:

          

    Q2:什么是动态加载?

    A2:将链接延迟到运行时。通常用于系统库。可执行文件只是包含了文件名,让载入器在运行时能够寻找程序所需要的函数库。

         收集模块准备执行的三个规范名称是:链接-编辑,载入 和 运行时链接。静态链接的模块被链接编缉并载入以便运行。动态链接的模块被链接编辑后载入,并在运行时进行链接以便运行。程序执行时,在mian()函数被调用之前,运行时载入器把共享的数据对象载入到进程的地址空间,外部函数被真正调用之前,运行时载入器并不解析它们,故即使链接了函数库,若没有实际调用,不会带来额外开销。

         如果有动态链接,二进制镜像中每个库程序的应用都有一个存根(stub)。存根是一小段代码,用以指出如何定位适当的内存驻留的库程序,或如果该程序不在内存中应如何安装入库。不管怎样,存根会用子程序地址来代替自己,并开始执行子程序。因此,下次再执行该子程序代码时,就可以直接进行,而不会因动态链接产生任何开销。采用这种方案,使用语言库的所有进程只需要一个库代码副本就可以了。见图:

    在这种模型中,两个程序只应用一个库,这个目标文件在内存中只有一份,供所有程序使用。

    并且在程序运行过程中动态调用库文件,很方便,又不占空间,但是动态链接有一个缺点就是可移植性太差。

    2.交换

    基本思想:把处于等待(阻塞)状态(或在CPU调度原则下被剥夺运行权利)的程序(进程)从内存移到辅存(外存),把内存空间腾出来,这一过程又叫换出。把准备好竞争CPU运行的程序从辅存移到内存,这一过程又称为换入。中级调度(策略)就是釆用交换技术。

    举例:交换策略的变种被用在基于优先级的调度算法中。如果有一个更高优先级的进程且需要服务,内存管理器可以交换出低优先级的进程,以便装入和执行更高优先级的的进程。当高优先级进程执行完后,低优先级进程可以交换回内存继续执行。

    2.1 交换需要备份存储:通常是快速磁盘,容纳所有用户的内存镜像副本,提供对这些内存镜像的直接访问。

    就绪队列:包括在备份存储或内存中等待运行的所有进程。当CPU调度程序决定执行进程时,它调用调度程序。调度程序检查队列中的下一进程是否在内存中,如果不在内存中且没有空闲内存空间,调度程序将一个已在内存中的进程交换出去,并换入所需要的进程。然后,它重新装载寄存器,并将控制转交给所选择的进程。Ps:输入队列是在磁盘中等待调入内存,就绪队列是在备份存储或在内存中等待运行。

    2.2 上下文切换时间比较长

    2.3 受其他因素限制,如果要换出进程,那么必须确保该进程完全处于空闲状态。尤其注意待处理I/O。

    2.4  交换空间通常作为磁盘的一整块,且独立与文件系统,因此使用就可能很快。通常并不执行交换,但当有许多进程运行且内存空间吃紧时,交换开始启动。如果系统负荷降低,交换就暂停。

    3.连续内存分配

    3.1 内存映射与保护

    当CPU调度器选择一个进程来执行时,作为上下文切换工作的一个部分,调度程序会用正确的值来初始化重定位寄存器和界限地址寄存器。

    3.2 内存分配

         可变分区方案里,OS有一个表,记录哪些内存可用和哪些内存不可用。将内存分为若干个不同大小的孔。一开始,所有内存都可用于用户进程,因此可以作为一大块可用内存。

         通常,一组不同大小的孔分散在内存中。当新进程需要内存时,系统为进程查找足够大的孔。如果孔太大,那么就分成两块:一块分配给新进程,另一块还回到孔集合,当进程终止时,它将释放其内存,改内存将还给孔集合。如果孔与其他孔相邻,那么将这些孔合并为大孔。这时,系统可以检查是否有进程在等待内存空间,新合并的内存空间是否满足等待进程。

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

    3.3 碎片

    外部碎片和内部碎片区别:

    内部碎片就是已经被分配出去(能明确指出属于哪个进程却不能被利用的内存空间;

    外部碎片指的是还没有被分配出去(不属于任何进程),但由于太小了无法分配给申请内存空间的新进程的内存空闲区域。

    解决办法:1.紧缩,移动内存内容,使所有空闲空间合并成一个整块;仅在重定位是动态并在运行时可采用。

                      2.允许物理地址为非连续。

    4.  分页     实现了进程的物理地址空间非连续

    4.1 基本方法

    帧:物理内存被分为的固定大小的块;

    页:逻辑内存被分为的固定大小的块;

    硬件支持如图所示:

    页号(p):页表的索引;       页偏移(d)

    计算方法:若逻辑地址空间为2^m,页大小为2^n单元(字节或字),则逻辑地址的高m-n位表示页号,低n位表示页偏移,如图所示:

    Q1:如图所示,页大小为4B,物理内存为32B(8帧),则用户视角内存如何映射到物理内存?

    A1:考虑字符c的位置:字符c的地址在逻辑地址空间为2,可表示为0010,前两位对应页号,后两位对应页偏移。(由于逻辑地址空间为0-15,故m=4;由于页大小为4B,故n=2)。因此根据页号查表得,页号0对应的帧为帧5,故物理地址空间表示为10110,十进制为22,因此得到字符c的物理地址。

    采用分页技术不会产生外部碎片(因为允许物理地址非连续),但有内部碎片。比如最后一个帧虽分配给进程,但进程并不利用其全部内存。

    具体分配内存过程:

       

        当系统进程需要执行时,它将检查该进程的大小(按页计算)。进程的每页都需要一帧。因此,如果进程需要n页,那么内存中至少应有n个帧。如果有那么就分配给新进程。进程的第一页装入一个已分配的帧,帧号放入进程的页表中。下一页分配给另一帧,其帧号也放入进程的页表中。如图所示。

    帧表:保存物理内存的分配细节:占用帧,空闲帧等。

    操作系统为每个进程维护一个页表的副本,该副本可用来将逻辑地址转换为物理地址。

    4.2 硬件支持:

    当前问题:访问用户内存太浪费时间。因为先要查找页表得到帧号,而页表保存在内存中(用页表基寄存器,PTBR指向页表),因此需要访问内存;然后加上页偏移,得到物理地址,因此需要再次访问内存。结论:访问一个字节需要两次内存访问(一次查找页表,一次访问该字节)

    解决办法:采用转换表缓冲区(TLB)。TLB是关联的快速内存,小但专用且快速,由“键(标签)”和“值”组成。

    TLB使用方法:TLB只包括页表中的一小部分条目。当CPU产生逻辑地址后,其页号提交给TLB。若找到页号,则得到帧号。如果页号不在TLB中(称为TLB失效),那么就需要访问页表。得到帧号,就可访问内存,并将页号和帧号增加到TLB中。如果TLB中的条目已满,那么操作系统会选择一个来替换。如图所示:

    页号在TLB中被查找到的概率为命中率

    计算方法如下:

    80%的命中率意味着有80%的时间可以在TLB中找到所需的页号。

    假如查找TLB需要20ns,访问内存需要100ns,如果访问位于TLB中的页号,那么采用内存映射访问需要120ns。如果不能在TLB中找到(20ns),那么必须先访问位于内存中的页表得到帧号(100ns),并进而访问内存中所需字节(100ns),这总共需要220ns。为了得到有效内存访问时间,必须根据概率对每种情况进行加权。

    有效内存访问时间 = 0.80 * 120 + 0.2 * 220 = 140(ns)

    对于这种情况,现在内存访问速度要慢40%(100ns~140ns)

    如果命中率为98%,那么

    有效内存访问时间 = 0.98 * 120 + 0.02 * 220 = 122(ns)

    由于提高了命中率,内存访问时间只慢了22%

    4.3 保护

    有效-无效位。有效,表示相关的页在进程的逻辑地址空间内,因此是合法的页;无效,表示相关的页不在进程的逻辑地址空间内。通过使用有效-无效位可以捕捉非法地址。操作系统通过对该位可以允许或不允许对某页的访问。

    有些系统提供硬件如页表长度寄存器(page-table length register,PTLR)来表示页表的大小,该寄存器的值可用于检查每个逻辑地址以验证其是否位于进程的有效范围内,如果检测无法通过,会被操作系统捕获。

    4.4 共享页

        考虑一个支持40个用户的系统,每个用户都执行一个文本编辑器。如果文本编辑器包括150kb的代码和50kb的数据空间。则需要8000kb来支持这40个用户。如果代码是可重入代码(reentrant code,也称为纯代码),则可以共享。如图所示,看到3个页的编辑器(每页50kb)在三个进程间共享,而每个进程都有自己的数据页。通过这种方法,只需要在物理内存中保存一个编辑器副本。每个用户的页表映射到编辑器的同一物理副本,而数据页映射到不同帧。因此,为支持40位用户,只需要一个编辑器副本(150k)再加上40个用户数据空间副本50kb,总的需求空间为2150kb。

    5 页表结构

    5.1 层次页表

       使用两级分页算法,将页表再分页。以一个4KB页大小的32位系统为例。逻辑地址被分为20位页码和12位页偏移,20位页码被分为10位的页码和10位的页偏移。如图中所示,p1访问外部页表索引,p2是外部页表的页偏移。此方案也称为向前映射页表

    5.2 哈希页表

       常用来处理超过32位地址空间,并以虚拟页作为哈希值。

       哈希表每一条目都包括一个链表的元素,这些元素哈希成同一位置,每个元素有3个域:(1)虚拟页码;(2)所映射的帧号;(3)指向链表中的下一个元素。

       该算法按照如下方式工作:虚拟地址中的虚拟页号转换为哈希表号,用虚拟页号与链表中的每一个元素的第一个域相比较。如果匹配,那么相应的帧号(第二个域)就用来形成物理地址,如果不匹配,就对链表中的下一个节点进行比较,以寻找一个匹配的页号。

    5.3 反向页表(这里应该有问题,应当是由帧号索引页号)

         反向页表对于每个真正的内存帧或页才有一个条目。每个条目包含保存在真正内存位置的页的虚拟地址以及拥有该页的进程的信息。因此,整个系统只有一个表,对每个物理内存的页只有一条相应的条目。    

        由于系统只有一个页表,而有很多地址空间映射物理内存,所以反向页表的条目中通常需要一个地址空间标识符(唯一地标识进程),以确保一个特定进程的一个逻辑页可以映射到相应的物理帧。

        如图,process-id作为地址空间的标识符。当需要内存引用时,由process-id和page-number组成的虚拟地址部分送交内存子系统,通过查找反向页表来寻找匹配。如果匹配找到,例如条目i,那么就产生了物理地址i+offset。如果没有匹配,那就是试图访问非法地址。

    这里写图片描述

    6 分段

    逻辑地址空间由一组段组成的。每个段都有名称和长度。地址指定了段名称和段内偏移。

    地址是通过段表(segment table)来实现的。段表的每个条目都有段基地址段界限段基地址包含该段在内存中的开始物理地址,而段界限指定该段的长度。

    一个逻辑地址由两部分组成:段号s段内的偏移d。段号用来做段表的索引,逻辑地址的偏移d用位于0和段界限之间

    这里写图片描述
  • 相关阅读:
    java里面嵌套执行python脚本
    session的token令牌机制防止表单重复提交
    springIOC实现原理模拟(springIOC底层使用xml解析+反射实现)
    service层使用接口的好处
    javap -verbose输出结果详解
    skiplist
    Jmeter内存溢出解决方法
    Jmeter参数化设置的5种方法
    方法入参获取泛型类型
    并发编程笔记
  • 原文地址:https://www.cnblogs.com/dzy521/p/9385022.html
Copyright © 2011-2022 走看看