dlmalloc 圖解

 

2. 標記結構

本章節將介紹基本的內存標記結構,包括chunk, tree chunk, sbin, tbin, segment, mstate等.這些重要的機構組成了dlmalloc分配算法的基礎.算法

2.1 chunk

chunk是dlmalloc中最基本的一種結構,它表明了一塊通過劃分後被管理的內存單元. dlmalloc全部對內存的操做幾乎都聚焦在chunk上.須要注意的是, chunk雖然看似基礎,但不表明它能管理的內存小.事實上chunk被劃分爲兩種類型,小於256字節的稱爲small chunk,而大於等於256字節的被稱爲tree chunk.緩存

2.1.1 chunk佈局

在dlmalloc的源碼中有一幅用字符畫成的示意圖,套用Doug Lea本人的話說 「is misleading but accurate and necessary」.初次接觸這種結構劃分會以爲既古怪又彆扭. 所以,接下來的幾節內容須要認真體會,讓大腦適應這種錯位的思考.數據結構

對於一個已分配chunk, 它在內存中多是這個樣子,多線程

上圖爲了便於理解特地使用了兩種顏色來描述,藍色區域的部分表明當前的已分配chunk,而黃色區域表明上一個和下一個chunk的部分區域.app

解釋一下其中的含義, 從藍色區域開始看, 在已分配chunk的開始記錄當前chunk的size信息,同時在最後兩個bit位分別記錄當前chunk和前一個chunk是否被使用,簡稱爲C和P兩個bit位.之因此能夠作到這一點,是由於在dlmalloc中全部的chunk size至少對齊到大於8並以2爲底的指數邊界上.這樣,至少最後3位都是0,所以這些多餘的bit位就能夠拿來記錄更多的信息. size信息後面是payload部分,顯然,當前chunk的使用信息也會記錄在下一個chunk開始的P位上.less

而對於一個空閒chunk, 其內存佈局應該是這樣的,函數

與以前的一致, 藍色區域表明當前空閒chunk, 黃色區域表明相鄰的先後chunk.能夠看到空閒chunk與以前的區別在於開始的size後多了next和prev指針,它們把具備相同大小的空閒chunk連接到一塊兒.另外一個區別是,在空閒chunk最後一樣記錄該chunk的大小信息.那麼爲何一樣的信息要記錄兩次呢?在下一個小節中會解釋這個問題.佈局

另外, 還有一點須要說明的是,空閒chunk的相鄰chunk必然是已分配的chunk.由於若是存在相鄰兩個chunk都是空閒的,那麼dlmalloc會把它們合併爲一個更大的chunk.post

2.1.2 邊界標記法(Boundary Tag)

上一節中介紹的chunk佈局其實只是理論上的東西,而實現它使用了被稱爲邊界標記法 (boundary tag)的技術.據做者說,此方法最先是大神Knuth提出的.實現邊界標記法使用了名爲malloc_chunk的結構體,它的定義以下,測試

該結構體(之後簡稱mchunk)由四個field組成.最開始是prev_foot,記錄了上一個鄰接chunk的最後4個字節.接下來是head,記錄當前chunk的size以及C和P位.最後兩個是fd, bk指針,只對空閒chunk起做用,用於連接相同大小的空閒chunk.

爲了不讀者感到困惑, 在上一節的圖中並無畫出對應的mchunk, 如今補充完整以下,

上圖用不一樣顏色畫了幾個連續交錯的chunk,並故意在中間斷開,標註出mchunk以及chunk的實際範圍,由此獲得以下的結論,

1.       mchunk的做用是將連續內存劃分爲一段段小的區塊, 並在內部保留這些區塊的信息.

2.       mchunk最後的fd, bk是能夠複用的,對於空閒chunk它們表明連接指針,而已使用chunk中這兩個field實際上存放的是payload數據.

3.       mchunk與chunk不是一個等同的概念.這一點是容易讓人混淆和困惑的. mchunk只是一個邊界信息,它實際上橫跨了兩個相鄰chunk.儘管通常認爲mchunk能夠指代當前的chunk,由於你能夠從它推算出想要的地址.但從邏輯上,它既不表明當前chunk也不表明prev chunk.

4.       prev_foot字段一樣是一個可複用的字段. 通常狀況下它有三種含義,若是前一個相鄰chunk是空閒狀態,它記錄該chunk的大小(圖中mchunk C).其目的就是你能夠很方便的從當前chunk得到上一個相鄰chunk的首地址,從而快速對它們進行合併.這也就是上一小節介紹的空閒chunk會在head和footer存在兩個chunk size的緣由,位於footer的size實際上保存在下一個mchunk中.若是前一個相鄰chunk處於in used狀態,那麼該字段可能有兩種狀況.一種狀況是FOOTERS宏爲1,這時它保存一個交叉檢查值,用於在free()的時候進行校驗.若是該宏爲0,則它沒有多餘的含義,單純只是前面chunk的payload數據.

5.       最後還有一點須要注意的是, 儘管前面提到全部chunk size都是對齊到至少等於8的2的指數.但這不意味着mchunk就對齊到這一邊界上,由於如前所述mchunk和chunk是兩碼事.我本人最先在看這部分代碼時曾天真的覺得mchunk也是對齊的,結果到後面竟然徹底看不懂,後來才發現這傢伙根本沒有對齊.

到目前爲止, 已經瞭解了dlmalloc的邊界標記法是如何實現的.可能有人會對此感到不覺得然,做者爲何要把代碼寫成這個樣子呢?其實,要理解這個意圖請換位思考一下,若是實現一個相似的東西,你會怎麼寫呢?人們很快會發現,不按照這種方式設計,想要達到一樣目的幾乎是很困難的.由於一個chunk在頭和尾存在head和footer(也可能不存在footer),中間是一片長度沒法肯定的區域.假設按照正常從頭至尾的順序寫,這個結構體中間填充多長的內容是無法肯定的.所以, Doug Lea巧妙的把除了payload以外的overhead部分合並在一塊兒,做爲boundary tag來分割整個內存區域,既能作到很方便的計算,又準確地表達了chunk的結構.

2.1.3 chunk操做

針對mchunk的設計,源碼中定義了大量的宏來對其進行操做.因爲數量不少加上嵌套,對於閱讀源碼會形成不小的障礙,所以這裏會對一些經常使用宏進行梳理和簡單講解.

定義size_t的大小和bit位數.注意, dlmalloc中對size_t規定爲必須爲無符號類型,且與指針類型(void*)等寬度.所以若是目標平臺還在使用有符號的size_t,只能使用較舊版本的dlmalloc.

常量定義, 這裏使用size_t做爲強制類型是爲了不在某些平臺上出現錯誤.

 

對齊邊界和對齊掩碼, 邊界默認定義爲兩倍指針寬度, 固然還能夠修改的更大,但必須以2爲底的指數.有關對齊掩碼相關知識請參考1.4.1節.

判斷給定地址是否對齊在邊界上,原理一樣在1.4.1節中解釋,再也不贅述.

計算給定地址須要對齊的偏移量,請參考1.4.3節中的介紹.

chunk size(其實是mchunk size,後面再也不特地說明)以及overhead大小.注意,若是FOOTERS等於1, overhead將多出4個字節,其實就是在chunk最後放置交叉檢查項多出的負載.另外,無論是否有FOOTERS, chunk size自己是不變的.

最小chunk大小. dlmalloc中即便調用malloc(0)也是會分配內存的.這種狀況下徹底不考慮用戶payload了,僅僅將chunk size作了對齊.該值的意義在於,這是dlmalloc中除了mmap chunk之外的最壞負載損耗.

這是兩個最經常使用的宏之一, 在chunk和用戶地址以前切換.

該宏的意思是, 將指定chunk的用戶地址對齊,返回對齊後的新chunk地址.理解很容易,從chunk獲取用戶地址,計算對齊偏移,再移動chunk指針.從這裏能夠看到, chunk中對齊的並不是是mchunk指針,而是用戶地址.

將用戶請求的內存值轉化成實際的內部大小.該宏也是經常使用宏之一,請求大小先加上overhead(根據FOOTERS而有所不一樣),再對齊到邊界上.

該宏是對上面一系列宏的封裝, 若是用戶請求小於最小請求, 直接使用最小chunk大小,不然認爲正常,轉化成內部可用大小.

上面這一組定義了C位和P位,其中FLAG4_BIT並無實際用途,只是做爲將來的擴展.

測試C和P位,分別用掩碼作與運算.着重說一下最後兩個判斷. is_inuse是用C和P的掩碼分別測試,而後與P的掩碼對比.有人會問這個直接用前面的cinuse不就行了嗎?由於存在一種特殊狀況,就是被mmap出來的chunk是不見得有鄰接chunk的,因此其C和P都被置0. is_inuse就是爲了判斷包括mmap chunk在內的全部類型chunk的使用狀況.理解了這個, is_mmapped就很容易理解了.

計算指定chunk的大小,清除最後3bit後獲得的就是size.

對P位的操做.

這是兩個計算偏移量的宏, 返回結果被認爲是mchunk, 通常用做chunk切割.

這兩個也是很是重要的宏. 分別用來計算前一個或後一個鄰接chunk的首地址. next_chunk先獲取當前chunk的size做爲偏移量,再移動chunk指針. prev_chunk則是直接加上前一個chunk的footer做爲偏移量.須要注意的是, prev_chunk僅僅適用於前一個鄰接chunk爲空閒塊的狀況.如前所述,非空閒chunk這裏放置的多是用戶數據或者交叉檢查項.實際上這個宏就是用於free時試圖與前一個free chunk合併時的判斷.

獲取下一個chunk的P位,應該與當前chunk的C是一致的.該宏通常用做交叉檢查項.

獲取和設置當前chunk的footer,由於footer被放在下一個鄰接chunk的prev_foot中,所以須要加上size做爲偏移量.

該宏是個複合操做, 用於對free chunk進行設置.由於free chunk在head和footer都保存其size,因此首先將其head中寫入size.又由於free chunk的P必定是1(不然會與前面合併),所以還須要與P掩碼進行一次位與.接着,利用前面的set_foot宏在footer中一樣寫入size.

這個宏與上面的區別在於多了一個next chunk的P位維護.由於是針對free chunk, next chunk的P須要置0.

2.1.4 tree chunk

以前介紹的chunk其實都屬於小型chunk,而較大型的chunk是經過名爲malloc_tree_chunk的結構體來描述的.

同mchunk相似tree chunk(之後簡稱tchunk)開始的field是如出一轍的,這就保證了兩種類型在基礎結構上具備較好的兼容性,下降了代碼編寫難度. tchunk與前者的區別在於管理上使用不一樣的結構. mchunk是經過雙鏈表組織起來的,而tchunk使用了一種樹形結構,在介紹分箱時會詳細說明.所以tchunk中child分別表明了左右子樹節點, parent表明父節點, index表明該chunk所在分箱的編號.而fd, bk與mchunk一致,都是用於連接相同size的chunk.另外,須要說明的是,上述結構體字段一樣只適用於free chunk,在used chunk上它們都是可複用的

 

 

 

 

=======================================================================================

