作業系統虛擬記憶體
應用程式通過malloc函式申請記憶體的時候,實際申請的是虛擬記憶體,并不會分配物理記憶體
當應用程式讀寫了這塊虛擬記憶體,CPU就會去訪問這個虛擬記憶體,這時會發現虛擬記憶體沒有映射到物理記憶體,CPU就會產生缺頁中斷,行程會從用戶態切換到記憶體態,并將缺頁中斷交給內核的page Fault handler(缺頁中斷函式)處理,
缺頁中斷處理函式會看是否有空閑的物理記憶體
- 如果有,就直接分配物理記憶體,并建立虛擬記憶體和物理記憶體之間的映射關系
- 如果沒有空閑的物理記憶體,內核就會開始進行回收記憶體的作業,如果回收記憶體作業結束后,空閑的物理記憶體仍然無法滿足此次物理記憶體的申請,那么內核就會觸發OOM(Out of Memory)機制
32位作業系統和64位作業系統的虛擬地址空間大小是不同的,在Linux作業系統中,虛擬地址空間的內部又被分為內核空間和用戶空間兩部分:

- 2位系統的內核空間占用1G,位于最高處,剩下的3G是用戶空間;
- 64位系統的內核空間和用戶空間都是128T,分別占據整個記憶體空間的最高和最低處,剩下的中間部分是未定義的,
在32位作業系統上,行程最多申請3GB大小的虛擬記憶體空間,所以行程申請8GB記憶體的話,在申請虛擬記憶體階段就會失敗(可能錯誤原因是OOM)
在64位作業系統,行程可以使用128T大小的虛擬記憶體空間,所以行程申請8GB記憶體是沒問題的,因為行程申請記憶體是申請虛擬記憶體,只要不讀寫這個虛擬記憶體,作業系統就不會分配物理記憶體,
測驗方案:
先查尋當前設備物理記憶體
free -m
在機器上連續申請4次1GB記憶體,只單純分配了虛擬記憶體,并沒有使用此虛擬記憶體
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define MEM_SIZE 1024 * 1024 * 1024
int main() {
char* addr[4];
int i = 0;
for(i = 0; i < 4; ++i) {
addr[i] = (char*) malloc(MEM_SIZE);
if(!addr[i]) {
printf("執行 malloc 失敗, 錯誤:%s\n",strerror(errno));
return -1;
}
printf("主執行緒呼叫malloc后,申請1gb大小得記憶體,此記憶體起始地址:0X%x\n", addr[i]);
}
//輸入任意字符后,才結束
getchar();
return 0;
}
雖然物理記憶體只有2GB,但是程式正常分配了4G大小的虛擬記憶體:

通過一下命令查看行程(test)的虛擬記憶體大小:
ps aux
其中,VSZ 就代表行程使用的虛擬記憶體大小,RSS 代表行程使用的物理記憶體大小,可以看到,VSZ 大小為 4198540,也就是 4GB 的虛擬記憶體,


Swap機制的作用
如果申請物理記憶體大小超過了空閑物理記憶體的大小,就要看作業系統有沒有開啟Swap機制:
- 如果沒有開啟Swap機制,程式就會直接OOM
- 如果有開啟Swap機制,程式可以正常運行
什么是Swap機制?
當系統的物理記憶體不夠用的時候,就需要將物理記憶體中的一部分空間釋放出來,以供當前運行程式使用,那些被釋放的空間可能來自一些很長時間沒有什么操作的程式,這些被釋放的空間會被臨時保存到磁盤,等到那些程式要運行時,再從磁盤中恢復資料到記憶體中,
當記憶體使用存在壓力的時候,會開始觸發記憶體回收行為,會把這些不常訪問的記憶體先寫到磁盤中,然后釋放這些記憶體,給其他更需要的行程使用,再次訪問這些記憶體時,重新從磁盤中讀入記憶體就可以了,
這種,將記憶體資料換出磁盤,又將磁盤中恢復資料到記憶體的程序,就是Swap機制負責的,
Swap就是把一塊磁盤空間或者本地檔案,當成記憶體來使用,包含換出和換入兩個程序:
- 換出(Swap Out),是把行程暫時不用的記憶體資料存盤到磁盤中,并釋放這些資料占用的記憶體
- 換入(Swap In),是在行程再次訪問這些記憶體的時候,把它們從磁盤讀到記憶體中來;

