内存管理(操作系统)

1.什么是物理内存

我们常说的物理内存大小就是指内存条的大小,一般买电脑时都会看下内存条是多大容量的,话说如果内存条大小是100G,那这100G就都能够被使用吗?不一定的,更多的还是要看CPU地址总线的位数,如果地址总线只有20位,那么它的寻址空间就是1MB,即使可以安装100G的内存条也没有意义,也只能视物理内存大小为1MB。

2.使用物理内存有什么缺点

这种方式下每个程序都可以直接访问物理内存,有两种情况:

2.1.一个进程在运行

1.系统中只有一个进程在运行:如果用户程序可以操作物理地址空间的任意地址,它们就很容易在不经意间破坏了操作系统,使系统出现各种奇奇怪怪的问题;

这里大家要注意一个细节,可能有人会说程序不是跑在操作系统之上的吗?

操作系统怎么可能让程序去破坏它自己呢,肯定会阻止啊。但是你不要忘了,cpu在任意一个时刻只能执行一条指令,也就是说此时cpu被程序占用了,此时操作系统属于“瘫痪”的状态,自然程序就可以破坏操作系统了。

2.2.多个进程在运行

2.系统有多个进程同时在运行:如图,理想情况下可以使进程A和进程B各占物理内存的一边,两者互不干扰,但这只是理想情况下,谁能确保程序没有bug呢,进程B在后台正常运行着,程序员在调试进程A时有可能就会误操作到进程B正在使用的物理内存,导致进程B运行出现异常,两个程序操作了同一地址空间,第一个程序在某一地址空间写入某个值,第二个程序在同一地址又写入了不同值,这就会导致程序运行出现问题,所以直接使用物理内存会使所有进程的安全性得不到保证

图片

3.解决方法

可以考虑为存储器创造新的抽象概念:地址空间。地址空间为程序创造了一种抽象的内存,是进程可用于寻址内存的一套地址集合,同时每个进程都有一套自己的地址空间,一个进程的地址空间独立于其它进程的地址空间

3.1.如何为程序创造独立的地址空间

最简单的办法就是把每个进程的地址空间分别映射到物理内存的不同部分。这样就可以保证不同进程使用的是独立的地址空间

3.2.实现

给每个进程提供一个基址A界限B,进程内使用的空间为x,则对应的物理地址为A + x,同时需要保证A + x < B,如果访问的地址超过的界限,需要产生错误并中止访问。为了达到目的CPU配置了两个特殊硬件寄存器:基址寄存器界限寄存器,当一个进程运行时,程序的起始物理地址和长度会分别装入到基址寄存器和界限寄存器里,进程访问内存,在每个内存地址送到内存之前,都会先加上基址寄存器的内容。

**缺点:**每次访问内存都需要进行加法和比较运算,比较运算很快,但是加法运算由于进位传递事件的问题,在没有使用特殊电路的情况下会显得很慢。

此外,每个进程运行都会占据一定的物理内存,如果物理内存足够大到可以容纳许多个进程同时运行还好,但现实中物理内存的大小是有限的,可能会出现内存不够用的情况,怎么办?

方法一:如果是因为程序太大,大到超过了内存的容量,可以采用手动覆盖技术,只把需要的指令和数据保存在内存中。

方法二:如果是因为程序太多,导致超过了内存的容量,可以采用自动交换技术,把暂时不需要执行的程序移动到外存中。

3.3.覆盖技术

  • 把程序按照自身逻辑结构,划分成多个功能相互独立的程序模块,那些不会同时执行的模块可以共享到同一块内存区域,按时间顺序来运行:

  • 将常用功能需要的代码和数据常驻在内存中;

  • 将不常用的功能划分成功能相互独立的程序模块,平时放到外存中,在需要的时候将对应的模块加载到内存中;

  • 那些没有调用关系的模块平时不需要装入到内存,它们可以共用一块内存区,需要时加载到内存,不需要时换出到外存中;

  • 图片

    虽然这样确实是可行的,但是它对程序员的要求就高很多了。而且,如果

    是这样的话,开发效率太慢,开发周期太长。

3.4.交换技术

