linux进程虚拟内存

update
2019-06-28 20:40
在 stack exchange 社区上看到一个通俗易懂的回答:https://superuser.com/a/1165426


在现代操作系统中,进程之间共享使用cpu和内存,但是内存资源有限,为了更加高效地使用内存,现代操作系统提供一个内存抽象—虚拟内存。虚拟内存巧妙地利用内存,地址转换,磁盘文件和操作系统内核来为每一个进程提供足够大的统一的私有地址空间。虚拟内存提供三个重要的能力:1)将内存当作磁盘的缓存,在内存中只保留常用数据,必要时从内存和磁盘之间交换数据。2)简化内存管理,为每个进程提供统一的地址空间。3)保护进程的内存空间不受其他进程影响。
虚拟内存是计算机系统里的一个很棒的方案。它的实现对于程序员来说是透明的,但是程序员为什么需要了解它的工作机制呢?1)虚拟内存的位置比较核心,它渗透到计算机系统的方方面面,比如硬件异常设计,汇编器,加载器,共享对象文件,文件和进程。2)虚拟内存功能非常强大,它能给应用程序提供创建销毁一块内存并将其映射到磁盘文件的能力,还能与其他进程共享内存。比如,你能通过读写特定内存地址来改变磁盘文件的内容,还可以在不需要copy操作的情况下,将磁盘文件的内容加载到内存里。

物理地址与虚拟地址

linux进程虚拟内存_第1张图片
上图是cpu通过物理地址读取4个字节示意图。当cpu执行加载操作,它将有效的物理地址通过地址总线传给内存,内存从地址4开始读取4个字节,将这4个字节内容返回给cpu。

linux进程虚拟内存_第2张图片
上图是cpu通过虚拟地址读取4个字节示意图。cpu在加载虚拟地址(VA)之前,MMU(地址管理单元)会将VA转换成PA(物理地址),然后在执行正常的读取操作。

VM作为缓存工具

从概念上来讲,虚拟内存一种存储在磁盘上的N个连续字节的数组。对应与数组中的每个字节都有其唯一的虚拟地址。数组中的部分字节内容缓存在内存中。内存与磁盘之间交换的时候以内存页(VPs)为最小单位,对应地,物理内存在分页,PPs。
linux进程虚拟内存_第3张图片
在某个时刻,虚拟内存页(VPs)可以有一下三种:

  1. 未分配的。虚拟内存系统还没分配,这种情况下,不会占用磁盘空间。
  2. 缓存的。已分配的页,并且缓存在内存中。
  3. 未缓存的。已分配的页,没有缓存在内存中。

页表

VM system 必须确认某个虚拟页(VP) 是否存储在DRAM中. 如果在,还要确定存储在那个物理内存页中(PP).
linux进程虚拟内存_第4张图片

命中缓存

linux进程虚拟内存_第5张图片

未命中缓存

linux进程虚拟内存_第6张图片
这种情况会出发操作系统内核page fault exception,系统内核会选择一个内存页剔除,将存储在磁盘上对VP(图中就是VP3)加载到内存中。这里假设内核选择了将VP4剔除出去,这时如果VP4内容相比在磁盘中的VP4有变更,内核会将DRAM中的VP4拷贝到磁盘中,然后修改页表内对应的PTE的valid位为0,标明该VP没有在DRAM中。

分配页

操作系统通过系统调用malloc来分配一页虚拟内存。例如下图就是一个分配虚拟内存的例子。在磁盘上为虚拟内存页VP5分配空间,并且在页表上登记VP5。
linux进程虚拟内存_第7张图片

VM作为内存管理工具

上面讲了那么多都是关于VM作为缓存工具的内容,其实有意思的是,在很多计算机系统里虚拟内存比物理内存要小,这样看起来就没必要使用缓存了。但是VM另有大用,它可以作为有效的内存管理工具来保护内存。
上面的内容都是假设有一张页表,将一个虚拟内存空间映射到物理内存空间,其实,操作系统为每个进程提供来单独的页表和单独的虚拟内存空间。请看下面的示意图。
linux进程虚拟内存_第8张图片
进程i的VP1映射到PP2,VP2映射到PP7,进程j的VP1映射到PP7,VP2映射到PP10,进程i和共享PP7.
VM能够简化链接加载代码与数据共享以及程序到内存分配。

  • 简化链接。每个进程有自己单独的内存空间,这样它们的内存镜像可以使用相同的基本格式,比如在Linux操作系统上,每个进程都有相同的内存布局。代码段(text section)总是从虚拟内存0x08048000(32bit system)或者0x400000(64bit system)开始。紧接着是数据段(data section and bss section)。进程的栈空间总是占据内存空间的高区,并且向下“膨胀”。这样的统一使得链接器的设计和实现变得简单,不管代码区或者数据区在物理内存的那个地方,链接器总能够生产一个可执行文件。
  • 简化加载。VM能够是加载器很容易地加载可执行文件和共享对象文件到内存中。ELF格式的可执行文件里的.text段和.data段是连续相邻的,为了将其加载到内存中,加载器从0x08048000(32bit system)或者0x400000(64bit system)开始分配一块连续虚拟内存,并在页表中标记valid位为0(没有命中缓存,not cached),然后将对应到PTE执行可执行文件到对应位置(这里就是.text段)。可以看出,这个过程加载器并不需要从磁盘copy内容到内存里,CPU加载或者执行指令时候返现相应到VP没有在缓存中,这时才会触发数据从磁盘copy内容到内存。操作系统将连续的虚拟内存映射到一个文件到任意位置的能力称作内存映射(memory mapping)。Unix操作系统的mmap系统调用允许应用程序做内存映射操作。
  • 简化共享。单独的地址空间为操作系统提供了一个统一的机制用于管理用户进程和操作系统之间的内存共享。正常情况下,每个进程都有自己的代码、数据、堆和栈区域,进程之间不会共享,操作系统为每个进程创建的页表将进程虚拟页映射到不相交到物理内存中。但是有时候需要需要进程之间共享数据和代码。比如每个进程都要调用相同到操作系统内核代码,每个C程序都要调用标准C库函数printf函数。这时就要用到上图示意的共享PP的场景了。
  • 简化内存分配。VM为用户态进程提供了一个简单机制用于分配额外内存。比如说,进程想额外从堆上分配内存(通过malloc系统调用),操作系统会分配k个连续堆虚拟内存页(k of VPs),因为有页表的存在,在物理内存DRAM上没必要分配连续k页内存,k页可以分散开来。