使用 Swap 機制優點是,應用程式實際可以使用的記憶體空間將遠遠超過系統的物理記憶體,由于硬碟空間的價格遠比記憶體要低,因此這種方式無疑是經濟實惠的,當然,頻繁地讀寫硬碟,會顯著降低作業系統的運行速率,這也是 Swap 的弊端,
Linux中的Swap機制會在記憶體不足和記憶體閑置的場景下觸發:
- 記憶體不足:當系統需要的記憶體超過了可用的物理記憶體時,內核會將記憶體中不常使用的記憶體頁交換到磁盤上為當前行程讓出記憶體,保證正在執行的行程的可用性,這個記憶體回收的程序是強制的直接記憶體回收(Direct Page Reclaim),直接記憶體回收是同步的程序,會阻塞當前申請記憶體的行程,
- 記憶體閑置:應用程式在啟動階段使用的大量記憶體在啟動后往往都不會使用,通過后臺運行的守護行程(kSwapd),我們可以將這部分只使用一次的記憶體交換到磁盤上為其他記憶體的申請預留空間,kSwapd 是 Linux 負責頁面置換(Page replacement)的守護行程,它也是負責交換閑置記憶體的主要行程,它會在空閑記憶體低于一定水位時,回收記憶體頁中的空閑記憶體保證系統中的其他行程可以盡快獲得申請的記憶體,kSwapd 是后臺行程,所以回收記憶體的程序是異步的,不會阻塞當前申請記憶體的行程,
Linux 提供了兩種不同的方法啟用 Swap,分別是 Swap 磁區(Swap Partition)和 Swap 檔案(Swapfile):
- Swap 磁區是硬碟上的獨立區域,該區域只會用于交換磁區,其他的檔案不能存盤在該區域上,我們可以使用 Swapon -s 命令查看當前系統上的交換磁區;
- Swap 檔案是檔案系統中的特殊檔案,它與檔案系統中的其他檔案也沒有太多的區別
Swap換入換出的是什么型別的記憶體?
內核快取的檔案資料,因為都有對應的磁盤檔案,所以在回收檔案資料的時候, 直接寫回到對應的檔案就可以了,
但是像行程的堆、堆疊資料等,它們是沒有實際載體,這部分記憶體被稱為匿名頁,而且這部分記憶體很可能還要再次被訪問,所以不能直接釋放記憶體,于是就需要有一個能保存匿名頁的磁盤載體,這個載體就是 Swap 磁區,
匿名頁回收的方式是通過 Linux 的 Swap 機制,Swap 會把不常訪問的記憶體先寫到磁盤中,然后釋放這些記憶體,給其他更需要的行程使用,再次訪問這些記憶體時,重新從磁盤讀入記憶體就可以了,
實驗測驗:
我的Linux系統鏡像是32位作業系統,物理記憶體設定2GB,帶有Swap磁區:

測驗代碼直接申請4GB虛擬記憶體后,通過memset函式訪問
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define MEM_SIZE 1024 * 1024 * 1024
int main() {
char* addr[4];
int i = 0;
for(i = 0; i < 4; ++i) {
addr[i] = (char*) malloc(MEM_SIZE);
if(!addr[i]) {
printf("執行 malloc 失敗, 錯誤:%s\n",strerror(errno));
return -1;
}
printf("主執行緒呼叫malloc后,申請1gb大小得記憶體,此記憶體起始地址:0X%x\n", addr[i]);
}
for(i = 0; i < 4; ++i) {
printf("開始訪問第 %d 塊虛擬記憶體(每一塊虛擬記憶體為 1 GB)\n", i + 1);
memset(addr[i], 0, MEM_SIZE);
}
//輸入任意字符后,才結束
getchar();
return 0;
}
運行結果:

結論:
在有Swap機制的情況下,雖然物理記憶體只有2GB,也是可以申請4GB虛擬記憶體并使用的
修改測驗代碼:
申請8G記憶體(因為物理記憶體加Swap區接近8G),測驗結果如下:

8G記憶體可以正常申請,但是當訪問前7G虛擬記憶體區域時都是正常的,當訪問第8G時,被系統kill,意味著發生了OOM,查詢日志檔案,發現確實kernel發生了oom-kill,

總結
因為手頭暫時沒有現成的64位鏡像,所64位結論采用參考文章的結論
-
在32位作業系統,因為行程最大只能申請3GB大小的虛擬記憶體,所以直接申請8G記憶體會申請失敗
-
在64位作業系統,因為行程最大能申請128T大小的虛擬記憶體,即使物理記憶體只有4GB,申請8GB也是沒有問題的,因為申請的記憶體是虛擬記憶體,如果這塊虛擬記憶體被訪問了,要看系統有沒有Swap磁區:
-
如果沒有 Swap 磁區,因為物理空間不夠,行程會被作業系統殺掉,原因是 OOM(記憶體溢位);
-
如果有 Swap 磁區,即使物理記憶體只有 2GB,程式也能正常使用 4GB 的記憶體(4G物理記憶體,申請8G記憶體同理),行程可以正常運行;
參考文章: https://mp.weixin.qq.com/s/QstLn7xmzS6TXb27_oLoSg
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/499569.html
標籤:Linux
上一篇:認識網路IO模型
下一篇:認識網路IO模型
