【Linux】深入理解Linux中內存管理


  主題:Linux內存管理中的分段和分頁技術

  回顧一下歷史,在早期的計算機中,程序是直接運行在物理內存上的。換句話說,就是程序在運行的過程中訪問的都是物理地址。

  如果這個系統只運行一個程序,那么只要這個程序所需的內存不要超過該機器的物理內存就不會出現問題,我們也就不需要考慮內存管理這個麻煩事了,反正就你一個程序,就這么點內存,吃不吃得飽那是你的事情了。

  然而現在的系統都是支持多任務,多進程的,這樣CPU以及其他硬件的利用率會更高,這個時候我們就要考慮到將系統內有限的物理內存如何及時有效的分配給多個程序了,這個事情本身我們就稱之為內存管理

  舉一個早期的計算機系統中,內存分配管理的例子,以便於理解。

  假如我們有三個程序,程序A,B,C,程序A運行的過程中需要10M內存,程序B運行的過程中需要100M內存,而程序C運行的過程中需要20M內存。

  如果系統同時需要運行程序A和B,那么早期的內存管理過程大概是這樣的,將物理內存的前10M分配給A,接下來的10M-110M分配給B。

  這種內存管理的方法比較直接,好了,假設我們這個時候想讓程序C也運行,同時假設我們系統的內存只有128M,顯然按照這種方法程序C由於內存不夠是不能夠運行的。

  大家知道可以使用虛擬內存的技術,內存空間不夠的時候可以將程序不需要用到的數據交換到磁盤空間上去,已達到擴展內存空間的目的。

  下面來看看這種內存管理方式存在的幾個比較明顯的問題。

進程地址空間不能隔離

  由於程序直接訪問的是物理內存,這個時候程序所使用的內存空間不是隔離的。

  舉個例子,就像上面說的A的地址空間是0-10M這個范圍內,但是如果A中有一段代碼是操作10M-128M這段地址空間內的數據,那么程序B和程序C就很可能會崩潰(每個程序都可以訪問系統的整個地址空間)。這樣很多惡意程序或者是木馬程序可以輕而易舉地破快其他的程序,系統的安全性也就得不到保障了,這對用戶來說也是不能容忍的。

內存使用的效率低  

  如上面提到的,如果我們要像讓程序A、B、C同時運行,那么唯一的方法就是使用虛擬內存技術將一些程序暫時不用的數據寫到磁盤上,在需要的時候再從磁盤讀回內存。

  這里程序C要運行,將A交換到磁盤上去顯然是不行的,因為程序是需要連續的地址空間的,程序C需要20M的內存,而A只有10M的空間,所以需要將程序B交換到磁盤上去,而B足足有100M,可以看到為了運行程序C我們需要將100M的數據從內存寫到磁盤,然后在程序B需要運行的時候再從磁盤讀到內存,我們知道IO操作比較耗時,所以這個過程效率將會十分低下。

程序運行的地址不能確定

  程序每次需要運行時,都需要在內存中分配一塊足夠大的空閑區域,而問題是這個空閑的位置是不能確定的,這會帶來一些重定位的問題,重定位的問題確定就是程序中引用的變量和函數的地址,如果有不明白童鞋可以去查查編譯原理方面的資料。

  內存管理無非就是想辦法解決上面三個問題,如何使進程的地址空間隔離,如何提高內存的使用效率,如何解決程序運行時的重定位問題?

  引用計算機界一句無從考證的名言:“計算機系統里的任何問題都可以靠引入一個中間層來解決。”

  現在的內存管理方法就是在程序和物理內存之間引入了虛擬內存這個概念。

  1. 虛擬內存位於程序和物理內存之間,程序只能看見虛擬內存,再也不能直接訪問物理內存。
  2. 每個程序都有自己獨立的進程地址空間,這樣就做到了進程隔離。這里的進程地址空間是指虛擬地址。
  3. 顧名思義,既然是虛擬地址,也就是虛的,不是現實存在的地址空間。

  既然我們在程序和物理地址空間之間增加了虛擬地址,那么就要解決怎么從虛擬地址映射到物理地址,因為程序最終肯定是運行在物理內存中的,主要有分段和分頁兩種技術。

分段(Segmentation)

  這種方法是人們最開始使用的一種方法,基本思路是將程序所需要的內存地址空間大小的虛擬空間映射到某個物理地址空間。

  

  每個程序都有其獨立的虛擬的獨立的進程地址空間,可以看到程序A和B的虛擬地址空間都是從0x00000000開始的。我們將兩塊大小相同的虛擬地址空間和實際物理地址空間一一映射,即虛擬地址空間中的每個字節對應於實際地址空間中的每個字節,這個映射過程由軟件來設置映射的機制,實際的轉換由硬件來完成。

  這種分段的機制解決了開始提到的3個問題中的進程地址空間隔離和程序地址重定位的問題。

  程序A和程序B有自己獨立的虛擬地址空間,而且該虛擬地址空間被映射到了互相不重疊的物理地址空間,如果程序A訪問虛擬地址空間的地址不在0x00000000-0x00A00000這個范圍內,那么內核就會拒絕這個請求,所以它解決了隔離地址空間的問題。我們應用程序A只需要關心其虛擬地址空間0x00000000-0x00A00000,而其被映射到哪個物理地址我們無需關心,所以程序永遠按照這個虛擬地址空間來放置變量,代碼,不需要重新定位。

  無論如何分段機制解決了上面兩個問題,是一個很大的進步,但是對於內存效率問題仍然無能為力。  

  因為這種內存映射機制仍然是以程序為單位,當內存不足時仍然需要將整個程序交換到磁盤,這樣內存使用的效率仍然很低。

  那么,怎么才算高效率的內存使用呢。事實上,根據程序的局部性運行原理,一個程序在運行的過程當中,在某個時間段內,只有一小部分數據會被經常用到。

  所以我們需要更加小粒度的內存分割和映射方法,此時是否會想到Linux中的Buddy算法和slab內存分配機制呢。另一種將虛擬地址轉換為物理地址的方法分頁機制應運而生了。

分頁機制

  分頁機制就是把內存地址空間分為若干個很小的固定大小的頁,每一頁的大小由內存決定,就像Linux中ext文件系統將磁盤分成若干個Block一樣,這樣做是分別是為了提高內存和磁盤的利用率。

  試想一下,如果將磁盤空間分成N等份,每一份的大小(一個Block)是1M,如果我想存儲在磁盤上的文件是1K字節,那么其余的999字節是不是浪費了。所以需要更加細粒度的磁盤分割方式,我們可以將Block設置得小一點,這當然是根據所存放文件的大小來綜合考慮的,好像有點跑題了,我只是想說,內存中的分頁機制跟ext文件系統中的磁盤分割機制非常相似。

  Linux中一般頁的大小是4KB,我們把進程的地址空間按頁分割,把常用的數據和代碼頁裝載到內存中,不常用的代碼和數據保存在磁盤中,我們還是以一個例子來說明,如下圖:

  

  可以看到進程1和進程2的虛擬地址空間都被映射到了不連續的物理地址空間內(這個意義很大,如果有一天我們的連續物理地址空間不夠,但是不連續的地址空間很多,如果沒有這種技術,我們的程序就沒有辦法運行),甚至他們共用了一部分物理地址空間,這就是共享內存

  進程1的虛擬頁VP2和VP3被交換到了磁盤中,在程序需要這兩頁的時候,Linux內核會產生一個缺頁異常,然后異常管理程序會將其讀到內存中。

  這就是分頁機制的原理,當然Linux中的分頁機制的實現還是比較復雜的,通過了頁全局目錄,頁上級目錄,頁中級目錄,頁表等幾級的分頁機制來實現的,但是基本的工作原理是不會變的。

  分頁機制的實現需要硬件的實現,這個硬件名字叫做MMU(Memory Management Unit),他就是專門負責從虛擬地址到物理地址轉換的,也就是從虛擬頁找到物理頁。


 文章出處

  http://blog.chinaunix.net/uid-26611383-id-3761754.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM