内存管理
内存管理功能
- 内存的分配与回收:当作业或进程创建后系统会为他们分配内存空间,当结束后内存空间也会被回收。
- 地址转换:将程序中的逻辑地址转换成内存中的物理地址
- 内存空间的扩充:利用虚拟存储技术或自动覆盖技术,从逻辑上扩充内存
- 存储保护:保证个个作业在自己的内存空间内运行,互不干扰
程序执行过程
编译、链接、装入
编译
- 编译:由编译程序将用户源代码编译成若干个目标模块
链接
- 链接:由链接程序将变以后的模块和所需要的库函数连接在一起,形成一个完整的装入模块
- 程序链接的三种方式:
- 静态链接:在程序运行之前,就将所有模块和库函数的连在一起不再分开。
- 装入时动态链接:将用户编译后的一组模块在装入内存的时候,边链接边装入。
- 运行时动态链接:在运行程序执行中需要哪些模块才将其连接装入,便于修改与更新。
装入
- 装入:由装入程序将装入模块装入内存运行
- 装入的三种方式:
- 绝对装入:程序编译的时候如果已经知道要将目标模块放在内存中的哪一个位置,由于程序的逻辑地址与物理地址相同,所以直接装入
- 可重定位装入:在多道程序环境下,多个目标模块逻辑地址的起始地址都是从0开始的,装入是对程序中的逻辑地址进行修改从而得到物理地址。比如:一个模块分配的地址在内存中是从100开始的,此时它里面标号为69的逻辑地址实际上是169。
- 动态运行时装入:装入程序版装入模块装入内存后,并不立即把装入模块中的地址转换为绝对地址,而是推迟到程序要执行的时候才进行转换,一次装入内存后都为相对地址。
逻辑地址和物理地址
编译器将程序代码分成若干个模块,每个目标模块都从0号单元开始编址,这就是该目标模块的相对地址(逻辑地址);当每个目标模块链接成一个完整的可执行目标程序的时候,连接程序会依稀按照各个模块的相对地址统一构成从0号单元开始编址的绝对地址(物理地址)
内存保护
- 概念:在分配内存的时候,为了保证操作系统不受进程的影响,同时保证进程之间互不干扰,从而引出了内存保护。
- 方法:
- 在CPU中设置一堆上下限寄存器,存放用户作业在主存中的上限地址与下限地址。当CPU要访问内存的时候,分别用这两个地址和要访问的地址做比较。
- 重定位寄存器(基址寄存器)和界地址寄存器(限长寄存器)。重定位寄存器存储改作业的物理地址最小值;界地址寄存器存储改作业逻辑地址最大值。当CPU要访问内存的时候,分别用这两个地址数值之和与要访问的地址做比较。
扩充内存
覆盖与变换
覆盖
- 概念:由于程序在运行的时候并不是任何时候都要访问程序的所有数据和代码,所以可以将用户空间分成一个固定区和如若干个覆盖区。将经常使用的程序段放在固定区(不会被调出)。而那些互斥使用的程序可以交替使用覆盖区,如果不使用的话,会被调出内存。
- 特点:程序的层次结构必须由程序猿来申明,操作系统完成自动覆盖,缺点对用户不透明,增加用户负担。
交换
- 概念:把处于等待状态的进程从内存移到辅村,内存空间腾出来,这个叫做换出;把将要调用的进程在从辅村调到内存,这个过程叫做换入。
- 特点:交换一般是根据交换时长与执行时长比较,前者长的话就不适合;也会根据进程的优先级,首先换出优先级低的进程,但这里要防止低优先级进程饥饿;硬盘其实是分为文件区和交换区的,换出去的进程是存放在交换区。
覆盖和交换区别
- 覆盖是在同一个程序或者进程之间的
- 交换是在不同进程和程序之间的
- 所以覆盖技术与交换技术可以一起使用
连续分配
- 概念:连续分配为用户分配一个连续的内存空间,比如某个作业需要100mb的内存空间,就为这个作业在内存中划分一个100mb的内存空间。
- 介绍两个概念便于后面理解:
- 内部碎片:给一个进程分配一块空间,这块空间没有用完的部分叫做内部碎片。
- 外部碎片:给每个进程分配空间以后,内存中会存在一些区域由于太小而无法利用的空间,叫做外部碎片。
单一连续分配
- 分配方法:将内存去划分为系统区域用户区,系统区为操作系统使用,剩下的用户区给一个进程或作业使用。
- 特点:操作简单、没有外部碎片,适合单道处理系统。但是会有大量的内部碎片浪费资源,存储效率低。
固定分区分配
- 分配方法:(1)分区大小相等:将内存的用户区分成大小相等的区域,每个进程只能申请一块区域;(2)分区大小不等:将内存的用户区分成大小不等的区域,分配原则是多个较小的区域、适量中等大小区域、少量的最大分区。每个进程根据大小只能申请一块区域。
- 特点:固定分区分配虽然没有外部碎片,但是会造成大量的内部碎片。分区大小相等缺乏灵活性,大的进程可能放不进去;分区大小不等可能会造成大量的内部碎片,利用率极低。
动态分区分配
- 分配方法:不会先划分内存区域,当进程进入内存的时候才会根据进程大小动态的为其建立分区,使分区大小刚好适合进程的需要。
- 特点:在开始是很好的,进程一次按照顺序存入内存,但是运行久了以后随着进程的消亡,会出现很多成段的内存空间,时间越来越长就会导致很多不可利用的外部碎片,降低内存的利用率。这时需要分配算法来解决
分配算法
- 首次适应算法:进程进入内存之后从头开始查找第一个适合自己大小的分区。空间分区就是按照地址递增的顺序排列。算法开销小,回收后放到原位置就好。综合看这个算法性能最好。
- 最佳适应算法:将分区从从小到大排列(容量递增),找到最适合自己的分区,这样会有更大的分区被保留下来,满足别的进程需要。但是算法开销大,每次进程消亡产生新的区域后要重新排序。并且当使用多次后会产生很多外部碎片。
- 最坏适应算法:将分区从从大到小排列(容量递减),进程每次都找最大的区域来使用。可以减少难以利用的外部碎片。但是大分区很快就被用完了,大的进程可能会有饥饿的现象。算法开销也比较大。
- 邻近适应算法:空间分区按照地址递增的顺序进行排列,是由首次适应演变而来,进程每次寻找空间,从上一次查找的地址以后开始查找(不同于首次适应,首次适应每次从开头查找)。算法开销小,大的分区也会很快被用完。
- 注意:以上进程使用空间不会产生内部碎片,当进程大小为60mb的进程找到了一块100mb的空间,他只会使用60mb,剩下的40mb会给别的进程。
非连续分配
可以将一个进程分散的装入内存分区。根据分区的大小是否固定可以分成分页存储管理(固定)与分段存储管理(不固定),为了避免两者的缺点,还可以二者混用成段页式存储管理。再根据进程运行作业时是否将作业的的全部代码装入内存,又分为基本分页存储管理(全部装入内存)和请求分页存储管理(非一次全装入内存)。
基本分页存储管理
思想与基本概念
- 思想:把主存空间划分为大小相等的块,块相对较小,作为主存的基本单元。每个进程也以块为单位划分,进程执行时,以块为单位申请内存空间
- 概念:
- 块:外存中的块
- 页(页面):进程里面的块
- 页框(页帧):内存中的块
- 地址结构:地址结构包含两部分,第一部分是页号(P),根据页号的位数可以算出地址结构可容纳最大页数;第二部分是页内偏移量(W),可以计算出页面的大小。地址结构决定了虚拟内存的寻址空间有多大(页面的总数,比如P有二十位,那么地址空间最多有$2^{20}$个页面)。
- 页表:为了便于在内存中找到进程的每个页面所对应的物理块,系统为每个进程建立一张页表,记录每个页面(进程中的块)在内存中的物理块号,一般放在内存中。页表项由两部分构成,第一部分存储页号,第二部分储存物理内存中的块号。
- 注意:进程中的块的各个代码,在内存中对应的物理地址是=页表中物理内存块号+地址结构中页内偏移量
地址变换机制及变化过程
- 概念:主要用于将逻辑地址转换成内存中的物理地址。借助于页表来实现的。
- 步骤:
- 1、在系统中会设置一个页表寄存器(PTR),用来储存页表在内存中的起始地址F和页表长度M
- 2、根据逻辑地址计算出页号和业内偏移量
- 3、判断页号是否越界
- 4、查询页表找到页号对应的页表项,确定页面的内存块号(第一次访存,因为页表在内存中)
- 5、用内存块号和业内偏移量的到物理地址
- 6、访问内存目标单元(第二次访存)
- 问题:(1)每次访问内存都需要地址转换(逻辑地址->物理地址),浪费时间;(2) 内存中快表不能占有太大的内存,不然降低了内存利用率。
具有快表(TLB)的地址变换机构
- 快表(TLB):又称为相连存储器,用来存放当前访问的若干页表象,以加速地址的变换过程。主存中的页表称为慢表。(学过组成原理的知道,页表其实和cache比较相似)
- 步骤:
- 1、CPU给出逻辑地址后,由硬件进行地址转换,将页号送入高速缓存寄存器,并将次页号与快表中的页号做比较
- 2、如果找到,直接从快表去除该页对应的页框号,与地址结构的地址偏移量计算出物理地址访存
- 3、如果没有找到,再去慢表中找,然后如上访存,之后将这个页表项加入到快表中。注意有的系统为了节省时间,会在快表中找与在慢表中找同时进行,这样可以节约系统时间。
两级页表
- 背景:一级页表,页表必须是连续存放的,因此当页面很大的时候,需要占用很多个连续的叶匡没必要让整个页表常驻内存,所以引出二级页表。
- 步骤
- 按照地址结构将逻辑地址拆分成三部分(一级页号、二级页号、页内偏移量)
- 从PCB中读取页目录表起始地址,再根据一级页号查页目录表,找到下一级页表在内存中的存放位置
- 根据二级也好查表,找到最想访问的内存块号
- 结合页内偏移量得到物理地址
基本分段式存储管理
背景
分页存储是从计算机的角度设计的,目的是为了提高内存的利用率,提升计算机的性能。分段存储的提出是考虑到程序员和用户,以满足方编程、数据共享、信息保护、动态增长、动态链接的需要。
基本概念
- 分段:按照进程自然划分炒年成逻辑空间,例如进程由主程序、两个子程序、栈和数据组成。于是可以把这个进程分成5段,每一段的逻辑地址从0开始编址,并分配一段连续的内存空间。(注意这里段内必须连续,段与段之间可以分散)
- 逻辑地址结构:是由两部分组成第一部分为段号S,看进程是哪一个段,段号的位数决定了进程分了多少段;第二部分为段内偏移量W,段内偏移量决定了这段进程的最大长度。
- 段表:每个进程都以一张逻辑空间与内存空间映射的段表,每个段表对应进程的一段,段表项季度该段在内存中的起始地址和长度。段表由三个部分组成:段号、段长、本段在主存中的起始地址(基址)。
地址变换机构
- 进切换内核程序回复进程的运行环境,从PCB中找到段表寄存器
- 根据逻辑地址得到段号、段内地址
- 将段号与段表长度比较,判断段号是否越界,产生越界中断
- 查询段表,找到对应的段表项,段表项存放的地址为段表起始地址+段号*段表项长度(因为为了节省内存空间,可以参略段表中的段号,那么计算段表项的起始地址就可以用这种方法)
- 对段内地址W进行检查,是否超过段长。如果超越就产生中断
- 根据段的基址和和段内地址得到物理地址
- 访问目标内存单元
分页与分段的区别
1、页是信息的物理单位,分页的主要目的是为了实现离散分配,提高内存的利用率。分页仅仅是系统管理上的需求,安全是系统行为对用户是不可见的 段是信息的逻辑单位,分段的主要目的是更好地满足用户需求,一个段通常包含一组数语一个逻辑板块的信息。分段是用户可见的,用户编程时需要显示的给出段名。
2、页的大小是固定的,系统绝决定;段的大小是不固定的,取决于系统程序
3、分页的用户地址空间是一维的,程序员只需要给出一个记忆符就可以表示一个地址 分段存储管理的地址空间是二维的,程序员需要在标识一个地址的时候,既要给出段名,也要给出段内地址
4、分段比分页更容易实现信息的共享和保护 注意:不能修改的代码称为纯代码(可重入代码),这样的代码段不是临界资源,可以共享。可修改的代码是不可以共享的(比如由很多变量的代码段) 比如:生产者进程的一个进程段,是用来判断该缓冲区此时是否可以访问,这个时候消费者进程的段表项也可以指向这里 为什么分业管理不方便实现代码共享? 因为将生产者进程分段,由于页面的空间有限,一段可能被装入多个空间,一个空间也可能有多个代码段被装进来,所以适合共享,达不到安全的效果
5、访问一个逻辑地址需要几次访问内存? 单级页表:1.查内存中的页表——2.访问目标内存单元 分段:1.查询内存中的段——2.访问目标内存单元 分段与分页系统相似,分段系统也可以引入快表机构,将近期访问过的段表放到快表中,这样可以少一次访问,加快地址变换速度
6、分页:内存空间利用率高,不会产生外部碎片,只有少量的内部碎片;不方便按照逻辑模块实现信息的共享与保护 分段:方便按照逻辑模块实现信息的共享与保护;如果段太长,为其分配很大的存储空间很不方便,容易产生外部碎片(这个虽然可以用前面的一些紧凑技术解决一部分,但是时间代价很大)
段页式管理方式
背景与概念
- 背景:由于分段与分页各有利弊,页式存储提高内存利用率,段式存储反应程序逻辑结构有利于共享数据,所以可以结合二者来组成新的内存管理方式。
- 概念:首先将进程根据逻辑结构划分成若干个逻辑段,每个段都有自己的段号,然后将这些段划分成若干个大小固定的页。这样对内存空间的管理依然和分页式管理相似,将内存分成和页面大小相同的存储块,对内存分配一存储块为单位。
- 逻辑地址结构:逻辑地址结构由段号S(决定每个进程的段数)、页号P(决定每段的页数)、页内偏移量W(页面的大小和内存块的大小)组成。
地址变换机制及变化过程
- 根据逻辑地址得到段号、页号、页内偏移量
- 将段号S与段表长度比较,判断段号是否越界
- 查询段表,找到对应的段表项,段表项存放的地址为段表起始地址+段号*段表项长度(因为为了节省内存空间,可以参略段表中的段号,那么计算段表项的起始地址就可以用这种方法)(第一次访存)
- 将页号与页表长度比较,检查页号是否越界
- 根据页表存放块号、页号查询页表,找到对应的页表项(第二次访存)
- 根据内存块号、页内偏移量得到最终的物理地址
- 访问目标内存单元(第三次访存)
- 注意:1、段表寄存器和页表寄存器作用有两个,一是在段表或页表中寻址,二是判断是否越界;2、在一个进程中段表只能有一个,页表可以有很多个。3、分段是可见的,分页是不可见的所以段页式管理结构是二维的。4、*这里也可以引入快表机构,用段号和页号作为查询快表的关键字,如果快表命中就只需要访存一次
虚拟内存
背景
传统的内存管理方式具有一次性(作业必须一次性的装入内存,其实每次运行的只是一小部分)和驻留性(作业被装入内存后会一直驻留在内存中直到所有的作业结束)。这两个特性导致了内存空间存储效率极低,所以引入了虚拟内存的概念。
局部性原理
局部性原理分为时间局部性与空间局部性。 时间局部性:程序中的一条指令一旦执行,不久后改指令还可能再次被执行。产生时间局部性的原因是程序中存在大量的循环操作。 空间局部性:一旦程序访问了某个存储单元,在不久后,其附近的存储单元也会被访问。因为指令的顺序通常是顺序存储、顺序执行的。数据的存储也是向量、数组、表等形式
概念
基于局部性原理,在程序装入内存时,只会将程序的一部分装入内存,就可以启动程序。在程序执行过程中如果所需要的信息不在内存中,可以由操纵系统将需要的那一部分数据再调入内存。如果操作系统暂时不适用某些内容,可以将其调到外存上,从而腾出空间供别的作业使用。以上就称为虚拟存储器。
特性
- 多次性:是指作业在运行的过程中不是一次性全部调入内存,而是分成多次调入内存。
- 对换性:是指作业在运行过程中不需要一直存放在内存中,需要的作业从外存换入,不需要的可以暂时换出
- 虚拟性:从逻辑上扩充了内存的容量,使用户看到内存容量很大的程序却很顺利的运行在很小的内存上。
实现
虚拟内存的实现需要建立在离散分配的内存管理方式上 主要实现方式:
- 请求分页存储管理
- 请求分段存储管理
- 请求段页式存储管理
- 硬件支持:
- 一定的内存与外存空间
- 页表机制或者段表机制
- 中断机制,当用户要访问的程序调入内存需要中断
- 地址变换机制,逻辑地址转换成物理地址
请求分页
请求分页管理方式时间里在分页管理方式的基础之上,请求分页系统中只需要将当前需要的一部分页面装入内存中作业就可以正常运行,当访问的页面不在内存中的时候,可以采用换入将外存中的页面换入内存。
请求分页的组成
页表机制
为了发现和处理页面不在内存中的情况,引入来了页表机制。
- 页表机制组成:
- 页号:页的编号
- 内存块号(物理块号):储存物理地址内存中的块号
- 状态位P:用来表示该页是否已经调入内存
- 访问字段A:记录页面指标。记录本页在一段时间内被访问的次数,或者记录本页进入内存多长时间未被访问。共页面置换算法换出页面时访问。
- 修改位M:标记本页在调入内存后有没有被修改过,因为修改过的页面是要重新写入内存的,在选择换出时可以选择这种页面。
- 外存地址:用来表示该页在外存中的地址。
缺页中断机构
每当内存需要访问的页面不在内存中的时候,系统就会产生一个缺页中断,请求操作系统将缺少的页面调入内存。这个时候缺页的进程会发生阻塞,也为它缺少某一资源。
- 缺页中断过程:保护CPU环境、分析中断原因、装入缺页中断处理程序、回复CPU环境
- 与一般中断区别:
- 在指令的执行期间,不是一条指令执行完成后产生中断,它是属于内中断。
- 一条指令的执行过程中可能会发生多次中断。因为进程需要申请缺少的资源
地址变换机构
页面置换算法
页面置换算法主要是决哪一页被换入,哪一页被换出。
最佳置换算法(OPT)
- 思想:最佳置换算法是淘汰以后不会使用的,或者是在长时间内不在访问的页面,以保证获得最低的缺页率。
- 特点:这个算法是很难实现的,由于很难预估那个页面是以后不会访问的。
- 注意:最长时间不访问和以后访问次数最小是两个概念。
先进先出页面置换算法(FIFO)
- 思想:淘汰最早进入内存的页面,就是在内存中驻留最久的页面。
- 特点:这个算法容易实现,但是不符合常理,因为在进程中有的页面会被经常访问到。
- 注意:这个算法可能产生换页次数不减反增的现象,称为Belady现象
最近最久未使用置换算法(LRU)
- 思想:选择最近最长时间没有被访问的页面
- 特点:性能好,但是需要寄存器和栈的硬件支持,
时钟置换算法(CLOCK)
- 简单的时钟置换算法
- 思想:让一个指针循环扫描缓冲区,像时钟转动一样。会给每一页面增加一个附加位,称为使用位。当页面被调入内存的时候和页面被使用后将他的使用位置为1。页面需要替换的时候,指针会扫描每一页的使用位,如果为1,扫描过后置成0;如果为0,就将该页置换出去。如果所有的页面都为1的话,会继续扫描第二遍。
- 特点:这个算法的思想其实和最近最久未使用页面置换算法相似,只是这个实现起来更加的方便。
- 高级时钟置换算法
- 思想:再增加一位,称为修改位(因为被修改过的页面在被替换前一定要重新写回外存,所以这样可以节省时间)。 设使用位为u,修改位为m。会有以下四种情况:
- 步骤:
- 从指针开始的位置开始扫描,第一轮不对任何数据修改,查找(u=0,m=0)的页面换出
- 如果第1步失败,从头开始扫描,查找(u=0,m=1)的换出,如果不是就将每个页面的使用位置成0;
- 如果第2步失败,从头开始扫描,查找(u=0,m=0)的页面换出。如果不是就将页面的修改位改成0;
- 如果第3步失败,从头开始扫描,查找(u=0,m=0)的页面置换出。
页面分配策略
驻留集大小
- 驻留集:操作系统必须决定读取多少页,决定个特定的进程分配几个页框,给每一个进程分配的物理页框的集合就是驻留集
- 注意:
- 分配给一个进程的存储量越大,任何时候驻留在主存中的进程数就越多,可以提高处理机的时间利用率。
- 如果一个进程在主存中的页数过少,尽管局部性原理,页面的错误率也会很高
- 页数过多由于局部性原理,给特定的进程分配更多的主存空间对该进程的错误率没有影响。
抖动
刚刚换出的页面又要换入内存,刚刚换入内存的页面马上又要换出内存。主要原因是某个进程频繁访问的页面数目高于系统给他分配的物理页帧数目。
工作集
- 驻留集:请求分页存储管理中给进程分配的内存块的集合
- 工作集:在某段时间内,进程访问页面的集合
- 根据工作集的大小来确定驻留集的大小(驻留集>=工作集),如果违背可能会出现抖动的现象
三种分配策略
- 固定分配局部置换:每个进程分配一定的物理块,在整个运行过程中如果发生缺页,只能从这些物理块中去换出,然后再换入
- 可变分配全局置换:为每个进程分配一定数目的物理块,操作系统也会保持出一个空闲的物理块队列。当发生缺页的时候,系统会将空闲的物理块取出给这个进程使用。这个方法是最灵活的,也是最容易实现的。
- 可变分配局部置换:操作系统为每一个进程分配一定数目的物理块,当进程缺页的时候只能从该进程的内部的页面选出一页换出,不会影响其他进程。如果经常出现缺页,系统会为该进程再分配一些物理块供他使用,这里体现了动态性。
调入页面的时机
为了确定系统进程的运行时所需要的页面调入内存的时机,会有两个策略预调页策略和请求调页策略,一般的系统中会结合使用。 预调页策略:根据局部性原理,一次调入多个页面肯定比一次调入一个页面更高效,但是一次调入太多的页面会浪费内存使用率。所以先预计哪些页面会使用,就先将其调入。这个方法主要用于进程的首次调入,由程序员和用户完成。 请求调页策略:在运行过程中,如果进程需要哪个页面不在内存中,就会提出请求,系统将页面调入内存。这个方式一次只能调入一页,调入调出会加大I/O开销。
从何处调入页面
请求分页系统的外存其实是分成两个区的,一个文件区(用来存放文件)和对换区(用来存放对换的页面)。对换区通产采用连续分配,文件区采用离散分配,所以对换区的效率更高。 如果系统拥有足够的对换区空间,可以全部从对换区调入内存,以提高调页的速度。这样的话需要在进程运行前将文件区的作业复制到对换区。 如果缺少足够的对换区空间,凡是不会被修改的文件放到文件区,会被修改的页面放到对换区。因为那些不被修改的页面不需要换出。 * UNIX方式:与进程有关的页面放在文件区,对换出来的页面放在对换区。