多个程序同时运行,可以将暂时不能运行的程序送到外存,获得更多的空闲内存,操作系统将一个进程的整个地址空间内容换出到外存中,再将外存中某个进程的整个地址空间信息换入到内存中,换入换出内容的大小是整个程序的地址空间。

交换技术在实现上有很多困难:

  • 需要确定什么时候发生交换:简单的办法是可以在内存空间不够用时换出一些程序;

  • 交换区必须足够大:多个程序运行时,交换区(外存)必须足够大,大到可以存放所有程序所需要的地址空间信息;

  • 程序如何换入:一个程序被换出后又重新换入,换入的内存位置可能不会和上一次程序所在的内存位置相同,这就需要动态地址映射机制。

3.5.覆盖技术和交换技术的比较

覆盖只能发生在那些相互之间没有调用关系的程序模块之间,因此程序员必须给出程序内的各个模块之间的逻辑覆盖结构。

交换技术是以在内存中的程序大小为单位来进行的,它不需要程序员给出各个模块之间的逻辑覆盖结构。

通俗来说:覆盖发生在程序的内部,交换发生在程序与程序之间

但是这两种技术都有缺点

覆盖技术:需要程序员自己把整个程序划分为若干个小的功能模块,并确定各个模块之间的覆盖关系,增加了程序员的负担,很少有程序员擅长这种技术;

交换技术:以进程作为交换的单位,需要把进程的整个地址空间都换进换出,增加了处理器的开销,还需要足够大的外存。

那有没有更好的解决上述问题的方法呢?答案是虚拟内存技术

4.什么是虚拟内存

虚拟内存,那就是虚拟出来的内存,它的基本思想就是确保每个程序拥有自己的地址空间,地址空间被分成多个块,每一块都有连续的地址空间,同时物理空间也分成多个块,块大小和虚拟地址空间的块大小一致,操作系统会自动将虚拟地址空间映射到物理地址空间,程序所关注的只是虚拟内存,请求的也是虚拟内存,而真正使用的是物理内存。

  • 虚拟内存技术有覆盖技术的功能,但它不是把程序的所有内容都放在内存中,因而能够运行比当前的空闲内存空间还要大的程序。它比覆盖技术做的更好,整个过程由操作系统自动来完成,无需程序员的干涉.

  • 虚拟内存技术有交换技术的功能,能够实现进程在内存和外存之间的交换,因而获得更多的空闲内存空间。它比交换技术做的更好,它只对进程的部分内容在内存和外存之间进行交换

4.1.虚拟内存技术的具体实现

虚拟内存技术一般是在页式管理(下面介绍)的基础上实现

在装入程序时,不必将其全部装入到内存,而只需将当前需要执行的部分页面装入到内存,就可让程序开始执行;

在程序执行过程中,如果需执行的指令或访问的数据尚未在内存(称为缺页)。则由处理器通知操作系统将相应的页面调入到内存,然后继续执行程序;

将页面导入内存是由操作系统来完成的。

我的猜测是,处理器通知操作系统的方式是通过中断。

另一方面,操作系统将内存中暂时不使用的页面调出保存在外存上,从而腾出更多空闲空间存放将要装入的程序以及将要调入的页面。

4.2.虚拟内存技术的特点

通过把物理内存与外存相结合,提供给用户的虚拟内存空间通常大于实际的物理内存,即实现了两者的分离。如32位的虚拟地址理论上可以访问4GB,而可能计算机上仅有256M的物理内存,但硬盘容量大于4GB;

  • 部分交换:与交换技术相比较,虚拟存储的调入和调出是对部分虚拟地址空间进行的;
  • 连续性:程序可以使用一系列相邻连续的虚拟地址来映射物理内存中不连续的大内存缓冲区;
  • 安全性:不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。

5.虚拟内存技术硬件支持

CPU里有一个内存管理单元(Memory Management Unit),简称MMU,虚拟内存不是直接送到内存总线,而是先给到MMU,由MMU来把虚拟地址映射到物理地址,程序只需要管理虚拟内存就好,映射的逻辑自然有其它模块。

6.分页内存管理

