写在前面: 这一章的内容主要是讲解存储的管理,其中包括以下几个方面
- 程序的装入与链接
- 连续分配方法
- 离散分配方法
- 分页式
- 分段式
- 段页式
- 虚存技术
- 分页实现
- 分段实现
- 页面置换算法
- 共享与保护的实现
一、程序的装入与链接
总所周知的,一个源代码如何生成一个可执行文件,需要经过如下步骤:
- 编译,最后生成
.obj
文件 - 链接,将目标文件与库函数链接在一起,形成一个装入模块
- 装入,将装入模块放入内存当中执行
1.1 程序的装入
1.绝对装入方式:
装入模块就用实际地址,装入预定的位置,但是可想而知,只能用于单道处理。
2.可重定位方式:
装入模块为相对地址,由可重定位的程序在装入时再修改为绝对地址,适合于多道处理。而且这种方式在修改为绝对地址之后就不再发生变化,称为静态重定位。
3.运行时动态装入方式:
先把程序装入内存中,等程序运行的适合,再进行地址的转化,这种需要特定的硬件来支持。
1.2 程序的链接
1.静态链接:
直接把所有的模块装在一起,对相对地址进行修改,同时变换符号调用,对于相同的模块会重复链接。
- 简单
- 但是不易共享
2.装入时动态链接:
总之就是用一个共享区。在windows
中,根据输入库将DLL
输出函数链接到应用中,可直接调用。个人理解就是在一个项目中,输入输出函数是只链接一次的。
- 便于修改与更新
- 便于对目标模块的共享
3.运行时动态链接:
如果在运行之前,就把所有的都链接起来装入,就会造成浪费,因此是需要哪一个部分,在运行过程中就链接哪一个部分。在Windows
中,需要有如下代码:
hLibrary=LoadLibrary(“c:\\test\\debug\\dbdll.dll”);
CallProc=(ALLPROC)GetProcAddress(hLibrary,“choose”);
res=(char *)(*CallProc)(tt3);
举例:一个程序如果需要100DLL
,那么在运行过程中,不可能一次性都需要这100
。
- 节省内存
- 时间会变慢
- 所以说,具体问题具体分析。如果内存大,就装入时动态;如果内存小,速度快,就运行时动态链接。
动态链接的优点:
- 节省内存
DLL
中如果函数改变,是不需要重新编译链接的- 不同开发工具可调用相同的
DLL
- 用运行时动态链接可以使得本次不用的函数不链接
二、连续分配存储管理方式
2.1 单一分配连续
将内存分为两个区,用户区与系统区,程序可在用户区上跑。
- 简单
- 内存利用率低
CPU
利用率低,需要等待I/O
2.2 固定分区分配
将内存分成大小不定的若干分区,OS
占一个区,剩下的每个作业可以占一个区。同时系统需要设定一个分区表,分配时从表中找一个符合的分配。
2.3 动态分区分配
为了使分配的大小与作业的大小相同,将分配后剩下的内存组织起来,进行分配。
2.3.1 首次适应算法FF
从分区表的首部依次查找满足的分区,然后分配给作业。
- 先利用低地址,可以省下高地址
- 低地址留下碎片,而且每次找低地址,会查找变慢
2.3.2 循环首次适应算法
在FF
的基础上,从下一个分区往下找。但是如果找到一个大空间之后,来了一个小作业,就会被占下一个更大的空间。
- 使内存中空闲区域均匀,节省查找时间
- 会缺乏空闲的大空间
2.3.3 最佳适应算法
前两种,是按照低地址到高地址排序,而这个算法是按照空闲区的大小由小到大排序。要注意这个区别!
- 会留下许多小碎片
2.3.4 分配流程
2.3.5 回收
回收有四种情况,很简单,看看图即可。
2.3.6 动态重定位分区分配
将剩下的小碎片拼接起来,分配给作业,但是要解决重定位的问题。在拼接的时候,是将分配过的挪动,最终出现一个连续的空闲内存。
2.3.7 对换
对换的意思其实与挂起差不多的。
总结:
以上方式都想尽了一切办法减少资源的浪费,但是连续分配存在着致命的缺点,因此在现在的计算机中,都采用离散的方式进行分配。
三、分页存储管理方式
3.1 基本概念
页/页面: 将程序按照一定大小,划分成若干个大小相等的页/页面
块/页框: 相应地,把内存划分成等大小的块/页框
页内碎片: 在装入的时候,最后一片经常会造成小碎片
页/块设置过大: 页内碎片过大
页/块设置过小: 页表的大小会比较大,占据内存,同时降低了页面换进换出的效率。
页表: 是描述页面与页框的一个映射关系的数据结构
地址结构: 将地址分成了两个部分,前部分是页号,后一部分是页内偏移
对于给定地址划分如下:
-
内存中最多有多少个页:
2^20
-
页的大小:
2^12
-
两个进程的页大小可以不一样吗:
- 显然是不可能的
3.2 地址变换结构
3.2.1 最基本的变换结构
- 首先从页表寄存器中,获取页表的起始地址
- 然后根据页号与页表起始地址,去找到块号
- 根据块号与偏移量,计算出真实的地址
以上地址转换,要读几次内存?
Answer:
两次,第一次是读页表,另一次是读真正的内存。
3.2.2 通过Cache的思想,引入快表
为什么可以这么做?
Answer:
局部性原理。
因此当我们地址转换时,首先查快表,如果快表中没有查到,那么再去查页表
3.3 两级与多级页表
页表可以不可以放到离散的空间?
Answer:
根据转换机制,单纯地相加去查块号导致页表是不能离散的。
页表过大怎么办?
Answer:
采用多级页表的方式,从而做到页表可以离散地分到内存中。经典的划分为二级页表:
此时访问内存需要多少次?
Answer:
一共需要三次。
四、分段存储管理系统
4.1 分段的引入
只采用分页的思想,会有什么缺点?
Answer:
- 不利于模块化编程
- 不利于分段共享
- 不利于保护
- 不利于动态链接
- 不利于动态生长,在分段中可以进行扩充,而分页不容易
- 归根到底,跟用户编程的风格不一样
4.2 分段基本原理
类似于分页,地址分为段号与段内地址。同时还要设计一个段表,但不同的是,会多一个段的大小。
为什么要有段长呢?
Answer:
很显然的一点是因为分段是不固定大小的。
地址转换:
- 首先检查段号是否越界,如果不越界,进行下一步
- 检查段内地址是否越界,如果不越界,进行下一步
- 将基址与段内地址相加,即可得到。
- 那么需要几次访问内存?
2
次。 - 因为访问要两次,那么如何解决这个问题?快表!
- 快表的道理是什么?程序局部性原理
4.3 分页与分段的区别
- 页是信息的物理单位,对用户是透明的,由系统决定;而段是由用户控制的,例如说程序段、数据段、代码段
- 页的大小固定而段的大小不固定
- 分页是一维的,而分段是二维的
4.4 信息共享
为什么分段可以很容易地实现共享?
Answer:
因为页是非常碎的,如果将分页做成共享的话,会有许多的指针,而分段的大小会相对来说大一点,使共享更容易实现。
4.5 段页式存储思想
为了结合分段与分页的优点,可以将分段与分页相结合,因此修改成如下的地址:
那么地址的转换顺序也将改变:
同理,该方法也需要访问内存三次。
以上分页与分段式的方法,在《计算机组成原理》中都学到过,不难,计算起来仔细点即可!
五、虚拟内存空间
5.1 虚存的引入
1.一个非常非常经典的问题,我的电脑只有512MB,但是我就是很贪,我想玩GTA 5,那么请问我可以玩这个游戏吗?
Answer:
- 你在想屁吃,重买电脑可以解决问题
- 当然可以,就是慢了
- 因为在运行游戏的时候,我也不可能是所有的代码都在运行,因此我每次装入一部分到内存中,就可以了(虽然这样很慢,很没有游戏体验)
2.继续上一个问题,如果装入的过程中,满了怎么办?
Answer:
需要把一些不用的页给它置换出去。
3.这样做的依据是什么?
Answer
:
程序的局部性原理可以说也用到了很多。
- 时间局部性
- 空间局部性
虚存的定义:
可以使用户在一块很大的空间中编程,将内存与外存相结合。
5.2 虚存的实现
1.连续分配的内存可以实现虚存吗?
Answer:
不可以,因为在连续分配是一次性将作业全部装入内存之中,这显然是不可以的
5.2.1 请求分页机制
- 请求分页机制
- 缺页中断
- 地址变换结构
页表机制:
- 状态位:代表该页是否在内存还是外存
- 访问字段:记录本页在一段时间内访问次数
- 修改位:代表该页在内存中是否被访问过(为了写回外存)
请求分页的流程:
缺页中断与一般中断的区别:
- 缺页中断可以在指令执行间得到服务
- 一条指令可造成多次缺页中断
5.2.2 请求分段机制
- 请求分段机制
- 缺段中断
- 地址变换结构
请求分段流程:
地址变换过程:
5.2.3 分段的共享与保护
共享段表:
- 共享进程计数器:记录有多少个进程在共享
- 存取控制字段:访问控制(只读、只写这种)
- 段号:不同进程可以使用不同的段号表示一个共享段
共享段的分配:
- 对第一个请求,调入段区
- 对其他请求,不需再调入内存,直接分配
共享段的回收:
- 如果
COUNT != 1
,那么直接撤销即可 - 如果
COUNT == 1
,那么要回收该段
5.3 页面置换算法
抖动: 刚被置换出的页面,又要被访问,那么需要重新调入,频繁地换进换出,会造成大量时间上的浪费。因此如何置换,是一个非常重要的事情。
置换原则:
- 换出页面再访问时间越长越好
- 修改过的页面代价高于未修改过的(是因为要重写修改过的页)
5.3.1 最佳算法
可以说是最尴尬的算法,因为不知道后续的作业顺序,所以仅停留在理论角度。该算法的核心思想为;
将物理块中最长时间不访问的页面置换出去。
5.3.2 先进先出算法FIFO
核心思想:淘汰掉在内存中待的时间最长的页面。
- 适合于按顺序访问
- 会淘汰掉频繁重复被访问的页面
5.3.3 最近最久未使用LRU
核心思想:淘汰掉最近最久未使用过的页面。
硬件实现:
- 寄存器:移位寄存器,定期右移,定期置1。调出最小的就可以。
- 栈:栈顶始终是最近刚访问的
5.3.4 CLOCK算法
该算法类似于LRU
算法,基本上不会考到。
同时还有一个改进的CLOCK
,个人感觉也不会考到。
5.3.5 最近最少使用LFU
也是LRU
的变种,采用一个计数,统计在一段时间内使用次数最少的页面。一定要把这个和LRU
区别开!!!
在页面置换算法中,页面中断与页面置换是有区别的!计算起来一定要注意!