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