1. 程式人生 > >Ptmalloc2筆記-1

Ptmalloc2筆記-1

學習Glibc 記憶體管理 Ptmalloc2 原始碼分析.pdf

基礎

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

核心資料結構mm_struct的成員start_codeend_code是程序程式碼段的起始終止地址,start_dataend_data是程序資料段的起始終止地址,start_stack是程序堆疊段起始地址,start_brk是程序動態記憶體分配起始地址,還有一個brk

是動態記憶體分配當前的終止地址.

#include <unistd.h>
int brk(void *addr);
void *sbrk(intptr_t increment);

brk()改變mm_struct結構的成員變數brk的值 sbrk()的引數為0時,sbrk()返回程序的當前brk,為正數時擴充套件brk值,為負值時收縮brk值.

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void
*addr, size_t length);

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

length:對映區的長度.

prot:期望的記憶體保護標誌,不能與檔案的開啟模式衝突. PROT_EXEC //頁內容可以被執行,ptmalloc中沒有使用 PROT_READ //頁內容可以被讀取,ptmalloc直接用mmap分配記憶體並立即返回給使用者時設定該標誌 PROT_WRITE //頁可以被寫入,ptmalloc

直接用mmap分配記憶體並立即返回給使用者時設定該標誌 PROT_NONE //頁不可訪問,ptmallocmmap向系統”批發”一塊記憶體進行管理時設定該標誌

flags:指定對映物件的型別,對映選項和對映頁是否可以共享 MAP_FIXED //使用指定的對映起始地址,如果由addrlength引數指定的記憶體區重疊於現存的對映空間,重疊部分將會被丟棄.如果指定的起始地址不可用,操作將會失敗.並且起始地址必須落在頁的邊界上.Ptmalloc在回收從系統中”批發”的記憶體時設定該標誌. MAP_PRIVATE //建立一個寫入時拷貝的私有對映.記憶體區域的寫入不會影響到原檔案.這個標誌和以上標誌是互斥的,只能使用其中一個.Ptmalloc每次呼叫mmap都設定該標誌. MAP_NORESERVE //不要為這個對映保留交換空間.當交換空間被保留,對對映區修改的可能會得到保證.當交換空間不被保留,同時記憶體不足,對對映區的修改會引起段違例訊號.Ptmalloc向系統”批發”記憶體塊時設定該標誌. MAP_ANONYMOUS //匿名對映,對映區不與任何檔案關聯.Ptmalloc每次呼叫mmap都設定該標誌.

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

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

C記憶體管理程式

較著名的幾個C記憶體管理程式: 1. Doug Lea Malloc:一組分配程式,包括Doug Lea,GNU libc的分配程式和ptmalloc.Doug Lea的分配程式加入了索引,並且可以將多個沒有被使用的塊組合為一個大的塊.還支援快取,以便更快地再次使用最近釋放的記憶體.ptmallocDoug Lea Malloc的一個擴充套件版本,支援多執行緒. 2. BSD Malloc:BSD Malloc是隨4.2 BSD發行的實現,包含在FreeBSD之中,可以從預先確實大小的物件構成的池中分配物件.它有一些用於物件大小的size類,這些物件的大小為2的若干次冪減去某一常數.所以,如果您請求給定大小的一個物件,它就簡單地分配一個與之匹配的size類.這樣就提供了一個快速的實現,但是可能會浪費記憶體. 3. Hoard:編寫Hoard的目標是使記憶體分配在多執行緒環境中進行非常快.構造以鎖的使用為中心,從而使所有程序不必等待分配記憶體.可以顯著地加快那些進行很多分配和回收的多執行緒程序的速度. 4. TCMalloc:(Thread-Caching Malloc)google的開源工具google-perftools中的成員.TCMalloc是一種通用記憶體管理程式,集成了記憶體池和垃圾回收的優點,對於小記憶體,按8的整數次倍分配,對於大記憶體,按4K的整數次倍分配.TCMalloc還有一套高效的機制回收這些空閒的記憶體.當一個執行緒的空閒記憶體比較多的時候,會交還給程序,程序可以把它調配給其他執行緒使用;如果某種長度交還給程序後,其他執行緒並沒有需求,程序則把這些長度合併成記憶體頁,然後切割成其他長度.如果程序佔據的資源比較多呢,據說不會交回作業系統.週期性的記憶體回收,避免可能出現的記憶體爆炸式增長的問題.TCMalloc有比較高的空間利用率,只額外花費1%的空間.儘量避免加鎖(一次加鎖解鎖約浪費100ns),使用更高效的spinlock,採用更合理的粒度.小塊記憶體和大塊記憶體分配採取不同的策略:小於32K的被定義為小塊記憶體,小塊記憶體按大小被分級.不是某個級別整數倍的大小都會被分配向上取整.分配時,首先在本執行緒相應大小級別的空閒連結串列裡面找,如果找到的話可以避免加鎖操作.如果找不到的話,則嘗試從中心記憶體區的相應級別的空閒連結串列裡搬一些物件到本執行緒的連結串列.如果中心記憶體區相應連結串列也為空的話,則向中心頁分配器請求記憶體頁面,然後分割成該級別的物件儲存.大塊記憶體處理方式:按頁分配,每頁大小是4K,然後記憶體大小分類,相同大小的記憶體塊也用連結串列連線