将虚拟地址空间分成若干个块,每个块都有固定的大小,物理地址空间也被划分成若干个块,每个块也都有固定的大小,物理地址空间的块和虚拟地址空间的块大小相等,虚拟地址空间这些块就被称为页面,物理地址空间这些块被称为

在页面与帧之间有对应关系,对应关系的集合就是页表。

关于分页这里有个问题,页面的大小是多少合适呢?页面太大容易产生空间浪费,程序假如只使用了1个字节却被分配了10M的页面,这岂不是极大的浪费,页面太小会导致页表(下面介绍)占用空间过大,所以页面需要折中选择合适的大小,目前大多数系统都使用4KB作为页的大小。

上面关于虚拟内存如何映射到物理内存程只介绍了MMU,但是MMU是如何工作的还没有介绍,MMU通过页表这个工具将虚拟地址转换为物理地址。32位的虚拟地址分成两部分(虚拟页号和偏移量),MMU通过页表找到了虚拟页号对应的物理页号,物理页号+偏移量就是实际的物理地址。

图片

图只表示了页表的大体功能,页表的结构其实还很复杂,下面会具体介绍。

页表的目的就是虚拟页面映射为物理内存的页框,页表可以理解为一个数学函数,函数的输入是虚拟页号,函数的输出是物理页号,通过这个函数可以把虚拟页面映射到物理页号,从而确定物理地址。不同机器的页表结构不同,通常页表的结构如下:

图片

  • **页框号:**最主要的一项,页表最主要的目的就是找到物理页号;
  • **有效位:**1表示有效,表示该表项是有效的,如果为0,表示该表项对应的虚拟页面现在不在内存中,访问该页面会引起缺页中断,缺页中断后会去物理空间找到一个可用的页框填回到页表中;
  • **保护位:**表示一个页允许什么类型的访问,可读可写还是可执行;
  • **修改位:**该位反应了页面的状态,在操作系统重新分配页框时有用,在写入一页时由硬件自动设置该位,重新分配页框时,如果一个页面已经被修改过,则必须把它这个脏页写回磁盘,如果没有被修改过,表示该页是干净的,它在磁盘上的副本依然是有效的,直接丢弃该页面即可。
  • **访问位:**该位主要用于帮助操作系统在发生缺页中断时选择要被淘汰的页面,不再使用的页面显然比正在使用的页面更适合被淘汰,该位在页面置换算法中发挥重要作用。
  • 高速缓存禁止位: 该位用于禁止该页面被高速缓存。

6.1.如何加快地址映射速度

每次访问内存都需要进行虚拟地址到物理地址的映射,每次映射都需要访问一次页表,所有的指令执行都必须通过内存,很多指令也需要访问内存中的操作数,因此每条指令执行基本都会进行多次页表查询,为了程序运行速度,指令必须要在很短的时间内执行完成,而页表查询映射不能成为指令执行的瓶颈,所以需要提高页表查询映射的速度。

如何才能提高速度呢?可以为页表提供一个缓存,通过缓存进行映射比通过页表映射速度更快,这个缓存是一个小型的硬件设备,叫快表(TLB),MMU每次进行虚拟地址转换时,首先去TLB中查找,找到了有效的物理页框则直接返回,如果没有找到则进行正常的页表访问,页表中找到后则更新TLB,从TLB中淘汰一个表项,然后用新找到的表项替代它,这样下次相同的页面过来时可以直接命中TLB找到对应的物理地址,速度更快,不需要继续去访问页表。

这里之所以认为TLB能提高速度主要依靠程序局部性原理,程序局部性原理是指程序在执行过程中的一个较短时间,所执行的指令地址和要访问的数据通常都局限在一块区域内,这里可分为时间局部性和空间局部性:

  • 时间局部性:一条指令的一次执行和下次执行,一个数据的一问和下次访问都集中在一个较短时间内;

  • 空间局部性:当前指令和邻近的几条指令,当前访问的数据和邻近的几个数据都集中在一个较小区域内。

