虚拟地址到物理内存的映射 | 分段机制 分页机制 多级页表 多级页表 快表 段页式

本文详细介绍了内存管理的几种机制,包括静态和动态重定向、分段、分页以及多级页表。分段机制解决了地址空间隔离和程序运行地址不确定性的问题,但存在内存碎片和交换效率低的缺点。分页机制通过固定大小的页提高了内存利用率,但页表占用了大量空间,因此引入了多级页表来减少空间消耗。快表的使用进一步加快了地址转换速度。Linux系统结合了分页和弱化的段机制,通过线性地址空间提高效率。
摘要由CSDN通过智能技术生成

一.地址映射

为了保证CPU执行指令时可以正确访问存储单元,需将用户程序中的逻辑地址转换为运行时由机器直接寻址的物理地址,这一过程称为地址映射.

注意:地址映射的最小单位为1页,4k大小.

地址映射分类:

  1. 静态重定向:当用户程序装入内存时,一次性实现逻辑地址到物理地址的转换,以后不再转换(一般在装入内存时由软件完成)
  2. 动态重定向:在程序运行中要访问数据时再进行地址变换()

当每个进程创建的时候,内核都会为每个进程分配虚拟内存,这个时候数据和代码还都在磁盘上,当运行到对应的程序时,进程去寻找页表,如果发现页表中地址没有存放在物理内存上,而是在磁盘上,于是发生缺页异常,将磁盘上的数据拷贝到物理内存中并更新页表,下次再访问该虚拟地址就可以命中了.

二.物理地址

早期的程序是直接运行在物理地址上的,于是便出现了很多问题:

  1. 内存缺乏访问控制,安全性不足
  2. 各进程同时访问物理内存,可能会互相产生影响,没有独立性
  3. 物理内存极小,而并发执行进程所需内存又大,容易导致内存不足
  4. 进程所需空间不一,容易导致内存碎片化问题

基于上述几点原因,linux通过mm_struct来描述了一个虚拟的,连续的,独立的地址空间,也就是虚拟地址空间.通过增加一个中间层,利用一种间接的地址访问方法访问物理内存.按照这种方法,程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。这样,只要操作系统处理好虚拟地址到物理内存地址的映 射,就可以保证不同的程序最终访问的内存地址位于不同的区域,没有重叠,达到内存地址空间隔离的效果。

注意:在不同机制下虚拟地址的构成都是不同的.(虚拟地址并不是简单的地址)

三.分段机制

在最开始的时候,人们采用的是分段的方法.为了简化地址管理,所以将虚拟内存空间中的虚拟内存按照其逻辑划分为代码段,数据段,堆段,栈段几部分.通过段寄存器中的段表,来将虚拟地址与物理地址进行映射.段表中存储了每一个逻辑段的段号以及对应的物理内存的起始地址.

对于每一个虚拟内存中存储的数据,其虚拟地址都以其所在的段号以及段内偏移组成.

因此虚拟地址与物理地址的转换方式如下:

  1. 根据虚拟地址中的段号查询段表
  2. 根据段表查询到对应的段的物理内存的起始地址
  3. 物理内存起始地址加上段内偏移,即为其对应的物理地址

如上图,例如变量A段号为2,段内偏移为500。首先根据段号查询段表,得知物理内存起始地址位于3000的位置,接着找到对应的起始地址,加上段内偏移500,此时3500的位置即为其对应的物理地址。

通过分段的方式,解决了地址空间不隔离程序运行的地址不确定的问题.但是对于内存的使用效率,分段仍然存在以下两个问题

  1. 内存碎片

  2. 内存交换的效率低

为什么会存在内存碎片的问题呢?

在上面的讲解中可以看出,在分段存储中,一个段内可能保存有多个变量,而这些变量都是从同一个物理地址起始位置开始偏移。因此在物理内存中,同一个段中的数据使用了连续的地址空间。

