原文博客: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星辰