前言

一、存储器的发展

二、缓存的意义

三、虚拟内存和物理内存

四、mmap知识点补充

五、虚拟内存 - Page falut

六、细节补充


对于虚拟内存和物理内存的理解,我感觉应该从他们的历史和计算机底层来理解。

首先需要了解计算机运行究竟在做什么?如下图(冯诺依曼结构)

所谓计算机处理任务,就是根据输入内容,数据/程序从存储器送往CPU进行处理,然后再将结果输出。

一、存储器的发展

1、CPU和磁盘

我们理想中的存储器应该符合5个特点:

  • (1)稳定、断电不丢失数据
  • (2)存储容量大
  • (3)读写速度快
  • (4)价格便宜
  • (5)体积小

但是像符合以上特点的存储器市面上应该还没有。可能未来能做到。
像我们目前常用的存储设备有:磁盘,类似flash,SD卡,ROM等从广义上来讲,也可以称为磁盘。因为它们的作用都是存储数据,掉电后不丢失。还有硬盘,其实硬盘是最常见的磁盘类型。
像平时的开发中,做数据持久化,意思就是说将数据存储起来,掉电之后不丢失,这其实就是存储在磁盘上面。

目前我们都知道CPU的处理速度是非常快的,但是磁盘的读写速度是很慢的,就算我们现在都用SSD,它的速度也远远跟不上CPU的处理速度。正因为CPU太快,磁盘太慢,所以他们两个是不能互相通信的。所以我们需要加一层过度,这一层过度就是内存。这就是几百块钱一根的内存条的作用和功能。

2、CPU和内存

一般情况下,内存的读写速度比磁盘快几十万倍左右。它终于够资格和CPU直接通信了。
注意:这里说的内存,主要是指主存,就是主板上插的内存条。

有了内存,程序执行过程基本是:
CPU执行任务时,只和内存通信,它从内存获取指令/数据或者写回数据。
内存再与磁盘通信,内存从磁盘读取数据/指令,或者内存将数据写回磁盘。

但是随着CPU快速发展,内存于CPU的速度之间也逐渐增大差距。
它的读写速度比磁盘快了几十万倍。但是相对于CPU的速度依旧还是慢。那么主存和CPU之间,可以继续添加速度更快的过度层。比如intel i7的存储器层次结构是这样的。

根据上图,我们将存储器分为两类:

(1)、易失性存储器:包括内存、SRAM(高级缓存)、DRAM(主存)等,特点是读写速度很快,掉电了数据会丢失,价格贵,并且存储容量较小。
(2)、非易失性存储器:包括磁盘,Flash,光盘,机械硬盘,SSD等,与易失性存储器相比,它们读写速度很慢,但是掉电不丢失数据,存储容量比较大,价格也便宜。

3、关于一些存储器的理解

(1) RAM(Random-Access Memory) 随机访问存储器(易失性存储器)。

也可以访问两类:SRAM(静态的)和DRAM(动态的),并且SRAM的读写速度比DRAM更快,价格也更贵。
在上图中也可以看到, SRAM做L1-L3级缓存,而DRAM做L4级的主存。
静态随机存取存储器(Static Random-Access Memory,SRAM)是随机存取存储器的一种。所谓的“静态”,是指这种存储器只要保持通电,里面储存的数据就可以恒常保持。
相对之下,动态随机存取存储器(DRAM)里面所储存的数据就需要周期性地更新。然而,当电力供应停止时,SRAM储存的数据还是会消失(被称为volatile memory),这与在断电后还能储存资料的ROM或闪存是不同的。

(2) ROM(read-only memory):只读存储器(非易失性存储器)。

这个名字容易让人产生误解,它既可以读,也可以写,称之为read-only只是历史原因。

(3) 闪存(Flash memory):(非易失性存储器)。

SSD,SD卡都属于Flash技术,如果从概念上来讲,他们都属于ROM,这类存储器经常用在手机,相机等设备上。而机械硬盘常用在个人计算机,服务器上。

另外我们在电脑主板上可以看到内存条(L4主存)。硬盘(L5),但是却没看到L3-L0。原因是:他们都是集成在CPU芯片内部的。

二、缓存的意义

不管你中间怎么加缓存,也不管中间的什么SRAM,DRAM的读写速度有多快,然而磁盘的读写速度就是那么慢,所以磁盘与主存之间的交互速度很慢。
CPU归根到底需要向磁盘读写数据。整个环节速度瓶颈就是在磁盘那里,这个根本快不了,那加那么多级缓存,意义有何在呢?

首先先看下图:了解一下硬盘,内存条等之间是怎么通信的。

上图存在三条总线,IO总线,存储器总线(通常称为内存总线),系统总线。在主板上,就是那一排排的32/64根并行的导线。这些导线用来连接CPU,内存,硬盘,以其他外围设备。CPU与存储器,输入输出设备等通信,都是通过总线。不同总线的速度也有差异。

我们来看看,CPU如何读取磁盘中的一个数据。

(1)CPU 将相关的命令和地址,通过系统总线和IO总线传递给磁盘,发起一个磁盘读。
(2)磁盘控制器将相关的地址解析,并通过IO总线与内存总线将数据传给内存。
(3)第2步完成之后,磁盘控制器向CPU发送一个中断信号。这时CPU就知道了,数据已经发送到内存了。

以上三步,第二步磁盘操作很慢,但是第一步CPU发出信号后,就去做其他的事情了,不会参与第二步和第三步,等第三步通过中断,硬盘主动发信号给CPU,你需要的数据已经发送到内存了,然后此时它可以将线程再切换回来,接着和内存通信执行这个该线程的任务。

在执行一个程序时,启动阶段比较慢,因为需要从磁盘读取数据。(而CPU在这个阶段也没闲置浪费,它会进行线程切换执行其他任务)。 但是数据被送往内存之后,它执行起来就会快多了,并且伴随着执行过程,还可能越来越快,因为这些数据,有可能被一级一级的向上送,从L4,送到L3,再送到L2,L1。
所以这就是缓存存在的意义。

三、虚拟内存和物理内存

内存可以分为虚拟内存和物理内存,其中物理内存是实际占用的内存,也就是主存。虚拟内存是在物理内存之上建立的一层逻辑地址,保证内存访问安全的同时为应用提供了连续的地址空间。

首先看几个问题:
1、比如一个应用的汇编代码中,引用了0x123456这个内存地址,另一个程序也有可能引用该地址。为什么没有冲突和错误呢?
2、进程简单的来说就是一个应用程序。具体来说是关于某次数据集合的一次运动活动,是运行在它自己地址空间的一段自包容程序。在一个进程中,好像CPU、内存都只属于这个进程一样,当然这是假象,这些假象又是怎么做到的呢?

1、物理和虚拟寻址

(1)物理寻址

主存就是一个有M个字节大小的单元组成的数组,每字节都有一个唯一的物理地址。它的访问地址和数组一样,第一个地址为0,后面地址依次为 1,2,3 -- M-2,M-1; 这叫做线性地址空间。
早期计算机使用物理寻址方式。
目前有少数现代计算机系统依旧在使用物理寻址方式,比如DSP,嵌入式系统,超级计算机系统。这些系统的主要任务是执行单一任务,不像通用性计算机那样需要执行多任务。

(1)虚拟寻址

现在的多任务计算机时代,普遍使用的是虚拟寻址(virtual addressing)
下图是一个虚拟寻址系统

CPU通过一个虚拟地址来访问主存,这个虚拟地址在被送到主存之前会先转换成一个物理地址。

将虚拟地址转成物理地址的任务叫做地址翻译。
地址翻译需要 CPU 硬件和操作系统之间的配合。

CPU 芯片上叫做内存管理单元(Menory Management Unit, MMU)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理。

关于问题1,就很好解答了:
虚拟内存的概念出现后,程序的编写就不再直接操作物理内存了,对每个程序来说,它们就相当于拥有了所有的内存空间,可以随意操作,就不用担心自己操作的内存地址被其他程序占用的问题了。
同时,因为程序操作的是虚拟内存地址,这样就不会出现因为修改了其他应用程序内存地址中的数据而导致其他应用程序崩溃的问题了(安全,无法访问其他进程的数据)。
关于问题2,很简单,就是引入了虚拟内存。

2、物理内存坑点

  • 会出现内存不够用的情况
  • 会出现内存数据安全问题

3、虚拟内存的工作原理