Ptmalloc記憶體管理概述

Main_arenanon_main_arena

主分配區與非主分配區用環形連結串列進行管理.每一個分配區利用互斥鎖(mutex)使執行緒對於該分配區的訪問互斥.

每個程序只有一個主分配區,但可能存在多個非主分配區,分配區的數量一旦增加,就不會再減少了

主分配區可以訪問程序heap區域和mmap對映區域,可以使用sbrkmmap向作業系統申請虛擬記憶體.而非主分配區只能訪問程序的mmap對映區域,非主分配區使用mmap()向作業系統”批發”HEAP_MAX_SIZE(32位系統上預設為1MB,64位系統預設為64MB)大小的虛擬記憶體,使用者向非主分配區請求分配記憶體時再切割成小塊.主分配區可以訪問heap區域,如果使用者不呼叫brk()或是sbrk()函式,分配程式就可以保證分配到連續的虛擬地址空間.

當某一執行緒呼叫malloc()分配記憶體空間時,執行緒先檢視執行緒私有變數中是否存在一個分配區,存在則嘗試對分配區加鎖,如果加鎖成功,分配記憶體;如果失敗,執行緒搜尋迴圈連結串列試圖獲得一個沒加鎖的分配區.如果所有的分配區都已經加鎖,那麼malloc()會開闢一個新分配區,把該分配區加入到全域性分配區迴圈連結串列並加鎖,然後使用該分配區進行分配記憶體操作.在釋放操作中,執行緒同樣試圖獲得待釋放記憶體塊所在分配區的鎖,如果該分配區正在被別的執行緒使用,則需要等待直到其他執行緒釋放該分配區的互斥鎖之後才可以進行釋放操作

chunk

使用者請求分配的空間在ptmalloc中都使用一個chunk來表示

這裡寫圖片描述

使用中的chunk,chunk指標指向開始,mem指標才是真正返回給使用者的記憶體指標. chunk的第二個域的最低一位為P,表示前一個塊是否在使用中,P0表示前一chunk空閒,這時chunk的第一個域prev_size才有效,prev_size表示前一chunksize,程式可以使用這個值來找到前一chunk的開始地址.P1,表示前一個chunk正在使用,prev_size無效.ptmalloc分配的第一個塊總是將P設為1. chunk的第二個域的倒數第二個位為M,表示當前chunk是從哪個記憶體區域獲得的虛擬記憶體.M1表示是從mmap對映區域分配的,否則是從heap區域分配的. chunk的第二個域倒數第三個位為A,表示該chunk屬於主分配區或者非主分配區,如果屬於非主分配區,將該位置為1,否則置為0.

這裡寫圖片描述

空閒的chunk,當chunk空閒時,其M狀態不存在,只有AP狀態. 指標fd指向所在bins後一空閒chunk,bk指向所在bins前一空閒chunk,ptmalloc通過這兩個指標將大小相近的chunk連成一個雙向連結串列(bins). 對於large bin中的空閒chunk,還有兩個指標,fd_nextsizebk_nextsize,這兩個指標用於加快在large bin中查詢最近匹配的空閒chunk.

空間複用

