淺析Linux記憶體管理

來源:互聯網
上載者:User

 

Hi,MM !

刺蝟@http://blog.csdn.net/littlehedgehog

 

 

 

 

 

 

看得懂一段kernel代碼,但就是不太清楚這段代碼在Linux中究竟有什麼作用,可能很多的Linux kernel初學愛好者都對此深有感觸吧。這裡其實是看核心的一個絆腳石,很多的初學者對Linux其實並不是很熟悉的,或者是只是簡單的在Linux環境"遊歷"了一番,並沒有進行Linux環境下編程(或者說調用過Linux API),這樣很多人模模糊糊讀懂了一大段核心代碼,知其然而不知其所以然,這就是缺乏對Linux熟悉而造成的閱讀障礙。可以想象,如果起先有Linux編程經驗,那麼他讀kernel的時候盡可以做到知其然亦知其所以然。所以建議讀者們,在研讀核心代碼的同時看看網上有關的應用編程舉例,這裡我重點推薦可以研讀一些駭客小程式代碼,短小精悍,特別適合作為系統調用參照代碼。

 

下面這個也是從應用角度出發逐步剖析Linux核心記憶體管理。這對於核心程式員來說都是很簡單的內容。 好,我們切入正題。

 

一提到記憶體管理,我們頭腦中閃出的兩個概念,就是虛擬記憶體,與實體記憶體。這兩個概念主要來自於linux核心的支援。Linux在記憶體管理上份為兩類,一類是線性區,類似於00c73000-00c88000,對應於虛擬記憶體,它實際上不佔用實際實體記憶體;一級是具體的物理頁面,它對應我們機器上的實體記憶體。
這裡要提到一個很重要的概念,記憶體的延遲分配。Linux核心在使用者申請記憶體的時候,只是給它分配了一個線性區(也就是虛存),並沒有分配實際實體記憶體;只有當使用者使用這塊記憶體的時候,核心才會分配具體的物理頁面給使用者,這時候才佔用寶貴的實體記憶體。核心釋放物理頁面是通過釋放線性區,找到其所對應的物理頁面,將其全部釋放的過程。

 

  1. char *p=malloc(2048); //這裡只是分配了虛擬記憶體2048,並不佔用實際記憶體。 
  2. strcpy(p,”123”) ;//分配了物理頁面,雖然只是使用了3個位元組,但記憶體還是為它分配了2048位元組的實體記憶體。 
  3. free(p) ;//通過虛擬位址,找到其所對應的物理頁面,釋放物理頁面,釋放線性區。 

我們知道使用者的進程和核心是運行在不同的層級,進程與核心之間的通訊是通過系統調用來完成的。進程在申請和釋放記憶體,主要通過brk,sbrk,mmap,unmmap這幾個系統調用,傳遞的參數主要是對應的虛擬記憶體。
注意一點,在進程只能訪問虛擬記憶體,它實際上是看不到核心實體記憶體的使用,這對於進程是完全透明的
glibc記憶體管理器
那麼我們每次調用malloc來分配一塊記憶體,都進行相應的系統調用呢?
答案是否定的,這裡我要引入一個新的概念,glibc的記憶體管理器。
我們知道malloc和free等函數都是包含在glibc庫裡面的庫函數,我們試想一下,每做一次記憶體操作,都要調用系統調用的話,那麼程式將多麼的低效。實際上glibc採用了一種批發和零售的方式來管理記憶體。glibc每次通過系統調用的方式申請一大塊記憶體(虛擬記憶體),當進程申請記憶體時,glibc就從自己獲得的記憶體中取出一塊給進程。

記憶體管理器面臨的困難

我們在寫程式的時候,每次申請的記憶體塊大小不規律,而且存在頻繁的申請和釋放,這樣不可避免的就會產生記憶體碎塊。而記憶體碎塊,直接會導致大塊記憶體申請無法滿足,從而更多的佔用系統資源;如果進行碎塊整理的話,又會增加cpu的負荷,很多都是互相矛盾的指標,這裡我就不細說了。
我們在寫程式時,涉及記憶體時,有兩個概念heap和stack。傳統的說法stack的記憶體位址是向下增長的,heap的記憶體位址是向上增長的。

函數malloc和free,主要是針對heap進行操作,由程式員自主控制記憶體的訪問。在這裡heap的記憶體位址向上增長,這句話不完全正確。glibc對於heap記憶體申請大於128k的記憶體申請,glibc採用mmap的方式向核心申請記憶體,這不能保證記憶體位址向上增長;小於128k的則採用brk,對於它來講是正確的。128k的閥值,可以通過glibc的庫函數進行設定。

這裡我先講大塊記憶體的申請,也即對應於mmap系統調用。
對於大塊記憶體申請,glibc直接使用mmap系統調用為其劃分出另一塊虛擬位址,供進程單獨使用;在該塊記憶體釋放時,使用unmmap系統調用將這塊記憶體釋放,這個過程中間不會產生記憶體碎塊等問題。
針對小塊記憶體的申請,在程式啟動之後,進程會獲得一個heap底端的地址,進程每次進行記憶體申請時,glibc會將堆頂向上增長來擴充記憶體空間,也就是我們所說的堆地址向上增長。在對這些小塊記憶體進行操作時,便會產生記憶體碎塊的問題。實際上brk和sbrk系統調用,就是調整heap頂地址指標。
那麼heap堆的記憶體是什麼時候釋放呢?
當glibc發現堆頂有連續的128k的空間是閒置時候,它就會通過brk或sbrk系統調用,來調整heap頂的位置,將佔用的記憶體返回給系統。這時,核心會通過刪除相應的線性區,來釋放佔用的實體記憶體。
下面我要講一個記憶體空洞的問題:
一個情境,堆頂有一塊正在使用的記憶體,而下面有很大的連續記憶體已經被釋放掉了,那麼這塊記憶體是否能夠被釋放?其對應的實體記憶體是否能夠被釋放?
很遺憾,不能。
這也就是說,只要堆頂的部分申請記憶體還在佔用,我在下面釋放的記憶體再多,都不會被返回到系統中,仍然佔用著實體記憶體。為什麼會這樣呢?
這主要是與核心在處理堆的時候,過於簡單,它只能通過調整堆頂指標的方式來調整調整程式佔用的線性區;而又只能通過調整線性區的方式,來釋放記憶體。所以只要堆頂不減小,佔用的記憶體就不會釋放。

代碼佔用的記憶體
資料部分佔用記憶體,那麼我們寫的程式是不是也佔用記憶體呢?
在linux中,程式的載入,涉及到兩個工具,linker 和loader。Linker主要涉及動態連結程式庫的使用,loader主要涉及軟體的載入。
1、 exec執行一個程式
2、 elf為現在非常流行的可執行檔的格式,它為程式運行劃分了兩個段,一個段是可以執行的程式碼片段,它是唯讀,可執行;另一個段是資料區段,它是可讀寫,不能執行。
3、 loader會啟動,通過mmap系統調用,將代碼端和資料區段映射到記憶體中,其實也就是為其分配了虛擬記憶體,注意這時候,還不佔用實體記憶體;只有程式執行到了相應的地方,核心才會為其分配實體記憶體。
4、 loader會去尋找該程式依賴的連結庫,首先看該連結庫是否被映射進記憶體中,如果沒有使用mmap,將程式碼片段與資料區段映射到記憶體中,否則只是將其加入進程的地址空間。這樣比如glibc等庫的記憶體位址空間是完全一樣。
因此一個2M的程式,執行時,並不意味著為其分配了2M的實體記憶體,這與其運行了的代碼量,與其所依賴的動態連結程式庫有關。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.