咱們寫過不少C程序了,常常會分配內存。記得剛學C語言時老師說過,能夠向兩個地方申請內存:一個是棧、一個是堆。小塊內存向棧申請,函數調用結束後程序會自動釋放內存。大塊內存向堆申請,記得必定要本身釋放,不然會形成內存泄漏。向堆申請內存直接調用malloc()就能夠了,參數是你申請的內存量。釋放內存時直接調用free()就能夠了,參數是內存塊指針。

        看似平靜的海面,海底則波濤洶涌。當時尚未學操做系統原理,更沒有讀過Linux內核代碼。如今仔細想一想才發現申請動態內存是一件多麼麻煩的事情。動態內存管理涉及到兩個層面的問題:內核層面和用戶層面。系統中的內存如何管理這是內核考慮的事情,總不能讓應用程序隨便使用系統中的內存吧。內核嚮應用程序提供了接口(爲此Linux提供了兩個系統調用brk和mmap),當應用程序須要申請內存時向內核提出請求,內核查找並分配一塊可用內存供應用程序使用。這部份內容屬於內核範疇,不屬於C基礎庫,所以不深刻說了。那麼用戶層面作什麼呢?用戶層面須要合理管理內存申請和釋放請求。好比:brk()能夠擴充或收縮堆的大小,你總不能每分配一次內存就調用一次brk()吧?釋放內存時更麻煩,你必須保證內存塊的釋放順序。好比先申請了內存塊a,而後申請了內存塊b,而後釋放a(b仍然在使用),若是釋放a時調用了brk()就會出問題。你不能在使用b的同時釋放a。

        好在出現了一個叫作「內存分配器」的東西,內存分配器接管了應用程序申請內存和釋放內存的請求,應用程序不再須要直接調用brk()和mmap()了,而是向內存分配器提交申請。有了內存分配器,咱們只須要記住malloc()和free()兩個接口函數就能夠了,其餘繁雜事情所有交給內存分配器負責了。申請內存時,內存分配器會一次向內核申請大量內存,而後分批交給應用程序,從而提升了效率。釋放內存時,應用程序也是將內存釋放給內存分配器,內存分配器在合適的時候再將內存釋放會內核。

        dlmalloc就是一種內存分配器,由Doug Lea在1987年開發完成,這是Android系統中使用的內存分配器。而Linux系統中採用的是ptmalloc,ptmalloc在dlmalloc的基礎上進行了改進,以更好適應多線程。dlmalloc採用兩種方式申請內存,若是應用程序單次申請的內存量小於256kb,dlmalloc調用brk()擴展進程堆空間,可是dlmalloc向內核申請的內存量大於應用程序申請的內存量,申請到內存後dlmalloc將內存分紅兩塊,一塊返回給應用程序,另外一塊做爲空閒內存先保留起來。下次應用程序申請內存時dlmalloc就不須要向內核申請內存了,從而加快內存分配效率。當應用程序調用free()釋放內存時,若是內存塊小於256kb,dlmalloc並不立刻將內存塊釋放回內存,而是將內存塊標記爲空閒狀態。這麼作的緣由有兩個:一是內存塊不必定能立刻釋放會內核(好比內存塊不是位於堆頂端),二是供應用程序下次申請內存使用(這是主要緣由)。當dlmalloc中空閒內存量達到必定值時dlmalloc纔將空閒內存釋放會內核。若是應用程序申請的內存大於256kb,dlmalloc調用mmap()向內核申請一塊內存,返回返還給應用程序使用。若是應用程序釋放的內存大於256kb,dlmalloc立刻調用munmap()釋放內存。dlmalloc不會緩存大於256kb的內存塊,由於這樣的內存塊太大了,最好不要長期佔用這麼大的內存資源。

dlmalloc中,申請到的內存被分割成若干個內存塊,dlmalloc採用兩種不一樣的數據結構表示這些內存塊。小內存塊保存在鏈表中,用struct malloc_chunk表示;大內存塊保存在樹形結構中,用struct malloc_tree_chunk表示。struct malloc_chunk結構以下:

[cpp] view plaincopy

  1. struct malloc_chunk {  
  2.   size_t               prev_foot;  /* Size of previous chunk (if free).  */  
  3.   size_t               head;       /* Size and inuse bits. */  
  4.   struct malloc_chunk* fd;         /* double links -- used only if free. */  
  5.   struct malloc_chunk* bk;  
  6. };  

 

        fd表示鏈表中後面一個malloc_chunk結構,bk表示鏈表中前一個malloc_chunk結構。head表示這個malloc_chunk表明內存塊的大小,另外還包含了一些標誌信息。prev_foot表示前一個malloc_chunk的大小,這裏的"前一個"不是鏈表中的"前一個",而是與這個malloc_chunk地址相鄰的"前一個"。經過prev_foot和size兩個字段dlmalloc就能夠快速找到地址相鄰的前一個和後一個malloc_chunk結構。

        當內存塊被分配給應用程序後,就會被從鏈表中摘除,這時malloc_chunk結構中的fd和bk兩個字段就沒有意義了,所以能夠供應用程序使用。咱們調用malloc()申請內存時,malloc()會返回一個指針,指向申請到的內存塊的起始地址p,其實這個地址前還有一個malloc_chunk結構,咱們能夠經過p-8獲得malloc_chunk結構的指針。反過來也能夠經過malloc_chunk指針獲得分配給應用程序的內存塊的起始地址。爲此dlmalloc定義了兩個宏:

[cpp] view plaincopy

  1. typedef struct malloc_chunk* mchunkptr;  
  2. // 32位Linux系統中,TWO_SIZE_T_SIZES的值是8  
  3. #define chunk2mem(p)        ((void*)((char*)(p)       + TWO_SIZE_T_SIZES))  
  4. #define mem2chunk(mem)      ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES))  

咱們看下面這個例子:

        上面這塊內存區域中包括兩個內存塊,分別爲chunk1和chunk2,緊接着malloc_chunk結構的就是供應用程序使用的內存。按照前面的分析,fd和bk兩個字段也能夠供應用程序使用。所以ptr1 = chunk1 + 8,ptr2 = chunk2 + 8。還有一點須要注意的是,只有當前面一個chunk空閒時malloc_chunk結構中的prev_foot才保存前一個chunk的大小;當前面一個chunk分配給應用程序後,prev_foot字段也能夠供應用程序使用。上圖中,當chunk1分配給應用程序後,chunk2中的prev_foot字段就沒有意義了,能夠供應用程序使用。dlmalloc返回給應用程序的地址是ptr1,這個內存塊的大小是size1 + 8 + 4。所以,malloc_chunk結構中,只有head字段永遠不會挪做他用,其餘三個字段均可以供應用程序使用,經過這種複用最大限度地減小了dlmalloc自己佔用的內存。

        dlmalloc對應用程序申請的內存長度有限制,要求內存塊長度(包括malloc_chunk結構佔用的內存)必須是8字節的倍數。假如應用程序調用malloc(13)申請長度爲13字節的內存塊,dlmalloc最終分配內存塊大小是24字節,除去malloc_chunk結構中head佔用的4字節,分配給應用程序的內存塊大小是20字節。固然,應用程序不要揣測內存塊的實際大小,雖然dlmalloc分配了20字節,可是應用程序最好只使用13字節,不要使用剩餘的7字節。不然有兩方面後果:(1)應用程序顯得混亂,其餘人可能沒法讀懂你的代碼。(2)返回多少字節與內存分配器的實現方式有關,換另一種內存分配器可能返回的就不是20字節了,若是應用程序使用超過13個字節就可能覆蓋其餘數據了,程序移植性差。

        malloc_chunk結構能夠表示的最小內存塊是16字節,最大內存塊是248字節,所以malloc_chunk能夠表示1六、2四、3二、40、......、248共30種長度的內存塊。dlmalloc定義了30條鏈表,相同長度的空閒內存塊保存在一個鏈表中。

        超過248字節的內存就屬於大塊內存了,大塊內存用malloc_tree_chunk表示,這個數據結構定義以下:

[cpp] view plaincopy

  1. struct malloc_tree_chunk {  
  2.   /* The first four fields must be compatible with malloc_chunk */  
  3.   size_t                    prev_foot;  
  4.   size_t                    head;  
  5.   struct malloc_tree_chunk* fd;  
  6.   struct malloc_tree_chunk* bk;  
  7.   
  8.   struct malloc_tree_chunk* child[2];  
  9.   struct malloc_tree_chunk* parent;  
  10.   bindex_t                  index;  
  11. };  

其中prev_foot和head的定義跟malloc_chunk中的定義徹底相同。那麼其餘幾個字段表示什麼含義呢?dlmalloc中小內存塊只有30種狀況,能夠用30條鏈表存儲;可是大內存塊有無數種狀況(25六、26四、27二、......),所以就不能用鏈表表示了,大內存塊保存在樹形結構中,dlmalloc定義了32棵樹存儲大內存塊,每棵樹中存儲若干種長度的內存塊,每棵樹保存的內存塊範圍以下:

dlmalloc中根據內存塊大小計算所在樹的編號的宏以下:

