1. 程式人生 > >glibc記憶體管理ptmalloc底層實現

glibc記憶體管理ptmalloc底層實現


前言:本文剖析了glibc記憶體管理ptmalloc的底層實現原理,以及用到的各種資料結構的分析。原始碼在本文中暫未剖析,接下來會學習。如果你想了解ptmalloc的底層實現,歡迎閱讀。

一、基礎知識: X86平臺Linux程序記憶體佈局
32位模式下程序記憶體經典佈局
這裡寫圖片描述
32位模式程序預設記憶體佈局
這裡寫圖片描述
從上圖可以看到,棧至頂向下擴充套件,並且棧是有界的。堆至底向上擴充套件, mmap 對映區域至頂向下擴充套件, mmap 對映區域和堆相對擴充套件,直至耗盡虛擬地址空間中的剩餘區域,這種結構便於 C 執行時庫使用 mmap 對映區域和堆進行記憶體分配。上圖的佈局形式是在核心2.6.7 以後才引入的,這是 32 位模式下程序的預設記憶體佈局形式

二、作業系統記憶體分配的相關函式


對 heap 的操作,作業系統提供了 brk()函式, C 執行時庫提供了 sbrk()函式;對 mmap 對映區域的操作,作業系統提供了 mmap()和 munmap()函式。 sbrk(), brk() 或者 mmap() 都可以用來向我們的程序新增額外的虛擬記憶體。 Glibc 同樣是使用這些函式向作業系統申請虛擬記憶體。

這裡要提到一個很重要的概念,記憶體的延遲分配, 只有在真正訪問一個地址的時候才建立這個地址的物理對映,這是 Linux 記憶體管理的基本思想之一。 Linux 核心在使用者申請記憶體的時候,只是給它分配了一個線性區(也就是虛擬記憶體),並沒有分配實際實體記憶體;只有當用戶使用這塊記憶體的時候,核心才會分配具體的物理頁面給使用者,這時候才佔用寶貴的實體記憶體。核心釋放物理頁面是通過釋放線性區,找到其所對應的物理頁面,將其全部釋放的過程。


1.heap操作的相關函式
Heap 操作函式主要有兩個, brk()為系統呼叫, sbrk()為 C 庫函式。系統呼叫通常提供一種最小功能,而庫函式通常提供比較複雜的功能。 Glibc 的 malloc 函式族( realloc, calloc 等)就呼叫 sbrk()函式將資料段的下界移動, sbrk()函式在核心的管理下將虛擬地址空間對映到內
存,供 malloc()函式使用。

核心資料結構 mm_struct 中的成員變數 start_code 和 end_code 是程序程式碼段的起始和終止地址, start_data 和 end_data 是程序資料段的起始和終止地址, start_stack 是程序堆疊段起始地址, start_brk 是程序動態記憶體分配起始地址(堆的起始地址),還有一個 brk(堆的當前最後地址),就是動態記憶體分配當前的終止地址。 C 語言的動態記憶體分配基本函式是malloc(),在 Linux 上的實現是通過核心的 brk 系統呼叫。 brk()是一個非常簡單的系統呼叫,只是簡單地改變 mm_struct 結構的成員變數 brk 的值。
這兩個函式的定義如下:
int brk(void *addr);
void *sbrk(intptr_t increment);
需要說明的是,但 sbrk()的引數 increment 為 0 時, sbrk()返回的是程序的當前 brk 值,
increment 為正數時擴充套件 brk 值,當 increment 為負值時收縮 brk 值


2.Mmap對映區域操作相關函式
mmap()函式將一個檔案或者其它物件對映進記憶體。檔案被對映到多個頁上,如果檔案的大小不是所有頁的大小之和,最後一個頁不被使用的空間將會清零。 munmap 執行相反的操作,刪除特定地址區域的物件對映。 函式的定義如下:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
在這裡不準備對這兩個函式做詳細介紹,只是對 ptmalloc 中用到的功能做一下介紹,其他的用法請參看相關資料。
引數:
start:對映區的開始地址。

length:對映區的長度。