例如我们有1G的物理内存,倘若我们运行了512M的程序A,接着运行了128M的程序B,128M的程序C。剩余内存为256M

在这里插入图片描述

倘若我们此时结束程序B,释放内存,此时总剩余空间为384M

在这里插入图片描述

倘若我们此时需要运行300M的进程D,但是这时候就会因为剩余空间不连续,导致我们的程序无法运行,这也就是我们常说的内存外碎片问题。

那么如何解决这个问题呢?这就会使用到内存交换。例如上面那种情况,我们就会将程序C写入硬盘的SWAP分区(交换分区,用于内存和硬盘的空间交换)。紧接着再将其从硬盘中读取回来,让其紧挨着程序A的那块内存,这样就能保证后面的空闲内存都是连续的了。

在这里插入图片描述

为什么内存交换的效率低呢?

由于分段对物理内存的映射是以程序为单位,按照其逻辑进行分段映射,如果我们的内存不足,那么被换入换出到硬盘中的都是整个程序,这样就必然会造成大量的磁盘访问操作,众所周知,磁盘IO的速度特别慢,因此就会严重影响我们的访问速度。

根据程序的局部性原理,当一个程序在运行时,在某个时间段内,它只是频繁地用到了一小部分数据,也就是说程序中的很多数据其实在一个时间段内都是不会被用到的。

而我们分段的最大问题就在于其以程序为单位进行映射,因此我们只需要使用更小粒度的存储单位,就可以解决这个问题,大大的提升内存的使用率。因此在后续的设计中,就以作为基本单位,这也就是分页机制的由来

四.分页机制

分页就是将内存空间人为的划分成固定大小的页,每一页的大小由硬件决定.在linux中,一页的大小是4KB. 与段表类似,虚拟地址与物理地址的映射是通过MMU(内存管理单元)中的页表来完成的.

MMU(内存管理单元):是一种负责处理中央处理器的内存访问请求的计算机硬件.功能包括:虚拟地址到物理地址的转换,内存保护,中央处理器高速缓存的控制.

页表中不仅保存了页号,物理内存地址,还保留了该物理页的访问权限,用以实现对页的访问控制

在分页机制下,虚拟地址由页号以及页内偏移组成

因此在分页机制下,虚拟地址与物理地址的转换方式如下

  1. 根据虚拟地址中的页号查询页表

  2. 根据页表查询到对应的页的物理内存起始地址

  3. 物理内存起始地址加上页内偏移,即为其对应的物理地址

 分页是如何解决分段的内存利用率低的问题?

1.使用更小粒度的内存单元

分段所面临的最大问题,无非就是内存碎片以及交换效率低。

导致内存碎片最大的原因就是各个逻辑段的数据需要连续存储,而逻辑段又过大,导致我们需求大量的连续空间。而当我们所有的内存分配释放都以页为单位时,就能够很好的解决这个问题了。

而当内存空间不够时,我们需要进行将内存中的数据暂时写入到硬盘中,之后再重新写回来这样的换入换出操作。而使用页为单位后,即使我们还是需要进行磁盘IO,但是由于我们交换的容量仅仅只有几个页,所以也不会花费过多的时间。

2、不需要将程序一次性加载进内存,什么时候需要,什么时候加载。 按照前面说的,为了满足程序的局部性原理。所以为了能够尽可能提高内存的利用率,在建立了虚拟内存空间后并不会直接分配物理内存,而是在我们程序运行中需要用到的时候,再将其加载进内存中。

所以如果在页表中查找不到时,此时就会由内核的请求分页机制产生缺页中断,然后进入内核态中分配物理内存、更新进程页表,最后再返回用户态,恢复进程的运行。

分页机制有一个致命的缺点就是页表项占用空间大。

