首頁  >  文章  >  運維  >  詳細介紹Linux中的記憶體管理

詳細介紹Linux中的記憶體管理

巴扎黑
巴扎黑原創
2017-08-23 15:50:171236瀏覽

前一段時間看了《深入理解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中的記憶體管理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn