虚拟内存和物理内存的区别
原文博客:TOMORROW星辰
本文将从单个进程能申请到的最大虚拟内存空间开始深入探讨Linux操作系统虚拟内存和物理内存的关系。
环境:
虚拟机:VMware12、2G 内存、2G 交换区。
编译器:gcc
CPU:Intel core i5 x64
为了高效、准确测试出该系统下,单个进程能够申请到的最大虚存空间,所以编写了一个Linux的测试程序。因为 64 位真的是个很可怕的数字,所以程序在申请内存空间时,先申请较大的内存块(100G),直到没有这么大的内存块,然后申请上次能申请到的内存块的一半。重复以上步骤,直到内存块变得足够小(小于 100Byte)。然后结束申请内存。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
#include<malloc.h> #define SZ_100G (50*2147483648) //100GB的字节数 int main() { int *p[1000000]; //存放申请内存块的指针以备释放 int *ptem; long long int block_sz,total_sz=0; int i,j; char c= 'c' ; printf ( "pid=%d\n" ,getpid()); getchar (); block_sz=SZ_100G; for (i=0;;i++) { printf ( "i=%d\n" ,i); p[i]=( int *) malloc (block_sz* sizeof ( char )); if (NULL==p[i]) //当所申请的内存块不成功时,把内存块大小减半重新申请 { block_sz=block_sz/2; p[i]=( int *) malloc (block_sz* sizeof ( char )); } total_sz=total_sz+block_sz; //累加所申请到的内存块 if (block_sz<100) //当内存块小于100个字节时结束内存申请 break ; } getchar (); ptem=p[0]; for (j=0;;j++) { if (0==j%1000) c= getchar (); if ( 'e' ==c) break ; *(ptem+=(2*1024*1024))=c; } for (;i>=0;i--) //释放所有内存块 free (p[i]); printf ( "total_sz=%ldByte\n" ,total_sz); return 0; } |
在终端1编译运行上面代码。
运行后,先在另一个终端(终端2)执行:
1
|
cat /proc/6674/status |
查看该进程的status文件如下图图一所示:
终端1 终端2
图一
对于status文件,本文只会关注以下几个参数:
VmPeak(进程所占用的虚存空间最大值)
VmRSS(进程正在占用物理内存大小)
VmSwap(进程占用交换区大小)
然后回车开始申请内存,当终端停止输出数字时,再次在终端2执行:
1
|
cat /proc/6674/status |
得到下图图二输出:
终端1 终端2
图二
对比图一和图二中的VmPeak:
137438953320K – 12044K = 140737475866624 Byte
= 111 1111 1111 1111 1111 1111 0100 0001 0111 0000 0000 0000(B) Byte
是的,如果你没有眼花,你数到上面得到的是一个47位!!!!二进制数。
47位什么概念?大概是128TB = 128*1024GB !!! (试问现在谁的个人电脑有这么大的硬盘??更不要说内存)
一个进程能够申请到这么恐怖的内存空间?这不但超过了物理内存、超过了物理内存+交换区、还超过了硬盘大小啊。这不科学啊。
但是从status读出来的数据错不了的。
首先,虚拟内存,顾名思义,虚拟的、并不是事实上存在,在一个进程的虚存空间里,只存在进程自己和系统内核,而不存在其他进程。这是为了方便编程和提高物理内存利用率而创造出来的一种机制(在过去内存是很贵的)。虚拟内存中对应着的是逻辑地址,逻辑地址通过操作系统和硬件的配合映射到物理内存上。(这里就不在多说虚拟内存的定义。如果把段页式内存管理机制理解后,虚拟内存也就理解了。关于段页式内存管理介绍可参考本博客稍后发的文章。)
其二,交换区,实际上就是物理内存不够用时,虚存空间的数据就必须映射到交换区上。
那么单个进程所能申请的最大虚存空间理应不会超过物理内存和交换区的和。然而实际却是超过那么多。
然后,网上查阅相关资料,msdn上看到了相关解释。
传送门:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/gettingstarted/virtual-address-spaces
该文章介绍到,Windows 32系统下,虚拟内存中,用户空间占用了低地址2G的空间,系统内核占用了高地址2G空间。总共虚存空间就是2^32Byte。
图三
那么64位系统中,就系统而言,总共的虚存空间应当是2^64Byte?
在该文章下面还有Windows 64位系统的虚存空间介绍,如下图图四所示。从图中看到用户虚存空间8TB+系统空间248TB=256TB=2^48 Byte ,这个数字似乎和上面所测得的单个进程能够申请到的最大虚存空间的数字有点接近了。
图四
注意看图四,还可以发现64位系统中还有很大很大的虚存空间保留没有被使用的。从这个出发继续查阅资料,然后找到了关于目前64位CPU的相关说明。由于目前还远远用不到64位那么大的空间,所以AMD 64位CPU目前只用了48位的寻址。而Intel的64位CPU是和AMD交叉授权,所以Intel 64CPU也同样只采用48位寻址。所以图三的保留空间就得到了解释。
再回到原先的问题,现在知道了就64位系统而言,虚拟内存空间是可以达到2^48Byte那么大的,参考Windows 64位系统虚存空间结构,可以猜测Linux 64位系统下,用户虚存空间和系统内核虚存空间分布和Windows是相似的,只是两者大小比例有所差别。(因为找了很久,没有找到Linux的官方文档说明,只找到很旧的、32位。所以不能提供准确的参考,如果有读者找到,希望可以告诉作者一下补上)。
不过,到现在,还有问题没有解决,为什么所申请的虚存空间会比物理内存与交换区的和大?
现在回到一开始没有运行完的程序,在终端1回车继续运行程序,程序接着会对所申请到的第一个100G内存块每隔2M空间进行写操作,每回车一次,会写1000次。回车几次后,在终端2再执行:
1
|
cat /proc/6674/status |
得到下图图五:
图五
由图五可以看到正在使用的物理内存VmRSS变小了,正在使用的交换区空间VmSwap迅速增大。但是两者之和是在一直增加的,这就说明,申请到的虚拟内存在未被使用之前,它只是一个数字,并没有实际的物理内存和交换区与之相对应。当对虚存进行写操作时,系统就会逐步分配物理内存,而物理内存的数据又会可能被系统调到交换区。现在问题逐渐明了了。
如果我不停地对虚存空间进行写操作会怎样,为了解决疑惑,在终端1不停回车,偶尔在终端2中查看status文件中的状态,写到一定程度后,终端1出现了
1
|
[1] 7893 killed . /a .out |
如图六所示:
图六
在进程结束之前查看到的status文件显示VmRSS+VmSwap约等1.8G,加上系统占用和其他进程占用,那么说此时物理内存和交换区已经接近极限了。再继续运行写的时候,操作系统为了系统的正常运行选择把这个进程杀死了。那么所有的疑问也解决了。
系统所允许的申请的虚存空间是可以超过物理内存与交换区的和的。但是当进程所占用的物理内存加上交换区影响到了系统的正常运行就会被系统杀死。
最后,希望这篇文章能够帮到一些正在学习操作系统或者内存管理相关知识的朋友。
如果有错误,还望不吝指正。
原文博客:TOMORROW星辰