在上一篇裏面說到,vs 的標準分配器 std::allocator
並沒有做內存管理。gnu的歷史版本 2.9 中的標準分配器 std::allocator 也沒有做內存管理,但是它提供的 extended allocator 中,std::alloc 就是一個做了內存管理的版本。在 gnu 新版本中,__gnu_cxx::__pool_alloc<_Ty> 就是 G2.9 的 std::alloc 的化身。
下面先分析以下 std::alloc 的運行模式,再下一篇分析具體代碼。
不過先需要前三篇的基礎:
數據結構
- 一個指針數組
free_list[16]
,分別負責16條不同大小的 block,以 8 bytes 爲間隔。比如 #0 負責 8 bytes 的 block,#3 負責 32 bytes 的 block。當客戶端需要 size 大小的內存時,首先被調整到 8 的倍數,然後到free_list
相應的位置索取。 - 當用戶所索取的 block 大小超過了 free_list 所能提供的最大的大小也就是 128 字節時,會轉而調用 malloc 函數。
- 以用戶索取 size = 32 爲例。如果 #3 位置上沒有分配內存塊,那麼會一次性分配
20 * size * 2 + ROUNDUP()
這麼大的內存塊 (以下稱爲 chunk)。其中前面的20 * size
大小的內存塊會被分割成 20 個 blocks,被串成鏈表,並且把第一塊 block 返回給客戶。剩下的20 * size + ROUND()
大小的 chunk 會被用來當作 memory pool,其中有一個start_free
和end_free
指針分別指向這一塊內存的首尾位置。這一塊 memory pool 爲下一次用戶索取不同大小 size 的 block 做準備。ROUND() 是一個與當前分配內存總量相關的函數,下面再具體說明。 - 每一次對內存塊進行切割成 block 串成鏈表,鏈表的長度不會超過 20。
- 除了所分配的一整塊 chunk 的首尾 block 帶有 cookie 之外,其餘的都是 cookie free blocks。
實例分析
申請新的內存塊
- 由於 此時 memory pool 爲空,所以利用
malloc
函數,向操作系統索取 32 * 20 * 2 + ROUNDUP (0 >> 4) = 1280 大小的 chunk。將前面的 640 bytes 大小切割成 20 個 block ,串成鏈表,並將第一個 block 返回給客戶。 start_free
和end_free
指向剩餘 640 bytes 相對應的位置。- 這裏也可以看出,ROUNDUP 函數就是返回 當前已分配的內存總量 / 16 的結果。
- 由於此時 memory pool 有640 bytes 的餘量,所以直接對這一塊 chunk 進行切割。剛好能夠切割 10 個 block (這也是爲什麼前面說,鏈表的長度最大爲 20)。返回第一個 block。
- #3 和 # 7 所指向的內存塊,在物理上是連續的,但是在邏輯上,是不連續的。
- 此時累計申請 1280 bytes,memory pool 餘量爲 0 byte
- 由於此時 memory pool 爲空,又需要向操作系統索取 96 * 2 * 20 + ROUNDUP (1280 >> 4) = 3920 大小的 chunk。前 1920 串成鏈表,返回第一個,後 2000 作爲 memory pool。
- 此時累計申請 5200 bytes,memory pool 餘量爲 2000 byte
客戶端連續申請
內存碎片的處理
- 此時 memory pool 只有 80 bytes ,並不能夠分出一個 104 bytes 的 block,此時就產生了內存碎片。對於內存碎片,會將它以一個單位大小的 block 撥給
free_list
相對應的位置。比如再這裏,會將這一塊 80 bytes 的內存塊撥給 #9 的位置。最後再來分配新的內存。
- 此時 memory pool 只有 168 bytes,只能分出 3 個 48 bytes 的 block,剩下的 24 bytes 作爲 memory pool。
內存耗盡時
- 此時 memory pool 只有 24 bytes ,並不能夠分出一個 72 bytes 的 block,這個時候又產生了內存碎片。相同的,首先將這 24 bytes 的內存塊撥到 #2 位置。然後再來索取新的內存塊。
- 假設此時由於內存耗盡,操作系統無法一次性給予 72 * 20 * 2 + ROUNDUP(9688 >> 4) 這麼大的內存塊。這個時候,它就會向距離它最近的,比 block size 大的內存鏈表中索取一塊,從中切出 72 byte 的大小。這裏 #9 位置正好有一個 block ,它就會被轉接到 #8 位置上,原來 #9 的鏈表斷開。其中前 72 bytes 被返回給客戶,最後剩下的 8 bytes 作爲 memory pool。最後的結果就是 #8 #9 位置的鏈表都爲空了。
最終內存分配失敗
- 此時操作系統的內存耗盡,無法獲取新的內存。而且也沒有可用的已經獲得的內存鏈表。最終導致客戶端內存分配失敗。