操作系统之内存管理

一 基本概念

前提:
#1我们知道我们的代码是以可执行的文件保存在磁盘上
#2 多道程序设计模型允许多个程序同时进入内存
#3 每一个进程都有自己独立的地址空间,执行过程中是不能够访问另外的进程的地址空间
#4 进程地址空间需要加载物理内存空间才可以被执行;如果是多道程序设计模型,那就是有多个进程都要进入物理内存。
#5 进程运行前的地址不是物理地址,是逻辑地址;进程运行时候,被加载到内存之后的才是物理地址。
在这里插入图片描述

1.1地址空间(Address Space)

地址空间,就是地址范围或者地址集合,进程地址空间就是一个进程的地址集合。
我们知道进程在没有进入内存之前,进程的地址都是虚拟地址或者逻辑地址,所以进程地址空间也可以理解为进程的逻辑地址空间。只有加载到内存后的进程地址空间才是进程物理地址空间。而且进程地址空间里的地址是连续的唯一的。

然后我们知道内核程序和用户程序运行都是需要加载到内存中执行的,为了将两个隔离,区分为内核进程地址空间和用户进程地址空间。
以Linux32位系统举例子,主存寻址范围是2^32字节,那么就是4G的寻址范围,一般划分为0-3G(3)的用户地址空间和3-4G(1)的内核地址空间
在这里插入图片描述

1.2 地址重定位(Relocation)

地址重定位也被叫做地址转换、地址映射、地址翻译。因为我们的进程在没有加载到内存之前的地址都是虚拟或者逻辑地址,并不是实际的物理地址,进程在加载到内存之后需要根据该进程的首地址进行地址重定位,定位到真正的内存物理地址。
简单的说,就是CPU在执行指令的时候,将进程的逻辑地址转化为内存的物理地址。
静态重定位:当用户程序加载到内存,一次性把所有的逻辑地址转化为物理地址
动态重定位:在进程运行过程中进行地址转换,即逐条指令执行完成地址转换
在这里插入图片描述

1.3 逻辑地址和物理地址

逻辑地址:
用户程序经过编译、汇编之后形成的目标代码,目标代码经常采用相对地址的形式,首地址是0,其余地址相对于首地址进行编址。
CPU读取的指令,指令解析的地址如果是逻辑地址,CPU是从内存获取不到或者获取的数据是有问题的,所以需要进行地址重定位得到真正的物理地址。
物理地址:可以直接从内存对应的地址取到数据的

为什么要区分逻辑地址和物理地址?
第一:多道程序设计模式中,同时可能有多个进程进入内存,所以为了隔离各个进程,为各个进程分配一个地址空间,然后就可以和别的进程隔离开

1.4 操作系统主要是在哪些方面管理内存

1.4.1 内存空间的分配和回收

连续分配:单一连续分配、固定分区分配、动态分区分配
非连续分配:基本分页存储管理、基本分段存储管理、段页式存储管理

1.4.2 内存空间的扩充

覆盖和交换、虚拟存储

1.4.3 地址转换

1.4.4 存储保护

二 物理内存管理

2.1 等长划分(存储单元) 和 不等长

位图:每一个分配单元对应位图中的一位,0表示空闲,1表示占用
空闲区表、已分配区表:表中每一项记录了空闲区或者已分配区的起始地址、长度和标志
空闲块链表:每一个空闲区项用链表结构串起来

2.2 内存分配算法

2.2.1 首次适配算法(First Fit)

每次都是在空闲区表头找到第一个满足进程要求的空闲区

2.2.2 下次适配算法(Next Fit)

从上一次找到的空闲区处接着找进程要求的空闲区,避免每一次都从头开始找起

2.2.3 最佳适配算法(Best Fit)

查找整个空闲区表,找到能够满足空闲区要求的最小的空闲区,避免内存浪费

2.2.4 最差适配算法(Worst Fit)

查找整个空闲区表,找到能够满足空闲区要求的最大的空闲区。找到空闲区,分成两部分:一部分是供进程使用,另一部分形成新的空闲区,如图示:
在这里插入图片描述

在这里插入图片描述

2.3 回收算法

当某一块内存释放以后,前后空闲空间合并,修改内存空闲区表

三 伙伴系统

将内存按2的幂进行划分,组成若干块空闲块链表
查找该链表找到能够满足进程需求的最佳匹配快
#1 将整个可用空间看做一整块2^N
#2 假设进程申请的空间大小为s,如果满足2^(N-1) < s < 2N,则分配整个块,否则将块划分为大小相等的伙伴,大小为2(N-1)
#3 一直划分下去,直到产生大于或者等于s的最小块

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

