虚拟内存 :一个程序最多能使用多少内存?

虚拟内存


虚拟化技术中,操作系统设计了虚拟内存(理论上可以无限大的空间),受限于 CPU 的处理能力,通常 64bit CPU,就是 264 个地址。

虚拟化技术中,应用使用的是虚拟内存,操作系统管理虚拟内存和真实内存之间的映射。操作系统将虚拟内存分成整齐小块,每个小块称为一个页(Page)。之所以这样做,原因主要有以下两个方面。

  • 一方面应用使用内存是以页为单位,整齐的页能够避免内存碎片问题。
  • 另一方面,每个应用都有高频使用的数据和低频使用的数据。这样做,操作系统就不必从应用角度去思考哪个进程是高频的,仅需思考哪些页被高频使用、哪些页被低频使用。如果是低频使用,就将它们保存到硬盘上;如果是高频使用,就让它们保留在真实内存中。

如果一个应用需要非常大的内存,应用申请的是虚拟内存中的很多个页,真实内存不一定需要够用。

页(Page)和页表


接下来,我们详细讨论下这个设计。操作系统将虚拟内存分块,每个小块称为一个页(Page);真实内存也需要分块,每个小块我们称为一个 Frame。Page 到 Frame 的映射,需要一种叫作页表的结构。

上图展示了 Page、Frame 和页表 (PageTable)三者之间的关系。 Page 大小和 Frame 大小通常相等,页表中记录的某个 Page 对应的 Frame 编号。

页表也需要存储空间,比如虚拟内存大小为 10G, Page 大小是 4K,那么需要 10G/4K = 2621440 个条目。如果每个条目是 64bit,那么一共需要 20480K = 20M 页表。

操作系统在内存中划分出小块区域给页表,并负责维护页表。

虚拟内存

在用户的视角里,每个进程都有自己独立的地址空间A进程的4GB和B进程4GB是完全独立不相关的,他们看到的都是操作系统虚拟出来的地址空间。

但是呢,虚拟地址最终还是要落在实际内存的物理地址上进行操作的。

操作系统就会通过页表的机制来实现进程的虚拟地址到物理地址的翻译工作。其中每一页的大小都是固定的。

页表管理有两个关键点,分别是页面大小和页表级数。

页表维护了虚拟地址到真实地址的映射。每次程序使用内存时,需要把虚拟内存地址换算成物理内存地址,换算过程分为以下 3 个步骤:

  1. 通过虚拟地址计算 Page 编号
  2. 查页表,根据 Page 编号,找到 Frame 编号
  3. 将虚拟地址换算成物理地址。

下面我通过一个例子给你讲解上面这个换算的过程:如果页大小是 4K,假设程序要访问地址:100,000。那么计算过程如下。

页编号(Page Number) = 100,000/4096 = 24 余1619。 24 是页编号,1619 是地址偏移量(Offset)。

查询页表,得到 24 关联的 Frame 编号(假设查到 Frame 编号 = 10)。

换算:通常 Frame 和 Page 大小相等,替换 Page Number 为 Frame Number 物理地址 = 4096 * 10 + 1619 = 42579。

 

 

MMU


上面的过程发生在 CPU 中一个小型的设备——内存管理单元(Memory Management Unit, MMU)中。如下图所示:

当 CPU 需要执行一条指令时,如果指令中涉及内存读写操作,CPU 会把虚拟地址给 MMU,MMU 自动完成虚拟地址到真实地址的计算;然后,MMU 连接了地址总线,帮助 CPU 操作真实地址。

这样的设计,就不需要在编写应用程序的时候担心虚拟地址到物理地址映射的问题。我们把全部难题都丢给了操作系统——操作系统要确定MMU 可以读懂自己的页表格式。所以,操作系统的设计者要看 MMU 的说明书完成工作。

难点在于不同 CPU 的 MMU 可能是不同的,因此这里会遇到很多跨平台的问题。解决跨平台问题不但有繁重的工作量,更需要高超的编程技巧,Unix 最初期的移植性(跨平台)是 C 语言作者丹尼斯·里奇实现的。

学到这里,细心的同学可能会有疑问:MMU 需要查询页表(这是内存操作),而 CPU 执行一条指令通过 MMU 获取内存数据,难道可以容忍在执行一条指令的过程中,发生多次内存读取(查询)操作?难道一次普通的读取操作,还要附加几次查询页表的开销吗?当然不是,这里还有一些高速缓存的设计。

 

 

 

页表条目


上面我们笼统介绍了页表将 Page 映射到 Frame。那么,页表中的每一项(页表条目)长什么样子呢?下图是一个页表格式的一个演示。

页表条目本身的编号可以不存在页表中,而是通过偏移量计算。 比如地址 100,000 的编号,可以用 100,000 除以页大小确定。

Absent(“在”)位,是一个 bit。0 表示页的数据在磁盘中(不再内存中),1 表示在内存中。如果读取页表发现 Absent = 0,那么会触发缺页中断,去磁盘读取数据。

Protection(保护)字段可以实现成 3 个 bit,它决定页表用于读、写、执行。比如 000 代表什么都不能做,100 代表只读等。

Reference(访问)位,代表这个页被读写过,这个记录对回收内存有帮助。

Dirty(“脏”)位,代表页的内容被修改过,如果 Dirty =1,那么意味着页面必须回写到磁盘上才能置换(Swap)。如果 Dirty = 0,如果需要回收这个页,可以考虑直接丢弃它(什么也不做,其他程序可以直接覆盖)。