一個chunk正在被使用或者已經被free,所以chunk的中的一些域可以在使用狀態和空閒狀態表示不同的意義,來達到空間複用的效果. 以32位系統為例,空閒時,一個chunk中至少需要4個size_t(4B)的空間,用來儲存prev_size,size,fdbk ,chunk的大小要對齊到8B.一個chunk處於使用狀態時,下一個chunkprev_size域是無效的.這個空間可以被當前chunk使用.故而實際上,一個使用中的chunk的大小的計算公式應該是:in_use_size = (使用者請求大小+ 8 - 4 ) align to 8B.最後,因為空閒的chunk和使用中的chunk使用的是同一塊空間.所以肯定要取其中最大者作為實際的分配空間.即最終的分配空間chunk_size = max(in_use_size, 16)

空閒chunk容器

Bins

free的記憶體並不都會馬上歸還系統,ptmalloc會統一管理heapmmap對映區域中的空閒chunk,當下一次分配請求時,ptmalloc會試圖在空閒chunk中挑選一塊給使用者.ptmalloc將相似大小的chunk用雙向連結串列連結起來,被稱為一個bin.ptmalloc共維護了128bin,並使用一個數組來儲存這些bin.

這裡寫圖片描述

陣列中的第一個為unsorted bin. 陣列中從2開始的前64bin稱為small bins,同一個small bin中的chunk具有相同的大小.相鄰的small bin大小相差8bytes.small bins中的chunk按照最近使用順序進行排列,最後釋放的chunk被連結到連結串列的頭部,而申請chunk是從連結串列尾部開始. small bins後面的為large bins.每個large 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中.一些小的chunk會放到fast bins內.

Fast Bins

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,將相鄰的空閒chunk進行合併,並將合併後的chunk加入unsorted bin中,然後再將unsorted bin裡的chunk加入bins.

Unsorted Bin

unsorted bin的佇列使用bins陣列的第一個,如果釋放的chunk大於max_fast,或者fast bins中空閒chunk合併後,這些chunk首先會被放到unsorted bin中,進行malloc操作的時候,如果在fast bins中沒有找到合適的chunk,則ptmalloc會先在unsorted bin中查詢合適的空閒chunk,然後查詢bins.如果unsorted bin不能滿足分配要求.malloc便會將unsorted bin中的chunk加入bins中.然後再從bins中繼續進行查詢和分配過程.unsorted bin可以看做是bins的一個緩衝區.

特殊chunk

有三種特殊chunk.top chunk,mmaped chunklast remainder

Top chunk

對於非主分配區會預先從mmap區域分配一塊較大的空閒記憶體模擬sub-heap,通過管理sub-heap來響應使用者的需求,在空閒記憶體的最高處,必然存在著一塊空閒chunk,top chunk.

binsfast bins都不能滿足分配需要,ptmalloc會在top chunk中分出一塊記憶體,如果top chunk本身不夠大,分配程式會重新分配一個sub-heap,並將top chunk遷移到新的sub-heap上,新的sub-heap與已有的sub-heap用單向連結串列連線起來,然後在新的top chunk上分配所需的記憶體以滿足分配的需要.

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呼叫munmapsub-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的大小.

mmaped chunk

top chunk不能滿足分配需求時,ptmalloc會使用mmap來直接使用記憶體對映來將頁對映到程序空間.這樣分配的chunk在被free時將直接解除對映,於是就將記憶體歸還給了作業系統,再次對這樣的記憶體區的引用將導致segmentation fault錯誤.

Last remainder

last remainder是另外一種特殊的chunk,不在任何bins中.當需要分配small chunk,但small bins找不到合適的chunk,如果last remainder chunk大於所需small chunk大小,last remainder chunk被分裂成兩個chunk,其中一個chunk返回給使用者,另一個chunk變成新的last remainder chuk.

sbrk與mmap

.bss段之上的分配給使用者程式的空間被稱為heap.在malloc之前,brk等於start_brk.ptmalloc在開始時,若請求的空間小於mmap分配閾值(mmap threshold,預設值為128KB),主分配區會呼叫sbrk()增加一塊大小為 (128 KB + chunk_size) align 4KB的空間作為heap.非主分配區會呼叫mmap對映一塊大小為HEAP_MAX_SIZE(32位系統上預設為1MB,64位系統上預設為64MB)的空間作為sub-heap.