引用了虚拟内存后 , 在我们认为进程中有一大片连续的内存空间,也就是说从 0x000000 ~ 0xffffff 我们是都可以访问的。
实际上平时开发中访问的内存地址只是一个虚拟地址,这个虚拟地址通过一张映射表映射后才可以获取到真实的物理地址。也就是说,系统对真实物理内存访问做了一层限制,只有被写到映射表中的地址才是被认可可以访问的。虚拟地址 0x000000 ~ 0xffffff 这个范围内的任意地址我们都可以访问,但是这个虚拟地址对应的实际物理地址是计算机来随机分配到内存页上的。
虚拟内存系统将内存分割成大小固定的虚拟页,每一个虚拟页的大小为固定字节。
同样的,物理内存被分割为物理页,大小也为固定字节。

  • Linux 、 MacOs 一页的大小为:4k
  • IOS 从iPhone 6s 开始,物理内存的 Page 大小是 16K,iPhone6 和之前的设备都是 4K,这是 iPhone 6 相比 6s 启动速度断崖式下降的原因之一。
注意:映射表同样是以页为单位的,映射表只会映射到某一页,并不会映射到具体每一个地址。而且 , 应用每次被加载到内存中 , 实际分配的物理内存并不一定是固定或者连续的。

四、mmap知识点补充

mmap 即 memory map,也就是内存映射。

1、传统的读写文件

  • 把文件内容读入到内存中
  • 修改内存中的内容
  • 把内存的数据写入到文件中

参考网上查找的流程图如下:

页缓存(page cache) 是读写文件时的中间层,内核使用页缓存与文件的数据块关联起来。所以应用程序读写文件时,实际操作的是页缓存。
另外可以看出第二步进行了将页缓存的数据复制到了用户空间缓冲区。那么mmap的出现就是为了省掉这一步的操作,从而提高效率。

2、使用mmap读写文件

使用 mmap 系统调用可以将用户空间的虚拟内存地址与文件进行映射(绑定),对映射后的虚拟内存地址进行读写操作就如同对文件进行读写操作一样。

3、mmap数据同步

读写文件都需要经过页缓存,所以 mmap 映射的正是文件的页缓存,而非磁盘中的文件本身。由于 mmap 映射的是文件的页缓存,所以就涉及到同步的问题,即页缓存会在什么时候把数据同步到磁盘。
同步 mmap 映射的内存到磁盘有4个时机:

  • 调用 msync 函数主动进行数据同步(主动)。
  • 调用 munmap 函数对文件进行解除映射关系时(主动)。
  • 进程退出时(被动)。
  • 系统关机时(被动)。

五、虚拟内存 - Page falut

(1)页表

上面说的映射表,就是页表,具体页表的结构包括:

  • 页号
  • 页帧号(物理块号)
  • 状态位(有效位)
  • 访问位
  • 修改位
逻辑地址 = 页号+页内地址
物理地址 = 页帧号+页内地址

(2)Page Falut

我们通过逻辑地址、物理地址、有效位,来记录一下发生一次Page Falut的过程:

  • 当读取虚拟内存,其对应的文件内容在物理内存中不存在时(这里判断是文件是否存在,是通过有效位来判断的),就会出现缺页异常(Page Falut),此时系统发出中断处理程序(File Backed Page In)。
  • 然后将磁盘的数据写入到内存中(也就是对应的物理页中)。
  • 最后再一次读取虚拟内存,此时有效位为1(对应的文件在物理内存存在),从磁盘或者mmap读入数据。

当然一次Page Falut的时间非常的短,但是比如app的启动时刻(冷启动),此时内存中没有app的相关数据时,就会出现大量的pageFalut,从而就会影响到启动速度。

六、细节补充

(1)假如一个进程的物理内存满了,会怎么办。

这里会有一个淘汰原则(也就是会进行数据覆盖)。比如一个进程分配了4个物理页面,已经被使用满了,当其他虚拟页需要有对应的物理页内容时,就会将新的数据在4个物理页中找一页进行覆盖,也就是在覆盖之前,会淘汰一个物理页。
淘汰时会优先淘汰:状态位为1、访问位为0、修改位位0的物理页。

(2)有时候我们通过LLDB进行值打印时,会发现其内存地址指向的值都0。

这是因为加载到物理内存的值(要用的那块物理内存),被覆盖成了0。