[cpp] view plaincopy

  1. #define compute_tree_index(S, I)\  
  2. {\  
  3.   size_t X = S >> TREEBIN_SHIFT;  /* TREEBIN_SHIFT的值是8 */ \  
  4.   if (X == 0)\  
  5.     I = 0;\  
  6.   else if (X > 0xFFFF)\  
  7.     I = NTREEBINS-1;  /* NTREEBINS的值是32 */ \  
  8.   else {\  
  9.     unsigned int K;\  
  10.     __asm__("bsrl %1,%0\n\t" : "=r" (K) : "rm"  (X));\  
  11.     I =  (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\  
  12.   }\  
  13. }  

若是感興趣能夠採用這個宏計算一下。咱們看一下單棵樹中保存的空閒內存塊,以編號爲0的樹爲例,這棵樹中內存塊的範圍是[256, 384),按照前面規定內存塊的大小必須是8的倍數,所以這棵樹中保存的內存塊長度分別爲256, 264, 272, 280, 288, 296, 304, 312, 320, 328, 336, 344, 352, 360, 368, 376,共16種長度,每種長度的內存塊做爲樹中一個節點。這棵樹中可能保存了多個相同長度的內存塊,這些相同長度的內存塊構成了一棵鏈表,以下圖所示:

 

如今回過頭來看malloc_tree_chunk中各個字段的含義。

prev_foot表示前一個內存塊的大小

head表示本內存塊的大小

child表示兩個子節點

parent表示父節點

index表示內存塊所在樹的索引號

fd表示鏈表中下一個內存塊

bk表示鏈表中前面一個內存塊

一樣,這個結構中只有head字段保持不變,其餘字段均可以供應用程序使用。

 

如今咱們來看一個全局變量_gm_,這是struct malloc_state類型的變量,這個數據結構定義以下:

[cpp] view plaincopy

  1. struct malloc_state {  
  2.   binmap_t   smallmap;  
  3.   mchunkptr  smallbins[(NSMALLBINS+1)*2];  
  4.   
  5.   binmap_t   treemap;  
  6.   tbinptr    treebins[NTREEBINS];  
  7.   
  8.   mchunkptr  dv;  
  9.   size_t     dvsize;  
  10.   
  11.   mchunkptr  top;  
  12.   size_t     topsize;  
  13.   
  14.   char*      least_addr;  
  15.   size_t     trim_check;  
  16.   
  17.   size_t     magic;  
  18.   size_t     footprint;  
  19. #if USE_MAX_ALLOWED_FOOTPRINT  
  20.   size_t     max_allowed_footprint;  
  21. #endif  
  22.   size_t     max_footprint;  
  23.   flag_t     mflags;  
  24. #if USE_LOCKS  
  25.   MLOCK_T    mutex;  
  26. #endif /* USE_LOCKS */  
  27.   msegment   seg;  
  28. };  
  29.   
  30. static struct malloc_state _gm_;  

        咱們重點關注前8個字段。smallbins就是dlmalloc中定義的30條鏈表(加上長度爲0和8的內存塊,共32條鏈表)。smalbins[0]-smallbins[3]共16字節,表示一個malloc_chunk結構,對應長度爲0的鏈表。smalbins[2]-smallbins[5]共16字節,表示一個malloc_chunk結構,對應長度爲8的鏈表,以此類推。能夠看到相鄰兩個malloc_chunk結構有重合,這是由於做爲鏈表使用時,malloc_chunk結構中的prev_foot和head字段沒有意義,所以能夠重合使用。smallmap是smallbins的位圖,某個比特置位表示對應的鏈表上有空閒內存塊,比特清零表示對應的鏈表爲空。treebins表示dlmalloc中32棵樹,treemap是treebins的位圖,置位表示對應樹中有空閒內存塊,清零表示對應樹爲空。dv是一個特殊的內存塊,若是dlmalloc中找不到一個合適大小的內存塊分配給應用程序,那麼dlmalloc會將一個較大的內存塊分割成兩個較小的內存塊,一塊給應用程序使用,另一塊保存在dv中。下載再找不到合適大小的內存塊時,若是dv大小大於應用程序請求的內存塊,dlmalloc會將dv分割成兩塊,一塊給應用程序,另外一塊仍保存在dv中;若是dv小於應用程序請求的內存塊,dlmalloc首先將dv保存在鏈表或樹中,而後挑選另一個內存塊分割,一塊給應用程序,另外一塊保存在dv中。所以dlmalloc分配內存塊的原則是先匹配大小,後匹配位置,儘可能挑選合適大小的內存塊給應用程序,實在找不到合適的內存塊時就儘可能從同一個位置分割內存塊,以提升效率(程序執行的局部性原理)。dvsize就是dv表示內存塊的大小。top是另一個特殊的內存塊,表示堆空間中對頂端的內存塊。dlmalloc儘可能不使用這個內存塊,只有在_gm_中沒有合適大小的內存塊而且沒有更大的內存塊可供分割時才使用top中的內存。爲何儘可能不要使用top呢?由於當top被佔用時dlmalloc沒辦法釋放其餘空閒內存,dlmalloc收縮堆時必須從高地址向低地址收縮,因此主要高地址的內存被佔用,即便堆中有再多的空閒內存也沒辦法釋放。topsize表示top的大小。

 

[cpp] view plaincopy

  1. void* dlmalloc(size_t bytes) {  
  2.   /* 
  3.      Basic algorithm:   算法描述 
  4.      If a small request (< 256 bytes minus per-chunk overhead): 
  5.        1. If one exists, use a remainderless chunk in associated smallbin. 
  6.           (Remainderless means that there are too few excess bytes to 
  7.           represent as a chunk.) 
  8.        2. If it is big enough, use the dv chunk, which is normally the 
  9.           chunk adjacent to the one used for the most recent small request. 
  10.        3. If one exists, split the smallest available chunk in a bin, 
  11.           saving remainder in dv. 
  12.        4. If it is big enough, use the top chunk. 
  13.        5. If available, get memory from system and use it 
  14.      Otherwise, for a large request: 
  15.        1. Find the smallest available binned chunk that fits, and use it 
  16.           if it is better fitting than dv chunk, splitting if necessary. 
  17.        2. If better fitting than any binned chunk, use the dv chunk. 
  18.        3. If it is big enough, use the top chunk. 
  19.        4. If request size >= mmap threshold, try to directly mmap this chunk. 
  20.        5. If available, get memory from system and use it 
  21.  
  22.      The ugly goto's here ensure that postaction occurs along all paths. 
  23.   */  
  24.   
  25.   if (!PREACTION(gm)) {  
  26.     void* mem;  
  27.     size_t nb;  
  28.     // 若是申請的內存量小於244字節,表示是小塊內存.  
  29.     if (bytes <= MAX_SMALL_REQUEST) {    // 244字節  
  30.       bindex_t idx;  
  31.       binmap_t smallbits;  
  32.       // 修改申請的內存量,考慮malloc_chunk佔用的內存,考慮8字節對齊問題.  
  33.       nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);  
  34.       // 根據申請的內存大小計算在small bins中的索引號  
  35.       idx = small_index(nb);  
  36.   
  37.       // 檢查對應的鏈表或相鄰鏈表中是否有空閒內存塊  
  38.       smallbits = gm->smallmap >> idx;       
  39.       if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */  
  40.         mchunkptr b, p;  
  41.         // 若是對應鏈表爲空,就使用相鄰鏈表中的內存塊.  
  42.         idx += ~smallbits & 1;       /* Uses next bin if idx empty */  
  43.         b = smallbin_at(gm, idx);   // 取出這條鏈表  
  44.         p = b->fd;           // 這是鏈表中第一個空閒的內存塊,也正是要分配給應用程序使用的內存塊.  
  45.   
  46.         assert(chunksize(p) == small_index2size(idx));  
  47.         unlink_first_small_chunk(gm, b, p, idx);    // 將p從鏈表中摘除  
  48.         // 對內存塊作一些設置  
  49.         set_inuse_and_pinuse(gm, p, small_index2size(idx));  
  50.         mem = chunk2mem(p); // 這是返還給應用程序的內存塊的指針  
  51.         check_malloced_chunk(gm, mem, nb);  // 這是一個檢查函數  
  52.         goto postaction;    // 找到了,返回吧.  
  53.       }    
  54.       else if (nb > gm->dvsize) { // 申請的內存量比last remainder要大,那麼就不能使用last remainder了.  
  55.         // 可是其餘鏈表中還有空閒內存塊,從其餘鏈表中分配.  
  56.         if (smallbits != 0) { /* Use chunk in next nonempty smallbin */  
  57.           // 首先須要作的事情就是在small bins中查找一條合適的鏈表,這條鏈表非空,而且與請求的內存量差距最小。  
  58.           mchunkptr b, p, r;  
  59.           size_t rsize;  
  60.           bindex_t i;  
  61.           binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx));  
  62.           binmap_t leastbit = least_bit(leftbits);    
  63.           compute_bit2idx(leastbit, i);  
  64.           b = smallbin_at(gm, i);   // b就是找到的鏈表  
  65.   
  66.           p = b->fd; // 這是鏈表中第一個節點,也就是要分配個應用程序的內存塊。  
  67.           assert(chunksize(p) == small_index2size(i));  
  68.           unlink_first_small_chunk(gm, b, p, i);    // 將這個節點從鏈表中摘除.  
  69.           rsize = small_index2size(i) - nb; // 去除咱們申請的內存後,這個chunk中剩餘的空閒內存量.  
  70.           /* Fit here cannot be remainderless if 4byte sizes */  
  71.           if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE)  
  72.             set_inuse_and_pinuse(gm, p, small_index2size(i));  
  73.           else { // chunk中剩餘的內存量至少是8字節,所以能夠繼續做爲一個獨立的內存塊使用.  
  74.             set_size_and_pinuse_of_inuse_chunk(gm, p, nb);  
  75.             r = chunk_plus_offset(p, nb);   // 這就是分割nb後剩餘的內存構成的新內存塊.  
  76.             set_size_and_pinuse_of_free_chunk(r, rsize);  
  77.             replace_dv(gm, r, rsize);   // 用這個內存塊替換掉dv,原先的dv保存在合適的鏈表中.  
  78.           }  
  79.           mem = chunk2mem(p);   // 這是返還給用戶程序的緩衝區的指針.  
  80.           check_malloced_chunk(gm, mem, nb);  
  81.           goto postaction;  
  82.         } // end if (smallbits != 0)  
  83.         // small bins中沒有空閒內存塊了,所以使用tree bins中的內存塊.  
  84.         // 因爲這個內存塊大於咱們請求的內存量,所以將這個內存塊劃分紅兩個內存塊,  
  85.         // 一個返回給用戶程序使用,另外一個設置成dv.  
  86.         else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) {  
  87.           check_malloced_chunk(gm, mem, nb);  
  88.           goto postaction;  
  89.         } // end else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0)  
  90.       } // end else if (nb > gm->dvsize)   
  91.     } // end if (bytes <= MAX_SMALL_REQUEST)  
  92.   
  93.     else if (bytes >= MAX_REQUEST)   // 這個值是0xffffffc0  用戶申請的內存太大了,直接失敗.  
  94.       nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */  // #define MAX_SIZE_T   (~(size_t)0)  
  95.     else {  // 申請的內存量超過248字節,須要從tree bins中分配內存.  
  96.       nb = pad_request(bytes);  // 修改申請的內存量,考慮8字節對齊,考慮malloc_tree_chunk自己佔用的內存空間.  
  97.       // 若是tree bins中有空閒的節點 && 成功從tree bins中分配到了內存,那麼就使用這塊內存.  
  98.       if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) {  
  99.         check_malloced_chunk(gm, mem, nb);  
  100.         goto postaction;  
  101.       }  
  102.     }  
  103.   
  104.     // 若是申請的內存量小於dv,那麼就從dv中分割內存.  
  105.     if (nb <= gm->dvsize) {  
  106.       size_t rsize = gm->dvsize - nb;    // 這是分割dv後剩餘的內存量.  
  107.       mchunkptr p = gm->dv;  
  108.       if (rsize >= MIN_CHUNK_SIZE) { /* split dv */  // 剩餘的內存還能夠做爲一個內存塊使用  
  109.         mchunkptr r = gm->dv = chunk_plus_offset(p, nb); // 這是新的dv  
  110.         gm->dvsize = rsize;      // 這是新dv的長度  
  111.         // 進行一些設置  
  112.         set_size_and_pinuse_of_free_chunk(r, rsize);  
  113.         set_size_and_pinuse_of_inuse_chunk(gm, p, nb);  
  114.       }  
  115.       else { /* exhaust dv */ // 剩餘的內存過小了,已經不能單獨做爲一個內存塊使用了,那麼就將dv所有分給用戶程序  
  116.         size_t dvs = gm->dvsize; // 這是分給用戶程序的內存塊的大小  
  117.         gm->dvsize = 0;  
  118.         gm->dv = 0;  // 如今dv爲空了  
  119.         set_inuse_and_pinuse(gm, p, dvs);   // 進行一些設置  
  120.       }  
  121.       mem = chunk2mem(p);   // 這是返回給用戶程序的內存區的指針  
  122.       check_malloced_chunk(gm, mem, nb);  
  123.       goto postaction;  
  124.     }  
  125.     // dv中內存不夠了,那麼看看top chunk中是否有足夠的空閒內存.  
  126.     else if (nb < gm->topsize) { /* Split top */ // 若是top chunk中有足夠的空閒內存,那麼就使用top chunk中的內存.  
  127.       size_t rsize = gm->topsize -= nb;      // 分配nb後top chunk中剩餘的空閒內存.  
  128.       mchunkptr p = gm->top;  
  129.       mchunkptr r = gm->top = chunk_plus_offset(p, nb);  // 這是新的top chunk.  
  130.       r->head = rsize | PINUSE_BIT;  
  131.       set_size_and_pinuse_of_inuse_chunk(gm, p, nb);    // p是分配給用戶程序使用的chunk,設置長度和標誌.  
  132.       mem = chunk2mem(p);   // 這是返回給用戶程序使用的內存塊  
  133.       check_top_chunk(gm, gm->top);  
  134.       check_malloced_chunk(gm, mem, nb);  
  135.       goto postaction;  
  136.     }   
  137.   
  138.     mem = sys_alloc(gm, nb);    // dlmalloc中已經沒有足夠的空閒內存了,向內核申請內存.  
  139.   
  140.   postaction:  
  141.     POSTACTION(gm);  
  142.     return mem;     // 返回申請到的內存塊  
  143.   }  
  144.   
  145.   return 0;  
  146. }  

 

這個分配過程仍是很麻煩的,由於涉及到多種狀況。分析代碼流程時記住一個分配順序就能夠了:首選大小合適的內存塊,其次分割dv(只有申請的內存量不超過248字節(包括malloc_chunk佔用的內存)時才能使用dv),再其次分割一個大的內存塊,再其次使用top chunk,最後向內核申請內存。如今分析代碼,dlmalloc()首先根據申請的內存量區分了兩種狀況,由於small bins中內存塊的最大長度是248,所以當應用程序請求的內存量不超過AX_SMALL_REQUEST(244字節,由於malloc_chunk結構要佔用4字節)時能夠從small bins中分配內存;若是超過了244字節那麼就須要從tree bins中分配內存。

        先看不超過244字節的狀況。dlmalloc首先調整了申請的內存量nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);。pad_request()按照兩個因素進行了調整,首先增長malloc_chunk結構佔用的4字節,而後將長度按照8字節對齊,所以nb纔是dlmalloc須要分配的內存塊的大小。而後根據nb計算內存塊所在的鏈表。dlmalloc按照以下順序分配內存塊:

(1)從對應的鏈表或相鄰鏈表分配

        從nb對應的鏈表中分配內存塊是最理想的狀況,這種狀況下不須要對內存塊進行任何操做,直接從鏈表中取一個內存塊給應用程序使用就能夠了。若是對應鏈表爲空,能夠考慮從相鄰鏈表中分配內存塊,相鄰鏈表中內存塊長度比對應鏈表大8個字節,可是dlmalloc中內存塊的最小長度是16字節,所以多出來的8字節不能做爲一個單獨的內存塊。這種狀況下就沒有必要對內存塊進行分割了,直接將內存塊給應用程序使用就能夠了。

(2)從dv分配

        若是nb小於dv中內存塊大小,那麼就將dv分割成兩塊,一塊給應用程序使用,另外一塊繼續做爲dv。

(3)從其餘鏈表分配

        這種狀況下dlmalloc將一個大的內存塊分割成兩塊,一塊給應用程序使用,另外一塊保存在dv中,而dv中原先的內存塊保存在對應的鏈表中。因爲內存塊大於nb的鏈表不止一條,那麼分割哪條鏈表中的內存塊呢?dlmalloc挑選的是不爲空且內存塊長度與nb最接近的鏈表。

(4)從tree bins分配

        若是前面三種狀況均不能分配到內存,那麼dlmalloc就使用tree bins中的內存塊。因爲tree bins中全部內存塊長度都大於nb,所以dlmalloc從tree bins中挑選最小的內存塊分割,而後將這個內存塊分割成兩塊,一塊給應用程序使用,另外一塊保存在dv中,而dv中原先的內存塊保存在對應的鏈表中。這種狀況是在函數tmalloc_small()中完成的。

(5)從top chunk分配

        若是nb小於top chunk中的內存大小,dlmalloc就將top chunk分割成兩塊,一塊給應用程序使用,另外一塊繼續做爲top chunk。

(6)向內核申請內存

        這是最後一種狀況。程序執行到這裏說明dlmalloc中沒有合適的內存塊,只能向內核申請內存了。這是經過sys_alloc()完成的。

如今看超過244字節的狀況,這種狀況下也須要首先調整內存塊大小。因爲調整後的長度大於248字節,所以不可能從small bins中找到合適的內存塊,而且dlmalloc規定不能使用dv。包含三種狀況:

(1)從tree bins中分配內存

        若是tree bins中正好包含長度是nb的內存塊,那麼直接給應用程序使用就好了。若是沒有長度是nb的內存塊,那麼就須要將一塊更大的內存塊分割成兩塊,一塊給應用程序使用,另外一塊保存在small bins中(若是長度不超過248字節)或tree bins中(長度超過248字節)。這是在函數tmalloc_large()中實現的。

(2)從top chunk分配內存

(3)向內核申請內存

        這裏就不進一步講解tmalloc_small()和tmalloc_large()了,由於這兩個函數原理很簡單,就是從一棵樹中挑選一個合適的內存塊,而後分割成兩塊,一塊給應用程序使用,另外一塊繼續保存在dlmalloc中。下面詳細分析dlmalloc向內核申請內存的過程。向內核申請內存時首先要考慮的問題是向內核申請多少內存?若是隻知足本次需求,那麼極可能應用程序下次調用malloc()時dlmalloc還須要向內核申請內存。因爲系統調用效率比較低,所以比較好的辦法是dlmalloc向內核多申請一些內存,這樣下次就沒必要再向內核申請了。看下面一個數據結構:

[cpp] view plaincopy

  1. struct malloc_params {  
  2.   size_t magic;                 // 就是一個簡單的魔數  
  3.   size_t page_size;             // 這是內存頁大小  
  4.   size_t granularity;           // 每次向內核申請內存的最小量,通常狀況下就是內存頁的長度.  
  5.   size_t mmap_threshold;        // 這是一個閾值閾值,超過這個閾值的內存請求直接調用mmap().  
  6.   size_t trim_threshold;        // 這是收縮堆的閾值,top chunk的長度超過這個值時會收縮堆.  
  7.   flag_t default_mflags;        // 這是一些標誌  
  8. };  

這是dlmalloc向內核申請內存時使用的一個數據結構,咱們註釋了數據結構中各個字段的含義,所以dlmalloc每次至少向內核申請4kb內存。

[cpp] view plaincopy

  1. static void* sys_alloc(mstate m, size_t nb) {  
  2.   char* tbase = CMFAIL;     // CMFAIL表示申請內存失敗了.  
  3.   size_t tsize = 0;  
  4.   flag_t mmap_flag = 0;  
  5.   
  6.   init_mparams();   // 這是一個初始化函數,這個函數在初始化全局變量mparams.  
  7.   
  8.   /* Directly map large chunks */  
  9.   // 應用程序申請的內存量超過了256kb,直接使用mmap(2)申請內存.  
  10.   if (use_mmap(m) && nb >= mparams.mmap_threshold) {  
  11.     void* mem = mmap_alloc(m, nb);  // 使用mmap(2)向系統申請內存  
  12.     if (mem != 0)  
  13.       // 這種狀況下dlmalloc無論理申請到的內存  
  14.       return mem;   // 直接返回申請到的內存  
  15.   }  
  16.   
  17. #if USE_MAX_ALLOWED_FOOTPRINT   // 這個宏是0,跳過下面這段代碼.  
  18.   /* Make sure the footprint doesn't grow past max_allowed_footprint. 
  19.    * This covers all cases except for where we need to page align, below. 
  20.    */  
  21.   {  
  22.     size_t new_footprint = m->footprint +  
  23.                            granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);  
  24.     if (new_footprint <= m->footprint ||  /* Check for wrap around 0 */  
  25.         new_footprint > m->max_allowed_footprint)  
  26.       return 0;  
  27.   }   
  28. #endif  
  29.   
  30.   // 若是申請的內存不超過256kb,或者雖然超過256kb了可是mmap()失敗了  
  31.   // 會執行到下面的代碼.  
  32.   /* 
  33.     Try getting memory in any of three ways (in most-preferred to 
  34.     least-preferred order): 
  35.     1. A call to MORECORE that can normally contiguously extend memory. 
  36.        (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or 
  37.        or main space is mmapped or a previous contiguous call failed) 
  38.     2. A call to MMAP new space (disabled if not HAVE_MMAP). 
  39.        Note that under the default settings, if MORECORE is unable to 
  40.        fulfill a request, and HAVE_MMAP is true, then mmap is 
  41.        used as a noncontiguous system allocator. This is a useful backup 
  42.        strategy for systems with holes in address spaces -- in this case 
  43.        sbrk cannot contiguously expand the heap, but mmap may be able to 
  44.        find space. 
  45.     3. A call to MORECORE that cannot usually contiguously extend memory. 
  46.        (disabled if not HAVE_MORECORE) 
  47.   */  
  48.   
  49. #define is_mmapped_segment(S)  ((S)->sflags & IS_MMAPPED_BIT)  
  50. #define is_extern_segment(S)   ((S)->sflags & EXTERN_BIT)  
  51.   
  52.   // 經過brk()擴展內存,堆是連續的.  
  53.   if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) {  
  54.     char* br = CMFAIL;  
  55.     // 查找包含top chunk的segment.  segment究竟是什麼呢????  
  56.     msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top);  
  57.     size_t asize = 0;  
  58.     ACQUIRE_MORECORE_LOCK();  
  59.   
  60.   
  61.     // 若是尚未top chunk,或者top chunk不保存在任何segment中.  
  62.     // 這是第一次執行brk操做,先看看這種狀況.  
  63.     if (ss == 0) {  /* First time through or recovery */  
  64.       // char* base = (char*)sbrk(0);   經過向sbrk()傳入0能夠獲取進程中堆的結束地址  
  65.       char* base = (char*)CALL_MORECORE(0);  
  66.       if (base != CMFAIL) {  
  67.   
  68.         // 調整了向內核申請的內存量.  
  69.         asize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);  
  70.         /* Adjust to end on a page boundary */  
  71.         if (!is_page_aligned(base)) {   // 並且堆結束地址須要按照內存頁對齊  
  72.           asize += (page_align((size_t)base) - (size_t)base);     
  73. #if USE_MAX_ALLOWED_FOOTPRINT  
  74.           /* If the alignment pushes us over max_allowed_footprint, 
  75.            * poison the upcoming call to MORECORE and continue. 
  76.            */  
  77.           {  
  78.             size_t new_footprint = m->footprint + asize;  
  79.             if (new_footprint <= m->footprint ||  /* Check for wrap around 0 */  
  80.                 new_footprint > m->max_allowed_footprint) {  
  81.               asize = HALF_MAX_SIZE_T;  
  82.             }  
  83.           }  
  84. #endif  
  85.         } // end if (!is_page_aligned(base))  
  86.   
  87.         /* Can't call MORECORE if size is negative when treated as signed */  
  88.         // 這裏調用sbkr(2)向內核申請內存了.  
  89.         if (asize < HALF_MAX_SIZE_T &&  
  90.             // sbrk()返回修改前堆的結束地址.  
  91.             (br = (char*)(CALL_MORECORE(asize))) == base) {  
  92.           tbase = base;     // 這是堆修改前的地址  
  93.           tsize = asize;    // 這是長度  
  94.         }  
  95.       } // end if (base != CMFAIL)  
  96.     }  
  97.   
  98.     else { // 已經有top chunk了,除去top chunk中的空間,dl還須要申請這麼多空間.  
  99.       /* Subtract out existing available top space from MORECORE request. */  
  100.       asize = granularity_align(nb - m->topsize + TOP_FOOT_SIZE + SIZE_T_ONE);  
  101.       /* Use mem here only if it did continuously extend old space */  
  102.       // 這裏調用sbrk(2)向內核申請內存了.  
  103.       if (asize < HALF_MAX_SIZE_T &&  
  104.           (br = (char*)(CALL_MORECORE(asize))) == ss->base+ss->size) {  
  105.         tbase = br;  
  106.         tsize = asize;  
  107.       }  
  108.     }   // end if (ss == 0)  
  109.   
  110.   
  111.     // 內存分配過程當中中間步驟失敗了.  
  112.     if (tbase == CMFAIL) {    /* Cope with partial failure */  
  113.       if (br != CMFAIL) {    /* Try to use/extend the space we did get */  
  114.         if (asize < HALF_MAX_SIZE_T &&  
  115.             asize < nb + TOP_FOOT_SIZE + SIZE_T_ONE) {  
  116.           size_t esize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE - asize);  
  117.           if (esize < HALF_MAX_SIZE_T) {  
  118.             char* end = (char*)CALL_MORECORE(esize);    // 仍然在調用brk  
  119.             if (end != CMFAIL)  
  120.               asize += esize;  
  121.             else {            /* Can't use; try to release */  
  122.               CALL_MORECORE(-asize);  
  123.               br = CMFAIL;  
  124.             }  
  125.           }  
  126.         }  
  127.       }  
  128.       if (br != CMFAIL) {    /* Use the space we did get */  
  129.         tbase = br;  
  130.         tsize = asize;  
  131.       }  
  132.       else  
  133.         disable_contiguous(m); /* Don't try contiguous path in the future */  
  134.     } // end if (tbase == CMFAIL)  
  135.   
  136.     RELEASE_MORECORE_LOCK();  
  137.   } // end if (MORECORE_CONTIGUOUS && !use_noncontiguous(m))  
  138.   
  139.   
  140.   
  141.   // 前面申請內存失敗了  
  142.   if (HAVE_MMAP && tbase == CMFAIL) {  /* Try MMAP */  
  143.     size_t req = nb + TOP_FOOT_SIZE + SIZE_T_ONE;  
  144.     size_t rsize = granularity_align(req);  
  145.     if (rsize > nb) { /* Fail if wraps around zero */  
  146.       char* mp = (char*)(CALL_MMAP(rsize)); // 經過mmap(2)方式申請內存.  
  147.       if (mp != CMFAIL) {  
  148.         tbase = mp;  
  149.         tsize = rsize;  
  150.         mmap_flag = IS_MMAPPED_BIT;  
  151.       }  
  152.     }  
  153.   } // end if (HAVE_MMAP && tbase == CMFAIL)  
  154.   
  155.   
  156.   // 經過brk()申請非連續內存,Linux系統中堆應該是連續的,不存在不連續的堆.  
  157.   if (HAVE_MORECORE && tbase == CMFAIL) { /* Try noncontiguous MORECORE */  
  158.     size_t asize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);  
  159.     if (asize < HALF_MAX_SIZE_T) {  
  160.       char* br = CMFAIL;  
  161.       char* end = CMFAIL;  
  162.       ACQUIRE_MORECORE_LOCK();  
  163.       br = (char*)(CALL_MORECORE(asize));  
  164.       end = (char*)(CALL_MORECORE(0));  
  165.       RELEASE_MORECORE_LOCK();  
  166.       if (br != CMFAIL && end != CMFAIL && br < end) {  
  167.         size_t ssize = end - br;  
  168.         if (ssize > nb + TOP_FOOT_SIZE) {  
  169.           tbase = br;  
  170.           tsize = ssize;  
  171.         }  
  172.       }  
  173.     }  
  174.   } // end if (HAVE_MORECORE && tbase == CMFAIL)  
  175.   
  176.   
  177.   // tbase != CMFAIL 表示申請內存成功了,如今進行一些設置.  
  178.   if (tbase != CMFAIL) {  
  179.   
  180.     if ((m->footprint += tsize) > m->max_footprint)  
  181.       m->max_footprint = m->footprint;  
  182.   
  183.   
  184.     // 若是malloc_state結構尚未初始化,那麼先對malloc_state結構初始化.  
  185.     if (!is_initialized(m)) { /* first-time initialization */  
  186.       m->seg.base = m->least_addr = tbase;    // 這是起始地址  
  187.       m->seg.size = tsize;       // 這是長度  
  188.       m->seg.sflags = mmap_flag; // 標誌,是否經過mmap()建立的.  
  189.       m->magic = mparams.magic;      // magic  
  190.       init_bins(m);     // 這個函數在初始化small bins  
  191.       if (is_global(m))  
  192.         init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE);  
  193.       else {  
  194.         /* Offset top by embedded malloc_state */  
  195.         mchunkptr mn = next_chunk(mem2chunk(m));  
  196.         init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE);  
  197.       }  
  198.     }  
  199.   
  200.     else {  // 嘗試合併  
  201.       /* Try to merge with an existing segment */  
  202.       msegmentptr sp = &m->seg;      // 這是malloc_state中第一個segment.  
  203.       while (sp != 0 && tbase != sp->base + sp->size)  
  204.         sp = sp->next;   // 查找連續的segment.  
  205.   
  206.       if (sp != 0 &&  
  207.           !is_extern_segment(sp) &&  
  208.           (sp->sflags & IS_MMAPPED_BIT) == mmap_flag &&  
  209.           segment_holds(sp, m->top)) { /* append */  // 新申請的內存跟系統中某個segment連續.  
  210.         sp->size += tsize;       // 修改這個segment的長度  
  211.         init_top(m, m->top, m->topsize + tsize);  
  212.       }  
  213.       else { // 新申請的內存跟系統中已經存在的segment不連續.  
  214.         if (tbase < m->least_addr)  
  215.           m->least_addr = tbase; // 設置新的least_addr,這個值只供數據檢查使用.  
  216.         sp = &m->seg;        // 這是存放segment結構的鏈表頭節點  
  217.         while (sp != 0 && sp->base != tbase + tsize)  
  218.           sp = sp->next; // 查找新申請的內存是否位於某個segment以前.  
  219.         if (sp != 0 &&  
  220.             !is_extern_segment(sp) &&  
  221.             (sp->sflags & IS_MMAPPED_BIT) == mmap_flag) {  
  222.           char* oldbase = sp->base;  // 這是segment原先的起始地址  
  223.           sp->base = tbase;      // 從新設置新的起始地址和長度  
  224.           sp->size += tsize;  
  225.           return prepend_alloc(m, tbase, oldbase, nb);  
  226.         }  
  227.         else  
  228.           add_segment(m, tbase, tsize, mmap_flag);  // 將一個新的segment添加到鏈表中.  
  229.       }  
  230.   
  231.     } // end if (!is_initialized(m)))  
  232.   
  233.     // 從top chunk中分配內存.  
  234.     if (nb < m->topsize) { /* Allocate from new or extended top space */  
  235.       size_t rsize = m->topsize -= nb;   // 這是top chunk中剩餘的空閒內存量  
  236.       mchunkptr p = m->top;      // 這是top chunk的起始地址  
  237.       mchunkptr r = m->top = chunk_plus_offset(p, nb);   // 將這裏看做一個新的chunk,這是新的top chunk.  
  238.       r->head = rsize | PINUSE_BIT;  // 這是top chunk中的內存量  
  239.       set_size_and_pinuse_of_inuse_chunk(m, p, nb); // 設置供用戶程序使用的內存塊  
  240.       check_top_chunk(m, m->top);  
  241.       check_malloced_chunk(m, chunk2mem(p), nb);  
  242.       return chunk2mem(p);  // 返回內存塊的地址給應用程序.  
  243.     }  
  244.   } // end if (tbase != CMFAIL)  
  245.   
  246.   
  247.   MALLOC_FAILURE_ACTION;  
  248.   return 0;  
  249. }  

