Linux中的內存管理歷史

原文地址   http://blog.chinaunix.net/uid-26611383-id-3761754.html

前一段時間看了《深入理解Linux內核》對其中的內存管理部分花了不少時間,但是還是有很多問題不是很清楚,最近又花了一些時間複習了一下,在這裏記錄下自己的理解和對Linux中內存管理的一些看法和認識。

我比較喜歡搞清楚一個技術本身的發展歷程,簡而言之就是這個技術是怎麼發展而來的,在這個技術之前存在哪些技術,這些技術有哪些特點,爲什麼會被目前的技術所取代,而目前的技術又解決了之前的技術所存在的哪些問題。弄清楚了這些,我們才能比較清晰的把握某一項技術。有些資料在介紹某個概念的時候直接就介紹這個概念的意義,原理,而對其發展過程和背後的原理絲毫不提,彷彿這個技術從天上掉下來的一樣。介於此,還是以內存管理的發展歷程來講述今天的主題。

首先,我必須要闡述一下這篇文章的主題是Linux內存管理中的分段和分頁技術。

讓我們來回顧一下歷史,在早期的計算機中,程序是直接運行在物理內存上的。換句話說,就是程序在運行的過程中訪問的都是物理地址。如果這個系統只運行一個程序,那麼只要這個程序所需的內存不要超過該機器的物理內存就不會出現問題,我們也就不需要考慮內存管理這個麻煩事了,反正就你一個程序,就這麼點內存,吃不吃得飽那是你的事情了。然而現在的系統都是支持多任務,多進程的,這樣CPU以及其他硬件的利用率會更高,這個時候我們就要考慮到將系統內有限的物理內存如何及時有效的分配給多個程序了,這個事情本身我們就稱之爲內存管理。

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

加入我們有三個程序,程序1,2,3.程序1運行的過程中需要10M內存,程序2運行的過程中需要100M內存,而程序3運行的過程中需要20M內存。如果系統同時需要運行程序A和B,那麼早期的內存管理過程大概是這樣的,將物理內存的前10M分配給A, 接下來的10M-110M分配給B。這種內存管理的方法比較直接,好了,假設我們這個時候想讓程序C也運行,同時假設我們系統的內存只有128M,顯然按照這種方法程序C由於內存不夠是不能夠運行的。大家知道可以使用虛擬內存的技術,內存空間不夠的時候可以將程序不需要用到的數據交換到磁盤空間上去,已達到擴展內存空間的目的。下面我們來看看這種內存管理方式存在的幾個比較明顯的問題。就像文章一開始提到的,要很深層次的把握某個技術最好搞清楚其發展歷程。

1. 進程地址空間不能隔離

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

2.   內存使用的效率低

如上面提到的,如果我們要像讓程序A、B、C同時運行,那麼唯一的方法就是使用虛擬內存技術將一些程序暫時不用的數據寫到磁盤上,在需要的時候再從磁盤讀回內存。這裏程序C要運行,將A交換到磁盤上去顯然是不行的,因爲程序是需要連續的地址空間的,程序C需要20M的內存,而A只有10M的空間,所以需要將程序B交換到磁盤上去,而B足足有100M,可以看到爲了運行程序C我們需要將100M的數據從內存寫到磁盤,然後在程序B需要運行的時候再從磁盤讀到內存,我們知道IO操作比較耗時,所以這個過程效率將會十分低下。

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),他就是專門負責從虛擬地址到物理地址轉換的,也就是從虛擬頁找到物理頁。

 

 

參考文獻:

《深入理解Linux內核》

《程序員的自我修養》


發佈了33 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章