prot:期望的記憶體保護標誌,不能與檔案的開啟模式衝突。是以下的某個值,可以通過or 運算合理地組合在一起。 Ptmalloc 中主要使用瞭如下的幾個標誌:

PROT_EXEC //頁內容可以被執行, ptmalloc 中沒有使用

PROT_READ //頁內容可以被讀取, ptmalloc 直接用 mmap 分配記憶體並立即返回給使用者時設定該標誌

PROT_WRITE //頁可以被寫入, ptmalloc 直接用 mmap 分配記憶體並立即返回給使用者時設定該標誌

PROT_NONE //頁不可訪問, ptmalloc 用 mmap 向系統“批發”一塊記憶體進行管理時設定該標誌

flags:指定對映物件的型別,對映選項和對映頁是否可以共享。它的值可以是一個或者多個以下位的組合體

MAP_FIXED //使用指定的對映起始地址,如果由 start 和 len 引數指定的記憶體區重疊於現存的對映空間,重疊部分將會被丟棄。如果指定的起始地址不可用,操作將會失敗。並且起始地址必須落在頁的邊界上。
Ptmalloc 在回收從系統中“批發”的記憶體時設定該標誌。

MAP_PRIVATE //建立一個寫入時拷貝的私有對映。記憶體區域的寫入不會影響到原檔案。這個標誌和以上標誌是互斥的,只能使用其中一個。Ptmalloc每次呼叫 mmap都設定該標誌。

MAP_NORESERVE //不要為這個對映保留交換空間。當交換空間被保留,對對映區修改的可能會得到保證。當交換空間不被保留,同時記憶體不足,對對映區的修改會引起段違例訊號。 Ptmalloc 向系統“批發”記憶體塊時設定該標誌。

MAP_ANONYMOUS //匿名對映,對映區不與任何檔案關聯。 Ptmalloc 每次呼叫 mmap都設定該標誌。

fd:有效的檔案描述詞。如果 MAP_ANONYMOUS 被設定,為了相容問題,其值應為-1。

offset:被對映物件內容的起點。