這就是ptmalloc維護的分配空間.若需要分配的chunk小於mmap分配閾值,而heap又不夠,則此時主分配區會通過sbrk()呼叫來增加heap大小,非主分配區會呼叫mmap對映一塊新的sub-heap,也就是增加top chunk的大小,每次heap增加的值都會對齊到4KB.

使用者的請求超過mmap分配閾值,並且主分配區使用sbrk()分配失敗或是非主分配區在top chunk中不能分配到需要的記憶體時,ptmalloc會嘗試使用mmap()直接對映一塊記憶體到程序記憶體空間.

ptmalloc munmap chunk回收的chunk大於mmap分配閾值,小於DEFAULT_MMAP_THRESHOLD_MAX(32位系統預設為512KB,64位系統預設為32MB),ptmalloc會把mmap分配閾值調整為當前回收的chunk的大小,並將mmap收縮閾值(mmap trim threshold)設定為mmap分配閾值2倍.這就是ptmallocmmap分配閾值的動態調整機制,該機制預設開啟,可以用mallopt()關閉該機制

記憶體分配概述

  1. 分配演算法概述,以32系統為例.
    • 小於等於64位元組:用pool演算法分配.
    • 64512位元組之間:在最佳匹配演算法分配和pool演算法分配中取一種合適的.
    • 大於等於512位元組:用最佳匹配演算法分配.
    • 大於等於mmap分配閾值(預設值128KB):根據mmap分配策略分配,如果沒開啟mmap分配閾值的動態調整機制,大於等於128KB就直接呼叫mmap分配.否則,大於等於mmap分配閾值時才直接呼叫mmap()分配.
  2. ptmalloc的響應使用者記憶體分配要求的具體步驟為:
    • 獲取分配區的鎖,為了防止多個執行緒同時訪問同一個分配區,在進行分配之前需要取得分配區域的鎖.執行緒先檢視執行緒私有例項中是否已經存在一個分配區,如果存在嘗試對該分配區加鎖,如果加鎖成功,使用該分配區分配記憶體,否則,該執行緒搜尋分配區迴圈連結串列試圖獲得空閒(沒有加鎖)的分配區.如果所有的分配區都已經加鎖,那麼ptmalloc會開闢一個新的分配區,把該分配區加入到全域性分配區迴圈連結串列和執行緒的私有例項中並加鎖,然後使用該分配區進行分配操作.開闢出來的新分配區一定為非主分配區,因為主分配區是從父程序那裡繼承來的.開闢非主分配區時會呼叫mmap()建立一個sub-heap,並設定好top chunk.
    • 將使用者的請求大小轉換為實際需要分配的chunk空間大小.
    • 判斷chunk_size <= max_fast (max_fast 預設為 64B),如果是,嘗試在fast bins中取一個所需大小的chunk分配給使用者.如果不是或者找不到,下一步
    • 判斷chunk_size < 512B.如果是,根據所需分配的chunk的大小,找到具體所在的small bin,從該bin的尾部摘取一個恰好滿足大小的chunk,如果不是或者找不到,下一步
    • ptmalloc首先會遍歷fast bins中的chunk,將相鄰的chunk進行合併,並連結到unsorted bin中,然後遍歷unsorted bin中的chunk,如果unsorted bin只有一個chunk,並且這個chunk在上次分配時被使用過,並且所需分配的chunk大小屬於small bins,並且chunk的大小大於等於需要分配的大小,這種情況下就直接將該chunk進行切割,分配結束,否則將根據chunk的空間大小將其放入small bins或是large bins中,遍歷完成後,下一步
    • fast binsunsorted bin中所有chunk都清除了.從large bins中按照smallest-first,best-fit原則,找一個合適的chunk,從中劃分一塊所需大小的chunk,並將剩下的部分連結回到bins中.若操作成功,則分配結束,否則下一步.
    • 操作top chunk來進行分配.如果判斷top chunk大小滿足所需chunk的大小則從top chunk中分出一塊來.否則下一步。
    • 如果是主分配區,呼叫sbrk(),增加top chunk大小;如果是非主分配區,呼叫mmap來分配一個新的sub-heap,增加top chunk大小;或者使用mmap()來直接分配.在這裡,需要依靠chunk的大小來決定使用哪種方法.判斷所需分配的chunk大小是否大於等於mmap分配閾值,如果是的話,使用mmap系統呼叫為程式的記憶體空間對映一塊chunk_size align 4kB大小的空間.(第一次呼叫malloc,若是主分配區,則需要進行一次初始化工作,分配一塊大小為(chunk_size + 128KB) align 4KB大小的空間作為初始的heap)
    • Tips:第一次分配記憶體時,一般情況下只存在一個主分配區,也可能從父程序那裡繼承來了多個非主分配區.

