1. 程式人生 > >linux 程序的虛擬記憶體

linux 程序的虛擬記憶體

當我們建立一個程序時,我們知道程序有以下特點:

  1. 每個程序都有自己獨立的 4G 記憶體空間,各個程序的記憶體空間具有類似的結構
  2. 一個新程序建立的時候,將會建立起自己的記憶體空間,此程序的資料、程式碼等從磁碟拷貝到自己的程序空間,哪些資料在哪裡,都由程序控制表中的 task_struct 記錄,task_struct 中記錄中一條連結串列,記錄中記憶體空間的分配情況,哪些地址有資料,哪些地址無資料,哪些可讀,哪些可寫,都可以通過這個連結串列記錄
  3. 每個程序已經分配的記憶體空間,都與對應的磁碟空間對映

那麼問題來了:

  1. 計算機明明沒有那麼多記憶體(n 個程序的話就需要 n*4G)記憶體
  2. 建立一個程序,就要把磁碟上的程式檔案拷貝到程序對應的記憶體中去,對於一個程式對應的多個程序這種情況,浪費記憶體!

所以,實際上:

  1. 每個程序的 4G 記憶體空間只是虛擬記憶體空間,每次訪問記憶體空間的某個地址,都需要把地址翻譯為實際實體記憶體地址
  2. 所有程序共享同一實體記憶體,每個程序只把自己目前需要的虛擬記憶體空間對映並存儲到實體記憶體上。
  3. 程序要知道哪些記憶體地址上的資料在實體記憶體上,哪些不在,還有在實體記憶體上的哪裡,需要用頁表來記錄
  4. 頁表的每一個表項分兩部分,第一部分記錄此頁是否在實體記憶體上,第二部分記錄實體記憶體頁的地址(如果在的話)
  5. 當程序訪問某個虛擬地址,去看頁表,如果發現對應的資料不在實體記憶體中,則缺頁異常
  6. 缺頁異常的處理過程,就是把程序需要的資料從磁碟上拷貝到實體記憶體中,如果記憶體已經滿了,沒有空地方了,那就找一個頁覆蓋,當然如果被覆蓋的頁曾經被修改過,需要將此頁寫回磁碟

頁表工作原理及對映關係理解圖如下:
image

可以認為虛擬空間都被對映到了磁碟空間中,並且由頁表記錄對映位置,當訪問到某個地址的時候,通過頁表中的有效位,可以得知此資料是否在記憶體中,如果不是,則通過缺頁異常,將磁碟對應的資料拷貝到記憶體中,如果沒有空閒記憶體,則選擇犧牲頁面,替換其他頁面。

優點總結:

  1. 既然每個程序的記憶體空間都是一致而且固定的,所以連結器在連結可執行檔案時,可以設定記憶體地址,而不用去管這些資料最終實際的記憶體地址,這是有獨立記憶體空間的好處
  2. 當不同的程序使用同樣的程式碼時,比如庫檔案中的程式碼,實體記憶體中可以只儲存一份這樣的程式碼,不同的程序只需要把自己的虛擬記憶體對映過去就可以了,節省記憶體
  3. 在程式需要分配連續的記憶體空間的時候,只需要在虛擬記憶體空間分配連續空間,而不需要實際實體記憶體的連續空間,可以利用碎片。

事實上,在每個程序建立載入時,核心只是為程序 “建立” 了虛擬記憶體的佈局,具體就是初始化程序控制表中記憶體相關的連結串列,實際上並不立即就把虛擬記憶體對應位置的程式資料和程式碼(比如.text .data段)拷貝到實體記憶體中,只是建立好虛擬記憶體和磁碟檔案之間的對映就好(叫做儲存器對映),等到執行到對應的程式時,才會通過缺頁異常,來拷貝資料。還有程序執行過程中,要動態分配記憶體,比如 malloc 時,也只是分配了虛擬記憶體,即為這塊虛擬記憶體對應的頁表項做相應設定,當程序真正訪問到此資料時,才引發缺頁異常。