1. 程式人生 > >淺析實體記憶體、虛擬記憶體和程序的地址空間

淺析實體記憶體、虛擬記憶體和程序的地址空間

   ●在一個系統中,程序是和其他程序共享CPU和主存資源。但是共享資源會造成一些不可避免的問題,例如由於儲存器不夠而         程序無法執行,亦或是儲存器被外來的惡意程序破壞等。

早期的記憶體管理機制:

 分派方式一:程式載入

 

1》CPU要訪問程序中的資料,必須通過訪問訪問實體記憶體的方式物理定址

        當CPU要執行某條指令時,它會生成一個有效實體地址,通過儲存器匯流排,把它傳遞給主存,主存取出從實體地址4處開始的四個位元組的字,並將它返回給CPU,CPU會將它放在一個暫存器裡。

2》一個程式要執行,必須把這些程式載入到記憶體中。

3》程序的地址空間是連續的。因為程式是直接訪問實體記憶體,所以惡意的程式會隨意修改別的程序的記憶體資料以達到破壞的目         的;有些程式不是惡意的,但有bug的程式也會有可能修改了其他程式的記憶體,危害係數高。

4》記憶體使用效率低。如上圖,硬碟中三個可執行檔案不能同時載入到記憶體中當A和B同時執行,如果還想執行C,就必須把

     已經執行過的A或B的資料暫時拷貝到硬碟上,釋放出空間C才能載入到記憶體執行。

5》程式執行的地址是起始地址,而起始地址要載入到記憶體中才能確定。當A或者B釋放出空間夠C執行時,作業系統會在這段空         間中隨機分配連續的30M的空間給C程式,由於是隨機分配,所以程式執行的地址並不能確定。

 分派方式二:段式管理

說明:

  ●把程式和記憶體通過某種關係進行分段,每段有獨立的名字,如程式碼段、資料段。

  ●在編寫的時候,只要指明瞭所屬的段,程式碼段和資料段中出現的所有地址。都是從0開始,作業系統維護其對映關係。

  ●由於CPU將記憶體分割成不同的段,因此資料指令的有效地址並不是真正的實體地址,而是相對地址(相對於段首0的偏移地址)。

優點:

1》可以分別編寫和編譯。

2》由於段暫存器的存在,使得程序的地址空間得以隔離,越界問題就可以被判定出來。

3》實際程式碼和資料中的地址都是偏移量,第一條指令從0開始,而實際的實體地址可以通過計算得出,解決了程式執行地址不確定的問題。

缺點:

         分段管理在記憶體空間不足時,依舊要喚出整個程式或者整個程式段,在工程量大的情況下,就會造成記憶體和硬碟之間資料的大量拷貝,導致時間和效率都不高,進而影響效能。

虛擬地址空間的提出

     為了改善早期記憶體管理機制而造成的效能問題,為了保護實體記憶體,提出了虛擬地址空間的概念。

     增加一箇中間層,利用間接的地址訪問方法訪問實體記憶體:程式中訪問的記憶體地址不再是實際的實體記憶體地址,而是一個虛擬地址,然後由作業系統將這個虛擬地址對映到合適的實體記憶體地址上。這樣一來,只要作業系統處理好虛擬地址到實體地址的對映關係,就可以保證不同的程式最終訪問不同的區域,從而達到記憶體地址空間的隔離。

 

上圖就是程序的虛擬地址空間,它並不是記憶體!!而是一個結構體(mm_struct)。

程序的虛擬地址空間是連續的,地址從00000000到ffffffff,共有2^31位元組個地址,也就是4G,每一塊空間可以用初始地址和終止地址來表示這塊區域。

分析一段程式碼:

 1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int g_val=100;
  5 
  6 int main()
  7 {
  8     pid_t id=fork();
  9     if(id==0){//child
 10         printf("I am child,pid:%d,g_val:%d,&g_val:%p\n",getpid(),g_val,&g_val);
 11     sleep(2);
 12     }else{//parent
 13         sleep(3);
 14         printf("I am parent,pid:%d,g_val:%d,&g_val:%p\n",getpid(),g_val,&g_val);
 15     sleep(1);
 16     }
 17     return 0;
 18 }   

輸出結果:

            結果看出,父程序和子程序輸出的變數值都是一樣的。很好理解,因為子程序以父程序為模板,父子程序沒有對變數進行任何修改。

           ● 回顧內容:父程序創建出子程序,程式碼共享,資料以寫時拷貝的方式各自私有一份。

                寫時拷貝:只有需要的時候再給,物盡其用;只有要修改的時候才複製一份父程序的內容。

如果上述程式碼子程序將全域性變數g_val進行修改:

 1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int g_val=100;
  5 
  6 int main()
  7 {
  8     pid_t id=fork();
  9     if(id==0){//child
 10         g_val=300;
 11         printf("I am child,pid:%d,g_val:%d,&g_val:%p\n",getpid(),g_val,&g_val);
 12     sleep(2);
 13     }else{//parent
 14         sleep(3);
 15         printf("I am parent,pid:%d,g_val:%d,&g_val:%p\n",getpid(),g_val,&g_val);
 16     sleep(1);
 17     }
 18     return 0;
 19 }
 20 

輸出結果:

從結果可以看出,因為子程序對變數進行了修改,因此變數值不同,但輸出的地址依舊相同。

1》父子程序輸出變數值不一樣,說明輸出的變數不是同一個變數。

2》輸出地址相同但變數值不同,說明這個輸出的地址一定不是實體地址,這個地址叫做虛擬地址(不是真實存在的)。

3》實體地址存在於實體記憶體中,同理,虛擬地址存在於虛擬地址空間中。要進行資料訪問,必須由作業系統將虛擬地址轉化為實體地址。

4》我們在C語言和C++中看到的地址都是虛擬地址。

5》使用者是看不到實體地址的,實體地址由作業系統統一管理。

 

上面的例子我們用地址空間簡單表示:

  ●第一次是父子程序將虛擬地址通過頁表對映到了同一個實體地址,實現了共享

  ●第二次子程序修改了變數,父子程序以寫時拷貝的方式私有資料,相同的虛擬地址被對映到不同的實體地址,實現了私有

 

虛擬地址空間:

 

 同一個變數,地址相同其實是虛擬地址相同,內容不同是因為OS通過頁表和+MMU將虛擬地址對映到了不同的實體地址。

作業系統是通過軟硬體結合的方式完成了對映。(軟體:頁表   硬體:MMU)