Caching(缓存位),描述页可不可以被 CPU 缓存。CPU 缓存会造成内存不一致问题,在上个模块的加餐中我们讨论了内存一致性问题,具体你可以参考“模块四”的加餐内容。

Frame Number(Frame 编号),这个是真实内存的位置。用 Frame 编号乘以页大小,就可以得到 Frame 的基地址。

在 64bit 的系统中,考虑到 Absent、Protection 等字段需要占用一定的位,因此不能将 64bit 都用来描述真实地址。但是 64bit 可以寻址的空间已经远远超过了 EB 的级别(1EB = 220TB),这已经足够了。在真实世界,我们还造不出这么大的内存呢。

 

 

 

大页面问题


最后,我们讨论一下大页面的问题。假设有一个应用,初始化后需要 12M 内存,操作系统页大小是 4K。那么应该如何设计呢?

为了简化模型,下图中,假设这个应用只有 3 个区域(3 个段)——正文段(程序)、数据段(常量、全局变量)、堆栈段。一开始我们 3 个段都分配了 4M 的空间。随着程序执行,堆栈段的空间会继续增加,上不封顶。


上图中,进程内部需要一个页表存储进程的数据。如果进程的内存上不封顶,那么页表有多少个条目合适呢? 进程分配多少空间合适呢? 如果页表大小为 1024 个条目,那么可以支持 1024*4K = 4M 空间。按照这个计算,如果进程需要 1G 空间,则需要 256K 个条目。我们预先为进程分配这 256K 个条目吗? 创建一个进程就划分这么多条目是不是成本太高了?

为了减少条目的创建,可以考虑进程内部用一个更大的页表(比如 4M),操作系统继续用 4K 的页表。这就形成了一个二级页表的结构,如下图所示:

这样 MMU 会先查询 1 级页表,再查询 2 级页表。在这个模型下,进程如果需要 1G 空间,也只需要 1024 个条目。比如 1 级页编号是 2, 那么对应 2 级页表中 [2* 1024, 3*1024-1] 的部分条目。而访问一个地址,需要同时给出一级页编号和二级页编号。整个地址,还可以用 64bit 组装,如下图所示:

MMU 根据 1 级编号找到 1 级页表条目,1 级页表条目中记录了对应 2 级页表的位置。然后 MMU 再查询 2 级页表,找到 Frame。最后通过地址偏移量和 Frame 编号计算最终的物理地址。这种设计是一个递归的过程,因此还可增加 3 级、4 级……每增加 1 级,对空间的利用都会提高——当然也会带来一定的开销。这对于大应用非常划算,比如需要 1T 空间,那么使用 2 级页表,页表的空间就节省得多了。而且,这种多级页表,顶级页表在进程中可以先只创建需要用到的部分,就这个例子而言,一开始只需要 3 个条目,从 256K 个条目到 3 个,这就大大减少了进程创建的成本。

 

 

 

总结


那么通过这节课的学习,你现在可以尝试来回答本节关联的面试题目:一个程序最多能使用多少内存?

【解析】 目前我们主要都是在用 64bit 的机器。因为 264 数字过于大,即便是虚拟内存都不需要这么大的空间。因此通常操作系统会允许进程使用非常大,但是不到 264 的地址空间。通常是几十到几百 EB(1EB = 106TB = 109GB)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Redis是一个开源的内存数据库,它可以用来做缓存。在 Redis 中,数据是存储在内存中的,因此它的存储容量受到物理内存的限制。 Redis 最多能够存储的数据量取决于你的机器的物理内存大小。 你可以使用 Redis 的 `CONFIG GET maxmemory` 命令来查看 Redis 能够使用最大内存限制。默认情况下,Redis 会尽可能地使用机器的所有可用内存,但是你也可以使用 `CONFIG SET maxmemory` 命令来设置 Redis 的最大内存使用限制。 需要注意的是,虽然 Redis 是内存数据库,但是它也支持将数据写入磁盘,这样就可以在内存满了之后继续插入数据。但是,这样会导致 Redis 的性能下降,因此在实际使用中,你应该尽量避免将数据写入磁盘。 ### 回答2: Redis使用的是内存来存储数据,因此它的最大存储容量是由服务器的物理内存大小决定的。根据官方文档介绍,Redis可以支持的最大存储容量是2^64字节,大约为18,446,744,073,709,551,616字节。但是实际情况中,由于Redis在存储数据时还需要用到一些额外的内存空间来保存数据结构、索引以及其他元数据,所以实际可用的存储容量会比总内存容量小一些。具体可用的存储容量大小会根据数据的类型、数据结构和内存管理策略等因素而有所不同。 另外,还需要注意的是,由于Redis是单线程的应用程序,当达到可用内存的上限时,它将不可避免地导致性能下降,甚至可能会影响系统的稳定性。因此,在实际应用中,我们需要根据实际情况来合理配置Redis的内存容量,以确保系统的性能和稳定性。 ### 回答3: Redis缓存的数据存储量是根据实际情况而定的,并没有固定的数据存储上限。Redis使用的是内存存储方式,因此它的存储能力主要取决于服务器的可用内存大小。一般来说,Redis可以存储的数据量是非常大的,可以达到几十到几百GB。但是,需要注意的是Redis的内存使用效率非常高,通常可以达到物理内存使用率接近100%。但是,当数据量超过可用内存大小时,Redis会使用一种叫做虚拟内存的技术来将一部分数据存储在硬盘上,但这会导致读写速度的下降,因此最好在使用Redis时,保证服务器内存大小能够满足实际的存储需求,以获取较好的性能和响应速度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值