在 Linux中,可以并发的执行多个进程,而每个进程都有其自己的虚拟内存空间,那么也自然都有自己独有的页表。在32位Linux系统下,我们的虚拟内存空间的大小为4G,而每页的大小为4K,这也就意味着我们至少有2^20个内存页,倘若每个页表项为4Byte,那么每个页表大小也至少为4M。

倘若我们此时并发了两百个进程,那么占用则高达800M,即使是在现在,这个数字也是非常庞大的,因为并发数百个进程是非常常见的情况,更别提64位的操作系统,随着寻址范围的增加,页表将更为庞大。

为了解决这个问题,就引入了多级页表。

五.多级页表

将一级页表再进行分页,分成1024个二级页表,并且每个二级页表中存有1024个页表项,形成如下的二级分页的结构。

 虽然分级乍一看花费的物理内存变多了,但是实际上对于大多数程序来说,其使用到的空间远未达到 4G,所以会存在部分对应的页表项都是空的,根本没有分配。而对于已分配的页表项,如果存在最近一定时间未访问的页表,在物理内存紧张的情况下,操作系统会将页面换出到硬盘,也就是说不会占用物理内存。

如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了即可以在需要时才创建二级页表。假设每个二级页表大小为4M(1024 * 4K),而我们用到的一级页表只有20%

在这种情况下,页表所占用的物理内存就只有4K + 20% * 4M,即0.804M,比起只用了一级页表的4M,大大的节约了内存。

而在64位系统中,两级页表是肯定不够用的,因此又演变成了四级目录

  1. 全局页目录项 PGD

  2. 上层页目录项 PUD

  3. 中间页目录项 PMD

  4. 页表项 PTE

结构如下图所示:

  六.快表

多级页表虽然解决了页表项空间占用大的问题,但是由于其复杂化了地址的转换,因此也带来了大量的时间开销,使得地址转换速度减慢。

如果要解决这个问题,那么最简单的方式就是降低查询页表的频率,那么如何实现呢?这时候就需要用到缓存的技术

对于热点资源,我们可以将其提前缓存下来,到以后使用时就可以直接到缓存中查找。对于操作系统来说,也是这么一个道理。

在操作系统中,这个缓存就是CPU中TLB,也就是我们通常所说的快表。我们将最常访问的几个页表项存储到TLB中,在之后进行寻址时,CPU就会先到TLB中进行查找,如果没有找到,这时才会去查询页表。

七.段页式

虽然分段和分页各有优缺点,但他们直接并不是对立的,所以如今大部分的内存管理方式,都是将分段与分页相结合,也就是我们常说的段页式

它的原理非常简单,就是先对虚拟内存空间进行分段管理,然后再对每一个段进行分页管理。如下图

 所以此时的虚拟地址结构,就由段号、段内页号、页内偏移所组成。此时对于每个进程来说,都会建立一个段表,而对于段表中的每一个段,又会再分别建立一个页表,如下图

 

 所以此时的虚拟地址转换为物理地址,就需要以下三个步骤

  1. 访问段表,得到页表的起始地址

  2. 访问页表,得到物理页的起始地址

  3. 访问物理页,加上页内偏移,得到实际的物理地址

这种方法虽然增加了系统开销以及硬件成本,但是内存的利用率得到了巨大的提升

八.Linux内存管理机制

由于硬件问题的限制,Linux内存主要采用的是页式内存管理,但同时也不可避免的涉及了段机制.

在往常的机制中,地址的转换流程如下

 

但是在Linux中,并没有逻辑地址这一说(所有段起始地址相同),因为其将段机制进行了弱化,此时段只用于进行访问控制以及内存保护

Linux 系统中的每个段都是从 0 地址开始的整个 4GB 虚拟空间(32 位环境下),也就是所有的段的起始地址都是一样的。 这意味着,Linux系统中的代码,包括操作系统本身的代码和应用程序代码,所面对的地址空间都是线性地址空间(虚拟地址),这种做法相当于屏蔽了处理器中的逻辑地址概念,段只被用于访问控制和内存保护。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值