記憶體回收概述

free()函式的工作步驟: 1. free()函式首先需要獲取分配區的鎖 2. 判斷傳入的指標是否為0,不為0下一步. 3. 判斷所需釋放的chunk是否為mmaped chunk,如果是,則呼叫munmap()釋放mmaped chunk,解除記憶體空間對映.如果開啟了mmap分配閾值的動態調整機制,並且當前回收的chunk大於mmap分配閾值,將mmap分配閾值設定為該chunk大小,將mmap收縮閾值設定為mmap分配閾值的2倍,釋放完成.否則下一步 4. 判斷chunk大小和所處位置,若chunk_size <= max_fast,並且chunk不與top chunk相鄰,則將chunk放到fast bins中,不修改該chunk使用狀態位P.釋放結束.否則下一步 5. 判斷前一個chunk是否處在使用中,如果前一個塊也是空閒塊,則合併.並轉下一步. 6. 判斷當前釋放chunk的下一個塊是否為top chunk,如果是,則與top chunk合併,並更新top chunk的大小等資訊;如果不是,判斷下一chunk是否處在使用,如果空閒則合併,並將合併後的chunk放到unsorted bin中.下一步 7. 無論是不是.判斷合併的chunk大小是否大於FASTBIN_CONSOLIDATION_THRESHOLD(預設64KB),如果是,則觸發fast bins的合併操作,fast bins中的chunk將被遍歷,並與相空閒chunk合併,合併的chunk會被放到unsorted bin中.fast bins為空.判斷top chunk的大小是否大於mmap收縮閾值(預設128KB),如果是,對於主分配區,試圖歸還top chunk的一部分給作業系統.但最先分配的128KB空間不會歸還,ptmalloc會一直管理這部分記憶體,用於響應使用者的分配請求;如果為非主分配區,會進行sub-heap收縮,將top chunk的一部分返回給作業系統,如果top chunk為整個sub-heap,會把整個sub-heap還給作業系統.做完這一步之後,釋放結束.

配置選項概述

ptmalloc主要提供以下幾個配置選項,通過mallopt()設定 1. M_MXFAST:用於設定fast bins中儲存chunk的最大大小,預設值為64B.M_MXFAST的最大值為80B,如果設定該選項為0,就會不使用fast bins 2. M_TRIM_THRESHOLD:用於設定mmap收縮閾值,預設值為128KB.自動收縮只在free時發生,在發生記憶體收縮時,會重新設定mmap分配閾值和mmap收縮閾值.(作業系統呼叫mmap()分配記憶體是序列的,在發生缺頁異常時載入新的物理頁,需要對新的物理頁做清0操作)M_TRIM_THRESHOLD的值必須設定為頁大小對齊,設定為-1會關閉記憶體收縮設定. 3. M_MMAP_THRESHOLD:用於設定mmap分配閾值,預設值為128KB,ptmalloc預設開啟動態調整mmap分配閾值和mmap收縮閾值. 4. M_MMAP_MAX:用於設定程序中用mmap分配的記憶體塊的最大限制,預設值為64K,如果將M_MMAP_MAX設定為0,ptmalloc將不會使用mmap分配大塊記憶體.

ptmallocPER_THREAD的優化提供兩個選項,M_ARENA_TESTM_ARENA_MAX. ptmalloc沒有提供關閉mmap分配閾值動態調整機制的選項,mmap分配閾值動態調整預設開啟,如果要關閉mmap分配閾值動態調整機制,可以設M_TRIM_THRESHOLD,M_MMAP_THRESHOLD,M_TOP_PAD,M_MMAP_MAX中的任意一個.