Glibc堆管理機制基礎

unr4v31發表於2021-02-25

最近正在學習linux下堆的管理機制,收集了書籍和網路上的資料,以自己的理解做了整理,做個記錄。如果有什麼不對的地方歡迎指出!

Memory Allocator

常見的記憶體管理機制

  • dlmalloc:通用分配器
  • ptmalloc2:glibc分配器,繼承自dlmalloc,並提供了多執行緒支援,主要研究物件。
  • jemalloc:Firefox
  • tcmalloc:Chrome
  • 其他:程式語言記憶體分配及回收,比如python
  • ......

malloc工作機制

第一次呼叫malloc

記憶體分配機制

標頭檔案:#include<unistd.h>

  • brk()
  1. 函式原型:int brk(void* end_data_segment)
  2. 功能和作用:用於設定program_break指向的位置。
  • sbrk()
  1. 函式原型:void* sbrk(intptr_t increment)
  2. 功能和作用:同brk(),引數可以是負數。執行成功返回上一次program_break的值,可以設定引數為0返回當前的program_break.
  • mmap()
  1. 功能和作用:當使用者申請空間大於等於128kb,也就是0x20000位元組時,不再使用brk()進行分配,改為使用mmap()。
  • unmmap()
  1. 功能和作用:堆mmap()申請的空間進行回收。
    記憶體分配圖

  • 主執行緒的arena就是main_arena,包含start_brk和brk中間的連續記憶體,當main_arena不夠分配時,會使用brk()進行擴充套件。
  • 子執行緒arena可以有多片連續記憶體,但是大小是固定的,不可以擴充套件,如果不夠用的話需要再次呼叫mmap()來分配。

第二次呼叫malloc

  • 只要分配的空間不超過128kb,則不會再次向system申請空間,超過時才會呼叫brk()進行擴充套件。
  • 即使將main_arena全部free,也不會立即把記憶體還給作業系統,此時記憶體由glib進行管理。

chunk

chunk是glibc管理記憶體的基本單元。主要分為以下幾類:

  • alloced chunk:已分配正在使用中的chunk。
  • free chunk:已經free的chunk。
  • top chunk:可以理解為地址的最高處,還沒有分配的chunk。
  • last remainder chunk:是為了提高記憶體分配的區域性性。

chunk = chunk header + user data,malloc返回給使用者的其實是user data指標,具體如下圖:

alloced chunk結構

  • size:本chunk的大小,包括prev,大小為8的整數倍。32位以8位元組對齊,最小為0x10。64位以16位元組對齊,最小為0x20。其中低三位有特殊含義,分別為N、M、P
  • N位:是否屬於主程式。
  • M位:是否由mmap()分配。
  • P位:前一堆塊佔用標誌,1為佔用,0為空閒。
  • 當P位為0時,表示前一堆塊釋放,prev表示前一堆塊的大小。當P位為1,表示前一堆塊使用,prev表示前一堆塊的資料。
  • userdata為輸入的資料。
  • 將下一堆塊的P位設定為1。

free chunk

  • 其中fd、bk屬於連結串列指標,有特殊用途,後面會講到。
  • prev_size為當前釋放塊的大小(包含chunk header)
  • 下一堆塊P位通常被設定為0(fastbin除外)。

top chcunk

  • 該堆塊位於前兩種堆塊之後,頭部結構與alloced相似
  • size:top chunk還有多少空間可以分配。
  • 重要的是P位:0表示上一堆塊處於空閒,1表示上一堆塊處於使用狀態。主要用於判斷free時是否能與上一堆塊進行合併(fastbin除外)。

last remainder chunk

  • 在malloc時,如果有比較大的chunk可以分配,會把這個chunk分成兩部分,一部分返回給使用者,另一部分稱為remainder,加入到 unsorted bin,last remainder會記錄最近拆分的remainder。這個remainder大小至少要為MINSIZE,否則不能拆分。
  • 當下次malloc時,如果last remainder chunk夠大,則重複上一過程。
  • 拆分的情況:fast bin 和 small bin 都沒有合適的chunk,同時unsorted bin有且只有一個可拆分的chunk,並且這個chunk 是last remainder。

堆空閒塊管理結構bin

當alloced chunk被釋放後,會根據大小放入bin或者合併到top chunk 中去。bin的主要作用時加快分配速度,通過連結串列方式(chunk中的fd和bk指標)進行管理。主要有以下幾種,顧名思義:

  • fast bin
  • unsorted bin
  • small bin
  • large bin

fastbinsY:這是一個bin陣列,裡面有NFASTBINS個fast bin

bins:也是一個bin陣列,一共有126個bin,按順序分別是:

  • bin 1 為unsorted bin
  • bin 2 到 bin 63 為small bin
  • bin 64 到 bin 126 為 large bin

fast bin

  • 這類bin通常申請和釋放的堆塊都比較小,所以使用單連結串列結構,LIFO(後進先出)分配策略。
  • 為了速度,fast bin不會進行合併,下一個chunk始終處於使用狀態。
  • 在fastbinsY陣列裡按照從小到大的順序排列。
  • 以64位為例,fast bin結構如下(大小區間0x200x80,32位為0x100x40):

unsorted bin

  • 一定大小堆塊被釋放時,在加入small bin 和large bin 之前,會首先加入此bin,可以加快分配速度。使用雙連結串列結構,FIFO(先進先出)分配策略。
  • unsorted bin大小可能是不相同的。
  • 由於使用雙連結串列,一個bin會佔用bins的兩個元素。fd指向上一個chunk,bk指向下一個。
  • 以64位為例,unsorted bin結構如下(非連續記憶體,大小無限制):

small bin

  • 同一個small bin裡的chunk大小相同,使用雙連結串列結構,FIFO(先進先出)分配策略。
  • 由於fast bin和small bin 有重合部分,在某些情況下會加入到small bin
  • 根據大小分成62個不同的bin,0x20,0x30,0x40...0x80,0x90...1008
  • 以64位為例,small bin結構如下(大小區間:size<0x400byte):

large bin

  • 使用雙連結串列結構,FIFO(先進先出)分配策略。
  • free時bk後面多兩個此引數:fd_nextsize、bk_nextsize。分別指向前一個和後一個large chunk。
  • 根據大小分成63個不同的bin,大小不再固定。前32個bin為 0x400+64i,32-48 bin為 0x1380+512j,依此類推。並且會將大的chunk放在前面,小的放在後面,以加快速度。
  • 以64位為例,large bin大小區間:size>=1024byte。32位為:size>=512byte。
  • fd_nextsize和bk_nextsize指標用於指向第一個與自己大小不同的chunk,所以也只有在加入了大小不同的chunk時,這兩個指標才會被修改。

隨後附上glibc記憶體管理流程圖

看不清楚可以儲存下來放大。

相關文章