LINUX系統程式設計 LINUX 虛擬記憶體

luckyfriends發表於2016-12-21
LINUX 虛擬記憶體
以32位作業系統為例子,因為64位系統虛擬地址過大為2^64,32位僅僅為2^32=4G更利於描述,但是原理東西都一樣

這首先要從程式和程式之間的關係開始,我們一般寫好一段C\C++程式碼編譯後僅僅為可執行檔案假設為a.out,我們
執行a.out的時候,這個才叫程式,程式是OS級別抽象的實體(PCB task_struct結構體),為程式執行進行各種檢查和
系統資源分配,一個PCB包含部分資訊如下:
(摘至刑文鵬LINUX系統程式設計講義)
* 程式id。系統中每個程式有唯一的id,在C語言中用pid_t型別表示,其實就是一個非
負整數。
* 程式的狀態,有執行、掛起、停止、殭屍等狀態。
* 程式切換時需要儲存和恢復的一些CPU暫存器。
* 描述虛擬地址空間的資訊。
* 描述控制終端的資訊。
* 當前工作目錄(Current Working Directory)。
* umask掩碼。
* 檔案描述符表,包含很多指向file結構體的指標。
* 和訊號相關的資訊。
* 使用者id和組id。
* 控制終端、Session和程式組。
* 程式可以使用的資源上限(Resource Limit)

每個程式分配的記憶體包含很多稱之為段的部分組成並且放到0-3G使用者態虛擬地址空間中,3-4G為kernel太虛擬地址(注意我們以32位為列),
PCB就存放在我們的kernel態中。
下面描述0-3G使用者態虛擬記憶體段
由下向上分別是
1、程式碼段,是程式執行的機器程式碼,一個程式程式碼可以多個程式
   同時執行,那麼這個程式碼段可以同時存在於不同程式的不同
   虛擬記憶體地址中,等會用圖說明
2、初始化資料段,這個就是C\C++已經初始化的全域性變數和靜態變數
   我們知道靜態變數是存在於程式結束,而全域性變數(非靜態)的作用
   域也是全部程式碼塊,那麼這些變數需要放到一個非棧空間中
   (關於靜態變數可以檢視如下連結
  http://blog.itpub.net/7728585/viewspace-2119670/
   )
3、未初始化資料段,為初始化的全域性變數和靜態變數,未初始化本
   段的內容初始化為0
4、堆(heap)段,是在執行的時候動態程式分配的記憶體區域,比如malloc
下面以一段簡單程式碼說明,目的僅僅在於說明上面說的:
(未分配虛擬記憶體地址)
5、棧(stack)段,我們知道棧是一個後進先出的資料結構,用於儲存區域性
   變數,實參和返回值。它由棧幀組成(stack frames),每次新的函式呼叫
   都會分配一個新的棧幀比如下面的getv rev都在main函式棧幀裡面。
    而沒有使用到區域性變數t 則在add函式棧幀裡面

6、argc,environ 陣列資訊,固定大小


點選(此處)摺疊或開啟

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<string.h>

  4. typedef unsigned int uint;

  5. static uint step=1024;//全域性初始化靜態變數,初始化資料段
  6. uint iniv=1; //全域性初始化非靜態變數,可以使用extern訪問,初始化資料段

  7. static uint zero;//全域性未初始化靜態變數,未初始化資料段


  8. uint add(uint inv) //值傳遞 棧 for add funcation stack frame
  9. {
  10.      int t; // 棧 for add funcation stack frames
  11.      return inv*step+zero; //for add funcation stack frames
  12. }


  13. int main(void)
  14. {
  15.         uint getv = 10; //for main funcation stack frame
  16.         uint rev; //for main funcation stack frames

  17.         char* p; //for main funcation stack frames

  18.         rev = add(getv);
  19.         p = calloc(6,1); //
  20.         strcpy(p,"test:");
  21.         printf("%s%u\n",p,rev);
  22.         return 0;
  23. }
本來很多影像自己畫,但是發現比較麻煩,並且效果可能並不如原圖好,所以直接
摘錄.
關於程式各段組織如下(摘自UNIX/LINUX系統程式設計手冊)


關於程式使用者態和核心態的關係如下(摘自刑文鵬LINUX系統程式設計講義)




為了方便管理LINUX將記憶體分為叫做頁幀的單元(我們熟悉的4K),然後核心中就需要儲存一份程式虛擬地址到實際地址的對映表,如果訪問的資料不再實體記憶體
中就發生page fault,將磁碟中的資料複製到實體記憶體,建立虛擬地址到實體記憶體的對映關係,一個程式訪問資料是透過虛擬地址進行訪問,然後透過對映表對應
到實際的實體記憶體。
由於64位系統需要管理的記憶體頁非常巨大在LINUX中使用三級或者四級(核心2.6.11以上使用四級)對映表,關於對映表實際實現這裡沒有過多討論,因為這個屬於
LINUX核心原理的東西,我也沒有能力研究。
(實際是虛擬地址--》線性地址--》實體地址,但是LINUX中虛擬地址和線性地址是相同的。)


對映表直觀圖(摘自UNIX/LINUX系統程式設計手冊)

四級對映表(摘自pdf記憶體定址)


最後我們需要牢牢的記住的就是每個程式都有0-4G的虛擬地址空間可供分配,當然沒有分配就是未使用的,程式訪問的是記憶體虛擬地址,虛擬地址空間的資料可能並不
在實際記憶體中,當程式訪問到虛擬地址的資料並不在記憶體中,那麼發生page fault,將磁碟中的資料複製到實體記憶體,建立虛擬地址到實體記憶體的對映關係,如果在實際記憶體不足的情況下啟用swap做為實體記憶體的補充,將部分曾經使用過的資料而當前沒有使用的資料複製到SWAP中。而資料的過期處理一般為使用者程式自己控制比如LRU連結串列。
(這也是為什麼某些資料庫比如ORACLE MYSQL,在一臺64G的記憶體的機器上同時跑2個例項都分配64G左右記憶體能夠起來,但是過一段時間可能報記憶體不足的原因)


某些觀點為作者自己觀點如果有誤請指出
參考資料:
1、UNIX/LINUX系統程式設計手冊
2、LINUX作業系統原理與應用
3、刑文鵬LINUX系統程式設計講義
4、pdf記憶體定址

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/14710393/viewspace-2131124/,如需轉載,請註明出處,否則將追究法律責任。

相關文章