目錄
- 前言
- 1、heap初始化
- 2、第一次分配內存,計算真正區塊大小
- 3、new_region管理中心
- 4、__sbh_alloc_new_group()切割第一次分配好的內存
- 5、開始切割內存
前言
malloc與free帶來的內存管理是應付小區塊的,即SBH(small block heap),這點也可以從源代碼中看出:
VC6下會做門檻檢測
if(size <= __sbh_threshold) {
pvReturn = __sbh_alloc_block(size);
return pvReturn;
}
...
return HeapAlloc(_crtheap,0,size)
VC10下不會做門檻檢測,它總是會調用系統提供的HeapAlloc。這是因為操作系統提供的函數也有類似的功能了。
當然還需要注意,我們分析的步驟是按照函數調用的次序來的:(從下往上的函數調用次序如下)
1、heap初始化
首先向操作系統要一大塊內存(如4096),稱為_crtheap.
然后使用HeapAlloc從_crtheap中獲取16個HEADER大小的內存,并獲取內存指針。
每個HEADER長這樣:
![]() | ![]() |
2、第一次分配內存,計算真正區塊大小
在debug模式下,通過調用malloc_dbg,分配得到32個8字節的內存,即100h.
然后調整大小,擴充一個如下的結構:nDataSize記錄真正大小。gap用戶調試器檢查保護nsize內存。注意gap一共上下兩個,但是結構體里面只定義了上面的那個。
![]() | ![]() |
通過調用_heap_alloc_base來獲取每個blockSize內存的頭指針。
然后通過頭尾指針將所有分配出來的block串接起來,變為一個鏈表:
頭指針為_pFirstBlock;
尾指針為_pLastBlock;
_heap_alloc_base具體內容
之前在通過heap_alloc_dbg中調用_heap_alloc_base來獲取每個blockSize內存的頭指針。
現在來看看它具體步驟:
1、首先將擴充完的大小與__sbh_threshold進行比較,所過是小區塊就用shb服務,否則由操作系統服務。
小區塊的定義是:這塊內存大小+cookie大小 < 1024個字節
if(size <= __sbh_threshold) {
pvReturn = __sbh_alloc_block(size);
return pvReturn;
}
...
return HeapAlloc(_crtheap,0,size)
接下來看__sbh_alloc_block具體細節:
對擴充完的區塊再次進行擴充,加上8個字節(上下cookie)并且進行roundup操作(變為16的整數倍)
以上圖為例,最終得到的結果是0x130,但是在cookie中寫入的卻是131。這是因為由于是16的倍數,所以最后一位一定是0。最后一位0/1表示是在SBH手上還是已經分配出去了。
3、new_region管理中心
之前在第一步heap初始化的時候分配了16個HEADER,每個HEADER現在用來管理1MB的內存。每個HEADER有兩根指針:一根指向虛擬地址空間,一根指向管理中心。
管理中心被稱為new_region。
region設計如下:
它含有:
1個整數
64個char
32個Hi和32個Lo并起來,構成32組,每組64個bit。(用來管理哪些區塊有或者沒有)
32個group,每個group由64根雙向鏈表組成。
![]() | ![]() |
4、__sbh_alloc_new_group()切割第一次分配好的內存
已知擁有32個group,對應了1MB的虛擬內存空間,平均下來每個group管理32KB內存。
每32KB的內存還要再分割,分成8page(注意這里的內存都是連續的),每page4KB,然后用指針把8個page串起來。串起來之后將它們掛到group的最后一條鏈表。這便是SBH現在所作的事情。
下面紅色部分便是group與32KB內存的具體銜接圖。
0xfff…實際上就是-1,作用:
將來回收內存的時候需要合并內存,合并的過程中需要用-1作為阻隔器,即只能合并-1與-1之間的內存。
上-1下面有3格,第一格記錄的是兩個-1之間的內存,一開始大小為4080,后兩格作為指針將page從前到后串聯起來。
每個page大小4096字節,去掉兩個-1,剩下4088個字節。由于每個紅色區塊要保證大小是16倍數。所以需要將4088再分割出去8個字節(也就是保留)。
每個4K都這樣處理,形成如下的圖:
5、開始切割內存
先前講到了每個group中有64根雙向鏈表,平均分配下來:
第一條鏈表管理16個字節的內存
第二條鏈表管理32個字節的內存
…
最后一條管理1024個字節的內存(實際上大于1k的內存全都歸最后一條管理)
現在開始想象開始切割page1的內存,當page1內存小于1k的時候就不應該由最后一條鏈表來管,應該要計算,然后將指定鏈表拉過來。現在來看具體的切割:
在之前的步驟中我們以及知道我們需要的內存是多大了:130h(100h+debugHeader+cookie+roundup)
所以,現在還剩下ec0h的內存。切割完后,系統將紅色的地址:007d0ed0,傳出去。
客戶程序拿到該指針,便認為擁有了這一塊內存。
由上可知,切割其實只是cookie的調整 + 指針的傳送。
注意紅色指針還需要調整(扣除debugHeader),調整到右邊的結構塊的綠色部分,該部分才是使用者(ioinit)真正拿到的地址。