为什么进程使用的内存尺寸(虚拟存储)可以比物理内存还大?

系统
虚拟存储是计算机系统的重要概念,可以说,理解好虚拟存储便是掌握了内存管理的钥匙。

为什么一个进程所需的存储空间大小能超过物理内存的大小?操作系统是如何管理机器上运行的多个进程的内存的?进程间共享存储是如何做到的?通过top命令查看的VIRT和RES指标有什么不同?所有这些问题都跟虚拟存储这个概念相关,虚拟存储是计算机系统的重要概念,可以说,理解好虚拟存储便是掌握了内存管理的钥匙。

图片

  • 进程是执行中的程序,一个进程就是一个执行中的程序实例,同一个程序可以有多个执行的实例,对应多个进程。
  • 系统上同时运行的多个进程共享机器的CPU和存储资源,每个进程(线程)有一个独立的逻辑执行流,它提供一种假象,好像在独占的使用处理器;同时,每个进程有一个私有地址空间,这提供另一个假象,它好像在独占的使用存储器。
  • 虚拟存储是一层抽象,它为每个进程提供了一致的、私有的地址空间,借助这一层关键抽象,计算机科学中最深刻最成功的概念“进程”才得以实施。
  • 虚拟存储简化了存储器管理,链接器以独立于内存物理地址的方式构建可执行程序。

虚拟存储空间被分为内核空间和用户空间两部分,并非所有虚拟地址空间都会分配物理内存,只有实际使用的才会分配物理内存,这也体现在通过top命令查看进程的VIRT和RES两项指标的数值差异上。

  • 物理内存可视为一个巨大的字节数组,每个元素占用一个字节,有自己的独立编号(也就是地址),这个地址叫物理地址(Physical Address, PA)。
  • 物理内存资源被运行在系统中的所有进程共享,就像CPU资源被运行在系统中的所有进程/线程共享一样。
  • 应用程序中所使用的地址叫虚拟地址(Virtual Address, VA),每个进程都拥有独立的私有虚拟地址空间,进程之间的虚拟存储是隔离的,既进程A不通过特殊手段无法访问进程B的某个虚拟地址。
  • 虚拟地址在访问真正的物理内存的时候,需要被转换为物理地址, 虚拟地址到物理地址的转换过程叫地址翻译。

  • CPU硬件和操作系统合作完成地址翻译,CPU芯片上的存储管理单元(MMU)查询内存中的页表动态完成地址翻译,而页表的内容由操作系统管理维护,这项工作由底层系统默默完成,对应用程序透明。
  • 进程X和进程Y中的同一虚拟地址会被映射到不同物理地址,多个进程也可以通过共享存储技术(Shared Memory)映射到同一块物理内存。
  • 32位系统的虚拟地址范围是[0-2^32],总共4G Byte,这个地址的集合叫地址空间,64位系统拥有更大的地址空间,但不是2^64。

虽然进程拥有如此大的虚拟地址空间,但它通常不会真正占用这么大存储空间。

系统以页为单位管理存储,32位系统上,PageSize通常为4K字节,64位系统上,PageSize通常为8K字节。

物理存储器(内存)被以PageSize为单位分割为物理页(Physical Page, PP),[0-4096)的连续空间被分为第1页,[4096,8192)的连续空间被分为第2页,以此类推,PageSize大小的物理页也被称为页帧。

虚拟存储空间也被按同样的PageSize分割成虚拟页(Virtual Page, VP),虚拟页分为已分配和未分配两种状态,而已分配的页又被细分为未缓存和已缓存两种状态。

进程的所有已分配的虚拟存储页构成进程的有效虚拟存储空间,对未分配的虚拟存储空间的访问非法,将触发异常(例如常见的段错误)

通过调用malloc/new/mmap等编程接口分配虚拟存储页,对物理内存页的分配由系统负责。

分页后,一个虚拟地址被分为2部分:页编号 + 页内偏移。

操作系统在内存中为每个进程维护一个页表(PageTable, PT), 地址翻译硬件通过查询存放在物理存储器中的页表,来找到对应的物理地址。

每个虚拟页对应一个页表条目(Page Table Entry, PTE),页表视为页表条目的数组,页号就是数组下标(索引)。

每个页表条目包含该虚拟页是否已分配,对未分配的页的访问非法,如果已分配,则又要区分是否已缓存(对应到物理内存页)和未缓存。

如果已缓存(页命中),则页表条目包含该虚拟页对应的物理地址;如果未缓存(页命失),则页表条目包含该虚拟页对应磁盘上该虚拟页的起始位置,系统在物理存储器中挑选一个牺牲页,并将虚拟页从磁盘拷贝到内存,这个过程叫换页(swapping)

牺牲页的内容需要从内存拷贝到磁盘虚拟页,这个过程叫换出(swap out),被缓存的新页需要从磁盘拷贝到内存,这个过程叫换入(swap in),因为牵扯到磁盘操作,过程中,一直等待,成本很高,这个成本被称为命失惩罚,但根据局部性原理,程序经历启动阶段的初始后,通常只会在一个较小的活动页面集上工作,这便能保证,虽然惩罚的成本看似很高,但实际上,它依然工作的很好。

通过页面调度,我们的程序能够在超过物理内存容量的虚拟存储空间下工作,且多个进程间,能有效的共享稀缺的内存资源。

理解这个过程对掌握内存管理和优化技术至关重要。

linux进程虚拟存储空间

图片

linux进程的虚拟存储空间自底向上分为:代码段、全局变量段、堆、共享存储区、栈、内核段。

程序启动时,加载器会将编译后的可执行程序文件中的.text节拷贝到代码段,.data拷贝到全局变量段,每个函数(内联除外)编译后都会占存储空间,进文本节,程序执行中,会从代码段源源不断的加载指令。

堆向上生长,brk指向堆顶,通过malloc/new等编程接口从堆分配内存,动态分配的内存块需要手动释放。

栈向下生长,局部变量位于栈中,函数调用时的参数也经栈传递,函数调用链所需内存由栈提供,栈是自动伸缩的,每个线程会有独立的栈,每个栈的空间有限(4/8M,可调节),所以不能局部变量不能过大,递归过深有爆栈分险。

堆内存和栈内存本质上都是存储区,位于进程的不同区段,只有使用方式上的不同,没有物理介质上的不同,都会经地址翻译到物理内存。

栈和堆之间是共享存储区,通过共享存储编程接口shmget创建的存储段位于该段,标准库的代码在进程间也被共享,这样能够节省内存。

内核段存储空间,用户态代码不可访问,但用户代码调用系统调用、或者触发异常,进程陷入内核,会执行内核段的代码+访问内核态数据。

注意:代码段并非从0开始,而是从特定地址开始,0x8048000(32位系统),0x400000(64位系统)

图片

段页式内存管理

早期计算机系统,采用段页式的内存管理,既有分页,又有分段,段内包含页,但linux系统简化了这个管理方式,进程的虚拟地址空间只有1段,所以相当于变相的废弃了分段。

如果以4K为一页,那么32位系统,虚拟内存空间为4G(2^32),因为页表是进程私有,所以,一个进程的虚拟地址空间包含1M页,需要1M页表项(64位更多),而系统中经常成百上千个进程,这个内存占用量太大了,所以,实际上,操作系统使用多级页表巧妙的解决了这个问题。

图片

多级页表是压缩页表内存占用的惯用法,引入多级页表后,顶级页表的一个表项不再表示4K/8K的地址范围,它大的多,只有一级表项表示的范围被分配,其对应的2级页表才会被存储,所以极大的节省了内存空间,而因为进程实际分配的虚拟页,只是整个地址空间的很小一部分,所以,我们得以以小的存储代价,支撑很大的地址范围。

每次地址翻译,都需要查询内存页表,如果页表条目不在缓存中,则开销很大,为了加快内地翻译速度,便引入了TLB(翻译后备缓存),TLB是MMU中的一个PTE的小的缓存,MMU在走地址翻译的时候,先从TLB查找PTE,失败了再去PageTable里找,还是因为局部性,TLB极大的加速了地址翻译的过程。

软硬件协作

  • 地址翻译,需要操作系统、MMU(TLB)协作完成
  • 操作系统为每个进程维护页表
  • MMU先查TLB缓存,没找到,再查页表
  • 进程调度后,页表基址寄存器会修改指向新的运行进程的页表其实地址

page fault

  • page fault:实际上并不是真正的错误,没有名字看起来这么严重,指令执行时,引发缺页(page fault)会触发异常,操作系统的异常处理程序会妥善处置这个异常,并再次发射这个指令,这时候,因为请求的地址已经被装载到内存,指令得以正常进行。
  • major page fault: 也叫hard page fault,major page fault主要是由swapping机制引入的,因为牵扯到磁盘io,主要由软件完成,所以速度较慢,可通过swapon/swapoff开关系统交换分区,也可以设置修改系统设置:swappiness
  • minor page fault:也叫soft page fault,指需要访问的代码/数据已经在物理内存页中,但页表中的映射还没有建立,需要MMU建立物理内存和虚拟地址空间的映射关系。当一个进程在调用malloc获取虚拟空间地址后,首次访问该地址会发生一次soft page fault。多个进程访问同一个共享内存中的数据,当某些进程还没有建立起映射关系,访问时也会出现soft page fault。

可以通过命令:ps -eo min_flt,maj_flt,cmd 查看系统各个进程的page fault情况。

当程序通过malloc分配1G字节的内存时,系统并不会为进程分配1G的物理内存,而只是分配1G的虚拟内存页,当之后真正访问某个页的时候,系统才会为它分配物理内存,如果系统物理内存被耗尽,则会挑选一个内存页,交换出去(把页的内容写入磁盘页),通过这样的策略,我们能获得比物理内存更大的存储空间,提高了内存资源的利用率,也使得进程之间共享存储变得可能。

责任编辑:赵宁宁 来源: 码砖杂役
相关推荐

2019-12-26 08:45:46

Linux虚拟内存

2020-04-14 16:03:31

Linux虚拟内存操作系统

2011-05-24 16:39:09

Cfree()

2020-11-02 07:05:54

虚拟内存Go

2017-07-25 15:09:48

Linux地址转化

2023-10-18 13:25:00

操作系统进程

2016-05-26 12:11:00

Redis内存开源

2017-01-18 21:57:14

2012-06-06 09:37:58

虚拟化

2018-08-24 10:35:49

物理内存存储

2018-01-12 14:35:00

Linux进程共享内存

2021-08-10 09:58:59

ThreadLocal内存泄漏

2021-07-30 06:49:40

SSD内存CPU

2018-04-24 14:58:06

内存降价涨价

2019-07-29 07:50:42

Linux内存Windows

2020-02-12 09:34:37

软件微软硬件

2010-07-23 09:52:27

虚拟桌面虚拟服务器

2018-05-18 08:43:27

Linux内存空间

2010-06-10 17:12:23

Linux 内存监控

2014-03-26 10:09:14

指针指针使用
点赞
收藏

51CTO技术栈公众号