咱們知道,在32位機器上linux操做系統中的進程的地址空間大小是4G,其中0-3G是用戶空間,3G-4G是內核空間。其實,這個4G的地址空間是不存在的,也就是咱們所說的虛擬內存空間。linux
那虛擬內存空間是什麼呢,它與實際物理內存空間又是怎樣對應的呢,爲何有了虛擬內存技術,咱們就能運行比實際物理內存大的應用程序,它是怎麼作到的呢?呵呵,這一切的一切都是個迷呀,下面咱們就一步一步解開心中的謎團吧!shell
進程使用虛擬內存中的地址,由操做系統協助相關硬件,把它「轉換」成真正的物理地址。虛擬地址經過頁表(Page Table)映射到物理內存,頁表由操做系統維護並被處理器引用。內核空間在頁表中擁有較高特權級,所以用戶態程序試圖訪問這些頁時會致使一個頁錯誤(page fault)。在Linux中,內核空間是持續存在的,而且在全部進程中都映射到一樣的物理內存。內核代碼和數據老是可尋址,隨時準備處理中斷和系統調用。與此相反,用戶模式地址空間的映射隨進程切換的發生而不斷變化。編程
Linux進程在虛擬內存中的標準內存段佈局以下圖所示:數據結構
咱們來看看,當咱們寫好一個應用程序,編譯後它都有什麼東東?函數
例如: 佈局
用命令size a.out會獲得:spa
其中text是放的是代碼,data放的是初始化過的全局變量或靜態變量,bss放的是未初始化的全局變量或靜態變量。操作系統
因爲歷史緣由,C程序一直由下列幾部分組成:code
A、正文段。這是由cpu執行的機器指令部分。一般,正文段是可共享的,因此即便是常常執行的程序(如文本編輯程序、C編譯程序、shell等)在存儲器中也只須要有一個副本,另外,正文段經常是隻讀的,以防止程序因爲意外事故而修改器自身的指令。blog
B、初始化數據段。一般將此段稱爲數據段,它包含了程序中需賦初值的變量。例如,C程序中任何函數以外的說明:
int maxcount = 99;(全局變量)
C、非初始化數據段。一般將此段稱爲bss段,這一名稱來源於早期彙編程序的一個操做,意思是"block started by symbol",在程序開始執行以前,內核將此段初始化爲0。函數外的說明:
long sum[1000];
使此變量存放在非初始化數據段中。
D、棧。自動變量以及每次函數調用時所需保存的信息都存放在此段中。每次函數調用時,其返回地址、以及調用者的環境信息(例如某些機器寄存器)都存放在棧中。而後,新被調用的函數在棧上爲其自動和臨時變量分配存儲空間。經過以這種方式使用棧,C函數能夠遞歸調用。
E、堆。一般在堆中進行動態存儲分配。因爲歷史上造成的慣例,堆位於非初始化數據段頂和棧底之間。
從上圖咱們看到棧空間是下增加的,堆空間是從下增加的,他們會會碰頭呀?通常不會,由於他們之間間隔很大,如:
#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; }
運行結果以下:
呵呵,這裏咱們看到地址了,這個地址是虛擬地址,這些地址時怎麼來的呢?其實在咱們編譯的時候,這些地址就已經肯定了,以下圖中紅線。
也就是說,咱們不論咱們運行a.out程序多少次這些地址都是同樣的。咱們知道,linux操做系統每一個進程的地址空間都是獨立的,其實這裏的獨立說得是物理空間上得獨立。進程可使用相同的虛擬地址,這不奇怪,由於轉換後的物理地址並不是相同的。那相同的虛擬地址,不一樣的物理地址,他們之間是怎樣聯繫起來的呢?咱們繼續探究....
在linux操做系統中,每一個進程都經過一個task_struct的結構體描敘,每一個進程的地址空間都經過一個mm_struct描敘,c語言中的每一個段空間都經過vm_area_struct表示,他們關係以下 :
當運行一個程序時,操做系統須要建立一個進程,這個進程和程序之間都幹了些什麼呢?
當一個程序被執行時,該程序的內容必須被放到進程的虛擬地址空間(注意,是虛擬地址空間),對於可執行程序的共享庫也是如此。可執行程序並不是真正讀到物理內存中,而只是連接到進程的虛擬內存中(此時都是在進程的虛擬地址空間)。
當一個可執行程序映射到進程虛擬地址空間時,一組vm_area_struct數據結構將被產生。每一個vm_area_struct數據結構表示可執行印象的一部分;是可執行代碼,或是初始化的數據,以及未初始化的數據等。
linux操做系統是經過sys_exec對可執行文件進行映射以及讀取的,有以下幾步:
一、建立一組vm_area_struct;
二、圈定一個虛擬用戶空間,將其起始結束地址(elf段中已設置好)保存到vm_start和vm_end中;
三、將磁盤file句柄保存在vm_file中;
四、將對應段在磁盤file中的偏移值(elf段中已設置好)保存在vm_pgoff中;
五、將操做該磁盤file的磁盤操做函數保存在vm_ops中;