1.内存管理的主要内容
1.1 内存空间的分配与回收
由OS完成主存储器空间的分配和管理,从而避免了程序员自己分配内存的麻烦。
1.2 地址转换
在多道程序环境下,程序中的逻辑地址与内存中的物理地址不同,因此,需要将逻辑地址转换成相应的物理地址以进行内存相关的数据操作。
1.3 内存空间的扩充
利用虚拟存储技术或自动覆盖技术,从逻辑上扩充内容。
1.4 内存共享
允许多个进程对内存共享区域进行受控访问。
1.5 存储保护
保证各道作业在各自的存储空间内运行,互不干扰。
内存保护需要由
OS
和硬件机构
配合完成,以保证进程空间不被非法访问。
2.内存管理的基本原理
2.1 程序的链接与装入
2.1.1 概述
创建进程首先需要将程序和数据装入内存,将用户源程序变为可以在内存在执行的程序。
2.1.2 过程
- 编译 :由
编译程序
将用户源代码编译成若干目标模块
。 - 链接 :由
链接程序
将编译后形成的一组目标模块&它们所需的库函数链接在一起,形成一个完整的装入模块
。静态链接
:在程序运行之前,就将各个目标模块&它们所需的库函数链接成一个完整的装配模块,以后不再拆开。
该方式需要解决两个问题:
- 需要修改相对地址,编译后的所有目标模块都是从0开始的相对地址,当链接成一个装入模块时,需要修改相对地址。
- 变换外部调用符号,对每个模块中所用到的外部调用符号也都变换为相对地址。
装入时动态链接
:将目标模块装入内存时,采用边装入边链接的方式。这样做便于修改和更新,便于实现对目标模块的共享。运行时动态链接
:凡是在执行过程中未被用到的目标模块,都不会被链接到装入模块并调入内存,只有程序在运行过程中需要该目标模块时,才进行链接和装入操作。- 装入 :由
装入程序
将装入模块
装入内存运行。(还必须完成从代码中的逻辑地址转换到物理地址的工作)绝对装入
:该方式只适用于单道程序环境。在编译时,根据程序想要驻留在内存中的位置(绝对地址),从而产生相应位置处的目标代码。绝对装入程序将按照转入模块中的绝对地址,将程序和数据装入内存。
该方式下的程序中的逻辑地址与实际内存中的物理地址相同,无需进行地址转换。
可重定位装入/静态重定位
:由于目标模块的起始地址通常是从0开始的,且程序中的其他地址都是相对于起始地址的,因此可以在装入时,一次性地统一对程序中的指令和数据地址进行修改。动态运行时装入/动态重定位
:装入程序将装入模块装入到内存后,没有立即进行逻辑地址到物理地址的转换,而是推迟到程序真正要执行时,再去转换。
这种方式需要一个重定位寄存器的支持。
这种方式的优点:可以将程序分配到不连续的存储区,在程序运行之前可以只装入部分代码即可投入运行,然后在程序运行期间,根据需要动态申请分配内存,便于程序段的共享。
静态装入是指在编程阶段就把物理地址计算好。可重定位是指在装入时把逻辑地址转换为物理地址,但装入后不能改变。
动态重定位是指在程序执行时再决定装入的地址并装入,装入后还有可能会换出,所以同一个模块在内存中的物理地址是可能改变的。
编译之后的程序经过链接才能装载,而链接后形成的目标程序中的地址也就是逻辑地址。
2.2 逻辑地址&物理地址
2.2.1 逻辑地址
- 编译后形成的目标模块,其地址都将从0号单元开始编址,这便是相对地址(逻辑地址)。当链接完这些目标模块后,将形成一个统一的由0号单元开始编址的逻辑地址空间(虚拟地址空间)。
- 程序在运行时看到和使用到的地址都是逻辑地址。
- 内存管理中的具体机制对于用户和程序员是透明的。
- 不同进程可以有相同的逻辑地址,因为这些逻辑地址最终将被映射到不同的物理地址上面去。
- 对于一个
32位
的系统,其逻辑地址空间范围是0 ~ 232 - 1
。
2.2.2 物理地址
- 物理地址空间是指内存中物理单元的集合,它是地址转换的最终地址。进程在运行过程中对于指令和数据的访问,最终都要转成对物理地址对应的主存单元中的存取操作。
- OS通过使用
内存管理部件(MMU)
来将进程使用到的逻辑地址转换为物理地址。逻辑地址通过页表映射到物理内存,而页表则由OS负责维护并被处理器访问。
2.3 进程的内存映像
代码段
:进程的二进制代码,只读,可被多个进程共享。数据段
:程序运行时加工处理的对象,包括全局变量
&静态变量
。进程控制块(PCB)
:存放在系统内核区,OS通过PCB来控制和管理进程。堆
:存放动态分配的变量,由低地址向高地址增长。栈
:用来实现函数调用,由高地址向低地址增长。
代码段和数据段在调入内存时就指定了大小,而堆和栈时可动态变化的(可拓展和收缩)。每次调用一个函数,栈就会增长;每从一个函数返回,栈就会收缩。
2.4 内存保护
2.4.1 概述
确保每个进程都有一个单独的空间。内存分配前,不仅需要保护OS不受用户进程的影响,还需要保护用户进程不受其他用户进程的影响。
2.4.1 保护方式
- 在CPU中设置上下限两个寄存器,分别存放用户作业哎主存中的上限和下限两个地址。每当CPU要访问一个地址时,会与这两个上下限地址进行比较,判断要访问的地址是否介于上下限地址之间以此来判断访问有无越界。
- 采用重定位寄存器(基地址寄存器)和界地址寄存器。将要访问的逻辑地址与界地址寄存器中的逻辑地址最大值进行比较,若小于逻辑地址最大值,则将要访问的逻辑地址与基地址寄存器中的内容相加,作为物理地址进行访问。
注意:加载基地址寄存器和界地址寄存器的指令是特权指令,因此只有OS内核才可以加载并读写这两个寄存器的内容。
2.5 内存共享
并非所有的进程内存空间都适合共享,只有那些只读的区域才可以共享。
可重入代码/纯代码
:可允许多个进程同时访问但不允许被任何进程修改的代码。(可以在实际执行过程中,自己单独配一个局部数据区,从而只对自己的数据区进行修改而无需改变共享的代码)
2.6 内存分配与回收
在OS由单道向多道OS发展时,存储管理方式也由单一连续分配发展为固定分区分配,为了更好地适应不同大小的程序要求,又从固定分区分配发展为动态分区分配,为了提高内存的利用率,又发展到了离散分配(页式存储管理、段式存储管理、段页式存储管理)。
3.覆盖与交换
4.内存的连续分配
为一个用户程序分配一个连续的内存空间。
4.1 单一连续分配
4.1.1 概述
内存在此方式下分为系统区和用户区。系统区仅供OS使用,通常在低地址部分。用户区内存中,仅有一道用户程序驻留,即整个用户区内存均由一个程序所独占。
4.1.2 优点
简单,无外部碎片,无需进行内存保护(因为内存中只有一道程序)。
4.1.3 缺点
只能用于单用户、单任务的OS中,有内部碎片,存储器利用率极低。
单一连续分配可采用覆盖技术。
4.2 固定分区分配
4.2.1 概述
将用户分区进一步划分为若干个固定大小的分区,每个分区内只能装入一道作业。当有空闲分区时,可从外存中的后备作业队列选择一个作业调入该分区。
在划分分区时有两种不同的方法:
分区大小相等
。程序太小会造成浪费,程序太大又无法装入,该方式缺乏灵活性。分区大小不等
。该方式将用户区划分为大量较小的分区、适量的中等分区以及少量的大分区。
为了便于分配,通常要建立一张分区使用表,表项按大小排序,每个表项包括分区的起始地址、大小以及状态。
4.2.2 优点
- 该方式是针对于多道程序设计的最简单的存储分配方式,不会产生外部碎片。
4.2.3 缺点
- 程序太大而可能无法放进任何一个分区(需要采用覆盖技术来使用内存空间),程序太小(小于固定分区大小)会产生颞部碎片。
- 无法实现多进程共享一个主存区,所以存储空间利用率低。
4.3 动态分区分配/可变分区分配
4.3.1 概述
在进程装入内存时,根据进程的实际需要,动态地为之分配内存,并使分区大小正好适合进程的需要。
4.3.2 动态分区分配算法
首次适应(First Fit)算法
:空闲分区按照地址升序链接起来,分配内存时,从链首(地址最低)开始查找,一旦找到大小能满足要求的第一个分区,便立刻将其分配给作业。
该算法通常是最好和最快的,且该算法会在低地址处产生较多细小的空闲分区,且每次扫描都必须要经过这些细小的空闲分区,从而增加了开销。
邻近适应(Next Fit)算法/循环首次适应算法
:由首次适应算法演变而来,不同于它,该算法分配内存时,将从上次查找结束的位置开始继续查找。
该算法常常导致在内存空间的尾部产生小的碎片。(因为在一遍扫描中,内存前面的部分使用后再释放时,不会参与分配)
该算法性能也差与首次适应算法。
最佳适应(Best Fit)算法
:空闲分区按照容量升序链接起来,然后从链首(容量最小)开始查找,一旦找到大小能满足要求的第一个分区,便立刻将其一部分分割下来并分配给作业,避免了"大材小用"。
该算法性能通常很差,会留下数量最多、大小很小的难以利用的内存块(外部碎片)。
最坏适应(Worst Fit)算法
:空闲分区按照容量降序链接起来,然后从链首(容量最小)开始查找,一旦找到大小能满足要求的第一个分区,便立刻将其一部分分割下来并分配给作业。
该算法通常导致没有可用的大内存块,因此性能也非常差。
4.3.3 评估
动态分区刚开始用效果不错,但随着时间的推移,用户内存区中会出现越来越多的小的空闲内存块(外部碎片),内存的利用率随之下降。克服外部碎片的方法是采用紧凑/拼接
技术(需要基地址寄存器的支持),OS将不时地对进程占用的内存块进行移动和整理。
Windows系统中也由磁盘碎片整理程序,只不过它是对外存空间进行整理的。
动态分区管理在内存回收,会先根据待回收的内存块的起始地址找到空闲链表中的对应的插入点,然后查看插入点左右两侧是否存在空闲分区,若存在则与之融合,然后修改现存的空闲表的表项,若不存在,则新增一个空闲表项。
5.内存的非连续分配
5.1 基本分页存储管理
5.1.1 概述
分页思想:将主存空间划分为大小相等且位置固定的块,块相对较小,并以块作为主存的基本单位。每个进程也以块为单位进行划分,进程在执行时,也以块为单位逐个申请内存中的块空间。
分页管理不会产生外部碎片,但会产生很小的内部碎片(平均是半个块大小)。
进程中的块称为页
或页面(Page)
,内存中的块称为页框
或页帧(Page Frame)
,外存中的块称为块
或盘块(Block)
。
为方便转换,页面大小必须为
2的整数幂
。页面大小应适中。页面太大,使得页内碎片增多,页面利用率下降;页面太小,使得进程中的页面太多,进而导致页表过长,占用大量内存,同时也会增加硬件地址转换的开销。
5.1.2 逻辑地址结构
该地址结构的位数决定了虚拟地址空间的大小。
逻辑地址的页号和页内偏移量对用户是透明的。
5.1.3 页表
- 页表用于寻找进程的指定页面所对应的物理块。
- 系统会为每个进程建立一张页表。
- 页表一般存放在主存中。
- 系统中通常设置一个
页表寄存器(PTR)
,用于存放页表在内存的起始地址和页表长度。进程未运行之前,页表的起始地址和页表长度内容将存放在进程的PCB
中,进程被调度运行时,才将页表的起始地址和页表长度内容装入PTR
。 - 页表由一个个的页表项组成,而页表项由页号+物理块号组成。
5.1.4 地址变换机构
- 地址变换机构负责将逻辑地址转换为内存中的物理地址。
- 地址变换是借助于页表实现的。
逻辑地址 -> 物理地址的变换过程:
- 计算页号P(
P = A/L
)和页内偏移量W(W = A%L
)。 - 比较页号P和页表长度,若
P ≥ M
,则产生越界中断,否则继续执行。 - 页表中页号P对应的
页表项地址 = 页表地址F + 页表项长度 × 页号P
,然后取出对应的物理块号B。 - 然后计算物理地址
E = B × L + W
,然后访问该物理地址对应的主存单元。
5.1.5 具有快表的地址变化机构
若页表全部放在内存中,则存取一个数据或指令至少需要两条指令。因此,为了加快地址变换的过程,在地址变换机构中增设一个具有并行查找能力的Cache------快表/相联存储器(TLB)
。(与之相对应的,主存的页表称为慢表)
TLB中存放当前访问的若干页表项。
快表的有效性基于局部性原理。
逻辑地址 -> 物理地址的变换过程:
- 在CPU给出逻辑地址后,将交由硬件进行地址转换产生页号,然后将页号送入TLB,与TLB中所存储的所有页号进行比较。
- 若找到了匹配的页号,则说明要访问的页表项存在于TLB中,因此直接从TLB中取出页表项,进而得到对应的物理块号,然后形成所要访问的物理地址。这样仅需访存一次便可存取数据。
- 若找不到匹配的页号,则需要进一步去访问主存中的页表,读出页表项,然后存入TLB,若TLB此时已满,则需要按照一定算法淘汰一个旧的页表项。
有些处理机设计为快表慢表同时查找,若在快表中找到则停止慢表的查找。
5.1.6 两级页表
由于在某些情况下,单单一个页表就要占据不小的空间,且一个一个顺次查找大量的页表项效率也很低,因此,有必要在现有页表的基础上,针对于页表本身,在其之上再建立一个页表,作为页表的页表。
二级页表实际上就是在原有的页表结构上再加上一层页表,其目的在于建立索引,以便不再浪费主存空间去存储大量无用的页表项,同时也不再盲目地顺次查找大量页表项了。
注意:为了保证查询方便,规定顶级页表只能有一个页面。
分页是通过硬件机制实现的,对用户完全透明,用户感知不到页面的存在。
5.2 基本分段存储管理
5.2.1 概述
段式管理方式是按照用户进程中的自然段划分逻辑空间。每个段是一个连续存储区,每个段不一定等长,段与段之间可以连续也可以不连续。
5.2.2 逻辑地址结构
段号和段内偏移量必须由用户显式给出(因为段长是不固定的,无法通过对逻辑地址进行取模、取余操作),在高级程序设计语言中,这个工作由编译程序来完成。
5.2.3 段表
- 进程可通过查找段表,找到每段所对应的内存区。
- 与页式存储管理一样,依旧需要在系统中设置一个段表寄存器,用于存储段表起始地址和段表长度。
5.2.4 地址变换机构
逻辑地址 -> 物理地址的变换过程:
- 根据给出的逻辑地址A,得到段号S和段内偏移量W。
- 比较段号S与段表长度M。若
S ≥ M
,则产生越界中断,否则继续执行。 - 根据段号S,得到
段表项的地址 = 段表起始地址 + 段号S × 段表项长度
。然后取出段表项中的段长C,若W ≥ C
,则产生越界中断,否则继续执行。 - 取出该段表项的起始地址B,然后计算出物理地址
E = B + W
,进而访问对应的主存单元。
5.2.5 段的共享与保护
段的共享是通过两个作业中的段表中相应表项指向的被共享的段的同一个物理副本来实现的。
保护方法
存取控制保护
。地址越界保护
:分别进行段号与段表长度、段内偏移量与段长两次比较以判定是否发生越界。
采用段式管理存储时,一个程序如何分段是在用户编程时决定的。
程序的动态链接与程序的逻辑结构有关,分段存储管理将程序按照逻辑段划分,因此有助于程序的动态链接。
引入段式存储管理方式,主要是满足用户下列要求:方便编程、分段共享、分段保护、动态链接和动态增长。
5.3 段页式管理
5.3.1 概述
在段页式系统中,作业的地址空间首先被划分为若干逻辑段,每段都有自己的段号,然后每段进一步划分若干大小相同的页。
为实现地址变换,系统将为进程建立一张段表,每个段有一张页表。系统还需要配置一个段表寄存器。
在一个进程中,段表只能有一个,页表可以有多个。
采用二级页表的分页系统中,CPU页表基地址寄存器的内容是当前进程的一级页表的起始物理地址
。
5.3.2 逻辑地址结构
逻辑地址 -> 物理地址的变换过程:
首先通过段表找到页表起始地址,然后根据页号找到物理块号,最后结合页内偏移量形成物理地址。
全程将访存三次,当然,我们同样页可以引入TLB来加快查找速度。
分页式存储管理、段页式存储管理、固定分区存储管理有内部碎片。(最小单位是页,页是大小固定的,其有极大概率会产生内部碎片)
分段式存储管理有外部碎片。(最小单位是段,段是自适应大小的)
采用分页和分段管理后,提供给用户的无礼地址空间是不确定的。因为系统提供给用户的总物理空间大小 = 总空间大小 - 页表/段表的长度
。
重定位寄存器在整个系统中只设置一个就行,在执行程序或访问数据时,真正访问的地址由相对地址和重定位寄存器相加而来,这时将程序起始地址装入重定位寄存器,后续地址访问通过硬件变换实现即可。
上述所有的存储管理方式下,对存储器的访问依旧是以字节或字为单位。
在分页存储管理中,由于页长是固定的,因此只需一个记忆符就可以表示地址,因此其地址结构是一维的。而段式存储管理中,由于段长是不固定的,因此除了需要给出段号外,还需要给出段内地址来标识一个地址,因此段式存储管理的地址结构是二维的。
总之,确定一个地址需要几个参数,那作业的地址空间就是几维的。