三、ptmalloc
1.簡述:ptmalloc 實現了 malloc(), free()以及一組其它的函式. 以提供動態記憶體管理的支援。 分配器處在使用者程式和核心之間,它響應使用者的分配請求,向作業系統申請記憶體,然後將其返回給使用者程式,為了保持高效的分配, 分配器一般都會預先分配一塊大於使用者請求的記憶體,並通過某種演算法管理這塊記憶體。來滿足使用者的記憶體分配要求,使用者釋放掉的記憶體也並不是立即就返回給作業系統,相反, 分配器會管理這些被釋放掉的空閒空間,以應對使用者以後的記憶體分配要求。也就是說, 分配器不但要管理已分配的記憶體塊,還需要管理空閒的記憶體塊,當響應使用者分配要求時, 分配器會首先在空閒空間中尋找一塊合適的記憶體給使用者,在空閒空間中找不到的情況下才分配一塊新的記憶體。 為實現一個高效的分配器,需要考慮很多的因素。比如, 分配器本身管理記憶體塊所佔用的記憶體空間必須很小,分配演算法必須要足夠的快。
2.記憶體管理的資料結構
注:要想透徹理解ptmalloc管理記憶體的原理,接下來的這些資料結構非常重要,只有理解並掌握這些資料結構,才能對整體的流程有較深的認識。
①Main_arena 與 non_main_arena(主分配區與非主分配區)
在 Doug Lea 實現的記憶體分配器中只有一個主分配區( main arena),每次分配記憶體都必須對主分配區加鎖,分配完成後釋放鎖,在 SMP 多執行緒環境下,對主分配區的鎖的爭用很激烈,嚴重影響了 malloc 的分配效率。於是 Wolfram Gloger 在 Doug Lea 的基礎上改進使得Glibc 的 malloc 可以支援多執行緒,增加了非主分配區( non main arena)支援, 主分配區與非主分配區用環形連結串列進行管理。 每一個分配區利用互斥鎖( mutex)使執行緒對於該分配區的訪問互斥。每個程序只有一個主分配區,但可能存在多個非主分配區, ptmalloc 根據系統對分配區
的爭用情況動態增加非主分配區的數量,分配區的數量一旦增加,就不會再減少了。 主分配區可以訪問程序的 heap 區域和 mmap 對映區域,也就是說主分配區可以使用 sbrk 和 mmap向作業系統申請虛擬記憶體。而非主分配區只能訪問程序的 mmap 對映區域, 非主分配區每次使用 mmap()向作業系統“批發” HEAP_MAX_SIZE( 32 位系統上預設為 1MB, 64 位系統預設為 64MB) 大小的虛擬記憶體,當用戶向非主分配區請求分配記憶體時再切割成小塊“零售”出去,畢竟系統呼叫是相對低效的,直接從使用者空間分配記憶體快多了。所以 ptmalloc 在必要的情況下才會呼叫 mmap()函式向作業系統申請虛擬記憶體。
接下來用一張圖表示分配區的相關知識:
這裡寫圖片描述
②chunk的組織
不管記憶體是在哪裡被分配的,用什麼方法分配,使用者請求分配的空間在 ptmalloc 中都使用一個 chunk 來表示。使用者呼叫 free()函式釋放掉的記憶體也並不是立即就歸還給作業系統,相反,它們也會被表示為一個 chunk,ptmalloc 使用特定的資料結構來管理這些空閒的 chunk。
正在使用中的chunk如下圖:
這裡寫圖片描述
在圖中, chunk 指標指向一個 chunk 的開始,一個 chunk 中包含了使用者請求的記憶體區域和相關的控制資訊。圖中的 mem 指標才是真正返回給使用者的記憶體指標。 chunk 的第二個域的最低一位為 P,它表示前一個塊是否在使用中, P 為 0 則表示前一個 chunk 為空閒,這時chunk 的第一個域 prev_size 才有效, prev_size 表示前一個 chunk 的 size,程式可以使用這個值來找到前一個 chunk 的開始地址。當 P 為 1 時,表示前一個 chunk 正在使用中, prev_size16無效,程式也就不可以得到前一個 chunk的大小。不能對前一個 chunk進行任何操作。ptmalloc分配的第一個塊總是將 P 設為 1,以防止程式引用到不存在的區域。
Chunk 的第二個域的倒數第二個位為 M,他表示當前 chunk 是從哪個記憶體區域獲得的虛擬記憶體。 M 為 1 表示該 chunk 是從 mmap 對映區域分配的,否則是從 heap 區域分配的。
Chunk 的第二個域倒數第三個位為 A, 表示該 chunk 屬於主分配區或者非主分配區,如果屬於非主分配區,將該位置為 1,否則置為 0。
空閒 chunk 在記憶體中的結構如圖所示:
這裡寫圖片描述
當 chunk 空閒時, 其 M 狀態不存在,只有 AP 狀態, 原本是使用者資料區的地方儲存了四個指標,指標 fd 指向後一個空閒的 chunk,而 bk 指向前一個空閒的 chunk, ptmalloc 通過這
兩個指標將大小相近的 chunk 連成一個雙向連結串列。 對於 large bin 中的空閒 chunk,還有兩個指標, fd_nextsize 和 bk_nextsize,這兩個指標用於加快在 large bin 中查詢最近匹配的空閒chunk。 不同的 chunk 連結串列又是通過 bins 或者 fastbins 來組織的。
chunk中的空間複用:
當一個 chunk 處於使用狀態時, 它的下一個 chunk 的 prev_size域肯定是無效的。所以實際上,這個空間也可以被當前 chunk 使用。這聽起來有點不可思議,但確實是合理空間複用的例子。故而實際上,一個使用中的 chunk 的大小的計算公式應該是:in_use_size = (使用者請求大小+ 8 - 4 ) align to 8B, 這裡加 8 是因為需要儲存 prev_size 和 size,但又因為向下一個 chunk“借”了 4B, 所以要減去 4。 最後, 因為空閒的 chunk 和使用中的chunk 使用的是同一塊空間。 所以肯定要取其中最大者作為實際的分配空間。 即最終的分配空間 chunk_size = max(in_use_size, 16)。 這就是當用戶請求記憶體分配時, ptmalloc 實際需要分配的記憶體大小, 在後面的介紹中。 如果不是特別指明的地方, 指的都是這個經過轉換的實際需要分配的記憶體大小, 而不是使用者請求的記憶體分配大小。