這個函數也至關複雜,由於dlmalloc適用於各類操做系統,每種系統申請內存的方式不必定相同。Linux系統包含兩種方式:(1)brk()擴展堆;(2)mmap()映射一塊新的內存區。malloc_params結構中的mmap_threshold是一個閾值,默認值是256kb,當申請的內存量超過這個閾值時dlmalloc首先mmap()方式映射一塊單獨的內存區域,若是mmap()失敗了dlmalloc嘗試brk()方式擴展堆。若是申請的內存量沒有超過這個閾值dlmalloc首先brk()方式,若是brk()失敗了dlmalloc再嘗試mmap()方式。

[cpp] view plaincopy

  1. void* dlmalloc(size_t bytes) {  
  2.   /* 
  3.      Basic algorithm:   算法描述 
  4.      If a small request (< 256 bytes minus per-chunk overhead): 
  5.        1. If one exists, use a remainderless chunk in associated smallbin. 
  6.           (Remainderless means that there are too few excess bytes to 
  7.           represent as a chunk.) 
  8.        2. If it is big enough, use the dv chunk, which is normally the 
  9.           chunk adjacent to the one used for the most recent small request. 
  10.        3. If one exists, split the smallest available chunk in a bin, 
  11.           saving remainder in dv. 
  12.        4. If it is big enough, use the top chunk. 
  13.        5. If available, get memory from system and use it 
  14.      Otherwise, for a large request: 
  15.        1. Find the smallest available binned chunk that fits, and use it 
  16.           if it is better fitting than dv chunk, splitting if necessary. 
  17.        2. If better fitting than any binned chunk, use the dv chunk. 
  18.        3. If it is big enough, use the top chunk. 
  19.        4. If request size >= mmap threshold, try to directly mmap this chunk. 
  20.        5. If available, get memory from system and use it 
  21.  
  22.      The ugly goto's here ensure that postaction occurs along all paths. 
  23.   */  
  24.   
  25.   if (!PREACTION(gm)) {  
  26.     void* mem;  
  27.     size_t nb;  
  28.     // 若是申請的內存量小於244字節,表示是小塊內存.  
  29.     if (bytes <= MAX_SMALL_REQUEST) {    // 244字節  
  30.       bindex_t idx;  
  31.       binmap_t smallbits;  
  32.       // 修改申請的內存量,考慮malloc_chunk佔用的內存,考慮8字節對齊問題.  
  33.       nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);  
  34.       // 根據申請的內存大小計算在small bins中的索引號  
  35.       idx = small_index(nb);  
  36.   
  37.       // 檢查對應的鏈表或相鄰鏈表中是否有空閒內存塊  
  38.       smallbits = gm->smallmap >> idx;       
  39.       if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */  
  40.         mchunkptr b, p;  
  41.         // 若是對應鏈表爲空,就使用相鄰鏈表中的內存塊.  
  42.         idx += ~smallbits & 1;       /* Uses next bin if idx empty */  
  43.         b = smallbin_at(gm, idx);   // 取出這條鏈表  
  44.         p = b->fd;           // 這是鏈表中第一個空閒的內存塊,也正是要分配給應用程序使用的內存塊.  
  45.   
  46.         assert(chunksize(p) == small_index2size(idx));  
  47.         unlink_first_small_chunk(gm, b, p, idx);    // 將p從鏈表中摘除  
  48.         // 對內存塊作一些設置  
  49.         set_inuse_and_pinuse(gm, p, small_index2size(idx));  
  50.         mem = chunk2mem(p); // 這是返還給應用程序的內存塊的指針  
  51.         check_malloced_chunk(gm, mem, nb);  // 這是一個檢查函數  
  52.         goto postaction;    // 找到了,返回吧.  
  53.       }    
  54.       else if (nb > gm->dvsize) { // 申請的內存量比last remainder要大,那麼就不能使用last remainder了.  
  55.         // 可是其餘鏈表中還有空閒內存塊,從其餘鏈表中分配.  
  56.         if (smallbits != 0) { /* Use chunk in next nonempty smallbin */  
  57.           // 首先須要作的事情就是在small bins中查找一條合適的鏈表,這條鏈表非空,而且與請求的內存量差距最小。  
  58.           mchunkptr b, p, r;  
  59.           size_t rsize;  
  60.           bindex_t i;  
  61.           binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx));  
  62.           binmap_t leastbit = least_bit(leftbits);    
  63.           compute_bit2idx(leastbit, i);  
  64.           b = smallbin_at(gm, i);   // b就是找到的鏈表  
  65.   
  66.           p = b->fd; // 這是鏈表中第一個節點,也就是要分配個應用程序的內存塊。  
  67.           assert(chunksize(p) == small_index2size(i));  
  68.           unlink_first_small_chunk(gm, b, p, i);    // 將這個節點從鏈表中摘除.  
  69.           rsize = small_index2size(i) - nb; // 去除咱們申請的內存後,這個chunk中剩餘的空閒內存量.  
  70.           /* Fit here cannot be remainderless if 4byte sizes */  
  71.           if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE)  
  72.             set_inuse_and_pinuse(gm, p, small_index2size(i));  
  73.           else { // chunk中剩餘的內存量至少是8字節,所以能夠繼續做爲一個獨立的內存塊使用.  
  74.             set_size_and_pinuse_of_inuse_chunk(gm, p, nb);  
  75.             r = chunk_plus_offset(p, nb);   // 這就是分割nb後剩餘的內存構成的新內存塊.  
  76.             set_size_and_pinuse_of_free_chunk(r, rsize);  
  77.             replace_dv(gm, r, rsize);   // 用這個內存塊替換掉dv,原先的dv保存在合適的鏈表中.  
  78.           }  
  79.           mem = chunk2mem(p);   // 這是返還給用戶程序的緩衝區的指針.  
  80.           check_malloced_chunk(gm, mem, nb);  
  81.           goto postaction;  
  82.         } // end if (smallbits != 0)  
  83.         // small bins中沒有空閒內存塊了,所以使用tree bins中的內存塊.  
  84.         // 因爲這個內存塊大於咱們請求的內存量,所以將這個內存塊劃分紅兩個內存塊,  
  85.         // 一個返回給用戶程序使用,另外一個設置成dv.  
  86.         else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) {  
  87.           check_malloced_chunk(gm, mem, nb);  
  88.           goto postaction;  
  89.         } // end else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0)  
  90.       } // end else if (nb > gm->dvsize)   
  91.     } // end if (bytes <= MAX_SMALL_REQUEST)  
  92.   
  93.     else if (bytes >= MAX_REQUEST)   // 這個值是0xffffffc0  用戶申請的內存太大了,直接失敗.  
  94.       nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */  // #define MAX_SIZE_T   (~(size_t)0)  
  95.     else {  // 申請的內存量超過248字節,須要從tree bins中分配內存.  
  96.       nb = pad_request(bytes);  // 修改申請的內存量,考慮8字節對齊,考慮malloc_tree_chunk自己佔用的內存空間.  
  97.       // 若是tree bins中有空閒的節點 && 成功從tree bins中分配到了內存,那麼就使用這塊內存.  
  98.       if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) {  
  99.         check_malloced_chunk(gm, mem, nb);  
  100.         goto postaction;  
  101.       }  
  102.     }  
  103.   
  104.     // 若是申請的內存量小於dv,那麼就從dv中分割內存.  
  105.     if (nb <= gm->dvsize) {  
  106.       size_t rsize = gm->dvsize - nb;    // 這是分割dv後剩餘的內存量.  
  107.       mchunkptr p = gm->dv;  
  108.       if (rsize >= MIN_CHUNK_SIZE) { /* split dv */  // 剩餘的內存還能夠做爲一個內存塊使用  
  109.         mchunkptr r = gm->dv = chunk_plus_offset(p, nb); // 這是新的dv  
  110.         gm->dvsize = rsize;      // 這是新dv的長度  
  111.         // 進行一些設置  
  112.         set_size_and_pinuse_of_free_chunk(r, rsize);  
  113.         set_size_and_pinuse_of_inuse_chunk(gm, p, nb);  
  114.       }  
  115.       else { /* exhaust dv */ // 剩餘的內存過小了,已經不能單獨做爲一個內存塊使用了,那麼就將dv所有分給用戶程序  
  116.         size_t dvs = gm->dvsize; // 這是分給用戶程序的內存塊的大小  
  117.         gm->dvsize = 0;  
  118.         gm->dv = 0;  // 如今dv爲空了  
  119.         set_inuse_and_pinuse(gm, p, dvs);   // 進行一些設置  
  120.       }  
  121.       mem = chunk2mem(p);   // 這是返回給用戶程序的內存區的指針  
  122.       check_malloced_chunk(gm, mem, nb);  
  123.       goto postaction;  
  124.     }  
  125.     // dv中內存不夠了,那麼看看top chunk中是否有足夠的空閒內存.  
  126.     else if (nb < gm->topsize) { /* Split top */ // 若是top chunk中有足夠的空閒內存,那麼就使用top chunk中的內存.  
  127.       size_t rsize = gm->topsize -= nb;      // 分配nb後top chunk中剩餘的空閒內存.  
  128.       mchunkptr p = gm->top;  
  129.       mchunkptr r = gm->top = chunk_plus_offset(p, nb);  // 這是新的top chunk.  
  130.       r->head = rsize | PINUSE_BIT;  
  131.       set_size_and_pinuse_of_inuse_chunk(gm, p, nb);    // p是分配給用戶程序使用的chunk,設置長度和標誌.  
  132.       mem = chunk2mem(p);   // 這是返回給用戶程序使用的內存塊  
  133.       check_top_chunk(gm, gm->top);  
  134.       check_malloced_chunk(gm, mem, nb);  
  135.       goto postaction;  
  136.     }   
  137.   
  138.     mem = sys_alloc(gm, nb);    // dlmalloc中已經沒有足夠的空閒內存了,向內核申請內存.  
  139.   
  140.   postaction:  
  141.     POSTACTION(gm);  
  142.     return mem;     // 返回申請到的內存塊  
  143.   }  
  144.   
  145.   return 0;  
  146. }  