五 基本的内存管理方案

5.1 整个进程进入内存一片连续的区域

5.1.1 单一连续区

一段时间内,只有一个进程在内存,简单但是利用率低

5.1.2 固定分区

#1把内存空间分为若干个区域,称为分区
#2 每个分区大小可以相同也可以不同
#3 分区大小固定不变
#4 每一个分区只能装载一个进程
在这里插入图片描述

5.1.3 可变分区

#1 根据进程需要,把内存空闲空间分割出一个分区,分配给进程
#2 剩余部分成为新的空余分区
但是这样,很容产生碎片,带来内存的浪费

在这里插入图片描述

5.2 整个进程进入内存一片不连续的区域

5.2.1 页式存储管理方案

5.2.1.1设计思想

第一:用户进程地址空间按照页存储指令和数据
第二:内存空间被动态划分为若干长度相等的区域,称为页框或者物理页或者内存块,每一个页框由页号和页内偏移地址(偏移量)决定
第三:以页为单位进行分配,分配给物理内存的页框,并按照进程需要的页数来分配,进程需要多少就分配多少。逻辑上相邻的页,物理上不一定相邻。

5.2.1.2 什么是页(page),什么是页框(page frame)? 两者有何区别?

页:用户进程地址空间被划分成大小相等的部分,每一个部分称为页,也叫逻辑页,大小一般为4K
页面:是物理内存地址空间也被划分成大小相等的部分,每一个部分称为页框,或者页帧或者物理页或者内存块,一般情况下页框和页大小相等,比如4K
页和页面有何联系和区别
相同点:
第一:都是被划分为成大小相等部分,而且一般情况下大小也相等
第二:一般都是从0开始编号
不同点:
第一:页是逻辑地址空间或者说用户地址空间的概念;页框是物理内存地址空间的概念

5.2.1.3 逻辑地址

逻辑地址由两部分组成:页号和页内偏移地址,如图示:
在这里插入图片描述

假设一个机器字长=存储字长的32位计算机,每一页大小为4K,如图示:
因为大多数的页大小是4K=2^12,即4096字节,如果我们按照字节编址,则是这个页的地址偏移量范围是0-4095,占据低12位,那么剩余的高(32-12)位便是页号或者说主存块标记位。
页并不是存储的基本单位,存储单元一般是1个字节,所以1个页是由多个存储单元组成的。
我们知道,逻辑地址在用户地址空间是连续的,但是在物理地址不一定是连续存放的如图示:
在这里插入图片描述

那么问题来了,CPU读取指令都是按照顺序读取,然后顺序执行指令,除非是跳转之类的指令。如果在物理内存中某一页读取完了之后,下一个页不是当前进程的页,那么程序是肯定不能正确执行的。所以在进程进入主存的时候,有专门的一个组件来记录页和页框的对应关系,这个组件就是页表(page table)。如图示:
在这里插入图片描述

页表:
#1 页表项记录逻辑页号和物理页框号的对应关系
#2 每一个进程都有一个页表,一般也会将页表放入内存
#3 如果页表放入内存,页表起始地址保存在CPU页表基地址寄存器中

5.1.2.5 地址转换

#1 CPU取到逻辑地址,自动将页号和页内地址划分出来
#2用页号查询页表,得到物理内存对应的页框号
#3然后再与页内偏移地址结合获取真正的物理地址

5.1.2.6 基本地址转换机构

基地址变换机构可以借助进程的页表将逻辑地址转化为物理地址,通常会在系统中设置一个页表寄存器(page table register) 存放页表在内存中的起始地址和也表长度。

为什么需要页表寄存器? 我们知道,一个进程一般对应一个页表,页表也是存放在内存中,但是如何知道这个页表在内存中的地址呢? 所以也需要一个专门的物理硬件来存储页表所在的页的起始地址和页表的长度,比如A进程的页表在内存中是0x00000111这位置,页表长度256,所以0x00000111-0x00000367都是存储存储这个页表的信息。
第一:当进程开始执行的时候,PCB中包含了进程的起始逻辑地址获取这个逻辑地址,取出页号和页内偏移量
第二:拿着页号去页表寄存器中获取对应的页框号(起始地址),去物理内存获取对应的页表
第三:根据页表的起始地址(页框号)和长度从物理内存获取对应的页表
第四:根据这个页表的起始地址也是上面页表寄存器中保存的起始地址加上逻辑地中的页内偏移量计算要访问的数据的物理内存地址
第五:根据获取到的物理地址进行数据读写

5.2.1.7 页式存储优缺点

优点:页长是等长的,方便内存分配和管理
缺点:容易产生页碎片,比如分配了5页,但是还多了一个指令,所以必须有要分配6页,就造成这第6个页碎片产生。

5.2.2 段式存储管理方案

5.2.2.1 设计思想

第一:用户进程地址空间按照程序的自身的逻辑关系或者模块划分为若干个程序段,每一个程序段都有一个段名。
第二:内存空间被动态划分为若干长度不相同的区域,称为物理段,每一个物理段由起始地址和长度决定
第三:以段为单位进行内存分配,每段在内存中占用连续的内存空间,但各个段可以不相邻

5.2.2.2 逻辑地址

在这里插入图片描述

5.2.2.3 段表

#1 用户进程地址空间的段和物理内存的段并不一定是连续的,那么如何顺利读取下一条指令呢?和页是存储一样,段式存储也需要一个表,来记录用户进程地址空间的段和物理内存的段的映射关系。
#2 段表项记录段名、段号、段长、首地址信息等
#3 段表和页表一样,也需要存储在内存中;段表的基地址存在段表基地址寄存器
在这里插入图片描述

5.2.2.4 段式存储地址转换

CPU从内存中取到指令,分析出逻辑地址;得到该段段号和段内首地址;然后从段表基地址寄存器中查找该段的物理基地址;

5.2.2.5 段式存储优缺点

优点:按照应用程序模块或者逻辑关系分段,易于编译、管理、修改和保护
缺点:段长各不相同,起点和终点不定,变化很大,给主存分配带来很大麻烦

5.2.3 段页式存储管理方案

5.2.3.1 设计思想

用户进程地址空间按照程序逻辑关系或者模块划分成若干段;每一个段再进行分页,进入主存仍然以页为基本单位。
逻辑地址由段地址、页地址和页偏移量三部分组成
用段表和页表进行两级定位管理

5.2.3.2 逻辑地址

在这里插入图片描述

我们需要知道从哪一个段的哪一个页的哪一个地址读取。

5.2.3.3 段表和页表

段页式存储需要段表和页表来记录一些映射关系,段表项主要是记录页表再内存的页框号和页表的长度,页表项主要是记录逻辑页号和物理页框之间的映射关系。
在这里插入图片描述

#1进程上CPU运行之前,会从进程控制块(PCB)中读取段表的起始地址和段表的长度,然后放入段表寄存器之中。

#2 从主存中获取指令,解析形式地址,获取逻辑地址中的段号、页号和页内偏移量
#3 判断段号是否越界,如果越界则中断;没有则继续处理
#4 根据段号获取段表在内存中的起始地址和长度获取内存中的段表数据
#5 根据段表的页表在内存中页框号,则获取这个页表
#6 根据页表中页号对应的页框,确定物理页框号
#7 根据页内偏移量地址,计算物理内存地址
#8 根据物理地址访问内存存储单元

5.2.3.4 页表和快表(TLB)
5.2.3.4.1 页表和二级页表

我们知道,一个进程对应一个页表,假设每一个页大小是4K,每一个页表项是4字节,那么一个页上的页表最多存放1024个页表项,那如果现在进程的页数量超过1024个,那一个页就无法全部存储所有的页表项,也就是说一个进程的所有页需要多个页表来存储。这些存放页表的页可能就是不连续的,则需要引入地址索引目录,即页表目录(page directory), 一个进程对应一个页表目录,有时候也叫顶级页表。
如图示:
在这里插入图片描述

如果页表层级发生了变化,那么逻辑地址或者虚拟地址(VA)的结构也需要发生变化,因为CPU需要知道到底是在页表目录哪一项,在二级页表哪一项。假设,4G内存,每一个页块大小为4K,每一个页表项的大小为4B,那么我们可以得到以下结果:
#1 我们一共有(2^32/1024/4) = 2^20页
#2 每一个页表项4字节,那么每一个页可以存放(4*1024/4)=1024个页表项
#3 那么现在的页块最多可以分为(2^20/1024)=1024个页
#4 那么这1024个页所在的页块也是有地址的或者页框号,所以我们需要一个页表目录记录着1024个页表的页框号
#5 那既然如此,我们在访问的时候既需要知道页表目录(一级页表的页号),也需要二级页表的页号
所以,因为我们这里页块大小4K,即212需要占用12位,二级页表数量为1024,即210,也就是需要10位,那么剩下的10位表示页表目录。虚拟地址结构如下:
在这里插入图片描述

那么页目录表也是需要占用内存的,也是有对应的物理内存块的块号,那这个也表目录的起始地址就是放在进程的PCB中。

5.2.3.4.2 快表(TLB)

我们知道,现在很多内存都是二级或者多级页表机制,那就意味着,要进行一个逻辑地址的转换,需要从页表寄存器获取也表物理地址,访问物理地址,获取页表,通过页内偏移量获取要转换的页号对应的页框号,然后在次访问物理内存获取物理存储单元的数据,总共需要2-3次的访存,如果这样的访问很频繁,所以引入了快表机制。

什么是快表(TLB)?
TLB: 是Translation Lookaside Buffer的缩写,快表是一种寄存器和内存之间一种高速缓冲存储器,速度比内存快的多,用来存放当前访问的若干页表项,用于加速逻辑地址到物理地址的转换,而与此对应,内存中页表称为慢表。说白了,就是根据局部性原理,将短时间内可能会被继续访问的页表存放在高速缓冲存储器中缓存起来,CPU访问的快表的速度快于访问内存,所以可以加速地址转换。

引入快表机制后的地址转换流程:
在这里插入图片描述

第一:CPU获取逻辑地址,查询快表
第二:快表命中页号,直接根据页框号去物理内存,结合页内偏移量获取物理地址
第三:快表如果没有命中,则查询页表寄存器,根据页号获取页表起始地址
第四:根据起始地址获取物理内存中这个页的页表数据
第五:然后根据

六 覆盖和交换技术

如果我们有很多进程,需要进入内存,但是毕竟内存容量有限制,内存不足的时候,该怎么呢? 即解决在较小的内存空间里运行较大的进程呢?

6.1 覆盖(overlaying)

#1 将程序划分成多个独立的模块,A,B,C,D,E等
#2 确定各个模块之间的依赖的关系
#3 将都需要依赖的模块常驻内存
#4 将没有依赖关系的同级模块划分为一个组,从组内找到需要内存最多的模块,然后根据这个模块内存大小在内存分配一个组内模块共享区域,即覆盖区
如图所示:
在这里插入图片描述

6.2 交换技术(swapping)

内存空间紧张的时候,系统将内存中某些进程暂时换出到磁盘,把磁盘某些进程换入到内存,占据前者所占用的内存区域

进程哪些内容要交换到磁盘?
运行时创建或者修改的内容:栈和堆

在磁盘的什么位置保存被换出的进程?
交换区:系统会指定一块的特殊的磁盘区域作为交换空间(swap space),包含连续的磁道,操作系统可以使用底层的磁盘读写操作对其高效访问。有的系统对这个区域叫做页文件(Page File).

交换的时机?
当进程很少使用或者内存不够就需要换出去

如何选择被换出的进程?
处于等待I/O状态的进程不能被交换到磁盘

当多个进程执行的时候,因为某些进程的运行导致部分进程运行时候内存不足,不讨论执行单个程序内存不足的情况
#1 将暂时不能运行的程序放到外存
#2 换入换出的单位是一个进程,把外存的某一个进程整个地址空间读入到内存;或者把整个进程的地址空间存储到外存

七 虚拟存储技术

7.1 传统存储管理特征和缺点

传统存储管理指的是对内存的分配和回收,体现为连续和非连续两个种类,连续的包括单一连续分配、固定分区分配和动态分区分配;非连续主要是页式管理、段式管理、段页式管理。
特点:
一次性:进程必须一次性全部装入内存后才能开始运行。造成2个问题:
第一:进程很大的时候,不能全部装入,导致大进程无法运行。比如2G的内存但是进程却需要3G的内存。
第二:当大量进程要求运行时候,由于内存无法容纳所有进程,因此只有少量进程运行,导致多道程序并发度下降。

驻留性:一旦进程被装入内存,只要进程没有结束,就会一直驻留内存,直到进程运行结束。所以很有可能导致有一些用不到的数据长期驻留内存,导致内存利用率很低。

7.2 局部性原理

首先我们知道,CPU在访问存储器的时候,无论是取指令还是存取数据,所访问的存储单元一般都是在一块较小区域的内存中;或者说程序在一段时间内有可能多次访问一个数据块。

7.2.1 时间局部性原理

如果CPU执行了程序中某条指令很可能短期内会再次执行;如果某个数据块被访问过,很可能短期内再次被访问。

7.2.2 空间局部性原理

一旦某个存储单元被访问,很有可能不久后,其附近相邻的存储单元也会被访问(因为很多数据是连续存放的),比如循环变量数组。

7.3 虚拟内存的定义和特征

7.3.1 什么是虚拟存储技术?

根据局部性原理,在程序装入到内存的时候,可以将程序即将可能会用到的部分从磁盘装入到内存,暂时不用的换出到磁盘。当要执行的指令和访问的数据不在内存的时候,由操作系统自动将他们从磁盘调入到内存。

7.3.2 什么是虚拟内存?什么是虚拟地址空间和虚拟地址

在操作系统管理下,把内存和磁盘有机结合起来使用,通过虚拟内存技术实现的内存扩张后的内存,从而得到一个很大的内存,就是虚拟内存。
虚拟内存的最大容量是由计算机地址结构,即CPU寻址范围确定的,虚拟内存的时机容量大小 = Min(内存和外存容量和,CPU寻址范围)
比如机器字长为32位,按字节编址,内存大小为2G,外存大小128G,则虚拟内存最大容量为 2^32 = 4G,实际容量min(2+128,4)

虚拟地址空间:分配给进程的虚拟内存
虚拟地址:在虚拟内存中指令或者数据的位置,该位置可以被访问

7.3.3 虚拟页式存储系统

第一:进程开始运行前,不是装入全部页面,而是装入一个或者0个页面
第二:之后,根据进程运行的需要,动态装入其他页面
第三:当内存空间已满,而又需要装入新页面时候,根据某种算法置换内存中某个页面,以便于装入新的页面

具体来讲,有两种方式:请求调页(demanding paging)和预先调页(pre-paging)

7.4 页表和页表项

页表项应该保存页框号(物理内存的内存块块号)、有效位(是否已经载入内存)、访问位、修改位以及保护位
页框号:物理内存的内存块块号
有效位:表示该页在内存还是磁盘
访问位:引用位
修改位:此页在内存中是否发生修改
保护位:可读、可写
32位地址空间的页表规模?如果页面大小是4K,页表项为4字节,总共需要(2^32/1024/4 = 220)个页,那么也就是意味着需要220页表项,每一个页表项需要4字节,那么也就是需要2^20 * 4 字节 = 4MB大小,页表也是需要放在内存中,那么页表需要占用多少页呢? 4M(4096K)/4K = 1024,也就是说页表需要占用1024个。

7.5 请求分页管理方式

7.5.1 页表机制

我们知道,与基本分页管理相比,请求分页管理中为了实现请求调页,操作系统需要知道每一个页面是否已经调入内存;如果还没有调入,那么也需要知道该页面在磁盘上的存储位置。

当内存不足的时候操作系统也需要通过某些指标来决定到底换出哪一个页面。有的页面没有被修改过,那就没有必要重新写回磁盘,有的页面已经修改改过,则需要将磁盘中对应的块数据覆盖掉。所以操作系统也需要记录各个页面是否被修改过。
如图示:
在这里插入图片描述

页表项应该保存页框号(物理内存的内存块块号)、有效位(是否已经载入内存)、访问位、修改位以及保护位
页框号:物理内存的内存块块号
有效位:表示该页在内存还是磁盘
访问位:访问的次数或者时间
修改位:此页在内存中是否发生修改

7.5.2 缺页中断机构

什么是缺页? 什么是缺页中断?
缺页:当要访问的虚拟地址中的页号对应的页表,那么其有效位0,表示这个页没有在内存,还在磁盘上,这就是缺页(page default)
缺页中断:当发生缺页的时候,需要中断进程,保存现场,调用中断服务程序,从磁盘读取缺的页到内存
在这里插入图片描述

#1 假设要访问一个虚拟地址,首先去检查页表
#2 根据页号获取到对应的有效位或者状态位,判断这个页是否已经在内存还是磁盘
#3 如果在磁盘,则产生一个缺页中断指令,由操作系统的中断处理程序处理中断
#4 此时缺页的进程阻塞,放入阻塞队列,调页完成后将其唤醒,放入到就绪队列,等待被调度
#5 将磁盘的页调入内存还需要判断内存是否有空闲的块,如果有则为进程分配一个空闲的块,将所缺的页面装入该块,并修改页表中对应的状态位或者有效位
#6 如果没有空闲的内存块,则还需要有页面置换算法选择一个页面淘汰,淘汰的时候,如果这个页面已经发生了脏页,即被修改过,还需要回写磁盘;如果没有修改过,则直接替换即可

7.5.3 地址转换机构

7.6 页面置换算法

7.6.1 最佳置换算法

每一次选择淘汰的页是以后永不使用或者很长时间不再被访问的页面,这样可以保证最低的缺页率

7.6.2 先进先出置换算法(FIFO)

每次淘汰的页面是最早进入内存的页面,调入内存的页面按照先后顺序拍成一个队列即可实现。

7.6.3 近期最少使用(LRU)

每次淘汰的页面是最久未使用的页面,可以设置页表项中访问字段为上次访问的时间字段,距离现在最久的页就是最久未使用的。

  • 9
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫言静好、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值