空閒chunk容器:
1.Bins
使用者 free 掉的記憶體並不是都會馬上歸還給系統, ptmalloc 會統一管理 heap 和 mmap 對映區域中的空閒的 chunk,當用戶進行下一次分配請求時, ptmalloc 會首先試圖在空閒的chunk 中挑選一塊給使用者,這樣就避免了頻繁的系統呼叫,降低了記憶體分配的開銷。 ptmalloc將相似大小的 chunk 用雙向連結串列連結起來,這樣的一個連結串列被稱為一個 bin。 Ptmalloc 一共維護了 128 個 bin,並使用一個數組來儲存這些 bin( 如下圖所示)。
這裡寫圖片描述
陣列中的第一個為 unsorted bin, 陣列中從 2 開始編號的前 64 個 bin 稱為 small bins,同一個 small bin中的 chunk具有相同的大小。兩個相鄰的 small bin中的 chunk大小相差 8bytes。
small bins 中的 chunk 按照最近使用順序進行排列,最後釋放的 chunk 被連結到連結串列的頭部,而申請 chunk 是從連結串列尾部開始,這樣,每一個 chunk 都有相同的機會被 ptmalloc 選中。
Small bins 後面的 bin 被稱作 large bins。 large bins 中的每一個 bin 分別包含了一個給定範圍內的 chunk,其中的 chunk 按大小序排列。相同大小的 chunk 同樣按照最近使用順序排列。
ptmalloc 使用“ smallest-first, best-fit”原則在空閒 large bins 中查詢合適的 chunk。當空閒的 chunk 被連結到 bin 中的時候, ptmalloc 會把表示該 chunk 是否處於使用中的
標誌 P 設為 0( 注意, 這個標誌實際上處在下一個 chunk 中), 同時 ptmalloc 還會檢查它前後的 chunk 是否也是空閒的, 如果是的話, ptmalloc 會首先把它們合併為一個大的 chunk,然後將合併後的 chunk 放到 unstored bin 中。 要注意的是, 並不是所有的 chunk 被釋放後就立即被放到 bin 中。 ptmalloc 為了提高分配的速度, 會把一些小的的 chunk 先放到一個叫做fast bins 的容器內。
2.Fast bins:
一般的情況是, 程式在執行時會經常需要申請和釋放一些較小的記憶體空間。 當分配器合併了相鄰的幾個小的 chunk 之後, 也許馬上就會有另一個小塊記憶體的請求, 這樣分配器又需要從大的空閒記憶體中切分出一塊, 這樣無疑是比較低效的, 故而ptmalloc 中在分配過程中引入了 fast bins,不大於 max_fast(預設值為 64B)的 chunk 被釋放後,首先會被放到 fast bins中, fast bins 中的 chunk 並不改變它的使用標誌 P。 這樣也就無法將它們合併, 當需要給使用者分配的 chunk 小於或等於 max_fast 時, ptmalloc 首先會在 fast bins 中查詢相應的空閒塊,然後才會去查詢 bins中的空閒 chunk。在某個特定的時候,ptmalloc會遍歷 fast bins中的 chunk,18將相鄰的空閒 chunk 進行合併, 並將合併後的 chunk 加入 unsorted bin 中,然後再將 usorted
bin 裡的 chunk 加入 bins 中.
3.unsorted bin
unsorted bin 的佇列使用 bins 陣列的第一個, 如果被使用者釋放的 chunk 大於 max_fast,或者 fast bins 中的空閒 chunk 合併後, 這些 chunk 首先會被放到 unsorted bin 佇列中, 在進
行 malloc 操作的時候,如果在 fast bins 中沒有找到合適的 chunk,則 ptmalloc 會先在 unsortedbin 中查詢合適的空閒 chunk, 然後才查詢 bins。 如果 unsorted bin 不能滿足分配要求。 malloc便會將 unsorted bin 中的 chunk 加入 bins 中。 然後再從 bins 中繼續進行查詢和分配過程。 從這個過程可以看出來, unsorted bin 可以看做是 bins 的一個緩衝區, 增加它只是為了加快分配的速度。
4. Top chunk
並不是所有的 chunk 都按照上面的方式來組織,實際上,有三種例外情況。 Top chunk,mmaped chunk 和 last remainder,下面會分別介紹這三類特殊的 chunk。 top chunk 對於主分
配區和非主分配區是不一樣的。對於非主分配區會預先從 mmap 區域分配一塊較大的空閒記憶體模擬 sub-heap, 通過管理 sub-heap 來響應使用者的需求, 因為記憶體是按地址從低向高進行分配的, 在空閒記憶體的最高處, 必然存在著一塊空閒 chunk, 叫做 top chunk。 當 bins 和 fast bins 都不能滿足分配需
要的時候,ptmalloc 會設法在 top chunk 中分出一塊記憶體給使用者,如果 top chunk 本身不夠大,分配程式會重新分配一個 sub-heap,並將 top chunk 遷移到新的 sub-heap 上,新的 sub-heap與已有的 sub-heap 用單向連結串列連線起來,然後在新的 top chunk 上分配所需的記憶體以滿足分配的需要, 實際上, top chunk 在分配時總是在 fast bins 和 bins 之後被考慮, 所以, 不論 top chunk 有多大, 它都不會被放到 fast bins 或者是 bins 中。 Top chunk 的大小是隨著分配和回收不停變換的,如果從 top chunk 分配記憶體會導致 top chunk 減小,如果回收的 chunk 恰好與 top chunk 相鄰,那麼這兩個 chunk 就會合併成新的 top chunk,從而使 top chunk 變大。如果在 free 時回收的記憶體大於某個閾值, 並且 top chunk 的大小也超過了收縮閾值, ptmalloc會收縮 sub-heap,如果 top-chunk 包含了整個 sub-heap, ptmalloc 會呼叫 munmap 把整個sub-heap 的記憶體返回給作業系統。由於主分配區是唯一能夠對映程序 heap 區域的分配區,它可以通過 sbrk()來增大或是收縮排程 heap 的大小, ptmalloc 在開始時會預先分配一塊較大的空閒記憶體( 也就是所謂的 heap), 主分配區的 top chunk 在第一次呼叫 malloc 時會分配一塊(chunk_size + 128KB)align 4KB 大小的空間作為初始的 heap, 使用者從 top chunk 分配記憶體時,可以直接取出一塊記憶體給使用者。在回收記憶體時, 回收的記憶體恰好與 top chunk 相鄰則合併成新的 top chunk,當該次回收的空閒記憶體大小達到某個閾值, 並且 top chunk 的大小也超過了收縮閾值, 會執行記憶體收縮,減小 top chunk 的大小, 但至少要保留一個頁大小的空閒記憶體, 從而把記憶體歸還給作業系統。 如果向主分配區的 top chunk 申請記憶體, 而 top chunk 中沒有空閒記憶體, ptmalloc會呼叫 sbrk()將的程序 heap 的邊界 brk 上移,然後修改 top chunk 的大小。
5. mmaped chunk
當需要分配的 chunk 足夠大, 而且 fast bins 和 bins 都不能滿足要求, 甚至 top chunk 本身也不能滿足分配需求時, ptmalloc 會使用 mmap 來直接使用記憶體對映來將頁對映到程序空間。 這樣分配的 chunk 在被 free 時將直接解除對映, 於是就將記憶體歸還給了作業系統, 再次對這樣的記憶體區的引用將導致 segmentation fault 錯誤。 這樣的 chunk 也不會包含在任何bin 中。
6. Last remainder
Last remainder 是另外一種特殊的 chunk,就像 top chunk 和 mmaped chunk 一樣,不會在任何 bins 中找到這種 chunk。當需要分配一個 small chunk,但在 small bins 中找不到合適
的 chunk,如果 last remainder chunk 的大小大於所需的 small chunk 大小,last remainder chunk被分裂成兩個 chunk,其中一個 chunk 返回給使用者,另一個 chunk 變成新的 last remainder chuk。