這個分配過程仍是很麻煩的,由於涉及到多種狀況。分析代碼流程時記住一個分配順序就能夠了:首選大小合適的內存塊,其次分割dv(只有申請的內存量不超過248字節(包括malloc_chunk佔用的內存)時才能使用dv),再其次分割一個大的內存塊,再其次使用top chunk,最後向內核申請內存。如今分析代碼,dlmalloc()首先根據申請的內存量區分了兩種狀況,由於small bins中內存塊的最大長度是248,所以當應用程序請求的內存量不超過AX_SMALL_REQUEST(244字節,由於malloc_chunk結構要佔用4字節)時能夠從small bins中分配內存;若是超過了244字節那麼就須要從tree bins中分配內存。

        先看不超過244字節的狀況。dlmalloc首先調整了申請的內存量nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);。pad_request()按照兩個因素進行了調整,首先增長malloc_chunk結構佔用的4字節,而後將長度按照8字節對齊,所以nb纔是dlmalloc須要分配的內存塊的大小。而後根據nb計算內存塊所在的鏈表。dlmalloc按照以下順序分配內存塊:

(1)從對應的鏈表或相鄰鏈表分配

        從nb對應的鏈表中分配內存塊是最理想的狀況,這種狀況下不須要對內存塊進行任何操做,直接從鏈表中取一個內存塊給應用程序使用就能夠了。若是對應鏈表爲空,能夠考慮從相鄰鏈表中分配內存塊,相鄰鏈表中內存塊長度比對應鏈表大8個字節,可是dlmalloc中內存塊的最小長度是16字節,所以多出來的8字節不能做爲一個單獨的內存塊。這種狀況下就沒有必要對內存塊進行分割了,直接將內存塊給應用程序使用就能夠了。

(2)從dv分配

        若是nb小於dv中內存塊大小,那麼就將dv分割成兩塊,一塊給應用程序使用,另外一塊繼續做爲dv。

(3)從其餘鏈表分配

        這種狀況下dlmalloc將一個大的內存塊分割成兩塊,一塊給應用程序使用,另外一塊保存在dv中,而dv中原先的內存塊保存在對應的鏈表中。因爲內存塊大於nb的鏈表不止一條,那麼分割哪條鏈表中的內存塊呢?dlmalloc挑選的是不爲空且內存塊長度與nb最接近的鏈表。

(4)從tree bins分配

        若是前面三種狀況均不能分配到內存,那麼dlmalloc就使用tree bins中的內存塊。因爲tree bins中全部內存塊長度都大於nb,所以dlmalloc從tree bins中挑選最小的內存塊分割,而後將這個內存塊分割成兩塊,一塊給應用程序使用,另外一塊保存在dv中,而dv中原先的內存塊保存在對應的鏈表中。這種狀況是在函數tmalloc_small()中完成的。

(5)從top chunk分配

        若是nb小於top chunk中的內存大小,dlmalloc就將top chunk分割成兩塊,一塊給應用程序使用,另外一塊繼續做爲top chunk。

(6)向內核申請內存

        這是最後一種狀況。程序執行到這裏說明dlmalloc中沒有合適的內存塊,只能向內核申請內存了。這是經過sys_alloc()完成的。

如今看超過244字節的狀況,這種狀況下也須要首先調整內存塊大小。因爲調整後的長度大於248字節,所以不可能從small bins中找到合適的內存塊,而且dlmalloc規定不能使用dv。包含三種狀況:

(1)從tree bins中分配內存

        若是tree bins中正好包含長度是nb的內存塊,那麼直接給應用程序使用就好了。若是沒有長度是nb的內存塊,那麼就須要將一塊更大的內存塊分割成兩塊,一塊給應用程序使用,另外一塊保存在small bins中(若是長度不超過248字節)或tree bins中(長度超過248字節)。這是在函數tmalloc_large()中實現的。

(2)從top chunk分配內存

(3)向內核申請內存

        這裏就不進一步講解tmalloc_small()和tmalloc_large()了,由於這兩個函數原理很簡單,就是從一棵樹中挑選一個合適的內存塊,而後分割成兩塊,一塊給應用程序使用,另外一塊繼續保存在dlmalloc中。下面詳細分析dlmalloc向內核申請內存的過程。向內核申請內存時首先要考慮的問題是向內核申請多少內存?若是隻知足本次需求,那麼極可能應用程序下次調用malloc()時dlmalloc還須要向內核申請內存。因爲系統調用效率比較低,所以比較好的辦法是dlmalloc向內核多申請一些內存,這樣下次就沒必要再向內核申請了。看下面一個數據結構:

[cpp] view plaincopy

  1. struct malloc_params {  
  2.   size_t magic;                 // 就是一個簡單的魔數  
  3.   size_t page_size;             // 這是內存頁大小  
  4.   size_t granularity;           // 每次向內核申請內存的最小量,通常狀況下就是內存頁的長度.  
  5.   size_t mmap_threshold;        // 這是一個閾值閾值,超過這個閾值的內存請求直接調用mmap().  
  6.   size_t trim_threshold;        // 這是收縮堆的閾值,top chunk的長度超過這個值時會收縮堆.  
  7.   flag_t default_mflags;        // 這是一些標誌  
  8. };  

這是dlmalloc向內核申請內存時使用的一個數據結構,咱們註釋了數據結構中各個字段的含義,所以dlmalloc每次至少向內核申請4kb內存。