VM作为内存保护工具

通过在页表上加上三个权限位(sup, write, read)来控制内存的访问权限来保存内存。
linux进程虚拟内存_第9张图片
sup(supervisor)如果是yes,那么代表该VP只有在进程运行在kernal模式下才能访问,否则不能访问。read和write分别代表读写权限。

地址转换

这里介绍一下地址转换的基础知识。
地址转换就是一个映射的过程(map)。将N大小的虚拟地址空间映射到M大小的物理地址空间。

MAP: VAS → PAS ∪ ∅
where
MAP(A) 	= A′ if data at virtual addr A is present at physical addr A′ in PAS,
		= ∅ if data at virtual addr A is not present in physical memory.

先定义一些符号:
linux进程虚拟内存_第10张图片
下图展示了MMU如何利用页表来完成映射的。CPU里有个寄存器(page table base register)指向当前页表。n-bit虚拟地址分成两部分表示,其中p-bit位表示虚页offset(virtual page offset (VPO)),剩下(n-p)bit表示虚页号(virtual page number (VPN))。MMU根据VPN找到对应的PTE,比如VPN1对应PTE1,VPN2对应PTE2,类似,VPNn对应PTEn。虚页个数总是与页表PTE的个数一致。通过PTE里记录的物理地址页号( physical page number (PPN))组合VPO就可以得到物理地址了。假设虚拟页和物理页的offset都是p-bit,那么VPO和PPO是相等的。
linux进程虚拟内存_第11张图片
下面详细列举一下page hit的过程,如下图:
linux进程虚拟内存_第12张图片

1. CPU将虚拟地址传给MMU,
2. MMU生成PTE页表地址,去内存里拿页表。
3. 内存返回页表给MMU,
4. MMU根据页表查表后得到物理地址,去请求内存
5. 根据物理地址,内存将数据返回给CPU

上面的步骤看出,page hit 这种情况可以在硬件基础上完成,可是 page fault 就需要操作系统内核配合完成了,下图是page fault 情况下的寻址:
linux进程虚拟内存_第13张图片

1. 1,2,3步同上面相同。
4.  发现PTE的valid标志位是0,MMU将CPU的执行权交给操作系统内核的page fault 异常处理程序(page fault exception handler)。
5. 异常处理程序选中一个物理内存页(PP)剔除出去,如果该页内容相比之前有变动,同时将其内容写回磁盘。
6.  异常处理程序将需要的页从磁盘里读取到内存里,并且更新内存里的页表里的PTE。
7. 异常处理程序执行结束返回,CPU继续之前的寻址操作,现在一切情况都和 page hit 情况一样了。

不管是 page hit 还是 page fault,MMU想要查表就必须有表可查,表是放在内存中的,每次CPU寻址,都要去内存中去获取页表,貌似浪费很多个时钟周期啊。如果PTE缓存在CPU的L1缓存中,那么获取查表速度就提升很多了。目前translation lookaside buffer (TLB)就是干这个事儿的。

Linux进程虚拟内存模型

linux进程虚拟内存_第14张图片

操作系统内核如何追踪进程的各个区段?
linux进程虚拟内存_第15张图片

http://engineering.pivotal.io/post/virtual_memory_settings_in_linux_-_the_problem_with_overcommit/

https://manybutfinite.com/post/anatomy-of-a-program-in-memory/

http://www.science.unitn.it/~fiorella/guidelinux/tlk/node50.html

https://www.tldp.org/LDP/sag/html/vm-intro.html

https://stackoverflow.com/questions/4970421/whats-the-difference-between-virtual-memory-and-swap-space

你可能感兴趣的:(linux)