總結:
1.ptmalloc 的響應使用者記憶體分配要求的具體步驟為:
1) 獲取分配區的鎖, 為了防止多個執行緒同時訪問同一個分配區, 在進行分配之前需要取得分配區域的鎖。執行緒先檢視執行緒私有例項中是否已經存在一個分配區,如果存在嘗試對該分配區加鎖,如果加鎖成功,使用該分配區分配記憶體,否則,該執行緒搜
索分配區迴圈連結串列試圖獲得一個空閒( 沒有加鎖) 的分配區。如果所有的分配區都已經加鎖,那麼 ptmalloc 會開闢一個新的分配區,把該分配區加入到全域性分配區迴圈連結串列和執行緒的私有例項中並加鎖,然後使用該分配區進行分配操作。 開闢出來的
新分配區一定為非主分配區,因為主分配區是從父程序那裡繼承來的。開闢非主分配區時會呼叫 mmap()建立一個 sub-heap,並設定好 top chunk。

2) 將使用者的請求大小轉換為實際需要分配的 chunk 空間大小。

3) 判斷所需分配 chunk的大小是否滿足 chunk_size <= max_fast (max_fast 預設為 64B),如果是的話, 則轉下一步, 否則跳到第 5 步。

4) 首先嚐試在 fast bins 中取一個所需大小的 chunk 分配給使用者。 如果可以找到, 則分配結束。 否則轉到下一步。

