基础知识
什么是内存?
内存可以用来存储数据,并且CPU要处理数据,首先要将数据放到内存中,才能再进行处理
变量、数据和内存的关系?
内存是用来存放数据的,因此每一块内存在计算机中都会有一个内存地址,一般采用16进制数表示
而变量其实就是对某一块内存起一个别名,例如变量a其实将某一块内存临时命名为a,从而更加方便的存取数据
内存管理
内存管理的主要功能有:
- 内存空间的分配和回收:将内存的分配和回收交由操作系统完成,提高编程效率
- 地址转换:一般的,程序中的地址都是逻辑地址,而并非实际在内存上的地址。因此存储管理需要提供地址变换的功能,将逻辑地址转换为内存中的实际地址
- 内存共享:允许多个进程访问内存中的同一部分,因此需要支持对内存共享区域进行受控访问
- 内存空间扩充:使用虚拟存储技术或自动覆盖技术,从逻辑上扩充内存
- 存储保护:各个进程在各自的存储空间内运行,互不干扰
程序的链接和装入
创建进程需要将程序和数据都装入内存(指令和数据),将用户的源程序变成可以在内存中执行的程序,一般需要以下几个步骤:
- 编译:由编译程序将用户源代码编译成若干个目标模块
- 链接:由链接程序将编译后的一组目标模块以及它们所依赖的库函数链接在一起,形成一个完整的装入模块
- 装入:由装入程序将装入模块装进内存中运行
而程序的链接由如下的三种方式:
- 静态链接
- 装入时动态链接
- 运行时动态链接
装入模块在装入内存时,同样有如下三种方式:
- 绝对装入
- 可重定位装入
- 动态运行时装入
逻辑地址和物理地址
逻辑地址只是一个相对的关系 物理地址才是数据实际在内存上的地址
在编译之后,每个目标模块都从0号单元开始编址,这称为该目标模块的相对地址(或逻辑地址)
进程在运行时,看到和使用的都是逻辑地址。用户程序和程序员只需要知道逻辑地址,而内存管理的具体机制是完全透明的。不同进程可以有相同的逻辑地址,因为这些相同的逻辑地址会被映射到内存中的不同位置。
进程的内存映像
当一个程序调入内存中开始执行的时候,就构成了进程的内存映像。一个进程的内存影响一般分为如下几个部分:
- 代码段:程序的二进制代码
- 数据段:局部变量和全局变量
- 进程控制块PCB:操作系统通过PCB来控制和管理进程
- 堆:用来存放动态分配的变量。比如使用
malloc
函数分配地址空间- 栈:用来实现函数调用,比如函数的递归调用
内存保护
如上图所示,进程1只能访问进程1所拥有的地址空间,而不能随意访问操作系统以及其他进程(比如进程2)的地址空间
有两种方法来对内存进行保护:
方法1:上、下限寄存器
每次进程访问内存地址A时,首先要判断一下资源A的地址是否处于上下限寄存器地址之间。如果是,则允许访问,否则不允许访问
方法2:重定位寄存器
- 重定位寄存器中存放的是进程的开始地址
- 界地址寄存器中存放的是地址块的长度
如上图所示,对于进程1:
- 重定位寄存器:100
- 界地址寄存器:179
对于每一次的内存访问,同样都要判断是否超过了边界
内存分配
连续分配管理方式
单一连续分配
在单一连续分配中,内存被划分为系统区和用户区。 系统区 => 内存的低地址部分 用户区 => 内存的高地址部分
内存中只能有一道用户程序,用户程序独占整个用户区空间。
:::success 优点:
- 实现简单;无外部碎片
不一定需要内存保护(eg:早期的PC操作系统MS-DOS) ::: :::danger 缺点:
只能用于单用户、单任务的操作系统;
- 有内部碎片
- 存储器的利用率低下
:::
固定分区分配
目的:能在内存中装入多道程序,且这些程序之间又不会互相干扰,于是将用户空间划分为若干个固定大小的分区,在每个分区中都只装入一道作业
分区大小相等:缺乏灵活性,但是很适合用于控制多个相同对象的场合(比如:钢铁厂中有n个相同的炼钢炉,那么可以将内存分为n个相同大小的区域存放n个炼钢炉程序)
分区大小不等:增加了灵活性,可以满足不同大小的进程需求
既然每个分区的大小既然可以相同,也可以不同,那么如何标识每一个分区的大小?
操作系统需要建立一个数据结构——分区说明表,来实现各个分区的分配和回收。
如上图所示,分区说明表中的每个表项都对应一个分区,通常按照分区大小排列。每个表项包括对应分区大小、起始地址、状态(是否已分配),如下所示:
:::success
优点:
- 实现简单;无外部碎片
不一定需要内存保护(eg:早期的PC操作系统MS-DOS) ::: :::danger 缺点:
当用户程序太大的时候,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但是这又会导致性能的降低
- 会产生内部碎片,内存利用率降低(分给某个程序的空间可能不会被利用完全)
:::
:::warning
内部碎片和外部碎片
内部和外部其实是相对于分区来说的。
外部碎片:内存中的某些空闲分区由于太小而难以利用(分配给某个进程的空间一定是连续的)
内部碎片:分配给某进程的内存区域中,某些部分没有利用到(比如10M只用上了8M) :::动态分区分配
“动态”是指操作系统不会预先给进程分配空间,而是当进程被装入内存的时候,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。既然引入了动态分区分配,那么操作系统自然还需要完成分区内存的释放,从而有如下三个问题
操作系统使用什么样的数据结构来记录内存的使用情况?
和固定分区分配时使用到的内存分区说明表类似,这里采用的是空闲分区表和空闲分区链
当有很多个空闲分区都能满足程序的需求时,应该选择哪个分区进行分配?
根据使用的动态分区分配算法来具体判断选择哪个分区使用
如何进行分区的分配和回收(以空闲分区表为例)
具体情况具体分析,但是进行分区的分配和回收,会导致空闲分区表的变化,一定要记得修改空闲分区表中的内容
分区的分配
分区的回收