🔥🔥宏夏Coding网站,致力于为编程学习者、互联网求职者提供最需要的内容!网站内容包括求职秘籍,葵花宝典(学习笔记),资源推荐等内容。在线阅读:https://hongxiac.com🔥🔥
对于通用的计算机,存储层次至少应具有3层:最高层为CPU寄存器,中间层为主存储器,最低层为辅助存储器(简称辅存)。
对于较高档的计算机,存储层次可细分为:寄存器,高速缓存,主存储器,磁盘缓存,固定磁盘,可移动存储介质。层级越高,存储介质的访问速度越快,存储容量越小,价格也越高。
由于内存的访问速度远低于CPU执行指令的速度,为了缓解内存访问速度与CPU执行速度不匹配这一矛盾,引入了寄存器。
寄存器是CPU内部的一种告诉存储器,用于暂时存放参与运算的指令,数据和运算结果等内容,因为与CPU的运算单元相连,所以寄存器的访问速度远快于主存储器。
为了缓和内存与CPU速度之间的矛盾,引入了高速缓存。
它是介于寄存器和内存之间的存储器,主要用于备份内存中较常用的数据,以减少处理机对内存的访问次数。通常,进程的程序和数据存放在内存中,每当要访问他们时,才会被临时复制到一个速度较快的高速缓存中。
主存储器(Main Memory),简称主存或内存,也称为随机访问存储器(RAM),是计算机系统中的主要部件,用于保存进程运行时的程序和数据。通常由许多存储单元组成,每个存储单元都有唯一的地址。这些存储单元按字节(Byte)进行编址,每个字节都有一个唯一的地址,可以被读取或写入数据。
为了缓和内存和磁盘速度之间的矛盾,引入了磁盘缓存。
磁盘缓存和高速缓存不同,它本身并不是一种实际存在的存储器,而是利用内存中的部分存储空间,暂时存放从磁盘中读出或写入的信息。
一个用户程序要运行,需要将其装入内存中,然后将其转变成一个可执行的程序,一般需要经过三个步骤:编译->链接->装入。
编译器将源程序进行编译,形成若干模块,随后经过链接程序,将这些模块和运行所需要的库函数链接在一起,形成装入模块,最后通过装入程序将他们装入内存中。
分为静态链接、装入时动态链接、运行时动态链接
静态链接:编译器事先将程序所需的所有代码和库函数链接成一个装入模块,在运行时无需依赖于外部的库文件
装入时动态链接:采用边装入边链接的形式,即在装入时,若该模块中发生另一个外部模块调用事件,则装入程序会找出该外部模块,也把它装入到内存中。
运行时动态链接:一开始只链接和装入部分目标模块,当程序运行时发现需要其他模块时,再进行链接和装入(加快程序装入过程,节省内存空间)
分类 | 静态链接 | 装入时动态链接 | 运行时动态链接 |
---|---|---|---|
特点 | 直接将所有代码和库函数链接形成一个装配模块,不再拆开 | 装入时根据模块调用事件找出并装入相应外部模块 | 对某些模块的链接推迟到程序执行时才进行 |
优点 | 程序运行时,不依赖外部的库文件,可以独立地在目标系统上运行。具有独立性和可移植性。 | 各目标模块分开存放,便于修改和更新目标模块 | 加快程序装入过程,可节省大量内存空间 |
缺点 | 可执行文件较大,占用更多存储空间。 | 可能增加启动时间和管理工作 | 当找不到额外的库函数时,需要额外的开销进行加载 |
分为绝对装入、可重定位装入、动态运行时装入
逻辑地址(相对地址):指CPU生成的地址,是相对于进程而言的虚拟地址,一般从0开始
物理地址(绝对地址):程序装入内存中,在内存中的实际的物理位置,处理器能够直接访问内存单元的地址
逻辑地址是相对于进程而言的,而物理地址是相对于计算机系统的物理内存而言的。逻辑地址一般由操作系统的内存管理单元(MMU)进行翻译,将其转化为物理地址。
在处理用户程序的三个步骤中(编译,链接,装入),每个步骤的地址可能有着不同的地址形式。地址绑定是指将程序中使用的逻辑地址(或虚拟地址)映射到计算机实际物理内存中的物理地址的过程。
内存保护指的是两个方面
内存保护通过CPU硬件实现。可以通过两个寄存器分别记录该进程在内存中的起始物理地址和该程序在内存中所需要的大小,即基地址寄存器和界限寄存器,此时该进程的合法地址范围就是(基地址,基地址+界限地址)区间,如果该进程访问合法地址以外的范围,就会陷入OS内核,而OS内核则会将其作为致命错误来处理。
当内存空间不足或者进程所需的空间大于系统能够提供的,系统应该如何处理?一是拒绝该进程的运行请求,二是使用内存扩充技术。
所谓内存扩充,并不是增加系统的物理内存,而是指扩大内存的使用效率。常用的内存扩充技术,包括交换,覆盖,紧凑,虚拟存储器等。
指将内存中暂时不能运行的进程或暂时不用的程序和数据,转移到外存;再把已具备运行条件的进程或需要的程序和数据从外存装入内存中。
指在程序的执行过程中,根据需要加载不同的覆盖块到内存中,程序的不同部分相互替换。
在早期的计算机系统中,有限的内存无法装入整个程序,便将程序分割成多个部分(称为覆盖块或覆盖段),常用的部分常驻内存,不常用的根据需要再调入内存。
举个例子:
某程序由符号表(20KB)、公共例程(30KB)、分枝1(70KB)、分枝2(80KB)组成。如果只有150KB的内存空间,是无法将整个程序(200KB)装入内存的。因为分枝1和分枝2不会同时执行,所以两个分枝不必同时位于内存,可以使两个分枝共享一块存储区域,根据实际需要对这块共享存储区的内容进行替换。需要注意的是,使用覆盖技术须增加覆盖驱动程序(假设10KB),共享的存储区域为了能容纳分枝1和分枝2着两段代码,容量应该选择较大的那个,即分枝2的80KB。
当一台计算机运行了一段时间后,它的内存空间会被分割成许多小分区,而缺乏大的空闲空闲。即使这些小分区的容量总和大于要装入的程序,但因为这些小分区不连续、不相邻,所以程序无法被装入。
为了解决这一问题,我们可以通过移动内存中其他作业的位置,把原来分散的小分区拼接成一个大分区,这种方法就成为”紧凑“。
虽然思路简单,但会带来以下问题:
(所以,紧凑技术通过移动其他作业的位置,将多个小分区拼接成一个大的分区,使得内存能够连续分配,能够消除或减少内存碎片化,提高内存空间的使用效率。)
特点 | 区别 | |
---|---|---|
交换 | 将不需要的程序从内存转移到外存;将需要的程序从外存装入内存 | 在不同进程或程序之间进行 |
覆盖 | 根据需要,程序的不同部分相互替换 | 用于同一进程或程序 |
为了能将用户程序装入内存,则需要为程序分配一定大小的内存空间。那么系统是如何对内存进行划分以及管理的呢?
连续分配方式指的是为一个用户程序分配一个连续的内存空间,即程序中的代码或数据的逻辑地址相邻,体现在内存空间中则是为用户程序分配的物理地址相邻。
分为单一连续分配、固定分区分配、动态分区分配、动态重定位分区分配。
在早期的单道程序环境下,内存被分为系统区和用户区。系统区供OS使用,而用户区供用户使用,仅装有一道用户程序,即用户区中连续的内存空间都由单个进程独占。这样的存储器分配方式就称作单一连续分配。
因此,它具有以下特点:
同时,具有以下缺点:
早期的多道程序环境下,为了能够在内存中装入多道程序,就将内存中的用户空间划分为若干个固定大小的区域(称作分区),而每个分区中只装入一道程序。不同分区的大小可以相等,也可以不相等。
为了便于内存的分配,需要有一张表记录着每个分区的起始地址,大小,使用状况(即是否已经被分配),称作固定分区使用表。当有用户程序需要装入内存时,就需要查询固定分区使用表,找到满足条件的分区后分配给该程序,若找不到满足条件的分区则拒绝为该程序分配内存。
固定分区分配是最早出现的、可用于多道程序系统的存储管理方式。由于每个分区的大小固定,必然会造成存储空间的浪费,所以现在很少将它用于通用的OS中,更多用于控制多个相同对象的控制系统中(比如工业自动化中的生产线控制系统)
根据进程的实际需要,动态为其分配内存空间。
为了记录内存的使用情况。需要设置一张空闲分区表或空闲分区链,记录每个空闲分区的情况(分区号、大小、起始地址、分区状态...),可分别用数组或链表的数据结构实现。
为了动态的把新作业存入内存,就需要按照一定的分配算法,从空闲分区表或空闲分区链中选出空闲分区并分配给进程。
下面就介绍几种分配算法,包括首次适应算法,循环首次适应算法,最佳适应算法,最坏适应算法,快速适应算法,伙伴匹配系统,哈希算法这六种算法。
在空闲分区链中从低地址位置开始寻找空闲的分区,一找到合适的空闲分区就进行划分和分配。
具体的工作原理:
1.系统维护一个空闲内存块链表,按照地址递增的顺序排列。
2.当一个进程请求内存时,首次适应算法从链表的头部开始扫描,寻找第一个能够满足请求大小的空闲内存块。
3.如果找到了足够大的空闲块,将其分割成两部分:一部分用于分配给进程,另一部分则成为新的空闲块。
4.如果没有足够大的空闲块,则继续扫描链表,直到找到满足条件的空闲块或者扫描完整个链表。
5.如果链表中没有合适的空闲块,则内存分配失败,进程需要等待或者采取其他策略。
由于优先利用低地址空间,所以能保留高地址的大空闲分区,为以后大作业的分配保留空间。
但是每次都需要从低地址部分开始查找,增加查找的开销。
也称作临近适应算法。在首次适应算法的基础上加以改进,改进点为从上一次查找结束的位置继续往下搜索,若到最后一个位置仍无法找到,则循环到链首。
优先使用最小的空闲分区。
由于优先使用最小的空闲分区,所以每次分配后切割下来的剩余部分总是最小的,在存储器中就会留下许多难以利用的碎片。但是能够尽可能留下大的空闲分区,为大进程保留较大的空间。
优先使用最大的空闲分区。
由于优先使用最大的空闲分区,所以每次分割下来的空闲分区不至于太小,产生碎片的概率最小。但是会使较大的连续空闲分区被用完,不利于之后大进程的分配。
算法 | 思想 | 分区排序 | 优点 | 缺点 |
---|---|---|---|---|
首次适应 | 从低地址查找合适空间 | 地址递增 | 优先利用低地址空间 | 每次都需要从低地址开始,增加查找开销 |
循环首次适应 | 从上次查找处向后查找 | 地址递增 | 不用从头开始查找,减小查找开销 | 会使高地址大空闲分区也被用完 |
最佳适应 | 优先使用最小空闲空间 | 容量递增 | 保留大的空闲分区,以满足大进程需求 | 容易产生难以利用的小碎片 |
最坏适应 | 优先使用最大空闲空间 | 容量递减 | 不容易产生小碎片,查找效率高 | 大的空闲分区容易被用完,不利于大进程的分配 |
快速适应算法的基本思想是为不同大小的内存请求维护多个预定义的空闲链表或分区。这些链表或分区根据其大小将可用内存块划分为不同的类别。每个类别的链表中存放着相应大小的可用内存块。
当有一个内存请求时,快速适应算法会先检查与请求大小最接近的类别的链表。如果链表中有足够大的内存块,则将请求分配给该块。如果找不到足够大的内存块,算法会继续检查更大或更小的类别链表,直到找到适合的内存块或遍历完所有链表。
规定分区的大小都为2^k,对于具有相同大小的所有空闲分区,为其单独设立一个空闲分区双向链表。
当需要分配一个大小为n的空间时,首先计算一个值使得2^(i-1)<= n <= 2^i,然后在大小为2^i的空闲分区链表中进行查找,若大小为2^i的空闲分区已耗尽,则查找大小为2^(i+1)的空闲分区链,若存在则将这块空闲分区分割为两个相等大小的分区,这两个大小相等的分区就称为”一对伙伴“,其中一块分配给进程,另一块作为空闲分区添加到大小为2^i的空闲分区链中。
利用哈希快速查找的优点,以及空闲分区在可利用空闲分区链表中的分布规律,建立哈希函数,构造一张以空闲分区大小为关键字的哈希表。该表的每个表项均记录了一个对应的空闲分区链表表头指针。
当进行空闲分区分配时,根据所需空闲分区的大小,通过哈希函数计算得到哈希表中的位置,从而得到相应的空闲分区链表,最终实现分配。
在介绍分页存储管理方式之前,我们先来承上启下,为什么会有分页存储管理方式?
在前面介绍到的连续分配方式中,由于多个进程不断被加载到内存和从内存中卸载,会导致内存空间出现分散的未被使用的小块空间,这些小块空间就称为外部碎片。正是由于分配和释放内存空间的不连续性(一个进程的空间是连续的,但多个进程间可能不连续),使得内存中的可用空间不连续,无法满足大块连续内存的分配需求。
虽然可以通过“紧凑”等方式将许多碎片拼接成可用的大块空间,但是由于需要移动大量用户程序和数据,会带来时间和计算资源的开销,所以未必是好的选择。
如果允许将一个进程直接分散地装入许多不相邻的分区中,就可以充分利用内存空间而无需进行”紧凑“。因此就产生了离散分配存储管理方式,包括分页存储管理方式,分段存储管理方式,段页式存储管理方式。
分页存储管理方式将进程的地址空间分为若干个页,并为每个页加以编号,从0开始,如第0页,第1页。对应地,也将内存空间分为若干个块,也对每个块加以编号,从0开始。
在为进程分配内存时,以块为单位,将进程中的若干页分别装入多个可以不相邻的物理块中。由于进程的最后一页经常装不满一块,就形成了不可利用的碎片,称之为**”页内碎片“或”内碎片“**。
将进程分成了多个页,那么页面大小多少才合适呢?
如果页面过小,虽然可以减少内部碎片,有利于提高内存利用率,但是会使得每个进程占有较多的页面,导致进程的页表过长,占用大量内存,同时也会降低页面换入/换出的效率。
如果页面过大,虽然可以减少页表长度,提高页面换入/换出的效率,但又会使内部碎片增大。
因此,在内部碎片、页表长度、换入换出的综合影响下,页面的大小一般为1KB、2KB、4KB、8KB。
更多完整内容可以前往宏夏Coding查看