5) 判斷所需大小是否處在 small bins 中, 即判斷 chunk_size < 512B 是否成立。 如果chunk 大小處在 small bins 中, 則轉下一步, 否則轉到第 6 步。

6) 根據所需分配的 chunk 的大小, 找到具體所在的某個 small bin, 從該 bin 的尾部摘取一個恰好滿足大小的 chunk。 若成功, 則分配結束, 否則, 轉到下一步。

7) 到了這一步, 說明需要分配的是一塊大的記憶體, 或者 small bins 中找不到合適的chunk。於是, ptmalloc 首先會遍歷 fast bins 中的 chunk, 將相鄰的 chunk 進行合併,並連結到 unsorted bin 中, 然後遍歷 unsorted bin 中的 chunk,如果 unsorted bin 只有一個 chunk,並且這個 chunk 在上次分配時被使用過,並且所需分配的 chunk 大小屬於 small bins,並且 chunk 的大小大於等於需要分配的大小,這種情況下就直
接將該 chunk 進行切割,分配結束,否則將根據 chunk 的空間大小將其放入 smallbins 或是 large bins 中,遍歷完成後,轉入下一步。

8) 到了這一步,說明需要分配的是一塊大的記憶體,或者 small bins 和 unsorted bin 中都找不到合適的 chunk,並且 fast bins 和 unsorted bin 中所有的 chunk 都清除乾淨了。 從 large bins 中按照“ smallest-first, best-fit”原則, 找一個合適的 chunk, 從中劃分一塊所需大小的 chunk, 並將剩下的部分連結回到 bins 中。 若操作成功, 則分配結束, 否則轉到下一步。

9) 如果搜尋 fast bins 和 bins 都沒有找到合適的 chunk, 那麼就需要操作 top chunk 來進行分配了。 判斷 top chunk 大小是否滿足所需 chunk 的大小, 如果是, 則從 topchunk 中分出一塊來。 否則轉到下一步。

10) 到了這一步, 說明 top chunk 也不能滿足分配要求, 所以, 於是就有了兩個選擇: 如果是主分配區, 呼叫 sbrk(), 增加 top chunk 大小; 如果是非主分配區,呼叫 mmap來分配一個新的 sub-heap,增加 top chunk 大小; 或者使用 mmap()來直接分配。 在這裡, 需要依靠 chunk 的大小來決定到底使用哪種方法。 判斷所需分配的 chunk大小是否大於等於 mmap 分配閾值, 如果是的話, 則轉下一步, 呼叫 mmap 分配,否則跳到第 12 步, 增加 top chunk 的大小。

11) 使用 mmap 系統呼叫為程式的記憶體空間對映一塊 chunk_size align 4kB 大小的空間。然後將記憶體指標返回給使用者。

