操作系统是如何管理内存的?

本文主要介绍了计算机通过一种“虚拟内存”技术来实现精细化的内存管理。

虚拟内存是如何实现的?它又有哪些特性?我们又如何通过这些特性来最大化得优化我们的应用软件性能呢? 这是本文即将讨论的话题。

物理内存

计算机对于内存真正的载体是物理内存条,这个是实打实的物理硬件容量,所以在操作系统中定义这部门的容量叫物理内存。 实则物理内存的布局实际上就是一个内存大数组

1

一个操作系统是不可能只运行一个程序的,那么这个大数组物理内存势必要被N个程序分成N分,供每个程序使用。但是程序是活的,一个程序可能一会需要1MB的内存,一会又需要1GB的内存。操作系统只能取这个程序允许的最大内存极限来分配内存给这个进程,但这样会导致每个进程都会多要去一大部分内存,而这些多要的内存却大概率不会被使用。

2

虚拟内存

虚拟内存地址是基于物理内存地址之上凭空而造的一个新的逻辑地址,而操作系统暴露给用户进程的只是虚拟内存地址,操作系统内部会对虚拟内存地址和真实的物理内存地址做映射关系,来管理地址的分配,从而使物理内存的利用率提高。 从程序的角度来看,它觉得自己独享了一整块内存,且不用考虑访问冲突的问题。

3

虚拟内存还实现了“读时共享,写时复制”的机制,可以在物理层同一个字节的内存地址被多个虚拟内存空间映射。

4

上图所示如果一个进程需要进行写操作,则这个内存将会被复制一份,成为当前进程的独享内存。如果是读操作,可能会多个进程访问的物理空间是相同的空间。

如果一个内存几乎大量都是被读取的,则可能会多个进程共享同一块物理内存,但是他们的各自虚拟内存是不同的。当然这个共享并不是永久的,当其中有一个进程对这个内存发生写,就会复制一份,执行写操作的进程就会将虚拟内存地址映射到新的物理内存地址上。

虚拟内存也会“开疆拓土”从磁盘(硬盘)上虚拟出一定量的空间,挂在虚拟地址上,而且这个动作进程本身是不知道的,因为进程只能够看见自己的虚拟内存空间

5

6

虚拟内存的实现

PTE是Page Table Entry的缩写,表示页表条目。PTE是由一个有效位和N位地址字段构成,能够有效标识这个虚拟内存地址是否分配了物理内存,以及通过地址找到物理内存。

7

有效位:0 or 1(0:无效,1:有效)

9

页表实际上就是基于PTE的一个数组。

“页”、“页表条目”、“PTE”这三个概念是不同的。

页是操作系统中用来描述内存大小的一个基本单位,操作系统将虚拟内存空间分成一页一页的来管理,每页的大小为 4K。一般来讲,一页包含多个PTE。(4K 算是通过实践折中出来的通用值,太小了会出现频繁的置换,太大了又浪费内存。)

所以,也可以这样描述“页表”:页表实际上是基于页的一个数组。

8

虚拟页表(简称页表)虽然作为虚拟内存与物理内存的映射关系,但是本身也是需要存放在某个位置上,所以自身本身也是占用一定内存的。所以页表本身也被操作系统放在物理内存的指定位置。

MMU内存管理单元与TLB高速缓存

MMU是在CPU里的,专门用来管理虚拟内存和物理内存映射关系(Memory Management Unit)。

CPU 访问内存的流程

10

  1. CPU得到内存相关的寄存器指令。
  2. CPU加载该指令到寄存器。
  3. CPU对相关可能请求的内存生成虚拟内存地址。一个虚拟内存地址包括虚拟页号VPN(Virtual Page Number)和虚拟页偏移量VPO(Virtual Page Offset)。
  4. 从虚拟地址中得到虚拟页号VPN。
  5. 调用MMU内存管理单元。
  6. MMU通过虚拟页号查找对应的PTE条目(优先查TLB缓存查询)。
  7. 通过PTE的有效位来判断当前虚拟页是否在主存中。
  8. 如果PTE的有效位为1,则表示命中,将对应PTE上的物理内存页号PPN(Physical Page Number)和虚拟地址中的虚拟页偏移量VPO进行串联从而构造出主存中的物理地址PA(Physical Address),进入步骤(9)。
  9. 通过物理内存地址访问物理内存,当前的寻址流程结束。
  10. 如果PTE的有效位为0,则表示未命中,一般称这种情况为缺页。此时MMU将产生一个缺页异常,抛给操作系统。
  11. 操作系统捕获到缺页异常,开始执行异常处理程序。
  12. 此时将选择一个牺牲页并将对应的所缺虚拟页调入并更正新页表上的PTE,如果当前牺牲页有数据,则写入磁盘,得到物理内存页号PPN(Physical Page Number)。
  13. 缺页处理程序更新之前索引到的PTE,并且写入物理内存怒页号PPN,有效位设置为1。
  14. 缺页处理程序再次返回到原来的进程,且再次执行缺页指令,CPU重新将虚拟地址发给MMU,此时虚拟页已经存在物理内存中,本次一定会命中,通过(1)~(9)流程,最终将请求的物理内存返回给处理器。

以上就是一次CPU访问内存的详细流程。可以看出来上述流程中,从第(10)步之后的流程就稍微有一些繁琐。类似产生异常信号、捕获异常,再处理缺页流程,如选择牺牲页,还要将牺牲页的数据存储到磁盘上等等。所以如果频繁的执行(10)~(14)步骤会对性能影响很大。因为牺牲页有可能会涉及到磁盘的访问,而磁盘的访问速度非常的慢,这样就会引发程序性能的急剧下降。

一般从(1)~(9)步流程结束则表示页命中,反之为未命中,所以就会出现一个新的性能级指标,即命中率。命中率是访问次数与页命中次数之比。一般命中率低说明物理内存不足,数据在内存和磁盘之间交换频繁,但如果物理内存充分,则不会出现频繁的内存颠簸现象。

通过内存的局部性大幅度优化软件性能

上述了解到内存的命中率实际上是一衡量每次内存访问均能被页直接寻址到而不是产生缺页的指标。所以如果经常在一定范围内的内存则出现缺页的情况就会降低。这就是程序的一种局部性特性的体现。 局部性就是在多次内存引用的时候,会出现有的内存被经常引用多次,而且在该位置附近的其他位置,也有可能接下来被引用到。一般大多数程序都会具备局部性的特点。 实际上操作系统在设计过程中经常会用到缓存来提升性能,或者在设计解决方案等架构的时候也会考虑到缓存或者缓冲层的概念,实则就是利用程序或业务天然的局部性特征。因为如果没有局部性的特性,则缓存级别将起不到太大的作用,所以在设计程序或者业务的时候应该多考虑增强程序局部性的特征,这样的程序会更快。 下面是一个非常典型的案例来验证程序局部性的程序示例,具体代码如下:


原文链接:操作系统是如何管理内存的?

Buy me a coffee~
室长 支付宝支付宝
室长 微信微信
0%