一文喫透 Linux 虛擬內存:原理、機制與優化
在當今的數字化時代,計算機早已成爲我們生活和工作中不可或缺的夥伴。無論是日常辦公處理文檔,還是運行大型遊戲享受沉浸式娛樂體驗,亦或是企業服務器高效處理海量數據,計算機都需要迅速且穩定地對數據進行存取。而在這背後,Linux 虛擬內存技術扮演着至關重要的角色。它就像一位神奇的管家,巧妙地協調着物理內存與硬盤空間,爲計算機系統的高效運轉提供堅實保障。
你是否好奇,爲何計算機能同時運行多個看似內存需求巨大的程序,卻不會因內存不足而頻繁卡頓?Linux 虛擬內存究竟是如何在幕後默默施展它的 “魔法”,實現內存的高效管理和利用的?接下來,就讓我們一同深入 Linux 虛擬內存的奇妙世界,從原理到機制,再到優化策略,全方位喫透這一關鍵技術,探尋它爲計算機性能提升帶來的無限可能 。
一、虛擬內存技術
1.1 爲什麼需要使用虛擬內存
進程需要使用的代碼和數據都放在內存中,比放在外存中要快很多。問題是內存空間太小了,不能滿足進程的需求,而且現在都是多進程,情況更加糟糕。所以提出了虛擬內存,使得每個進程用於 3G 的獨立用戶內存空間和共享的 1G 內核內存空間。(每個進程都有自己的頁表,才使得 3G 用戶空間的獨立)這樣進程運行的速度必然很快了。而且虛擬內存機制還解決了內存碎片和內存不連續的問題。爲什麼可以在有限的物理內存上達到這樣的效果呢?
1.1 虛擬內存概述
在深入探討 Linux 虛擬內存之前,先來明晰虛擬內存的基本概念。虛擬內存,簡單來說,是一種內存管理技術,它爲每個進程提供了一個獨立的、連續的地址空間,讓進程誤以爲自己擁有一塊完整且足夠大的內存空間 ,而無需關心實際物理內存的具體佈局和大小限制。這就好比你擁有一個超大的虛擬倉庫,你可以隨意規劃貨物的擺放位置,而不用擔心倉庫空間不夠。
虛擬內存的主要作用之一是實現內存地址轉換。在 Linux 系統中,每個進程都有自己的虛擬地址空間,這個空間通過頁表(Page Table)與物理內存進行映射。頁表就像是一本地址翻譯字典,負責將進程使用的虛擬地址翻譯成實際的物理地址。
當程序運行時,它所訪問的內存地址都是虛擬地址。例如,當程序需要讀取某個變量的值時,它會給出一個虛擬地址。CPU 首先會根據這個虛擬地址中的頁號(Page Number)在頁表中查找對應的物理頁框號(Page Frame Number)。如果頁表中存在這個映射關係(即頁表項有效),CPU 就可以通過物理頁框號和虛擬地址中的頁內偏移(Offset)計算出實際的物理地址,從而訪問到物理內存中的數據。
但如果頁表中沒有找到對應的映射關係(即發生缺頁異常,Page Fault),系統會認爲這個虛擬頁還沒有被加載到物理內存中。此時,操作系統會介入,從磁盤的交換區(Swap Area)或者文件系統中找到對應的物理頁,並將其加載到物理內存中,同時更新頁表,建立虛擬地址與物理地址的映射關係。之後,程序就可以通過新建立的映射關係訪問到數據了。
爲了更直觀地理解,我們可以把虛擬內存想象成一個圖書館的目錄系統。每個進程就像是一個讀者,擁有自己的目錄(虛擬地址空間)。當讀者想要查找某本書(訪問數據)時,會先在自己的目錄中找到對應的條目(虛擬地址),然後通過這個條目去書架(物理內存)上找到實際的書。如果書架上沒有這本書(缺頁異常),圖書館管理員(操作系統)就會從倉庫(磁盤)中把書取出來放到書架上,並更新目錄(頁表),以便下次讀者能更快地找到這本書。
例如:對於程序計數器位數爲 32 位的處理器來說,他的地址發生器所能發出的地址數目爲 2^32=4G 個,於是這個處理器所能訪問的最大內存空間就是 4G。在計算機技術中,這個值就叫做處理器的尋址空間或尋址能力。
照理說,爲了充分利用處理器的尋址空間,就應按照處理器的最大尋址來爲其分配系統的內存。如果處理器具有 32 位程序計數器,那麼就應該按照下圖的方式,爲其配備 4G 的內存:
這樣,處理器所發出的每一個地址都會有一個真實的物理存儲單元與之對應;同時,每一個物理存儲單元都有唯一的地址與之對應。這顯然是一種最理想的情況。
但遺憾的是,實際上計算機所配置內存的實際空間常常小於處理器的尋址範圍,這是就會因處理器的一部分尋址空間沒有對應的物理存儲單元,從而導致處理器尋址能力的浪費。例如:如下圖的系統中,具有 32 位尋址能力的處理器只配置了 256M 的內存儲器,這就會造成大量的浪費:
另外,還有一些處理器因外部地址線的根數小於處理器程序計數器的位數,而使地址總線的根數不滿足處理器的尋址範圍,從而處理器的其餘尋址能力也就被浪費了。例如:Intel8086 處理器的程序計數器位 32 位,而處理器芯片的外部地址總線只有 20 根,所以它所能配置的最大內存爲 1MB:
在實際的應用中,如果需要運行的應用程序比較小,所需內存容量小於計算機實際所配置的內存空間,自然不會出什麼問題。但是,目前很多的應用程序都比較大,計算機實際所配置的內存空間無法滿足。
實踐和研究都證明:一個應用程序總是逐段被運行的,而且在一段時間內會穩定運行在某一段程序裏。
這也就出現了一個方法:如下圖所示,把要運行的那一段程序自輔存複製到內存中來運行,而其他暫時不運行的程序段就讓它仍然留在輔存。
當需要執行另一端尚未在內存的程序段(如程序段 2),如下圖所示,就可以把內存中程序段 1 的副本複製回輔存,在內存騰出必要的空間後,再把輔存中的程序段 2 複製到內存空間來執行即可:
在計算機技術中,把內存中的程序段複製回輔存的做法叫做 “換出”,而把輔存中程序段映射到內存的做法叫做 “換入”。經過不斷有目的的換入和換出,處理器就可以運行一個大於實際物理內存的應用程序了。或者說,處理器似乎是擁有了一個大於實際物理內存的內存空間。於是,這個存儲空間叫做虛擬內存空間,而把真正的內存叫做實際物理內存,或簡稱爲物理內存。
那麼對於一臺真實的計算機來說,它的虛擬內存空間又有多大呢?計算機虛擬內存空間的大小是由程序計數器的尋址能力來決定的。例如:在程序計數器的位數爲 32 的處理器中,它的虛擬內存空間就爲 4GB。
可見,如果一個系統採用了虛擬內存技術,那麼它就存在着兩個內存空間:虛擬內存空間和物理內存空間。虛擬內存空間中的地址叫做 “虛擬地址”;而實際物理內存空間中的地址叫做“實際物理地址” 或“物理地址”。處理器運算器和應用程序設計人員看到的只是虛擬內存空間和虛擬地址,而處理器片外的地址總線看到的只是物理地址空間和物理地址。
由於存在兩個內存地址,因此一個應用程序從編寫到被執行,需要進行兩次映射。第一次是映射到虛擬內存空間,第二次時映射到物理內存空間。在計算機系統中,第兩次映射的工作是由硬件和軟件共同來完成的。承擔這個任務的硬件部分叫做存儲管理單元 MMU,軟件部分就是操作系統的內存管理模塊了。
在映射工作中,爲了記錄程序段佔用物理內存的情況,操作系統的內存管理模塊需要建立一個表格,該表格以虛擬地址爲索引,記錄了程序段所佔用的物理內存的物理地址。這個虛擬地址 / 物理地址記錄表便是存儲管理單元 MMU 把虛擬地址轉化爲實際物理地址的依據,記錄表與存儲管理單元 MMU 的作用如下圖所示:
綜上所述,虛擬內存技術的實現,是建立在應用程序可以分成段,並且具有 “在任何時候正在使用的信息總是所有存儲信息的一小部分” 的局部特性基礎上的。它是通過用輔存空間模擬 RAM 來實現的一種使機器的作業地址空間大於實際內存的技術。
從處理器運算裝置和程序設計人員的角度來看,它面對的是一個用 MMU、映射記錄表和物理內存封裝起來的一個虛擬內存空間,這個存儲空間的大小取決於處理器程序計數器的尋址空間。
可見,程序映射表是實現虛擬內存的技術關鍵,它可給系統帶來如下特點:
-
系統中每一個程序各自都有一個大小與處理器尋址空間相等的虛擬內存空間;
-
在一個具體時刻,處理器只能使用其中一個程序的映射記錄表,因此它只看到多個程序虛存空間中的一個,這樣就保證了各個程序的虛存空間時互不相擾、各自獨立的;
-
使用程序映射表可方便地實現物理內存的共享。
-
二、Linux 虛擬內存工作機制
2.1 內存映射機制
以存儲單元爲單位來管理顯然不現實,因此 Linux 把虛存空間分成若干個大小相等的存儲分區,Linux 把這樣的分區叫做頁。爲了換入、換出的方便,物理內存也就按也得大小分成若干個塊。由於物理內存中的塊空間是用來容納虛存頁的容器,所以物理內存中的塊叫做頁框。頁與頁框是 Linux 實現虛擬內存技術的基礎。
虛擬內存的頁、物理內存的頁框及頁表
在 Linux 中,頁與頁框的大小一般爲 4KB。當然,根據系統和應用的不同,頁與頁框的大小也可有所變化。
物理內存和虛擬內存被分成了頁框與頁之後,其存儲單元原來的地址都被自然地分成了兩段,並且這兩段各自代表着不同的意義:高位段分別叫做頁框碼和頁碼,它們是識別頁框和頁的編碼;低位段分別叫做頁框偏移量和頁內偏移量,它們是存儲單元在頁框和頁內的地址編碼。下圖就是兩段虛擬內存和物理內存分頁之後的情況:
爲了使系統可以正確的訪問虛存頁在對應頁框中的映像,在把一個頁映射到某個頁框上的同時,就必須把頁碼和存放該頁映像的頁框碼填入一個叫做頁表的表項中。這個頁表就是之前提到的映射記錄表。一個頁表的示意圖如下所示:
頁模式下,虛擬地址、物理地址轉換關係的示意圖如下所示:
也就是說:處理器遇到的地址都是虛擬地址。虛擬地址和物理地址都分成頁碼(頁框碼)和偏移值兩部分。在由虛擬地址轉化成物理地址的過程中,偏移值不變。而頁碼和頁框碼之間的映射就在一個映射記錄表——頁表中**。**
話說回來,內存映射是 Linux 中一種重要的內存管理技術,它允許將一個文件或者其他對象映射到進程的虛擬地址空間中,使得進程可以像訪問內存一樣直接訪問文件 。這種技術的核心優勢在於提高了文件訪問的效率,減少了內核和用戶空間之間的數據拷貝。在 Linux 中,內存映射主要通過 mmap() 系統調用實現。
mmap() 函數將文件或其他對象映射到虛擬地址空間的一個連續區域,返回一個指向映射區域開始地址的指針 。對該指針進行讀寫操作,實際上就是在訪問文件內容。使用 munmap() 函數可以解除內存映射。例如,在 C 語言中,可以這樣使用 mmap() 函數:
#include <sys/mman.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
int fd;
char *map_start;
off_t file_size;
// 打開文件
fd = open("test.txt", O_RDWR);
if (fd == -1) {
perror("open");
return 1;
}
// 獲取文件大小
file_size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
// 創建內存映射
map_start = (char *)mmap(0, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map_start == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
// 關閉文件描述符,映射依然有效
close(fd);
// 訪問映射內存,就像訪問文件一樣
printf("Content of file: %s\n", map_start);
// 修改映射內存中的內容
sprintf(map_start, "This is a new content");
// 解除內存映射
if (munmap(map_start, file_size) == -1) {
perror("munmap");
return 1;
}
return 0;
}
在這個例子中,首先打開一個文件,然後使用 mmap() 函數將文件映射到進程的虛擬地址空間。通過返回的指針 map_start,可以像訪問普通內存一樣訪問文件內容。修改 map_start 指向的內存區域,實際上就是在修改文件內容。最後,使用 munmap() 函數解除內存映射。
內存映射在實際應用中有很多場景。比如在共享庫的加載中,多個進程可以映射同一個共享庫文件,實現代碼和數據的共享,減少內存佔用 。在文件 I/O 操作中,對於大文件的讀寫,內存映射可以避免頻繁的系統調用和數據拷貝,提高讀寫效率。例如,數據庫系統通常會使用內存映射來處理數據文件,加快數據的讀取和寫入速度。
2.2 請頁與交換
虛存頁面到物理頁框的映射叫做頁面的加載
當處理器試圖訪問一個虛存頁面時,首先到頁表中去查詢該頁是否已映射到物理頁框中,並記錄在頁表中。如果在,則 MMU 會把頁碼轉換成頁框碼,並加上虛擬地址提供的頁內偏移量形成物理地址後去訪問物理內存;如果不在,則意味着該虛存頁面還沒有被載入內存,這時 MMU 就會通知操作系統:發生了一個頁面訪問錯誤(頁面錯誤),接下來系統會啓動所謂的 “請頁” 機制,即調用相應的系統操作函數,判斷該虛擬地址是否爲有效地址。
如果是有效的地址,就從虛擬內存中將該地址指向的頁面讀入到內存中的一個空閒頁框中,並在頁表中添加上相對應的表項,最後處理器將從發生頁面錯誤的地方重新開始運行;如果是無效的地址,則表明進程在試圖訪問一個不存在的虛擬地址,此時操作系統將終止此次訪問。
當然,也存在這樣的情況:在請頁成功之後,內存中已沒有空閒物理頁框了。這是,系統必須啓動所謂地 “交換” 機制,即調用相應的內核操作函數,在物理頁框中尋找一個當前不再使用或者近期可能不會用到的頁面所佔據的頁框。找到後,就把其中的頁移出,以裝載新的頁面。對移出頁面根據兩種情況來處理:如果該頁未被修改過,則刪除它;如果該頁曾經被修改過,則系統必須將該頁寫回輔存。
系統請頁的處理過程如下所示:
爲了公平地選擇將要從系統中拋棄的頁面,Linux 系統使用最近最少使用(LRU)頁面的衰老算法。這種策略根據系統中每個頁面被訪問的頻率,爲物理頁框中的頁面設置了一個叫做年齡的屬性。頁面被訪問的次數越多,則頁面的年齡最小;相反,則越大。而年齡較大的頁面就是待換出頁面的最佳候選者。
2.3 快表
在系統每次訪問虛存頁時,都要在內存的所有頁表中尋找該頁的頁框,這是一個很費時間的工作。但是,人們發現,系統一旦訪問了某一個頁,那麼系統就會在一段時間內穩定地工作在這個頁上。所以,爲了提高訪問頁表的速度,系統還配備了一組正好能容納一個頁表的硬件寄存器,這樣當系統再訪問虛存時,就首先到這組硬件寄存器中去訪問,系統速度就快多了。這組存放當前頁表的寄存器叫做快表。
總之,使用虛擬存儲技術時,處理器必須配備一些硬件來承擔內存管理的一部分任務。承擔內存管理任務的硬件部分叫做存儲管理單元 MMU。存儲管理單元 MMU 的工作過程如下圖所示:
(1) 頁的共享
在多程序系統中,常常有多個程序需要共享同一段代碼或數據的情況。在分頁管理的存儲器中,這個事情很好辦:讓多個程序共享同一個頁面即可。
具體的方法是:使這些相關程序的虛擬空間的頁面在頁表中指向內存中的同一個頁框。這樣,當程序運行並訪問這些相關頁面時,就都是對同一個頁框中的頁面進行訪問,而該頁框中的頁就被這些程序所共享。下圖是 3 個程序共享一個頁面的例子:
(2) 頁的保護
由上可知,頁表實際上是由虛擬空間轉到物理空間的入口。因此,爲了保護頁面內容不被沒有該頁面訪問權限的程序所破壞,就應在頁表的表項中設置一些訪問控制字段,用於指明對應頁面中的內容允許何種操作,從而禁止非法訪問。下圖是頁表項中存放控制信息的一種可能的形式:
注意:其中的 PCD 位表示着是否允許高速緩存(cache)。
如果程序對一個頁試圖進行一個該頁控制字段所不允許的操作,則會引起操作系統的一次中斷——非法訪問中斷,並拒絕這種操作,從而保護該頁的內容不被破壞。
(3) 多級頁表
需要注意的是,頁表是操作系統創建的用於內存管理的表格。因此,一個程序在運行時,其頁表也要存放到內存空間。如果一個程序只需要一個頁表,則不會有什麼問題。但如果,程序的虛擬空間很大的話,就會出現一個比較大的問題。
比如:一個程序的虛擬空間爲 4GB,頁表以 4KB 爲一頁,那麼這個程序空間就是 1M 頁。爲了存儲這 1M 頁的頁指針,那麼這個頁表的長度就相當大了,對內存的負擔也很大了。所以,最好對頁表也進行分頁存儲,在程序運行時只把需要的頁複製到內存,而暫時不需要的頁就讓它留在輔存中。爲了管理這些頁表頁,還要建立一個記錄頁表頁首地址的頁目錄表,於是單級頁表就變成了二級頁表。二級頁表的地址轉換如下圖所示:
當然,如果程序的虛擬空間更大,那麼也可以用三級頁表來管理。爲了具有通用性,Linux 系統使用了三級頁表結構:頁目錄(Page Directory,PGD)、中間頁目錄(Page Middle Directory,PMD)、頁表(Page Table,PTE)。
2.4Linux 的頁表結構
爲了通用,Linux 系統使用了三級頁表結構:頁目錄、中間頁目錄和頁表。PGD 爲頂級頁表,是一個 pgd_t 數據類型(定義在文件 linux/include/page.h 中)的數組,每個數組元素指向一箇中間頁目錄;PMD 爲二級頁表,是一個 pmd_t 數據結構的數組,每個數組元素指向一個頁表;PTE 則是頁表,是一個 pte_t 數據類型的數組,每個元素中含有物理地址。
爲了應用上的靈活,Linux 使用一系列的宏來掩蓋各種平臺的細節。用戶可以在配置文件 config 中根據自己的需要對頁表進行配置,以決定是使用三級頁表還是使用二級頁表。
在系統編譯時,會根據配置文件 config 中的配置,把目錄 include/asm 符號連接到具體 CPU 專用的文件目錄中。例如,對於 i386CPU,該目錄符號會連接到 include/asm-i386,並在文件 pgable-2level-defs.h 中定義了二級頁表的基本結構,如下圖:
其中還定義了:
#define PGDIR_SHIFT 22 //PGD在線性地址中的起始地址爲bit22
#define PTRS_PER_PGD 1024 //PGD共有1024個表項
#define PTRS_PER_PTE 1024 //PTE共有1024個表項
#endif
在文件 include/asm-i386/pgtable.h 中定義了頁目錄和頁表項的數據結構,如下:
typedof struct { unsigned long pte_low; } pte_t; //頁表中的物理地址,頁框碼
typedof struct { unsigned long pgd; } pgd_t; //指向一個頁表
typedof struct { unsigned long pgprot; } pgprot_t; //頁表中的各個狀態信息和訪問權限
從定義可知,它們都是隻有一個長整型類型(32 位)的結構體。
注意:如上文的 “頁的保護” 部分,頁框碼代表物理地址,只需要高 20 位就夠了(因爲頁框的長度爲 4KB,因此頁內偏移 12 位)。而後 12 位可以存放各個狀態信息和訪問權限。但是 Linux 並沒有這樣做,反而重新定義了一個結構體來存放,通過 “或” 運算來將兩者結合。
2.5Swap 交換機制
首先呢,提一個概念,交換空間(swap space),這個大家應該不陌生,在重裝系統的時候,會讓你選擇磁盤分區,就比如說一個硬盤分幾個部分去管理。其中就會分一部分磁盤空間用作交換,叫做 swap space。其實就是一段臨時存儲空間,內存不夠用的時候就用它了,雖然它也在磁盤中,但省去了很多的查找時間啊。當發生進程切換的時候,內存與交換空間就要發生數據交換一滿足需求。所以啊,進程的切換消耗是很大的,這也說明了爲什麼自旋鎖比信號量效率高的原因。
那麼我們的程序裏申請的內存的時候,linux 內核其實只分配一個虛擬內存( 線性地址),並沒有分配實際的物理內存。只有當程序真正使用這塊內存時,纔會分配物理內存。這就叫做延遲分配和請頁機制。釋放內存時,先釋放線性區對應的物理內存,然後釋放線性區;"請頁機制" 將物理內存的分配延後了,這樣是充分利用了程序的局部性原來,節約內存空間,提高系統吞吐;就是說一個函數可能只在物理內存中呆了一會,用完了就被清除出去了,雖然在虛擬地址空間還在。(不過虛擬地址空間不是事實上的存儲,所以只能說這個函數佔據了一段虛擬地址空間,當你訪問這段地址時,就會產生缺頁處理,從交換區把對應的代碼搬到物理內存上來)
Swap 交換機制是 Linux 虛擬內存管理的另一個重要組成部分。簡單來說,Swap 是磁盤上的一塊區域,當物理內存不足時,系統會將一部分暫時不用的內存頁面(Page)交換到 Swap 空間中,騰出物理內存給更需要的進程使用 。當被交換出去的頁面再次被訪問時,系統會將其從 Swap 空間換回到物理內存中。
Swap 交換機制的工作原理涉及到內存回收和頁面置換算法。當系統內存緊張時,內核會啓動內存回收機制,掃描內存中的頁面,選擇一些不常用或最近最少使用的頁面進行回收。如果這些頁面是匿名頁面(沒有關聯到文件的內存頁面,如進程的堆和棧空間),就會被交換到 Swap 空間中;如果是文件映射頁面(關聯到文件的內存頁面,如共享庫、文件緩存等),則會根據情況進行處理,髒頁面(被修改過的頁面)會被寫回文件,乾淨頁面(未被修改過的頁面)可以直接釋放。
Swap 交換機制對系統性能有着重要的影響。當 Swap 使用頻繁時,說明物理內存不足,系統需要頻繁地在物理內存和 Swap 空間之間交換頁面,這會導致磁盤 I/O 增加,系統性能下降 。因爲磁盤的讀寫速度遠遠低於內存,過多的 Swap 操作會使系統變得遲緩。因此,在實際應用中,需要合理配置 Swap 空間的大小,並密切關注系統的內存使用情況,避免 Swap 過度使用。
例如,可以通過調整 / proc/sys/vm/swappiness 參數來控制系統對 Swap 的使用傾向,swappiness 的值範圍是 0 - 100,表示系統將內存頁面交換到 Swap 空間的傾向程度,值越大表示越傾向於使用 Swap 。一般來說,對於內存充足的系統,可以將 swappiness 設置爲較低的值,如 10 或 20,以減少不必要的 Swap 操作;對於內存緊張的系統,可以適當提高 swappiness 的值,但也要注意不要過高,以免嚴重影響性能。
三、Linux 虛擬內存管理工具
在 Linux 系統中,有多個實用工具可以幫助我們查看和管理虛擬內存,瞭解系統的內存使用狀態,下面介紹幾個常用的命令。
3.1top 命令
top 命令是一個功能強大的系統監控工具,它能夠實時顯示系統中各個進程的資源使用情況,包括 CPU、內存等 。通過 top 命令,我們可以直觀地瞭解到系統中哪些進程佔用了較多的虛擬內存。在終端中輸入 “top”,即可啓動該命令,其輸出結果大致如下:
top - 14:20:12 up 2 days, 1:23, 2 users, load average: 0.00, 0.01, 0.05
Tasks: 152 total, 1 running, 151 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.3 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8161844 total, 7433924 free, 149744 used, 578176 buff/cache
KiB Swap: 2097148 total, 2097148 free, 0 used. 7663360 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 12844 7448 4564 S 0.0 0.1 0:02.33 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
在 top 命令的輸出中,與虛擬內存相關的主要是 “VIRT” 列,它表示進程使用的虛擬內存總量,單位爲 KB 。例如,上述輸出中 PID 爲 1 的 systemd 進程,其 VIRT 值爲 12844KB,表示該進程使用了 12844KB 的虛擬內存。此外,“RES” 列表示進程使用的物理內存大小,“SHR” 列表示共享內存大小 。通過觀察這些指標,我們可以瞭解每個進程對內存資源的佔用情況,判斷是否存在內存使用異常的進程。在實際應用中,如果發現某個進程的 VIRT 值持續增長且佔用大量虛擬內存,可能需要進一步分析該進程的行爲,看是否存在內存泄漏等問題。比如,在一個長時間運行的服務器程序中,如果其 VIRT 值不斷上升,而業務量並沒有明顯增加,就需要檢查程序代碼,查看是否有未釋放的內存資源。
3.2free 命令
free 命令用於顯示系統內存的使用情況,包括物理內存和虛擬內存(交換空間) 。它可以幫助我們快速瞭解系統內存的整體使用狀態,判斷是否存在內存不足的情況。在終端中輸入 “free”,輸出結果如下:
total used free shared buff/cache available
Mem: 8161844 149744 7433924 34624 578176 7663360
Swap: 2097148 0 2097148
在 free 命令的輸出中,“total” 表示系統內存的總量,“used” 表示已使用的內存量,“free” 表示空閒內存量,“shared” 表示共享內存量,“buff/cache” 表示緩衝區和緩存使用的內存量,“available” 表示應用程序還可以申請到的內存 。其中,與虛擬內存相關的是 “Swap” 部分,“Swap total” 表示交換空間的總量,“Swap used” 表示已使用的交換空間量,“Swap free” 表示空閒的交換空間量 。例如,上述輸出中 Swap total 爲 2097148KB,Swap used 爲 0KB,Swap free 爲 2097148KB,說明當前系統的交換空間未被使用,這通常是一個比較理想的狀態,意味着系統的物理內存充足,不需要頻繁地進行內存交換操作。如果 Swap used 的值較大,說明系統的物理內存可能不足,需要將一部分內存數據交換到磁盤的交換空間中,這可能會導致系統性能下降,因爲磁盤的讀寫速度遠遠低於內存。在這種情況下,我們可以考慮增加物理內存,或者優化系統的內存使用,減少不必要的內存佔用。
3.3vmstat 命令
vmstat(Virtual Memory Statistics)命令用於顯示虛擬內存統計信息,同時也可以展示進程、CPU、I/O 等系統整體運行狀態 。它提供了更詳細的內存使用信息,對於深入分析系統性能非常有幫助。在終端中輸入 “vmstat”,輸出結果如下:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 7433924 149744 578176 0 0 1 1 1 1 0 0 100 0 0
在 vmstat 命令的輸出中,與虛擬內存相關的字段有 “swpd”“si” 和 “so” 。“swpd” 表示使用的虛擬內存量(單位爲 KB),“si” 表示從磁盤交換到內存的內存量(單位爲 KB/s),“so” 表示從內存交換到磁盤的內存量(單位爲 KB/s) 。例如,上述輸出中 swpd 爲 0,表示當前系統沒有使用虛擬內存;si 和 so 都爲 0,表示當前沒有發生內存交換操作。
當 si 和 so 的值不爲 0 時,說明系統正在進行內存交換,數值越大,說明內存交換越頻繁,這可能會對系統性能產生較大影響,需要進一步分析原因,採取相應的優化措施,如增加物理內存、調整應用程序的內存使用策略等。此外,vmstat 命令還可以通過指定刷新時間間隔和刷新次數來持續監控系統狀態,例如 “vmstat 2 10” 表示每 2 秒刷新一次,共刷新 10 次,這樣可以更直觀地觀察系統內存使用情況的變化趨勢。
四、Linux 虛擬內存優化策略
4.1 調整內核參數
在 Linux 系統中,通過調整內核參數可以對虛擬內存的性能進行優化 。其中,vm.swappiness 是一個非常重要的內核參數,它的值表示系統將內存頁面交換到 Swap 空間的傾向程度,取值範圍是 0 - 100 。當 vm.swappiness 的值爲 0 時,系統儘量不使用 Swap 空間,只有在物理內存完全耗盡時纔會考慮交換;當值爲 100 時,系統會非常積極地將內存頁面交換到 Swap 空間 。
可以使用 sysctl 命令來查看和臨時修改 vm.swappiness 的值。例如,要查看當前 vm.swappiness 的值,可以在終端中輸入:
sysctl vm.swappiness
如果要將 vm.swappiness 的值臨時修改爲 10,可以使用以下命令:
sysctl vm.swappiness=10
這種修改在系統重啓後會失效。如果想要永久修改 vm.swappiness 的值,可以編輯 / etc/sysctl.conf 文件,添加或修改 vm.swappiness = 10 這一行,然後執行 sysctl -p 使修改生效 。
vm.vfs_cache_pressure 也是一個重要的內核參數,它控制着文件系統緩存(VFS Cache)被回收的傾向 。該參數的值越大,文件系統緩存就越容易被回收;值越小,文件系統緩存就越不容易被回收 。默認值通常爲 100。如果系統中文件 I/O 操作頻繁,可以適當降低 vm.vfs_cache_pressure 的值,以減少文件系統緩存的回收,提高文件訪問性能 。同樣,可以使用 sysctl 命令來查看和修改這個參數,例如:
sysctl vm.vfs_cache_pressure
sysctl vm.vfs_cache_pressure=50
修改內核參數對虛擬內存性能有着顯著的影響。合理調整 vm.swappiness 可以避免系統過度依賴 Swap 空間,減少磁盤 I/O 操作,提高系統整體性能 。如果 vm.swappiness 設置過高,系統頻繁進行內存交換,會導致磁盤 I/O 負載增加,系統響應變慢;如果設置過低,當物理內存不足時,可能會導致進程因無法獲取足夠內存而被終止 。而調整 vm.vfs_cache_pressure 則可以優化文件系統緩存的使用,提高文件讀寫效率,對於那些依賴文件系統的應用程序(如數據庫、文件服務器等)來說,合理設置該參數能有效提升其性能 。
4.2 優化應用程序
應用程序的內存使用方式對虛擬內存性能有着直接的影響 。在實際開發中,常常會出現應用程序內存使用不當的情況,從而導致虛擬內存的浪費和系統性能的下降 。比如,在一些 Web 應用程序中,可能會存在內存泄漏的問題。以使用 Python 的 Flask 框架開發的 Web 應用爲例,如果在視圖函數中創建了大量的對象,卻沒有及時釋放,隨着時間的推移,這些未釋放的對象會佔用越來越多的內存,導致虛擬內存不斷增加 。假設我們有如下代碼:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
data = []
for i in range(10000):
# 創建大量對象但未及時釋放
obj = {'key': i}
data.append(obj)
return 'Hello, World!'
if __name__ == '__main__':
app.run()
在這個簡單的示例中,每次訪問 / 路由時,都會創建 10000 個字典對象並添加到 data 列表中,但在函數結束後,這些對象並沒有被及時釋放,會造成內存浪費。爲了優化應用程序的內存使用,可以採用以下方法:
首先,使用內存分析工具(如 Python 中的 memory_profiler、C++ 中的 Valgrind 等)來檢測應用程序中的內存泄漏和不合理的內存使用 。通過這些工具,可以定位到具體的代碼行,找出內存問題的根源 。其次,合理設計數據結構和算法,減少不必要的內存佔用 。比如,在存儲大量整數數據時,如果數據範圍較小,可以使用 uint8_t(無符號 8 位整數)類型代替 int(通常爲 32 位或 64 位整數)類型,這樣可以節省內存空間 。
另外,對於一些頻繁創建和銷燬的對象,可以考慮使用對象池技術 。以數據庫連接池爲例,在一個 Web 應用中,如果每次處理請求都創建新的數據庫連接,會消耗大量資源。使用數據庫連接池後,可以預先創建一定數量的數據庫連接對象,當有請求時,直接從連接池中獲取連接,使用完畢後再放回連接池,避免了頻繁創建和銷燬連接對象帶來的開銷 。
優化應用程序的內存使用對虛擬內存性能有着重要的作用。通過減少內存泄漏和優化內存使用,可以降低應用程序對虛擬內存的需求,減少系統內存管理的負擔,提高系統的整體性能和穩定性 。當應用程序能夠高效地使用內存時系統可以將更多的內存資源分配給其他需要的進程,避免因內存不足而導致的頻繁內存交換和系統性能下降 。
4.3 合理配置 Swap 空間
Swap 空間的大小和配置對系統性能有着顯著的影響 。如果 Swap 空間設置得太小,當物理內存不足時,系統可能無法及時將內存頁面交換到 Swap 空間中,導致進程因無法獲取足夠內存而出現異常,甚至系統崩潰 。相反,如果 Swap 空間設置得過大,會佔用過多的磁盤空間,而且在系統不需要使用 Swap 空間時,這些空間就被浪費了 。
在 Linux 系統中,可以通過創建交換分區或交換文件的方式來設置 Swap 空間 。以創建交換文件爲例,首先需要使用 dd 命令創建一個指定大小的文件,例如創建一個大小爲 2GB 的交換文件:
sudo dd if=/dev/zero of=/swapfile bs=1M count=2048
上述命令中,if=/dev/zero 表示從 / dev/zero 設備讀取數據(/dev/zero 是一個特殊的設備文件,它會不斷返回 0 值字節流),of=/swapfile 表示將數據寫入到 / swapfile 文件中,bs=1M 表示每次讀寫的數據塊大小爲 1MB,count=2048 表示總共讀寫 2048 次,即創建一個 2GB 大小的文件 。
創建好文件後,需要使用 mkswap 命令將其格式化爲交換文件:
sudo mkswap /swapfile
最後,使用 swapon 命令啓用這個交換文件:
sudo swapon /swapfile
如果希望系統在每次啓動時自動啓用這個交換文件,可以將其添加到 / etc/fstab 文件中,在文件末尾添加一行:
/swapfile none swap sw 0 0
除了設置 Swap 空間的大小,還可以設置 Swap 空間的優先級 。在 Linux 系統中,每個 Swap 設備都有一個優先級,取值範圍是 - 2^31 到 2^31 - 1 。優先級較高的 Swap 設備會優先被使用 。可以使用 swapon 命令的 - p 選項來設置 Swap 設備的優先級,例如:
sudo swapon -p 10 /swapfile
上述命令將 / swapfile 這個交換文件的優先級設置爲 10 。如果系統中有多個 Swap 設備,可以根據實際需求爲它們設置不同的優先級,以優化內存交換的性能 。比如,對於讀寫速度較快的 SSD 磁盤上的 Swap 分區,可以設置較高的優先級,使其在內存交換時優先被使用,減少因磁盤 I/O 速度慢而導致的性能下降 。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/oQuD_xNp6c0sN3xreplelg