12) 判斷是否為第一次呼叫 malloc, 若是主分配區, 則需要進行一次初始化工作, 分配21一塊大小為(chunk_size + 128KB) align 4KB 大小的空間作為初始的 heap。 若已經初始化過了, 主分配區則呼叫 sbrk()增加 heap 空間, 分主分配區則在 top chunk 中切割出一個 chunk, 使之滿足分配需求, 並將記憶體指標返回給使用者。
這裡寫圖片描述
2.記憶體回收概述
free() 函式接受一個指向分配區域的指標作為引數,釋放該指標所指向的 chunk。而具體的釋放方法則看該 chunk 所處的位置和該 chunk 的大小。 free()函式的工作步驟如下:
1) free()函式同樣首先需要獲取分配區的鎖,來保證執行緒安全。

2) 判斷傳入的指標是否為 0,如果為 0,則什麼都不做,直接 return。否則轉下一步。

3) 判斷所需釋放的 chunk 是否為 mmaped chunk,如果是,則呼叫 munmap()釋放mmaped chunk,解除記憶體空間對映,該該空間不再有效。如果開啟了 mmap 分配閾值的動態調整機制,並且當前回收的 chunk 大小大於 mmap 分配閾值,將 mmap分配閾值設定為該 chunk 的大小,將 mmap 收縮閾值設定為 mmap 分配閾值的 2倍,釋放完成,否則跳到下一步。

4) 判斷 chunk 的大小和所處的位置,若 chunk_size <= max_fast, 並且 chunk 並不位於heap 的頂部,也就是說並不與 top chunk 相鄰,則轉到下一步,否則跳到第 6 步。( 因為與 top chunk 相鄰的小 chunk 也和 top chunk 進行合併,所以這裡不僅需要判斷大小,還需要判斷相鄰情況)

5) 將 chunk 放到 fast bins 中, chunk 放入到 fast bins 中時, 並不修改該 chunk 使用狀態位 P。也不與相鄰的 chunk 進行合併。只是放進去, 如此而已。 這一步做完之後釋放便結束了, 程式從 free()函式中返回。

6) 判斷前一個 chunk 是否處在使用中, 如果前一個塊也是空閒塊, 則合併。 並轉下一步。

7) 判斷當前釋放 chunk 的下一個塊是否為 top chunk, 如果是, 則轉第 9 步, 否則轉下一步。

8) 判斷下一個 chunk 是否處在使用中, 如果下一個 chunk 也是空閒的, 則合併, 並將22合併後的 chunk 放到 unsorted bin 中。 注意, 這裡在合併的過程中, 要更新 chunk的大小, 以反映合併後的 chunk 的大小。 並轉到第 10 步。

9) 如果執行到這一步, 說明釋放了一個與 top chunk 相鄰的 chunk。則無論它有多大,都將它與 top chunk 合併, 並更新 top chunk 的大小等資訊。 轉下一步。10) 判斷合併後的 chunk 的大小是否大於FASTBIN_CONSOLIDATION_THRESHOLD(預設64KB), 如果是的話, 則會觸發進行 fast bins 的合併操作, fast bins 中的 chunk 將被遍歷,並與相鄰的空閒 chunk 進行合併,合併後的 chunk 會被放到 unsorted bin 中。fast bins 將變為空, 操作完成之後轉下一步。

11) 判斷 top chunk 的大小是否大於 mmap 收縮閾值(預設為 128KB), 如果是的話, 對於主分配區, 則會試圖歸還 top chunk 中的一部分給作業系統。 但是最先分配的128KB 空間是不會歸還的, ptmalloc 會一直管理這部分記憶體, 用於響應使用者的分配請求;如果為非主分配區,會進行 sub-heap 收縮,將 top chunk 的一部分返回給作業系統,如果 top chunk 為整個 sub-heap,會把整個 sub-heap 還回給作業系統。 做完這一步之後, 釋放結束, 從 free() 函式退出。 可以看出, 收縮堆的條件是當前free 的 chunk 大小加上前後能合併 chunk 的大小大於 64k,並且要 top chunk 的大小要達到 mmap 收縮閾值,才有可能收縮堆。
這裡寫圖片描述