基本概念
内存管理的主要功能/基本原理/要求
**内存管理的主要功能: **
- 内存空间的分配与回收。由操作系统完成主存储器空间的分配和管理,使程序员摆脱存储分配的麻烦,提高编程效率。
- 地址转换。在多道程序环境下,程序中的逻辑地址与内存中的物理地址不可能一致,因此存储管理必须提供地址变换功能,把逻辑地址转换成相应的物理地址。
- 内存空间的扩充。利用虚拟存储技术或自动覆盖技术,从逻辑上扩充内存。
- 内存共享。指允许多个进程访问内存的同一部分。例如,多个合作进程可能需要访问同一块数据,因此必须支持对内存共享区域进行受控访问。
- 存储保护。保证各道作业在各自的存储空间内运行,互不干扰
程序的链接和装入
创建进程首先要将程序和数据装入内存。将用户源程序变为可在内存中执行的程序,通常需要以下几个步骤:
- 编译。由编译程序将用户源代码编译成若干目标模块。
- 链接。由链接程序将编译后形成的一组目标模块及它们所需的库函数链接在一起,形成一
个完整的装入模块。
- 装入。由装入程序将装入模块装入内存运行。
程序的链接有以下三种方式。
(1) 静态链接
在程序运行之前,先将各目标模块及它们所需的库函数链接成一个完整的装配模块,以后不 再拆开。
将几个目标模块装配成一个装入模块时,需要解决两个问题:
①修改相对地址,编译后 的所有目标模块都是从0开始的相对地址,当链接成一个装入模块时要修改相对地址。
②变换外 部调用符号,将每个模块中所用的外部调用符号也都变换为相对地址。
(2) 装入时动态链接
将用户源程序编译后所得到的一组目标模块,在装入内存时,采用边装入边链接的方式。其优点是便于修改和更新,便于实现对目标模块的共享。
(3) 运行时动态链接
对某些目标模块的链接,是在程序执行中需要该目标模块时才进行的。凡在执行过程中未被用到的目标模块,都不会被调入内存和被链接到装入模块上。其优点是能加快程序的装入过程,还可节省大量的内存空间。
内存的装入模块在装入内存时,同样有以下三种方式:
(1) 绝对装入
绝对装入方式只适用于单道程序环境。在编译时,若知道程序将驻留在内存的某个位置,则编译程序将产生绝对地址的目标代码。绝对装入程序按照装入模块中的地址,将程序和数据装入内存。由于程序中的逻辑地址与实际内存地址完全相同,因此不需对程序和数据的地址进行修改。
另外,程序中所用的绝对地址,可在编译或汇编时给出,也可由程序员直接赋予。而通常情况下在程序中采用的是符号地址,编译或汇编时再转换为绝对地址。
(2) 可重定位装入
在多道程序环境下,多个目标模块的起始地址通常都从0开始,程序中的其他地址都是相对于起始地址的,此时应采用可重定位装入方式。根据内存的当前情况,将装入模块装入内存的适当位置。在装入时对目标程序中指令和数据地址的修改过程称为重定位,又因为地址变换通常是在进程装入时一次完成的,故称为静态重定位
当一个作业装入内存时,必须给它分配要求的全部内存空间,若没有足够的内存,则无法装入。此外,作业一旦进入内存,整个运行期间就不能在内存中移动,也不能再申请内存空间。
(3) 动态运行时装入
也称动态重定位。程序在内存中若发生移动,则需要采用动态的装入方式。装入程序把装入模块装入内存后,并不立即把装入模块中的相对地址转换为绝对地址,而是把这种地址转换推迟到程序真正要执行时才进行。因此,装入内存后的所有地址均为相对地址。这种方式需要一个重定位寄存器的支持
动态重定位的优点:可以将程序分配到不连续的存储区;在程序运行之前可以只装入部分代码即可投入运行,然后在程序运行期间,根据需要动态申请分配内存;便于程序段的共享。
逻辑地址与物理地址
编译后,每个目标模块都从0号单元开始编址,这称为该目标模块的相对地址(或逻辑地址)。 当链接程序将各个模块链接成一个完整的可执行目标程序时,链接程序顺序依次按各个模块的相对地址构成统一的从0号单元开始编址的逻辑地址空间(或虚拟地址空间),对于32位系统,逻辑地址空间的范围为。0〜232- 1。进程在运行时,看到和使用的地址都是逻辑地址。用户程序和程序员只需知道逻辑地址,而内存管理的具体机制则是完全透明的。不同进程可以有相同的逻辑地址,因为这些相同的逻辑地址可以映射到主存的不同位置。
物理地址空间是指内存中物理单元的集合,它是地址转换的最终地址,进程在运行时执行指令和访问数据,最后都要通过物理地址从主存中存取。当装入程序将可执行代码装入内存时,必 须通过地址转换将逻辑地址转换成物理地址,这个过程称为地址重定位。
操作系统通过内存管理部件(MMU)将进程使用的逻辑地址转换为物理地址。进程使用虚拟内存空间中的地址,操作系统在相关硬件的协助下,将它"转换”成真正的物理地址。逻辑地址通过页表映射到物理内存,页表由操作系统维护并被处理器引用。
进程的内存映像
不同于存放在硬盘上的可执行程序文件,当一个程序调入内存运行时,就构成了进程的内存映像。一个进程的内存映像一般有几个要素:
- 代码段:即程序的二进制代码,代码段是只读的,可以被多个进程共享。
- 数据段:即程序运行时加工处理的对象,包括全局变量和静态变量。
- 进程控制块(PCB):存放在系统区。操作系统通过PCB来控制和管理进程。
- 堆:用来存放动态分配的变量。通过调用malloc函数动态地向高地址分配空间。
- 栈:用来实现函数调用。从用户空间的最大地址往低地址方向增长。
代码段和数据段在程序调入内存时就指定了大小,而堆和栈不一样。当调用像malloc和free这样的C标准库函数时,堆可以在运行时动态地扩展和收缩。用户栈在程序运行期间也可以动态地扩展和收缩,每次调用一个函数,栈就会增长;从一个函数返回时,栈就会收缩。
下图是一个进程在内存中的映像。其中,共享库用来存放进程用到的共享函数库代码,如printf()函数等。在只读代码段中,.init是程序初始化时调用的—init函数;.text是用户程序的机器’代码;,rodata是只读数据。在读/写数据段中,.data是己初始化的全局变量和静态变量;.bss是未初始化及所有初始化为0的全局变量和静态变量。
内存保护
操作系统作为系统资源的管理者,需要对内存进行一下管理:
- 负责内存空间的分配与回收
- 提供某种技术从逻辑上堆内存空间进行扩充
- 提供地址转换功能,负责程序的逻辑地址与物理地址的转换
- 提供内存保护功能。保证各进程在各自存储空间内运行,互不干扰
确保每个进程都有一个单独的内存空间。内存分配前,需要保护操作系统不受用户进程的影响,同时保护用户进程不受其他用户进程的影响。内存保护可采取两种方法:
- 方法一
- 方法二
内存管理机构动态地将逻辑地址与界地址寄存器进行比较,若未发生地址越界,则加上重定位寄存器的值后映射成物理地址,再送交内存单元。
内存共享
并不是所有的进程内存空间都适合共享,只有那些只读的区域才可以共享。可重入代码又称纯代码,是一种允许多个进程同时访问但不允许被任何进程修改的代码。但在实际执行时,也可以为每个进程配以局部数据区,把在执行中可能改变的部分复制到该数据区,这样,程序在执行
时只需对该私有数据区中的内存进行修改,并不去改变共享的代码。
下面通过一个例子来说明内存共享的实现方式。考虑一个可以同时容纳40个用户的多用户系统,他们同时执行一个文本编辑程序,若该程序有160KB代码区和40KB数据区,则共需8000KB的内存空间来支持40个用户。如果160KB代码是可分享的纯代码,则不论是在分页系统中还是
在分段系统中,整个系统只需保留一份副本即可,此时所需的内存空间仅为40KBx40 + 160KB =1760KBo对于分页系统,假设页面大小为4KB,则代码区占用40个页面、数据区占用10个页面。为实现代码共享,应在每个进程的页表中都建立40个页表项,它们都指向共享代码区的物
理页号。此外,每个进程还要为自己的数据区建立10个页表项,指向私有数据区的物理页号。对于分段系统,由于是以段为分配单位的,不管该段有多大,都只需为该段设置一个段表项(指向共享代码段始址,以及段长160KB)。由此可见,段的共享非常简单易行。
内存分配与回收
存储管理方式随着操作系统的发展而发展。在操作系统由单道向多道发展时,存储管理方式便由单一连续分配发展为固定分区分配。为了能更好地适应不同大小的程序要求,又从固定分区 分配发展到动态分区分配。为了更好地提高内存的利用率,进而从连续分配方式发展到离散分配方式一一页式存储管理。引入分段存储管理的目的,主要是为了满足用户在编程和使用方面的要
求,其中某些要求是其他几种存储管理方式难以满足的