[cpp] view plaincopy

  1. static void* sys_alloc(mstate m, size_t nb) {  
  2.   char* tbase = CMFAIL;     // CMFAIL表示申請內存失敗了.  
  3.   size_t tsize = 0;  
  4.   flag_t mmap_flag = 0;  
  5.   
  6.   init_mparams();   // 這是一個初始化函數,這個函數在初始化全局變量mparams.  
  7.   
  8.   /* Directly map large chunks */  
  9.   // 應用程序申請的內存量超過了256kb,直接使用mmap(2)申請內存.  
  10.   if (use_mmap(m) && nb >= mparams.mmap_threshold) {  
  11.     void* mem = mmap_alloc(m, nb);  // 使用mmap(2)向系統申請內存  
  12.     if (mem != 0)  
  13.       // 這種狀況下dlmalloc無論理申請到的內存  
  14.       return mem;   // 直接返回申請到的內存  
  15.   }  
  16.   
  17. #if USE_MAX_ALLOWED_FOOTPRINT   // 這個宏是0,跳過下面這段代碼.  
  18.   /* Make sure the footprint doesn't grow past max_allowed_footprint. 
  19.    * This covers all cases except for where we need to page align, below. 
  20.    */  
  21.   {  
  22.     size_t new_footprint = m->footprint +  
  23.                            granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);  
  24.     if (new_footprint <= m->footprint ||  /* Check for wrap around 0 */  
  25.         new_footprint > m->max_allowed_footprint)  
  26.       return 0;  
  27.   }   
  28. #endif  
  29.   
  30.   // 若是申請的內存不超過256kb,或者雖然超過256kb了可是mmap()失敗了  
  31.   // 會執行到下面的代碼.  
  32.   /* 
  33.     Try getting memory in any of three ways (in most-preferred to 
  34.     least-preferred order): 
  35.     1. A call to MORECORE that can normally contiguously extend memory. 
  36.        (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or 
  37.        or main space is mmapped or a previous contiguous call failed) 
  38.     2. A call to MMAP new space (disabled if not HAVE_MMAP). 
  39.        Note that under the default settings, if MORECORE is unable to 
  40.        fulfill a request, and HAVE_MMAP is true, then mmap is 
  41.        used as a noncontiguous system allocator. This is a useful backup 
  42.        strategy for systems with holes in address spaces -- in this case 
  43.        sbrk cannot contiguously expand the heap, but mmap may be able to 
  44.        find space. 
  45.     3. A call to MORECORE that cannot usually contiguously extend memory. 
  46.        (disabled if not HAVE_MORECORE) 
  47.   */  
  48.   
  49. #define is_mmapped_segment(S)  ((S)->sflags & IS_MMAPPED_BIT)  
  50. #define is_extern_segment(S)   ((S)->sflags & EXTERN_BIT)  
  51.   
  52.   // 經過brk()擴展內存,堆是連續的.  
  53.   if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) {  
  54.     char* br = CMFAIL;  
  55.     // 查找包含top chunk的segment.  segment究竟是什麼呢????  
  56.     msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top);  
  57.     size_t asize = 0;  
  58.     ACQUIRE_MORECORE_LOCK();  
  59.   
  60.   
  61.     // 若是尚未top chunk,或者top chunk不保存在任何segment中.  
  62.     // 這是第一次執行brk操做,先看看這種狀況.  
  63.     if (ss == 0) {  /* First time through or recovery */  
  64.       // char* base = (char*)sbrk(0);   經過向sbrk()傳入0能夠獲取進程中堆的結束地址  
  65.       char* base = (char*)CALL_MORECORE(0);  
  66.       if (base != CMFAIL) {  
  67.   
  68.         // 調整了向內核申請的內存量.  
  69.         asize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);  
  70.         /* Adjust to end on a page boundary */  
  71.         if (!is_page_aligned(base)) {   // 並且堆結束地址須要按照內存頁對齊  
  72.           asize += (page_align((size_t)base) - (size_t)base);     
  73. #if USE_MAX_ALLOWED_FOOTPRINT  
  74.           /* If the alignment pushes us over max_allowed_footprint, 
  75.            * poison the upcoming call to MORECORE and continue. 
  76.            */  
  77.           {  
  78.             size_t new_footprint = m->footprint + asize;  
  79.             if (new_footprint <= m->footprint ||  /* Check for wrap around 0 */  
  80.                 new_footprint > m->max_allowed_footprint) {  
  81.               asize = HALF_MAX_SIZE_T;  
  82.             }  
  83.           }  
  84. #endif  
  85.         } // end if (!is_page_aligned(base))  
  86.   
  87.         /* Can't call MORECORE if size is negative when treated as signed */  
  88.         // 這裏調用sbkr(2)向內核申請內存了.  
  89.         if (asize < HALF_MAX_SIZE_T &&  
  90.             // sbrk()返回修改前堆的結束地址.  
  91.             (br = (char*)(CALL_MORECORE(asize))) == base) {  
  92.           tbase = base;     // 這是堆修改前的地址  
  93.           tsize = asize;    // 這是長度  
  94.         }  
  95.       } // end if (base != CMFAIL)  
  96.     }  
  97.   
  98.     else { // 已經有top chunk了,除去top chunk中的空間,dl還須要申請這麼多空間.  
  99.       /* Subtract out existing available top space from MORECORE request. */  
  100.       asize = granularity_align(nb - m->topsize + TOP_FOOT_SIZE + SIZE_T_ONE);  
  101.       /* Use mem here only if it did continuously extend old space */  
  102.       // 這裏調用sbrk(2)向內核申請內存了.  
  103.       if (asize < HALF_MAX_SIZE_T &&  
  104.           (br = (char*)(CALL_MORECORE(asize))) == ss->base+ss->size) {  
  105.         tbase = br;  
  106.         tsize = asize;  
  107.       }  
  108.     }   // end if (ss == 0)  
  109.   
  110.   
  111.     // 內存分配過程當中中間步驟失敗了.  
  112.     if (tbase == CMFAIL) {    /* Cope with partial failure */  
  113.       if (br != CMFAIL) {    /* Try to use/extend the space we did get */  
  114.         if (asize < HALF_MAX_SIZE_T &&  
  115.             asize < nb + TOP_FOOT_SIZE + SIZE_T_ONE) {  
  116.           size_t esize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE - asize);  
  117.           if (esize < HALF_MAX_SIZE_T) {  
  118.             char* end = (char*)CALL_MORECORE(esize);    // 仍然在調用brk  
  119.             if (end != CMFAIL)  
  120.               asize += esize;  
  121.             else {            /* Can't use; try to release */  
  122.               CALL_MORECORE(-asize);  
  123.               br = CMFAIL;  
  124.             }  
  125.           }  
  126.         }  
  127.       }  
  128.       if (br != CMFAIL) {    /* Use the space we did get */  
  129.         tbase = br;  
  130.         tsize = asize;  
  131.       }  
  132.       else  
  133.         disable_contiguous(m); /* Don't try contiguous path in the future */  
  134.     } // end if (tbase == CMFAIL)  
  135.   
  136.     RELEASE_MORECORE_LOCK();  
  137.   } // end if (MORECORE_CONTIGUOUS && !use_noncontiguous(m))  
  138.   
  139.   
  140.   
  141.   // 前面申請內存失敗了  
  142.   if (HAVE_MMAP && tbase == CMFAIL) {  /* Try MMAP */  
  143.     size_t req = nb + TOP_FOOT_SIZE + SIZE_T_ONE;  
  144.     size_t rsize = granularity_align(req);  
  145.     if (rsize > nb) { /* Fail if wraps around zero */  
  146.       char* mp = (char*)(CALL_MMAP(rsize)); // 經過mmap(2)方式申請內存.  
  147.       if (mp != CMFAIL) {  
  148.         tbase = mp;  
  149.         tsize = rsize;  
  150.         mmap_flag = IS_MMAPPED_BIT;  
  151.       }  
  152.     }  
  153.   } // end if (HAVE_MMAP && tbase == CMFAIL)  
  154.   
  155.   
  156.   // 經過brk()申請非連續內存,Linux系統中堆應該是連續的,不存在不連續的堆.  
  157.   if (HAVE_MORECORE && tbase == CMFAIL) { /* Try noncontiguous MORECORE */  
  158.     size_t asize = granularity_align(nb + TOP_FOOT_SIZE + SIZE_T_ONE);  
  159.     if (asize < HALF_MAX_SIZE_T) {  
  160.       char* br = CMFAIL;  
  161.       char* end = CMFAIL;  
  162.       ACQUIRE_MORECORE_LOCK();  
  163.       br = (char*)(CALL_MORECORE(asize));  
  164.       end = (char*)(CALL_MORECORE(0));  
  165.       RELEASE_MORECORE_LOCK();  
  166.       if (br != CMFAIL && end != CMFAIL && br < end) {  
  167.         size_t ssize = end - br;  
  168.         if (ssize > nb + TOP_FOOT_SIZE) {  
  169.           tbase = br;  
  170.           tsize = ssize;  
  171.         }  
  172.       }  
  173.     }  
  174.   } // end if (HAVE_MORECORE && tbase == CMFAIL)  
  175.   
  176.   
  177.   // tbase != CMFAIL 表示申請內存成功了,如今進行一些設置.  
  178.   if (tbase != CMFAIL) {  
  179.   
  180.     if ((m->footprint += tsize) > m->max_footprint)  
  181.       m->max_footprint = m->footprint;  
  182.   
  183.   
  184.     // 若是malloc_state結構尚未初始化,那麼先對malloc_state結構初始化.  
  185.     if (!is_initialized(m)) { /* first-time initialization */  
  186.       m->seg.base = m->least_addr = tbase;    // 這是起始地址  
  187.       m->seg.size = tsize;       // 這是長度  
  188.       m->seg.sflags = mmap_flag; // 標誌,是否經過mmap()建立的.  
  189.       m->magic = mparams.magic;      // magic  
  190.       init_bins(m);     // 這個函數在初始化small bins  
  191.       if (is_global(m))  
  192.         init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE);  
  193.       else {  
  194.         /* Offset top by embedded malloc_state */  
  195.         mchunkptr mn = next_chunk(mem2chunk(m));  
  196.         init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE);  
  197.       }  
  198.     }  
  199.   
  200.     else {  // 嘗試合併  
  201.       /* Try to merge with an existing segment */  
  202.       msegmentptr sp = &m->seg;      // 這是malloc_state中第一個segment.  
  203.       while (sp != 0 && tbase != sp->base + sp->size)  
  204.         sp = sp->next;   // 查找連續的segment.  
  205.   
  206.       if (sp != 0 &&  
  207.           !is_extern_segment(sp) &&  
  208.           (sp->sflags & IS_MMAPPED_BIT) == mmap_flag &&  
  209.           segment_holds(sp, m->top)) { /* append */  // 新申請的內存跟系統中某個segment連續.  
  210.         sp->size += tsize;       // 修改這個segment的長度  
  211.         init_top(m, m->top, m->topsize + tsize);  
  212.       }  
  213.       else { // 新申請的內存跟系統中已經存在的segment不連續.  
  214.         if (tbase < m->least_addr)  
  215.           m->least_addr = tbase; // 設置新的least_addr,這個值只供數據檢查使用.  
  216.         sp = &m->seg;        // 這是存放segment結構的鏈表頭節點  
  217.         while (sp != 0 && sp->base != tbase + tsize)  
  218.           sp = sp->next; // 查找新申請的內存是否位於某個segment以前.  
  219.         if (sp != 0 &&  
  220.             !is_extern_segment(sp) &&  
  221.             (sp->sflags & IS_MMAPPED_BIT) == mmap_flag) {  
  222.           char* oldbase = sp->base;  // 這是segment原先的起始地址  
  223.           sp->base = tbase;      // 從新設置新的起始地址和長度  
  224.           sp->size += tsize;  
  225.           return prepend_alloc(m, tbase, oldbase, nb);  
  226.         }  
  227.         else  
  228.           add_segment(m, tbase, tsize, mmap_flag);  // 將一個新的segment添加到鏈表中.  
  229.       }  
  230.   
  231.     } // end if (!is_initialized(m)))  
  232.   
  233.     // 從top chunk中分配內存.  
  234.     if (nb < m->topsize) { /* Allocate from new or extended top space */  
  235.       size_t rsize = m->topsize -= nb;   // 這是top chunk中剩餘的空閒內存量  
  236.       mchunkptr p = m->top;      // 這是top chunk的起始地址  
  237.       mchunkptr r = m->top = chunk_plus_offset(p, nb);   // 將這裏看做一個新的chunk,這是新的top chunk.  
  238.       r->head = rsize | PINUSE_BIT;  // 這是top chunk中的內存量  
  239.       set_size_and_pinuse_of_inuse_chunk(m, p, nb); // 設置供用戶程序使用的內存塊  
  240.       check_top_chunk(m, m->top);  
  241.       check_malloced_chunk(m, chunk2mem(p), nb);  
  242.       return chunk2mem(p);  // 返回內存塊的地址給應用程序.  
  243.     }  
  244.   } // end if (tbase != CMFAIL)  
  245.   
  246.   
  247.   MALLOC_FAILURE_ACTION;  
  248.   return 0;  
  249. }  

這個函數也至關複雜,由於dlmalloc適用於各類操做系統,每種系統申請內存的方式不必定相同。Linux系統包含兩種方式:(1)brk()擴展堆;(2)mmap()映射一塊新的內存區。malloc_params結構中的mmap_threshold是一個閾值,默認值是256kb,當申請的內存量超過這個閾值時dlmalloc首先mmap()方式映射一塊單獨的內存區域,若是mmap()失敗了dlmalloc嘗試brk()方式擴展堆。若是申請的內存量沒有超過這個閾值dlmalloc首先brk()方式,若是brk()失敗了dlmalloc再嘗試mmap()方式。

 

這篇文章咱們來說講釋放內存的過程,也就是free()的代碼流程。對於應用程序來講釋放內存很簡單,直接調用free(ptr)就能夠了,參數是要釋放的內存塊指針。那麼,釋放內存時dlmalloc作了哪些工做呢?

 