通过TLB可以加快虚拟地址到物理地址的转换速度,还有个问题,现在都是64位操作系统啦,有很大的虚拟地址空间,虚拟地址空间大那对应的页表也会非常大,又加上多个进程多个页表,那计算机的大部分空间就都被拿去存放页表,有没有更好的办法解决页表大的问题呢?答案是多级页表

tips:页表为什么大?32位环境下,虚拟地址空间有4GB,一个页大小是4KB,那么整个页表就需要100万页,而每个页表项需要4个字节,那整个页表就需要4MB的内存空间,又因为每个进程都有一个自己的页表,多个进程情况下,这简直就是灾难。

forecast:页表并不在内存地址空间当中。

图片

如图,以一个32位虚拟地址的二级页表为例,将32位虚拟地址划分为10位的PT1域,10位的PT2域,以及12位的offset域,当一个虚拟地址被送入MMU时,MMU首先提取PT1域并把其值作为访问第一级页表的索引,之后提取PT2域把把其值作为访问第二级页表的索引,之后再根据offset找到对应的页框号。

32位的虚拟地址空间下:每个页面4KB,且每条页表项占4B:

一级页表:进程需要1M个页表项(4GB / 4KB = 1M, 2^20个页表项),即页表(每个进程都有一个页表)占用4MB(1M * 4B = 4MB)的内存空间。

二级页表:一级页表映射4MB(2^22)、二级页表映射4KB,则需要1K个一级页表项(4GB / 4MB = 1K, 2^10个一级页表项)、每个一级页表项对应1K个二级页表项(4MB / 4KB = 1K),这样页表占用4.004MB(1K * 4B + 1K * 1K * 4B = 4.004MB)的内存空间。

二级页表占用空间看着貌似变大了,为什么还说多级页表省内存呢

每个进程都有4KB的虚拟地址空间,而显然对于大多数程序来说,其使用到的空间远未达到4KB,何必去映射不可能用到的空间呢?

也就是说,一级页表覆盖了整个4GB虚拟地址空间,但如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表。做个简单的计算,假设只有20%的一级页表项被用到了,那么页表占用的内存空间就只有0.804MB(1K4B+0.21K1K4B=0.804MB),对比单级页表的4M是不是一个巨大的节约?

那么为什么不分级的页表就做不到这样节约内存呢?我们从页表的性质来看,保存在主存中的页表承担的职责是将虚拟地址翻译成物理地址假如虚拟地址在页表中找不到对应的页表项,计算机系统就不能工作了。所以页表一定要覆盖全部虚拟地址空间,不分级的页表就需要有1M个页表项来映射,而二级页表则最少只需要1K个页表项(此时一级页表覆盖到了全部虚拟地址空间,二级页表在需要时创建)。

二级页表其实可以不在内存中:其实这就像是把页表当成了页面。当需要用到某个页面时,将此页面从磁盘调入到内存;当内存中页面满了时,将内存中的页面调出到磁盘,这是利用到了程序运行的局部性原理。我们可以很自然发现,虚拟内存地址存在着局部性,那么负责映射虚拟内存地址的页表项当然也存在着局部性了!这样我们再来看二级页表,根据局部性原理,1024个第二级页表中,只会有很少的一部分在某一时刻正在使用,我们岂不是可以把二级页表都放在磁盘中,在需要时才调入到内存。

7.什么是缺页中断

缺页中断就是要访问的页不在主存中,需要操作系统将页调入主存后再进行访问,此时会暂时停止指令的执行,产生一个页不存在的异常,对应的异常处理程序就会从选择一页调入到内存,调入内存后之前的异常指令就可以继续执行。

缺页中断的处理过程如下:

  1. 如果内存中有空闲的物理页面,则分配一物理页帧r,然后转第4步,否则转第2步;
  2. 选择某种页面置换算法,选择一个将被替换的物理页帧r,它所对应的逻辑页为q,如果该页在内存期间被修改过,则需把它写回到外存;
  3. 将q所对应的页表项进行修改,把驻留位置0;
  4. 将需要访问的页p装入到物理页面r中;
  5. 修改p所对应的页表项的内容,把驻留位置1,把物理页帧号置为x;
  6. 重新运行被中断的指令。

おすすめ

転載: blog.csdn.net/qq_46359697/article/details/110982376