我們知道,在32位機器上linux操作系統(tǒng)中的進程的地址空間大小是4G,其中0-3G是用戶空間,3G-4G是內(nèi)核空間。其實,這個4G的地址空間是不存在的,也就是我們所說的虛擬內(nèi)存空間。
那虛擬內(nèi)存空間是什么呢,它與實際物理內(nèi)存空間又是怎樣對應的呢,為什么有了虛擬內(nèi)存技術(shù),我們就能運行比實際物理內(nèi)存大的應用程序,它是怎么做到的呢?呵呵,這一切的一切都是個迷呀,下面我們就一步一步解開心中的謎團吧!
我們來看看,當我們寫好一個應用程序,編譯后它都有什么東東?
例如:
用命令size a.out會得到:
其中text是放的是代碼,data放的是初始化過的全局變量或靜態(tài)變量,bss放的是未初始化的全局變量或靜態(tài)變量。
由于歷史原因,C程序一直由下列幾部分組成:
A、正文段。這是由cpu執(zhí)行的機器指令部分。通常,正文段是可共享的,所以即使是經(jīng)常執(zhí)行的程序(如文本編輯程序、C編譯程序、shell等)在存儲器中也只需要有一個副本,另外,正文段常常是只讀的,以防止程序由于意外事故而修改器自身的指令。
B、初始化數(shù)據(jù)段。通常將此段稱為數(shù)據(jù)段,它包含了程序中需賦初值的變量。例如,C程序中任何函數(shù)之外的說明:
int maxcount = 99;(全局變量)
C、非初始化數(shù)據(jù)段。通常將此段稱為bss段,這一名稱來源于早期匯編程序的一個操作,意思是“block started by symbol”,在程序開始執(zhí)行之前,內(nèi)核將此段初始化為0。函數(shù)外的說明:
long sum[1000];
使此變量存放在非初始化數(shù)據(jù)段中。
D、棧。自動變量以及每次函數(shù)調(diào)用時所需保存的信息都存放在此段中。每次函數(shù)調(diào)用時,其返回地址、以及調(diào)用者的環(huán)境信息(例如某些機器寄存器)都存放在棧中。然后,新被調(diào)用的函數(shù)在棧上為其自動和臨時變量分配存儲空間。通過以這種方式使用棧,C函數(shù)可以遞歸調(diào)用。
E、堆。通常在堆中進行動態(tài)存儲分配。由于歷史上形成的慣例,堆位于非初始化數(shù)據(jù)段頂和棧底之間。
從上圖我們看到??臻g是下增長的,堆空間是從下增長的,他們會會碰頭呀?一般不會,因為他們之間間隔很大,如:
#include 《stdio.h》
#include 《stdlib.h》
int bss_var;
int data_var0 = 1;
int main()
{
printf(“Test location:\n”);
printf(“\tAddress of main(Code Segment):%p\n”,main);
printf(“_____________________________________\n”);
int stack_var0 = 2;
printf(“Stack location:\n”);
printf(“\tInitial end of stack:%p\n”,&stack_var0);
int stack_var1 = 3;
printf(“\tNew end of stack:%p\n”,&stack_var1);
printf(“_____________________________________\n”);
printf(“Data location:\n”);
printf(“\tAddress of data_var(Data Segment):%p\n”,&data_var0);
static int data_var1 = 4;
printf(“\tNew end of data_var(Data Segment):%p\n”,&data_var1);
printf(“_____________________________________\n”);
printf(“BSS location:\n”);
printf(“\tAddress of bss_var:%p\n”,&bss_var);
printf(“_____________________________________\n”);
printf(“Heap location:\n”);
char *p = (char *)malloc(10);
printf(“\tAddress of head_var:%p\n”,p);
return 0;
}
運行結(jié)果如下:
呵呵,這里我們看到地址了,這個地址是虛擬地址,這些地址時怎么來的呢?其實在我們編譯的時候,這些地址就已經(jīng)確定了,如下圖中紅線。
也就是說,我們不論我們運行a.out程序多少次這些地址都是一樣的。我們知道,linux操作系統(tǒng)每個進程的地址空間都是獨立的,其實這里的獨立說得是物理空間上得獨立。那相同的虛擬地址,不同的物理地址,他們之間是怎樣聯(lián)系起來的呢?我們繼續(xù)探究。。.。
在linux操作系統(tǒng)中,每個進程都通過一個task_struct的結(jié)構(gòu)體描敘,每個進程的地址空間都通過一個mm_struct描敘,c語言中的每個段空間都通過vm_area_struct表示,他們關(guān)系如下 :
當運行一個程序時,操作系統(tǒng)需要創(chuàng)建一個進程,這個進程和程序之間都干了些什么呢?
當一個程序被執(zhí)行時,該程序的內(nèi)容必須被放到進程的虛擬地址空間,對于可執(zhí)行程序的共享庫也是如此??蓤?zhí)行程序并非真正讀到物理內(nèi)存中,而只是鏈接到進程的虛擬內(nèi)存中。
當一個可執(zhí)行程序映射到進程虛擬地址空間時,一組vm_area_struct數(shù)據(jù)結(jié)構(gòu)將被產(chǎn)生。每個vm_area_struct數(shù)據(jù)結(jié)構(gòu)表示可執(zhí)行印象的一部分;是可執(zhí)行代碼,或是初始化的數(shù)據(jù),以及未初始化的數(shù)據(jù)等。
linux操作系統(tǒng)是通過sys_exec對可執(zhí)行文件進行映射以及讀取的,有如下幾步:
1、創(chuàng)建一組vm_area_struct;
2、圈定一個虛擬用戶空間,將其起始結(jié)束地址(elf段中已設(shè)置好)保存到vm_start和vm_end中;
3、將磁盤file句柄保存在vm_file中;
4、將對應段在磁盤file中的偏移值(elf段中已設(shè)置好)保存在vm_pgoff中;
5、將操作該磁盤file的磁盤操作函數(shù)保存在vm_ops中;
注意:這里沒有對應 的頁目錄表項創(chuàng)建頁表,更不存在設(shè)置頁表項了。
假設(shè)現(xiàn)在程序中有一條指令需要讀取上面vm_start--vm_end之間的某內(nèi)容
例如:mov [0x08000011],%eax,那么將會執(zhí)行如下序列:
1、cpu依據(jù)CR3(current-》pgd)找到0x08000011地址對應的pgd[i],由于該pgd[i]內(nèi)容保持為初始化狀態(tài)即為0,導致cpu異常。
2、.do_page_fault被調(diào)用,在該函數(shù)中,為pgd[i]在內(nèi)存中分配一個頁表,并讓該表項指向它,如下圖所示:
注意:這里i為0x08000011高10位,j為其中間10位,此時pt表項全部為0(pte[j]也為0);
3、為pte[j]分配一個真正的物理內(nèi)存頁面,依據(jù)vm_area_struct中的vm_file、vm_pgoff和vm_ops,調(diào)用filemap_nopage將磁盤file中vm_pgoff偏移處的內(nèi)容讀入到該物理頁面中,如下圖所示:
①分配物理內(nèi)存頁面;
②從磁盤文件中將內(nèi)容讀取到物理內(nèi)存頁面中
從上面我們可以知道,在進程創(chuàng)建的過程中,程序內(nèi)容被映射到進程的虛擬內(nèi)存空間,為了讓一個很大的程序在有限的物理內(nèi)存空間運行,我們可以把這個程序的開始部分先加載到物理內(nèi)存空間運行,因為操作系統(tǒng)處理的是進程的虛擬地址,如果在進行虛擬到物理地址的轉(zhuǎn)換工程中,發(fā)現(xiàn)物理地址不存在時,這個時候就會發(fā)生缺頁異常(nopage),接著操作系統(tǒng)就會把磁盤上還沒有加載到內(nèi)存中的數(shù)據(jù)加載到物理內(nèi)存中,對應的進程頁表進行更新。也許你會問,如果此時物理內(nèi)存滿了,操作系統(tǒng)將如何處理?
下面我們看看linux操作系統(tǒng)是如何處理的:
如果一個進程想將一個虛擬頁裝入物理內(nèi)存,而又沒有可使用的空閑物理頁,操作系統(tǒng)就必須淘汰物理內(nèi)存中的其他頁來為此頁騰出空間。
在linux操作系統(tǒng)中,物理頁的描敘如下:
struct mem_map
{
1、本頁使用計數(shù),當該頁被許多進程共享時計數(shù)將大于1
2、age描敘本頁的年齡,用來判斷該頁是否為淘汰或交換的好候選
3、map_nr描敘物理頁的頁幀號
}
如果從物理內(nèi)存中被淘汰的頁來自于一個映像或數(shù)據(jù)文件,并且還沒有被寫過,則該頁不必保存,它可以丟掉。如果有進程在需要該頁時就可以把它從映像或數(shù)據(jù)文件中取回內(nèi)存。
然而,如果該頁被修改過,操作系統(tǒng)必須保留該頁的內(nèi)容以便晚些時候在被訪問。這種頁稱為“臟(dirty)頁”,當它被從內(nèi)存中刪除時,將被保存在一個稱為交換文件的特殊文件中。
相對于處理器和物理內(nèi)存的速度,訪問交換文件要很長時間,操作系統(tǒng)必須在將頁寫到磁盤以及再次使用時取回內(nèi)存的問題上花費心機。
如果用來決定哪一頁被淘汰或交換的算法不夠高效的話,就可能出現(xiàn)稱為“抖動”的情況。在這種情況下,頁面總是被寫到磁盤又讀回來,操作系統(tǒng)忙于此而不能進行真正的工作。
linux使用“最近最少使用(Least Recently Used ,LRU)”頁面調(diào)度技巧來公平地選擇哪個頁可以從系統(tǒng)中刪除。這種設(shè)計系統(tǒng)中每個頁都有一個“年齡”,年齡隨頁面被訪問而改變。頁面被訪問越多它越年輕;被訪問越少越老。年老的頁是用于交換的最佳候選頁。
-
Linux
+關(guān)注
關(guān)注
87文章
11207瀏覽量
208721 -
ip地址
+關(guān)注
關(guān)注
0文章
291瀏覽量
16990 -
進程
+關(guān)注
關(guān)注
0文章
201瀏覽量
13938
發(fā)布評論請先 登錄
相關(guān)推薦
評論