虛擬記憶體是計算機作業系統提供的一種記憶體管理技術,它使得應用程式認為它擁有連續可用的記憶體(乙個連續完整的位址空間),而實際上,它通常是被分隔成多個物理記憶體碎片,還有部分暫時儲存在外部磁碟儲存器上,在需要時進行資料交換。與沒有使用虛擬記憶體技術的系統相比,使用這種技術的系統使得大型程式的編寫變得更容易,對真正的物理記憶體(例如ram)的使用也更有效率。
注意:虛擬記憶體不只是「用磁碟空間來擴充套件物理記憶體」的意思——這只是擴充記憶體級別以使其包含硬碟驅動器而已。把記憶體擴充套件到磁碟只是使用虛擬記憶體技術的乙個結果,它的作用也可以通過覆蓋或者把處於不活動狀態的程式以及它們的資料全部交換到磁碟上等方式來實現。對虛擬記憶體的定義是基於對位址空間的重定義的,即把位址空間定義為「連續的虛擬記憶體位址」,以藉此「欺騙」程式,使它們以為自己正在使用一大塊的「連續」位址。
那些需要快速訪問或者反應時間非常一致的嵌入式系統,和其他的具有特殊應用的計算機系統,可能會為了避免讓運算結果的可**性降低,而選擇不使用虛擬記憶體。
4g以內的物理記憶體,swap 設定為記憶體的2倍。
4-8g的物理記憶體,swap 等於記憶體大小。
8-64g 的物理記憶體,swap 設定為8g。
64-256g物理記憶體,swap 設定為16g。
乙個程序在某一時刻占用的記憶體並不會太多,這個會由作業系統來管理,無需使用的空間會被換出到磁碟上,但是程序是可以申請很大的記憶體空間的,也是有能力將記憶體撐爆的。
作業系統的虛擬空間是被劃分為使用者空間和核心空間的,linux的虛擬位址空間範圍為0~4g,linux核心將這4g位元組的空間分為兩部分, 將最高的1g位元組(從虛擬位址0xc0000000到0xffffffff)供核心使用,稱為「核心空間」。而將較低的3g位元組(從虛擬位址0x00000000到0xbfffffff)供各個程序使用,稱為「使用者空間」。因為每個程序可以通過系統呼叫進入核心,因此,linux核心由系統內的所有程序共享。於是,從具體程序的角度來看,每個程序可以擁有4g位元組的虛擬空間。有沒有想過為什麼每個程序空間都需要乙個1g的核心空間,不是都共享乙個的嗎?那把核心空間單獨擰出來,讓每個程序空間全都是使用者空間可以嗎?乙個虛擬空間應該是作業系統執行的乙個完整空間,包含系統和使用者程式,所有他們應該是乙個整體,反正是虛擬的空間,沒必要省,將每個程序空間都分配同乙個核心空間並不會浪費,反而會使設計更加統一簡單。
核心空間中存放的是核心**和資料,而程序的使用者空間中存放的是使用者程式的**和資料。不管是核心空間還是使用者空間,它們都處於虛擬空間中。 雖然核心空間佔據了每個虛擬空間中的最高1gb位元組,但對映到物理記憶體卻總是從最低位址(0x00000000),另外, 使用虛擬位址可以很好的保護 核心空間被使用者空間破壞,虛擬位址到實體地址轉換過程有作業系統和cpu共同完成(作業系統為cpu設定好頁表,cpu通過mmu單元進行位址轉換)。
看乙個程序切換的圖
藍色表示虛擬空間對映到物理空間,白色表示未對映,firefox占用了較多的記憶體,而ls程式使用了較少的記憶體。
程序虛擬空間的進一步細分:
幾乎每個程序的虛擬位址空間中各段的分布都與上圖完全一致。
程序位址空間中最頂部的段是棧,大多數程式語言將之用於儲存函式引數和區域性變數。呼叫乙個方法或函式會將乙個新的棧幀(stack frame)壓入到棧中,這個棧幀會在函式返回時被清理掉。由於棧中資料嚴格的遵守lifo的順序,這個簡單的設計意味著不必使用複雜的資料結構來追蹤棧中的內容,只需要乙個簡單的指標指向棧的頂端即可,因此壓棧(pushing)和退棧(popping)過程非常迅速、準確。程序中的每乙個執行緒都有屬於自己的棧。
通過不斷向棧中壓入資料,超出其容量就會耗盡棧所對應的記憶體區域,這將觸發乙個頁故障(page fault),而被linux的expand_stack()處理,它會呼叫acct_stack_growth()來檢查是否還有合適的地方用於棧的增長。如果棧的大小低於rlimit_stack(通常為8mb),那麼一般情況下棧會被加長,程式繼續執行,感覺不到發生了什麼事情。這是一種將棧擴充套件到所需大小的常規機制。然而,如果達到了最大棧空間的大小,就會棧溢位(stack overflow),程式收到乙個段錯誤(segmentation fault)。
動態棧增長是唯一一種訪問未對映記憶體區域而被允許的情形,其他任何對未對映記憶體區域的訪問都會觸發頁錯誤,從而導致段錯誤。一些被對映的區域是唯讀的,因此企圖寫這些區域也會導致段錯誤。
接下來的一塊記憶體空間是堆。與棧一樣,堆用於執行時記憶體分配;但不同的是,堆用於儲存那些生存期與函式呼叫無關的資料。大部分語言都提供了堆管理功能。在c語言中,堆分配的介面是malloc()函式。如果堆中有足夠的空間來滿足記憶體請求,它就可以被語言執行時庫處理而不需要核心參與,否則,堆會被擴大,通過brk()系統呼叫來分配請求所需的記憶體塊。堆管理是很複雜的,需要精細的演算法來應付我們程式中雜亂的分配模式,優化速度和記憶體使用效率。處理乙個堆請求所需的時間會大幅度的變動。實時系統通過特殊目的分配器來解決這個問題。堆在分配過程中可能會變得零零碎碎,如下圖所示:
最後,我們看看底部的記憶體段:bss,資料段,**段。
在c語言中,bss和資料段儲存的都是靜態(全域性)變數的內容。區別在於bss儲存的是未被初始化的靜態變數內容,他們的值不是直接在程式的原始碼中設定的。bss記憶體區域是匿名的,它不對映到任何檔案。如果你寫static intcntactiveusers,則cntactiveusers的內容就會儲存到bss中去。而資料段則儲存在源**中已經初始化的靜態變數的內容。資料段不是匿名的,它映**一部分的程式二進位制映象,也就是源**中指定了初始值的靜態變數。所以,如果你寫static int cntactiveusers=10,則cntactiveusers的內容就儲存在了資料段中,而且初始值是10。儘管資料段映**乙個檔案,但它是乙個私有記憶體對映,這意味著更改此處的記憶體不會影響被對映的檔案。
你可以通過閱讀檔案/proc/pid_of_process/maps來檢驗乙個linux程序中的記憶體區域。記住:乙個段可能包含許多區域。比如,每個記憶體對映檔案在mmap段中都有屬於自己的區域,動態庫擁有類似bss和資料段的額外區域。有時人們提到「資料段」,指的是全部的資料段+bss+堆。
你還可以通過nm和objdump命令來察看二進位制映象,列印其中的符號,它們的位址,段等資訊。最後需要指出的是,前文描述的虛擬位址布局在linux中是一種「靈活布局」,而且作為預設方式已經有些年頭了,它假設我們有值rlimt_stack。但是,當沒有該值得限制時,linux退回到「經典布局」,如下圖所示:
c語言程式例項分析如下所示:
#include#include void print(char *,int);
int main()
{ char *s1 = "abcde";
char *s2 = "abcde";
char s3 = "abcd";
long int *s4[100];
char *s5 = "abcde";
int a = 5;
int b =6;//a,b在棧上,&a>&b位址反向增長
printf("variables address in main function:n
s1=%ps2=%p s3=%p s4=%p s5=%p a=%p b=%pnn",
s1,s2,s3,s4,s5,&a,&b);
printf("variables address in processcall:n");
print("ddddddddd",5);//引數入棧從右至左進行,p先進棧,str後進 &p>&str
printf("nmain=%p print=%pn",main,print);
//列印**段中主函式和子函式的位址,編譯時先編譯的位址低,後編譯的位址高main&s2
char s3 = "abcdeee";//abcdeee在常量區,s3在棧上,陣列儲存的內容為abcdeee的乙份拷貝
long int *s4[100];
char *s5 = "abcde";
int a = 5;
int b =6;
int c;
int d;//a,b,c,d均在棧上,&a>&b>&c>&d位址反向增長
char *q=str;//
int m=p;//
char *r=(char *)malloc(1);
char *w=(char *)malloc(1);// r&w),r,w所指內容在堆中(即r
作業系統 虛擬記憶體
1.虛擬記憶體術語 虛擬記憶體 程式引用記憶體使用位址與記憶體系統用於識別物理儲存站點的位址是不同的,程式生成的位址會自動轉換成機器指令。虛擬記憶體的大小收到定址機制和可用的備用記憶體量的限制,而不受記憶體儲存位置實際數量的限制。虛擬位址 在虛擬記憶體中分配給某一位置的位址使該位置可以被訪問,彷彿它...
作業系統 虛擬記憶體
虛擬記憶體介紹 虛擬記憶體是計算機系統記憶體管理的一種技術。虛擬記憶體的重要意義是定義了乙個連續的虛擬位址空間,並且把記憶體擴充套件到硬碟空間。虛擬記憶體使得應用程式認為它擁有連續的可用記憶體,但實際上它通常被分隔成多個物理記憶體碎片,還有部分暫時儲存在外存上,在有需要時進行資料交換。虛擬記憶體技術...
作業系統記憶體管理 虛擬記憶體
按照固定大小將程序的位址空間分為多個頁面,每乙個頁面內部都有連續的位址.這些頁被對映到物理記憶體,但是並不是所有的頁都在記憶體中時程式才能執行.當程式需要已經在物理記憶體中的位址空間時,由硬體完成對映 當程式引用不在記憶體中的位址空間時,產生缺頁中斷,由作業系統將頁面調入記憶體.位址的轉化 建立頁表...