[cpp] view plaincopy

  1. // 這是釋放內存的函數,調用free()後執行到這裏.  
  2. // 參數mem: 這是將要釋放內存的指針  
  3. void dlfree(void* mem) {  
  4.   /* 
  5.      Consolidate freed chunks with preceeding or succeeding bordering 
  6.      free chunks, if they exist, and then place in a bin.  Intermixed 
  7.      with special cases for top, dv, mmapped chunks, and usage errors. 
  8.   */  
  9.   // 若是是空指針,那麼就不須要處理了.  
  10.   if (mem != 0) {  
  11.     mchunkptr p  = mem2chunk(mem);  // 首先找到內存塊的起始地址  p = mem - 8.  
  12. #if FOOTERS // 將這個宏看做是0就能夠了  
  13.     mstate fm = get_mstate_for(p);  
  14.     if (!ok_magic(fm)) {  
  15.       USAGE_ERROR_ACTION(fm, p);  
  16.       return;  
  17.     }  
  18. #else /* FOOTERS */  
  19. #define fm gm   // 全局變量_gm_的地址  
  20. #endif /* FOOTERS */  
  21.     if (!PREACTION(fm)) {   // 先加鎖  
  22.       check_inuse_chunk(fm, p); // 檢查這個chunk是否在使用中,這是一個檢查函數.  
  23.       if (RTCHECK(ok_address(fm, p) && ok_cinuse(p))) {  
  24.         size_t psize = chunksize(p);    // 計算這個內存塊的大小.  
  25.         mchunkptr next = chunk_plus_offset(p, psize);   // 從這裏開始是下一個內存塊了.  
  26.   
  27.         if (!pinuse(p)) { // 若是前面一個內存塊是空閒的,那麼這個內存塊釋放後就能夠跟前面一個內存塊合併了.  
  28.           size_t prevsize = p->prev_foot;    // 前面一個內存塊的大小  
  29.   
  30.           if ((prevsize & IS_MMAPPED_BIT) != 0) { // 若是是經過mmap方式建立的內存塊  
  31.             prevsize &= ~IS_MMAPPED_BIT;  
  32.             psize += prevsize + MMAP_FOOT_PAD;  
  33.             if (CALL_MUNMAP((char*)p - prevsize, psize) == 0)  
  34.               fm->footprint -= psize;  
  35.             goto postaction;  
  36.           }  
  37.           else { // 不是經過mmap方式建立的.  
  38.             mchunkptr prev = chunk_minus_offset(p, prevsize);   // 取出前面一個chunk的結構.  
  39.             psize += prevsize;  // 這是兩個內存塊的總長度  
  40.             p = prev;       // 這是內存塊的起始地址  
  41.             if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */  
  42.               if (p != fm->dv) { // 若是不是dv  
  43.                 unlink_chunk(fm, p, prevsize);  // 將這個內存塊從malloc_state結構中刪除.  
  44.               }  
  45.               // 若是是dv  
  46.               else if ((next->head & INUSE_BITS) == INUSE_BITS) {  // 後面一個內存塊在使用中,那麼就處理完畢了.  
  47.                 fm->dvsize = psize;  // 修改這個chunk的長度.  
  48.                 set_free_with_pinuse(p, psize, next);  
  49.                 goto postaction;    // 處理完畢  
  50.               }  
  51.               // 若是後面一個內存塊也是空間的,那麼還須要將後面一個內存塊合併到dv中.  
  52.             }  
  53.             else  
  54.               goto erroraction;  
  55.           } // end if ((prevsize & IS_MMAPPED_BIT) != 0)  
  56.         } // end if (!pinuse(p))  
  57.   
  58.         // 須要繼續檢查後面一個內存塊是否空閒.  
  59.         if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) {  
  60.           if (!cinuse(next)) {  /* consolidate forward */ // 後面一個內存塊也處於空閒狀態,那麼就能夠合併了.  
  61.             if (next == fm->top) { // 若是後面一個chunk是top chunk,那麼直接將當前合併到top chunk中就能夠了.  
  62.               size_t tsize = fm->topsize += psize;   // 這是合併後top chunk的大小  
  63.               fm->top = p;   // 這是合併後top chunk的起始地址  
  64.               p->head = tsize | PINUSE_BIT;  
  65.               if (p == fm->dv) { // 同時也是dv,那麼就撤銷dv.  
  66.                 fm->dv = 0;  
  67.                 fm->dvsize = 0;  
  68.               }  
  69.               // 如今檢查是否須要收縮堆空間,當top chunk大於2mb時收縮堆空間.  
  70.               if (should_trim(fm, tsize))  
  71.                 sys_trim(fm, 0);    // 只有這種狀況下執行到了sys_trim.  
  72.               goto postaction;  
  73.             }  
  74.             else if (next == fm->dv) {   // 若是後面一個chunk是dv,那麼直接將本內存塊合併到dv中就能夠了.  
  75.               size_t dsize = fm->dvsize += psize;    // 這是合併後dv的大小  
  76.               fm->dv = p;    // 設置dv新的起始地址  
  77.               set_size_and_pinuse_of_free_chunk(p, dsize);  // 設置dv新的長度  
  78.               goto postaction;  
  79.             }  
  80.             else { // 後面一個chunk是一個普通的chunk.  
  81.               size_t nsize = chunksize(next);  
  82.               psize += nsize;  
  83.               unlink_chunk(fm, next, nsize);    // 先將後面的chunk從malloc_state中摘除.  
  84.               set_size_and_pinuse_of_free_chunk(p, psize);  
  85.               if (p == fm->dv) {  
  86.                 fm->dvsize = psize;  
  87.                 goto postaction;  
  88.               }  
  89.             }  
  90.           } // end if (!cinuse(next))  
  91.           else // 後面一個chunk在使用中  
  92.             set_free_with_pinuse(p, psize, next);   // 修改一些標誌信息  
  93.   
  94.           insert_chunk(fm, p, psize);   // 將合併後內存塊的大小將內存塊添加到small bins或者tree bins中.  
  95.           check_free_chunk(fm, p);  
  96.           goto postaction;  
  97.         } // end if (RTCHECK(ok_next(p, next) && ok_pinuse(next)))  
  98.       }  
  99.     erroraction:  
  100.       USAGE_ERROR_ACTION(fm, p);  
  101.     postaction:  
  102.       POSTACTION(fm);  
  103.     }  
  104.   }  
  105. #if !FOOTERS  
  106. #undef fm  
  107. #endif /* FOOTERS */  
  108. }  

又是很長一大段代碼。這段代碼首先將內存塊標記爲空閒,而後根據內存申請方式分別處理。若是內存塊大於256kb,那麼立刻經過munmap()釋放內存。若是內存塊小於256kb,那麼檢查相鄰的兩個內存塊是否空閒,若是空閒就跟相鄰的內存塊合併。而後還須要檢查top chunk是否大於2mb。若是top chunk大於2mb,將top chunk釋放回內核。

內存塊大於256kb時釋放內存的代碼以下:

 

[cpp] view plaincopy

  1. size_t prevsize = p->prev_foot;       // 前面一個內存塊的大小  
  2. if ((prevsize & IS_MMAPPED_BIT) != 0) { // 若是是經過mmap方式建立的內存塊  
  3.   prevsize &= ~IS_MMAPPED_BIT;  
  4.   psize += prevsize + MMAP_FOOT_PAD;  
  5.   if (CALL_MUNMAP((char*)p - prevsize, psize) == 0)  
  6.     fm->footprint -= psize;  
  7.   goto postaction;  
  8. }  

p->prev_foot包含了兩項信息:前一個內存塊的長度和前一個內存塊的建立方式(mmap仍是brk)。當申請的內存塊大於256kb時dlmalloc經過mmap()申請內存,併爲這個內存塊建立了一個malloc_chunk結構。因爲只有一個malloc_chunk結構,沒有相鄰的malloc_chunk結構,所以malloc_chunk中的prev_foot字段就沒有意義了。這時dlmalloc將prev_foot中比特0用做標誌位IS_MMAPPED_BIT,表示這個內存塊是經過mmap()方式建立的。所以,若是prev_foot中的IS_MMAPPED_BIT置位了,那麼就調用munmap()釋放內存(CALL_MUNMAP)。
最後來看看dlmalloc收縮top chunk的代碼,這是在函數sys_trim()中實現的,代碼以下:

[cpp] view plaincopy

  1. static int sys_trim(mstate m, size_t pad) {  
  2.   size_t released = 0;  
  3.   if (pad < MAX_REQUEST && is_initialized(m)) {  
  4.     pad += TOP_FOOT_SIZE; /* ensure enough room for segment overhead */  
  5.   
  6.     // 調整pad,pad表示須要保留的內存量.  
  7.     if (m->topsize > pad) {  
  8.       /* Shrink top space in granularity-size units, keeping at least one */  
  9.       size_t unit = mparams.granularity;    // 申請/釋放內存須要是這個值的倍數.  
  10.       // 這是須要釋放的內存量.  
  11.       size_t extra = ((m->topsize - pad + (unit - SIZE_T_ONE)) / unit -  
  12.                       SIZE_T_ONE) * unit;  
  13.       // 取出包含top chunk的segment.  
  14.       msegmentptr sp = segment_holding(m, (char*)m->top);  
  15.   
  16.       if (!is_extern_segment(sp)) {  
  17.     // 這個segment是經過mmap方式建立的,那麼就經過munmap()或者mremap()方式釋放內存.  
  18.         if (is_mmapped_segment(sp)) {  
  19.           if (HAVE_MMAP &&  
  20.               sp->size >= extra &&    // extra是將要釋放的內存量  
  21.               !has_segment_link(m, sp)) { /* can't shrink if pinned */  
  22.             size_t newsize = sp->size - extra;   // 計算釋放後剩餘的內存量  
  23.             if ((CALL_MREMAP(sp->base, sp->size, newsize, 0) != MFAIL) ||  
  24.                 (CALL_MUNMAP(sp->base + newsize, extra) == 0)) {  
  25.               released = extra;  
  26.             }  
  27.           }  
  28.         }  
  29.         // 這個segment是經過brk方式建立的,那麼就經過brk()調整堆的結束位置.  
  30.         else if (HAVE_MORECORE) {  
  31.           if (extra >= HALF_MAX_SIZE_T) /* Avoid wrapping negative */  
  32.             extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit;  
  33.           ACQUIRE_MORECORE_LOCK();  
  34.           {  
  35.             /* Make sure end of memory is where we last set it. */  
  36.             char* old_br = (char*)(CALL_MORECORE(0));   // 獲取當前堆的結束地址.  
  37.             if (old_br == sp->base + sp->size) {  
  38.               // 開始收縮堆  
  39.               char* rel_br = (char*)(CALL_MORECORE(-extra));    // sbrk()  
  40.               char* new_br = (char*)(CALL_MORECORE(0));  
  41.               if (rel_br != CMFAIL && new_br < old_br)  
  42.                 released = old_br - new_br;  
  43.             }  
  44.           }  
  45.           RELEASE_MORECORE_LOCK();  
  46.         }  
  47.       }  
  48.   
  49.       if (released != 0) {  
  50.         sp->size -= released;  
  51.         m->footprint -= released;  
  52.         init_top(m, m->top, m->topsize - released);   // 從新初始化top chunk.  
  53.         check_top_chunk(m, m->top);  
  54.       }  
  55.     } // end if (m->topsize > pad)  
  56.   
  57.     /* Unmap any unused mmapped segments */  
  58.     if (HAVE_MMAP)  
  59.       released += release_unused_segments(m);  
  60.   
  61.     /* On failure, disable autotrim to avoid repeated failed future calls */  
  62.     if (released == 0)  
  63.       m->trim_check = MAX_SIZE_T;  
  64.   }  
  65.   
  66.   return (released != 0)? 1 : 0;  
  67. }  

當申請的內存量小於256kb時,dlmalloc首先經過brk()方式擴展堆,若是失敗了會嘗試經過mmap()方式申請內存。所以,top chunk多是經過brk()方式申請的,也多是經過mmap()方式申請的。若是經過brk()方式申請的,那麼就須要經過brk()收縮堆;若是經過mmap()方式申請的,那麼就須要經過munmap()或mremap()釋放內存

相關文章
相關標籤/搜索