mirror of
https://github.com/Didnelpsun/CS408.git
synced 2026-06-16 15:07:38 +08:00
Update 2-memory-management.md
This commit is contained in:
@@ -34,24 +34,29 @@
|
||||
|
||||
只有只读区域才能共享,这种代码就是**可重入代码**(纯代码)允许多个进程同时访问但是不允许任何进程修改的代码。可以复制副本后自己修改。
|
||||
|
||||
减少了对程序段的调入/调出,因此减少了对换数量。
|
||||
|
||||
### 程序载入过程
|
||||
|
||||
+ 程序运行过程:
|
||||
1. 编译:由编译程序将用户源代码编译成若干个目标模块(编译就是把高级语言翻译为机器语言)。
|
||||
2. 链接:由链接程序将编译后形成的一组目标模块,以及所需库函数链接在一起,形成一个完整的装入模块。
|
||||
3. 装入(装载):由装入程序将装入模块装入内存运行。
|
||||
4. 执行。
|
||||
|
||||
#### 链接
|
||||
|
||||
将独立的逻辑地址合并为完整的逻辑地址。
|
||||
将独立的逻辑地址合并为完整的逻辑地址:
|
||||
|
||||
1. 静态链接:在程序运行之前,先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块),之后不再拆开。
|
||||
2. 装入时动态链接:将各目标模块装入内存时,边装入边链接的链接方式。
|
||||
3. 运行时动态链接:在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享。
|
||||
1. 静态链接:在**编程**时先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块),之后不再拆开。
|
||||
2. 装入时动态链接:将各目标模块**装入**内存时,边装入边链接的链接方式。
|
||||
3. 运行时动态链接:在程序**执行**中需要该目标模块时,才对它进行链接,通过硬件转换机制进行转化。其优点是便于修改和更新,便于实现对目标模块的共享。
|
||||
|
||||
动态链接与程序逻辑结构有关,所以段式管理有利于动态链接。
|
||||
|
||||
#### 装入
|
||||
|
||||
将逻辑地址转换为物理地址):
|
||||
将逻辑地址转换为物理地址:
|
||||
|
||||
1. 绝对装入(单道程序阶段、未产生操作系统):在编译时,如果知道程序将放到内存中的哪个位置,编译程序将产生绝对地址的目标代码。装入程序按照装入模块中的地址,将程序和数据装入内存。
|
||||
+ 绝对装入只适用于单道程序环境。
|
||||
@@ -125,7 +130,7 @@
|
||||
+ 若回收区的后面或前面有一个相邻的空闲分区则合并为一个。
|
||||
+ 若回收区的后面和前面都有一个相邻的空闲分区则合并为一个。
|
||||
+ 若回收区的后面或前面都没有一个相邻的空闲分区,则增加一个表项。
|
||||
+ 动态分区分配会导致外部碎片,可用通过**紧凑**(拼凑)技术来移动进程位置合并空闲空间。
|
||||
+ 动态分区分配会导致外部碎片,可用通过**紧凑**(拼接)技术来移动进程位置合并空闲空间。
|
||||
|
||||
### 动态分区分配算法
|
||||
|
||||
@@ -148,7 +153,7 @@
|
||||
+ 如何实现:空闲分区按**容量**递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。当分配完后需要重新调整空闲分区链(或空闲分区表)。
|
||||
+ 优点:容易保存大分区。
|
||||
+ 缺点:
|
||||
+ 每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此这种方法会产生很多的外部碎片且很难查找回收。
|
||||
+ 每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此这种方法会产生**最多**的外部碎片且很难查找回收。
|
||||
+ 算法开销大,每次分区外需要对分区队列进程重新排序。
|
||||
|
||||
最坏适应算法($Worst\,Fit$)或最大适应算法($Largest\,Fit$):
|
||||
@@ -183,6 +188,7 @@
|
||||
+ 各个页面不必连续存放,也不必按先后顺序来,可以放到不相邻的各个页框中。
|
||||
+ 为了方便计算页号、页内偏移量、页面大小一般设为$2$的整数幂。
|
||||
+ 如果每个页面大小为$2^kB$,用二进制数表示逻辑地址,则末尾$k$位即为页内偏移量,其余部外就是页号。
|
||||
+ 由于是对程序根据内存大小进行分页,所以只对硬件和操作系统是可见的,对于连接装配程序、编译系统、用户都是透明的。
|
||||
|
||||
#### 地址结构
|
||||
|
||||
@@ -193,10 +199,11 @@
|
||||
#### 页表
|
||||
|
||||
+ 因为允许将进程的各个页离散地存储在内存不同的物理块中,但系统应能保证进程的正确运行,即能在内存中找到每个页面所对应的物理块,所以为了能知道进程的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表,其中页表大小也与页面一样被页框约束。
|
||||
+ 一个进程对应一张页表。
|
||||
+ 一个进程对应一张页表和一个逻辑地址。
|
||||
+ 进程的每一页对应一个页表项。
|
||||
+ 每个页表项由“页号”和“块号”组成。完成从逻辑的页号向物理的块号的映射。
|
||||
+ 页表记录进程页面和实际存放的内存块之间的对应关系。
|
||||
+ 当进程未执行,则页表始址和页表长度在其$PCB$中,执行时将其装入$PTR$中从而进驻内存。
|
||||
|
||||
假设某系统物理内存大小为$4GB$,页面大小为$4KB$,则每个页表项至少应该为多少字节?
|
||||
|
||||
@@ -210,7 +217,7 @@
|
||||
|
||||
#### 页式基本地址变换机构
|
||||
|
||||
+ 可用借助页表进行转换,通常会在系统中设置一个页表寄存器($PTR$),存放页表在内存中的起始地址$F$和页表长度$M$(即这个进程里有多少页)。进程未执行时,页表的始址和页表长度放在进程控制块($PCB$)中,当进程被调度时,操作系统内核会把它们放到页表寄存器中。
|
||||
+ 可用借助页表进行转换,通常会在系统中设置**一个**页表寄存器($PTR$),存放页表在内存中的起始地址$F$和页表长度$M$(即这个进程里有多少页)。进程未执行时,页表的始址和页表长度放在进程控制块($PCB$)中,当进程被调度时,操作系统内核会把它们放到页表寄存器中。
|
||||
+ 在页式存储管理的系统中时,只用确定页面大小和逻辑结构就能得到物理地址。
|
||||
|
||||
基本地址变换机构需要先查询页表,再查询内存两次操作:
|
||||
@@ -223,7 +230,7 @@
|
||||
+ 页面大小指的是一个页面占多大的存储空间。
|
||||
4. 最后物理地址$E$=内存块号$B$×页面大小$L$+页内偏移量$W$(如果内存块号和业内偏移量用二进制表示,则直接拼接起来就是最终物理地址了)。
|
||||
|
||||
#### 具有快表的地址变换机构
|
||||
#### 快表地址变换机构
|
||||
|
||||
+ 时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问(因为程序中存在大量的循环)。
|
||||
+ 空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问(因为很多数据在内存中都是连续存放的)。
|
||||
@@ -247,7 +254,7 @@
|
||||
地址转换过程:
|
||||
|
||||
1. 按照地址结构将逻辑地址拆分成三部分。
|
||||
2. 从$PCB$中读出页目录表始址,再根据一级页号查页目录表,找到下一级页表在内存中的存放位置。
|
||||
2. 从$PCB$中读出页目录表始址,再根据一级页号查页目录表,找到下一级页表在内存中的存放位置(物理地址)。
|
||||
3. 根据二级页号查表,找到最终想访问的内存块号。
|
||||
4. 结合页内偏移量得到物理地址。
|
||||
|
||||
@@ -259,6 +266,8 @@
|
||||
2. 第二次访存:访问内存中的二级页表。
|
||||
3. 第三次访存:访问目标内存单元。
|
||||
|
||||
多级页表会使用页表基址寄存器($PTBR$)来存储页表基址,此时$PTBR$会存储当前进程的一级页表的物理地址。
|
||||
|
||||
### 基本分段存储管理
|
||||
|
||||
#### 分段
|
||||
@@ -272,7 +281,13 @@
|
||||
1. 每个段对应一个段表项,其中记录了段号、该段在内存中的起始位置(又称“基址”)和段的长度。
|
||||
2. 各个段表项的长度是相同的。
|
||||
|
||||
某系统按字节寻址,采用分段存储管理,逻辑地址结构为(段号$16$位,段内地址$16$位),因此用$16$位即可表示最大段长。物理内存大小为$4GB$(可用$32$位表示整个物理内存地址空间)。因此,可以让每个段表项占$16+32=48$位,即$6B$。由于段表项长度相同,因此段号可以是隐含的,不占存储空间。若段表存放的起始地址为$R$,则K号段对应的段表项存放的地址为$R+K\times6$,段号并不占内存空间。
|
||||
某系统按字节寻址,采用分段存储管理,逻辑地址结构为(段号$16$位,段内地址$16$位),因此用$16$位即可表示最大段长。物理内存大小为$4GB$(可用$32$位表示整个物理内存地址空间)。因此,可以让每个段表项占$16+32=48$位,即$6B$。由于段表项长度相同,因此段号可以是隐含的,不占存储空间。若段表存放的起始地址为$R$,则$K$号段对应的段表项存放的地址为$R+K\times6$,段号并不占内存空间。
|
||||
|
||||
分段的优点:
|
||||
|
||||
+ 方便共享和保护。
|
||||
+ 方便编程。
|
||||
+ 方便动态链接和增长。
|
||||
|
||||
#### 段式存储地址变换过程
|
||||
|
||||
@@ -294,6 +309,8 @@
|
||||
+ 不能被修改的代码称为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的。可修改的代码是不能共享的(比如,有一个代码段中有很多变量,各进程并发地同时访问可能造成数据不一致)。
|
||||
+ 分页时页面不是按逻辑模块划分的。这就很难实现共享。
|
||||
|
||||
在计算地址时,段式存储的基址往往以十进制的位置给出,计算的结果需要与偏移量相加,最后转换为十六进制,页式存储的基址往往以十六进制的位置给出,计算的结果需要与偏移量直接拼接。
|
||||
|
||||
### 段页式存储管理
|
||||
|
||||
|优点|缺点
|
||||
@@ -340,9 +357,11 @@
|
||||
|
||||
前两种技术不是重点。
|
||||
|
||||
覆盖和交换技术本质是通过不断换入换出数据,以时间换空间,所以对外存对换区的管理应该以提高交换速度减少交换时间为目标。
|
||||
|
||||
#### 覆盖技术
|
||||
|
||||
覆盖技术在同一个程序或进程中执行。
|
||||
覆盖技术在同一个程序或进程中执行。可以使用在单一连续分配和固定分区分配的方式。
|
||||
|
||||
+ 覆盖技术的思想:将程序分为多个段(多个模块)。常用的段常驻内存,不常用的段在需要时调入内存。
|
||||
+ 内存中分为一个“固定区”和若干个“覆盖区”。
|
||||
@@ -380,25 +399,31 @@
|
||||
2. 当大量作业要求运行时,由于内存无法容纳所有作业,因此只有少量作业能运行,导致多道程序并发度下降。
|
||||
+ 驻留性:一旦作业被装入内存,就会一直驻留在内存中,直至作业运行结束。事实上,在一个时间段内,只需要访问作业的一小部分数据即可正常运行,这就导致了内存中会驻留大量的、暂时用不到的数据,浪费了宝贵的内存资源。
|
||||
|
||||
高速缓冲技术的思想:将近期会频繁访问到的数据放到更高速的存储器中,暂时用不到的数据放在更低速存储器中。
|
||||
高速缓冲技术的思想:将近期会频繁访问到的数据放到更高速的存储器中,暂时用不到的数据放在更低速存储器中。所以虚拟存储器技术不具备一次性和驻留性。
|
||||
|
||||
### 虚拟内存的基本概念
|
||||
|
||||
#### 虚拟内存定义
|
||||
|
||||
+ 基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。
|
||||
+ 在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
|
||||
+ 若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
|
||||
+ 在操作系统的管理下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存。
|
||||
|
||||
#### 虚拟内存容量
|
||||
|
||||
+ 虚拟内存的最大容量是由计算机的**地址结构**($CPU$寻址范围)确定的。
|
||||
+ 虚拟内存的实际容量=$\min$(内存和外存容量之和,$CPU$寻址范围)。
|
||||
+ 某计算机地址结构为$32$位,按字节编址,内存大小为$512MB$,外存大小为$2GB$。则虚拟内存的最大容量为$2^{32}B=4GB$,而实际内存是$2GB+512MB$。
|
||||
|
||||
虚拟内存特征:
|
||||
#### 虚拟内存特征
|
||||
|
||||
+ 多次性:无需在作业运行时一次性全部装入内存,而是允许被分成多次调入内存。
|
||||
+ 对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入、换出。
|
||||
+ 虚拟性:从逻辑上扩充可内存的容量,使用户看到的内存容量,远大于实际的容量。
|
||||
|
||||
#### 虚拟内存种类
|
||||
|
||||
虚拟内存实现需要基于离散分配的内存管理方式基础上。所以根据传统的非连续分配存储管理,可以将虚拟存储的实现分为:
|
||||
|
||||
+ 请求分页存储管理。
|
||||
@@ -420,8 +445,8 @@
|
||||
|
||||
#### 页表机制
|
||||
|
||||
+ 与基本分页管理相比,请求分页管理中,为了实现“请求调页”,操作系统需要知道每个页面是否已经调入内存:如果还没调入,那么也需要知道该页面在外存中存放的位置。
|
||||
+ 当内存空间不够时,要实现“页面置换”,操作系统需要通过某些指标来决定到底换出哪个页面:有的页面没有被修改过,就不用再浪费时间写回外存。有的页面修改过,就需要将外存中的旧数据覆盖,因此,操作系统也需要记录各个页面是否被修改的信息。
|
||||
+ 与基本分页管理相比,请求分页管理中,为了实现“请求调页”,操作系统需要知道每个页面是否已经调入内存;如果还没调入,那么也需要知道该页面在外存中存放的位置。
|
||||
+ 当内存空间不够时,要实现“页面置换”,操作系统需要通过某些指标来决定到底换出哪个页面。有的页面没有被修改过,就不用再浪费时间写回外存;有的页面修改过,就需要将外存中的旧数据覆盖。因此,操作系统也需要记录各个页面是否被修改的信息。
|
||||
|
||||
其中基本分页存储管理的页表项分为隐藏的页号与内存块号,所以请求分页管理存储的页表项分为:
|
||||
|
||||
@@ -446,7 +471,7 @@
|
||||
|
||||
与普通页表的地址变换机构不同的是,增加了请求调页、页面置换和请求修改内容三个部分。
|
||||
|
||||
1. 要算出逻辑地址$A$对应的页号$P$与页内偏移量$W$。页号$P$=逻辑地址$A$÷页面长度$L$(取除法的整数部分)。页内偏移量$W$=$A$逻辑地址%页面长度$L$(取除法的余数部分)。
|
||||
1. 要算出逻辑地址$A$对应的页号$P$与页内偏移量$W$。页号$P$=逻辑地址$A\div$页面长度$L$(取除法的整数部分)。页内偏移量$W$=$A$逻辑地址\%页面长度$L$(取除法的余数部分)。
|
||||
2. 检测页号$P$是否越界。如果页号$P$大于等于页表长度$M$,则内中断(因为页号从$0$开始,页表长度至少为$1$,从而$P=M$页会越界)。
|
||||
3. 在快表中查找对应页号,如果找到匹配的页号(即命中),说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表命中,则访问某个逻辑地址仅需一次访存即可。
|
||||
4. 快表中有的页面一定是在内存中的。若某个页面被换出外存,则快表中的相应表项也要删除,否则可能访问错误的页面。
|
||||
@@ -520,27 +545,29 @@
|
||||
|
||||
#### 时钟置换算法
|
||||
|
||||
即$CLOCK$算法。是一种性能和开销较均衡的算法,又称最近未用算法$NRU$。
|
||||
即$CLOCK$算法。是一种性能和开销较均衡的算法,又称最近未用算法$NRU$。即对$LRU$的简化,只用管最近未使用即可,不用管最久。
|
||||
|
||||
实现方法:
|
||||
|
||||
1. 为每个页面设置一个访问位:访问位为$1$,表示最近访问过;访问位为$0$,表示最近没访问过。
|
||||
1. 为每个页面设置一个访问位,初始化全部为$1$。访问位为$1$,表示最近访问过;访问位为$0$,表示最近没访问过。
|
||||
2. 再将内存中的页面都通过链接指针链接成一个循环队列。
|
||||
3. 当某页被访问时,其访问位置为$1$。当需要淘汰一个页面时,只需检查页的访问位。如果是$0$,就选择该页换出。
|
||||
4. 如果是$1$,则将它置为$0$,暂不换出,继续检查下一个页面,若第一轮扫描中所有页面都是$1$,则将这些页面的访问位依次置为$0$后,再进行第二轮扫描。
|
||||
5. 第二轮扫描中一定会有访问位为$0$的页面,因此简单的$CLOCK$算法选择一个淘汰页面,最多会经过两轮扫描。
|
||||
3. 当某页被访问时,其访问位置设为$1$。
|
||||
4. 当内存满需要淘汰一个页面时,只需检查页的访问位。
|
||||
5. 如果是$0$,表示进入内存后一直没有访问过,就选择该页换出。
|
||||
6. 如果是$1$,则将它置为$0$,暂不换出,继续检查下一个页面,若第一轮扫描中所有页面都是$1$,则将这些页面的访问位依次置为$0$后,再进行第二轮扫描。
|
||||
7. 第二轮扫描中一定会有访问位为$0$的页面,因此简单的$CLOCK$算法选择一个淘汰页面,最多会经过两轮扫描。
|
||||
|
||||
值得注意的是:访问和置换是不同的,扫描指针用于置换,只有缺页中断才会发生指向的变化,而访问和修改数据是另一个指针,会随着访问而不断移动,且是访问指针会影响访问位而不是扫描指针。
|
||||
|
||||
假设系统为某进程分配了五个内存块,并考虑到有以下页面号引用串:$1,3,4,2,5,6,3,4,7$。
|
||||
|
||||
首先第一步将$1,3,4,2,5$链接成为循环队列,代表置入内存的内存块,此时访问位全部为$1$,表示全部被访问并置入内存。
|
||||
|
||||
然后访问到$6$,需要置换出一个页面,所以循环访问$1,3,4,2,5$,将访问位全部改为$0$。
|
||||
|
||||
第二轮循环开始,第一个的$1$访问位为$0$,则将$1$换出,$6$换入,访问位置为$1$,变为$6,3,4,2,5$,扫描指针指向$3$。
|
||||
|
||||
接着访问$3$和$4$,将访问位都置为$1$,然后是$7$,因为$7$不在内存中,从$3$开始扫描,$3$、$4$访问位为$1$,而$2$访问位为$0$,所以$7$换入$2$换出,$7$的访问位置为$1$,变为$6,3,4,7,5$,扫描指针指向$5$。
|
||||
+ 首先第一步,由于有五个内存块,所以前五个页面全部正常进入,将$1,3,4,2,5$链接成为循环队列,代表置入内存的内存块,此时访问位全部为$1$,表示全部被访问并置入内存。
|
||||
+ 然后访问到$6$,需要置换出一个页面,所以循环访问块链,由于都访问过,所以扫描指针依次扫描$1,3,4,2,5$,将访问位全部改为$0$。
|
||||
+ 第二轮循环开始,扫描指针指向第一个的$1$访问位为$0$,则将$1$换出,$6$换入,访问位置为$1$,变为$6,3,4,2,5$,扫描指针指向$3$。
|
||||
+ 接着访问指针转动,访问$3$和$4$,由于都在链中,所以将其访问位都置为$1$。
|
||||
+ 访问指针指向是$7$,因为$7$不在内存中,需要换出页面。
|
||||
+ 扫描指针从上次的$3$开始扫描,$3$、$4$访问位为$1$,而$2$访问位为$0$,所以$7$换入$2$换出,$7$的访问位置为$1$,变为$6,3,4,7,5$,扫描指针指向$5$。
|
||||
+ 访问结束。
|
||||
|
||||
#### 改进型时钟置换算法
|
||||
|
||||
@@ -554,7 +581,7 @@
|
||||
|
||||
算法规则:
|
||||
|
||||
1. 将所有可能被置换的页面排成一个循环队列。初始的(访问位,修改位)可能是任何的$10$组合,这和简单时钟算法初始为全$0$不同。
|
||||
1. 将所有可能被置换的页面排成一个循环队列。初始的(访问位,修改位)可能是任何的$10$组合,这和简单时钟算法初始为全$0$或全$1$不同。
|
||||
2. 第一轮(没访问没修改):从当前位置开始扫描到第一个$(0,0)$的帧用于替换。本轮扫描不修改任何标志位。
|
||||
3. 第二轮(没访问有修改):若第一轮扫描失败,则重新扫描,查找第一个$(0,1)$的帧用于替换。本轮将所有扫描过的帧访问位设为$0$。
|
||||
4. 第三轮(有访问没修改):若第二轮扫描失败,则重新扫描,查找第一个$(0,0)$的帧用于替换。本轮扫描不修改任何标志位。
|
||||
|
||||
Reference in New Issue
Block a user