精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

glibc的內(nèi)存分配回收策略

科技綠洲 ? 來(lái)源:Linux開(kāi)發(fā)架構(gòu)之路 ? 作者:Linux開(kāi)發(fā)架構(gòu)之路 ? 2023-11-13 11:16 ? 次閱讀

Linux內(nèi)存空間簡(jiǎn)介

32位Linux平臺(tái)下進(jìn)程虛擬地址空間分布如下圖:

圖片

進(jìn)程虛擬地址空間分布

圖中,0xC0000000開(kāi)始的最高1G空間是內(nèi)核地址空間,剩下3G空間是用戶態(tài)空間。用戶態(tài)空間從上到下依次為stack棧(向下增長(zhǎng))、mmap(匿名文件映射區(qū))、Heap堆(向上增長(zhǎng))、bss數(shù)據(jù)段、數(shù)據(jù)段、只讀代碼段。

其中,Heap區(qū)是程序的動(dòng)態(tài)內(nèi)存區(qū),同時(shí)也是C++內(nèi)存泄漏的溫床。malloc、free均發(fā)生在這個(gè)區(qū)域。本文將簡(jiǎn)單介紹下glibc在動(dòng)態(tài)內(nèi)存管理方面的機(jī)制,拋磚引玉,希望能和大家多多交流。

Linux提供了如下幾個(gè)系統(tǒng)調(diào)用,用于內(nèi)存分配:

brk()/sbrk() // 通過(guò)移動(dòng)Heap堆頂指針brk,達(dá)到增加內(nèi)存目的 
mmap()/munmap() // 通過(guò)文件影射的方式,把文件映射到mmap區(qū)

這兩種方式分配的都是虛擬內(nèi)存,沒(méi)有分配物理內(nèi)存。在第一次訪問(wèn)已分配的虛擬地址空間的時(shí)候,發(fā)生缺頁(yè)中斷,操作系統(tǒng)負(fù)責(zé)分配物理內(nèi)存,然后建立虛擬內(nèi)存和物理內(nèi)存之間的映射關(guān)系。

那么,既然brk、mmap提供了內(nèi)存分配的功能,直接使用brk、mmap進(jìn)行內(nèi)存管理不是更簡(jiǎn)單嗎,為什么需要glibc呢?我們知道,系統(tǒng)調(diào)用本身會(huì)產(chǎn)生軟中斷,導(dǎo)致程序從用戶態(tài)陷入內(nèi)核態(tài),比較消耗資源。試想,如果頻繁分配回收小塊內(nèi)存區(qū),那么將有很大的性能耗費(fèi)在系統(tǒng)調(diào)用中。因此,為了減少系統(tǒng)調(diào)用帶來(lái)的性能損耗,glibc采用了內(nèi)存池的設(shè)計(jì),增加了一個(gè)代理層,每次內(nèi)存分配,都優(yōu)先從內(nèi)存池中尋找,如果內(nèi)存池中無(wú)法提供,再向操作系統(tǒng)申請(qǐng)。

一切計(jì)算機(jī)的問(wèn)題都可以通過(guò)加層的方式解決。

glibc的內(nèi)存分配回收策略

glibc中malloc內(nèi)存分配邏輯如下是:

圖片

malloc

  • 分配內(nèi)存 < DEFAULT_MMAP_THRESHOLD,走_(dá)_brk,從內(nèi)存池獲取,失敗的話走brk系統(tǒng)調(diào)用
  • 分配內(nèi)存 > DEFAULT_MMAP_THRESHOLD,走_(dá)_mmap,直接調(diào)用mmap系統(tǒng)調(diào)用

其中,DEFAULT_MMAP_THRESHOLD默認(rèn)為128k,可通過(guò)mallopt進(jìn)行設(shè)置。重點(diǎn)看下小塊內(nèi)存(size > DEFAULT_MMAP_THRESHOLD)的分配,glibc使用的內(nèi)存池如下圖示:

圖片

內(nèi)存池

內(nèi)存池保存在bins這個(gè)長(zhǎng)128的數(shù)組中,每個(gè)元素都是一雙向個(gè)鏈表。其中:

  • bins[0]目前沒(méi)有使用
  • bins[1]的鏈表稱(chēng)為unsorted_list,用于維護(hù)free釋放的chunk。
  • bins[2,63)的區(qū)間稱(chēng)為small_bins,用于維護(hù)<512字節(jié)的內(nèi)存塊,其中每個(gè)元素對(duì)應(yīng)的鏈表中的chunk大小相同,均為index*8。
  • bins[64,127)稱(chēng)為large_bins,用于維護(hù)>512字節(jié)的內(nèi)存塊,每個(gè)元素對(duì)應(yīng)的鏈表中的chunk大小不同,index越大,鏈表中chunk的內(nèi)存大小相差越大,例如: 下標(biāo)為64的chunk大小介于[512, 512+64),下標(biāo)為95的chunk大小介于[2k+1,2k+512)。同一條鏈表上的chunk,按照從小到大的順序排列。

chunk數(shù)據(jù)結(jié)構(gòu)

圖片

chunk結(jié)構(gòu)

glibc在內(nèi)存池中查找合適的chunk時(shí),采用了最佳適應(yīng)的伙伴算法。舉例如下:

1、如果分配內(nèi)存<512字節(jié),則通過(guò)內(nèi)存大小定位到smallbins對(duì)應(yīng)的index上(floor(size/8))

  • 如果smallbins[index]為空,進(jìn)入步驟3
  • 如果smallbins[index]非空,直接返回第一個(gè)chunk

2、如果分配內(nèi)存>512字節(jié),則定位到largebins對(duì)應(yīng)的index上

  • 如果largebins[index]為空,進(jìn)入步驟3
  • 如果largebins[index]非空,掃描鏈表,找到第一個(gè)大小最合適的chunk,如size=12.5K,則使用chunk B,剩下的0.5k放入unsorted_list中

3、遍歷unsorted_list,查找合適size的chunk,如果找到則返回;否則,將這些chunk都?xì)w類(lèi)放到smallbins和largebins里面

4、index++從更大的鏈表中查找,直到找到合適大小的chunk為止,找到后將chunk拆分,并將剩余的加入到unsorted_list中

5、如果還沒(méi)有找到,那么使用top chunk

6、或者,內(nèi)存<128k,使用brk;內(nèi)存>128k,使用mmap獲取新內(nèi)存

top chunk 如下圖示: top chunk是堆頂?shù)腸hunk,堆頂指針brk位于top chunk的頂部。移動(dòng)brk指針,即可擴(kuò)充top chunk的大小。當(dāng)top chunk大小超過(guò)128k(可配置)時(shí),會(huì)觸發(fā)malloc_trim操作,調(diào)用sbrk(-size)將內(nèi)存歸還操作系統(tǒng)。

圖片

chunk分布圖

free釋放內(nèi)存時(shí),有兩種情況:

  1. chunk和top chunk相鄰,則和top chunk合并
  2. chunk和top chunk不相鄰,則直接插入到unsorted_list中

內(nèi)存碎片

以上圖chunk分布圖為例,按照glibc的內(nèi)存分配策略,我們考慮下如下場(chǎng)景(假設(shè)brk其實(shí)地址是512k):

  1. malloc 40k內(nèi)存,即chunkA,brk = 512k + 40k = 552k
  2. malloc 50k內(nèi)存,即chunkB,brk = 552k + 50k = 602k
  3. malloc 60k內(nèi)存,即chunkC,brk = 602k + 60k = 662k
  4. free chunkA。

此時(shí),由于brk = 662k,而釋放的內(nèi)存是位于[512k, 552k]之間,無(wú)法通過(guò)移動(dòng)brk指針,將區(qū)域內(nèi)內(nèi)存交還操作系統(tǒng),因此,在[512k, 552k]的區(qū)域內(nèi)便形成了一個(gè)內(nèi)存空洞 ---- 內(nèi)存碎片。按照glibc的策略,free后的chunkA區(qū)域由于不和top chunk相鄰,因此,無(wú)法和top chunk 合并,應(yīng)該掛在unsorted_list鏈表上。

glibc實(shí)現(xiàn)的一些重要結(jié)構(gòu)

glibc中用于維護(hù)空閑內(nèi)存的結(jié)構(gòu)體是malloc_state,其主要定義如下:

struct malloc_state {
    mutex_t mutex; // 并發(fā)編程下鎖的競(jìng)爭(zhēng)
    mchunkptr        top; // top chunk
    unsigned int     binmap[BINMAPSIZE]; // bitmap,加快bins中chunk判定
    mchunkptr        bins[NBINS * 2 - 2]; // bins,上文所述
    mfastbinptr      fastbinsY[NFASTBINS]; // fastbins,類(lèi)似bins,維護(hù)的chunk更小(80字節(jié)的chunk鏈表)
...
}
static struct malloc_state main_arena; // 主arena

多線程下的競(jìng)爭(zhēng)搶鎖

并發(fā)條件下,main_arena引發(fā)的競(jìng)爭(zhēng)將會(huì)成為限制程序性能的瓶頸所在,因此glibc采用了多arena機(jī)制,線程A分配內(nèi)存時(shí)獲取main_arena鎖成功,將在main_arena所管理的內(nèi)存中分配;此時(shí)線程B獲取main_arena失敗,glibc會(huì)新建一個(gè)arena1,此次內(nèi)存分配從arena1中進(jìn)行。這種策略,一定程度上解決了多線程下競(jìng)爭(zhēng)的問(wèn)題;但是隨著arena的增多,內(nèi)存碎片出現(xiàn)的可能性也變大了。例如,main_arena中有10k、20k的空閑內(nèi)存,線程B要獲取20k的空閑內(nèi)存,但是獲取main_arena鎖失敗,導(dǎo)致留下20k的碎片,降低了內(nèi)存使用率。

普通arena的內(nèi)部結(jié)構(gòu):

圖片

普通arena結(jié)構(gòu)

  1. 一個(gè)arena由多個(gè)Heap構(gòu)成
  2. 每個(gè)Heap通過(guò)mmap獲得,最大為1M,多個(gè)Heap間可能不相鄰
  3. Heap之間有prev指針指向前一個(gè)Heap
  4. 最上面的Heap,也有top chunk

每個(gè)Heap里面也是由chunk組成,使用和main_arena完全相同的管理方式管理空閑chunk。多個(gè)arena之間是通過(guò)鏈表連接的。如下圖:

圖片

arena鏈表

main arena和普通arena的區(qū)別 main_arena是為一個(gè)使用brk指針的arena,由于brk是堆頂指針,一個(gè)進(jìn)程中只可能有一個(gè),因此普通arena無(wú)法使用brk進(jìn)行內(nèi)存分配。普通arena建立在mmap的機(jī)制上,內(nèi)存管理方式和main_arena類(lèi)似,只有一點(diǎn)區(qū)別,普通arena只有在整個(gè)arena都空閑時(shí),才會(huì)調(diào)用munmap把內(nèi)存還給操作系統(tǒng)。

一些特殊情況的分析

根據(jù)上文所述,glibc在調(diào)用malloc_trim時(shí),需要滿足如下2個(gè)條件:

1. size(top chunk) > 128K
2. brk = top chunk- >base + size(top chunk)

假設(shè),brk指針上面的空間已經(jīng)被占用,無(wú)法通過(guò)移動(dòng)brk指針獲得新的地址空間,此時(shí)main_arena就無(wú)法擴(kuò)容了嗎?glibc的設(shè)計(jì)考慮了這樣的特殊情況,此時(shí),glibc會(huì)換用mmap操作來(lái)獲取新空間(每次最少M(fèi)MAP_AS_MORECORE_SIZE<1M>)。這樣,main_arena和普通arena一樣,由非連續(xù)的Heap塊構(gòu)成,不過(guò)這種情況下,glibc并未將這種mmap空間表示為Heap,因此,main_arena多個(gè)塊之間是沒(méi)有聯(lián)系的,這就導(dǎo)致了main_arena從此無(wú)法歸還給操作系統(tǒng),永遠(yuǎn)保留在空閑內(nèi)存中了。如下圖示:

圖片

main_arena無(wú)法回收

顯而易見(jiàn),此時(shí)根本不可能滿足調(diào)用malloc_trim的條件2,即:brk !== top chunk->base + size(top chunk),因?yàn)榇藭r(shí)brk處于堆頂,而top chunk->base > brk.

#include < stdlib.h >
#include < stdio.h >
#include < string.h >
#include < unistd.h >
#include < sys/mman.h >
#include < malloc.h >

#define ARRAY_SIZE 127
char cmd[1024];

void print_info()
{
    struct mallinfo mi = mallinfo();           
    system(cmd);
    printf("theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lun
            tmmap_total=%lu mmap_count=%lun", mi.arena, mi.fordblks, mi.uordblks, mi.hblkhd, mi.hblks);
}

int main(int argc, char** argv)
{
    char** ptr_arr[ARRAY_SIZE];
    int i;
    char*  mmap_var;
    pid_t  pid;
    pid = getpid();
    sprintf(cmd, "ps aux | grep %lu | grep -v grep", pid);
    /* mmap占據(jù)堆頂后1M的地址空間 */
    mmap_var = mmap((void*)sbrk(0) + 1024*1024, 127*1024, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 
    printf("before mallocn");
    print_info();

    /* 分配內(nèi)存,總大小超過(guò)1M,導(dǎo)致main_arena被拆分 */
    for( i = 0; i < ARRAY_SIZE; i++) {
        ptr_arr[i] = malloc(i * 1024);
    }   
    printf("nafter mallocn");
    print_info();
    /* 釋放所有內(nèi)存,觀察內(nèi)存使用是否改變 */
    for( i = 0; i < ARRAY_SIZE; i++) {
        free(ptr_arr[i]);
    }
    printf("nafter freen");
    print_info();
    munmap(mmap_var, 127*1024);
    return 1;
}

圖片

異常運(yùn)行

作為對(duì)比,去除掉brk上面的mmap區(qū)再次運(yùn)行后結(jié)果如下:

圖片

正常運(yùn)行

可以看出,異常情況下(brk無(wú)法擴(kuò)展),free的內(nèi)存沒(méi)有歸還操作系統(tǒng),而是留在了main_arena的unsorted_list了;而正常情況下,由于滿足執(zhí)行malloc_trim的條件,因此,free后,調(diào)用了sbrk(-size)把內(nèi)存歸還了操作系統(tǒng),main_arena內(nèi)存響應(yīng)減少。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 數(shù)據(jù)
    +關(guān)注

    關(guān)注

    8

    文章

    6892

    瀏覽量

    88828
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3002

    瀏覽量

    73886
  • 程序
    +關(guān)注

    關(guān)注

    116

    文章

    3777

    瀏覽量

    80853
  • C++
    C++
    +關(guān)注

    關(guān)注

    22

    文章

    2104

    瀏覽量

    73497
  • Glibc
    +關(guān)注

    關(guān)注

    0

    文章

    9

    瀏覽量

    7493
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    C語(yǔ)言知識(shí)總結(jié):動(dòng)態(tài)內(nèi)存分配

    動(dòng)態(tài)內(nèi)存分配就 是指在程序執(zhí)行的過(guò)程中動(dòng)態(tài)地分配或者回收存儲(chǔ)空間的分配內(nèi)存的方法。動(dòng)態(tài)
    發(fā)表于 10-24 15:52 ?839次閱讀

    C語(yǔ)言既然可以自動(dòng)為變量分配內(nèi)存,為什么還要用動(dòng)態(tài)分配內(nèi)存呢?

    不知道大家在學(xué)習(xí)C語(yǔ)言動(dòng)態(tài)分配內(nèi)存的時(shí)候有沒(méi)有過(guò)這樣的疑問(wèn),既然系統(tǒng)可以自動(dòng)幫我們分配內(nèi)存,為什么還需要我們程序員自己去分配
    發(fā)表于 12-13 11:14 ?1008次閱讀

    glibc malloc內(nèi)存分配器的實(shí)現(xiàn)原理

    內(nèi)存(Heap Memory)是一個(gè)很有意思的領(lǐng)域。你可能和我一樣,也困惑于下述問(wèn)題很久了。
    的頭像 發(fā)表于 01-17 10:03 ?779次閱讀
    <b class='flag-5'>glibc</b> malloc<b class='flag-5'>內(nèi)存</b><b class='flag-5'>分配</b>器的實(shí)現(xiàn)原理

    Linux內(nèi)存系統(tǒng): Linux 內(nèi)存分配算法

    、伙伴系統(tǒng)算法——組織結(jié)構(gòu)1) 概念· 為內(nèi)核提供了一種用于分配一組連續(xù)的頁(yè)而建立的一種高效的分配策略,并有效的解決了外碎片問(wèn)題· 分配內(nèi)存
    發(fā)表于 08-24 07:44

    動(dòng)態(tài)內(nèi)存分配是什么意思

    所謂動(dòng)態(tài)內(nèi)存分配(Dynamic Memory Allocation)就是指在程序執(zhí)行的過(guò)程中動(dòng)態(tài)地分配或者回收存儲(chǔ)空間的分配
    發(fā)表于 12-17 08:17

    淺析cache控制器的分配策略與替換策略

    在cache的相關(guān)操作中,cache控制器需要根據(jù)需求做出許多不同的選擇。例如:分配策略是否需要將數(shù)據(jù)從主存中分配到cache中;替換策略組相聯(lián)cache中,所有的way都已經(jīng)有填充數(shù)
    發(fā)表于 06-15 16:24

    一種基于Buddy算法思想、高可靠性的內(nèi)存管理策略

    內(nèi)存管理是操作系統(tǒng)的中心任務(wù)之一,其主要任務(wù)是組織內(nèi)存以容納內(nèi)核和待執(zhí)行程序,跟蹤當(dāng)前內(nèi)存的使用情況,在需要時(shí)為進(jìn)程分配內(nèi)存,使用完畢后釋放
    發(fā)表于 11-30 16:34 ?1650次閱讀
    一種基于Buddy算法思想、高可靠性的<b class='flag-5'>內(nèi)存</b>管理<b class='flag-5'>策略</b>

    進(jìn)程虛擬內(nèi)存布局以及進(jìn)程的虛擬內(nèi)存分配釋放流程,涉及的代碼

    我們計(jì)劃通過(guò)一系列文章來(lái)介紹虛擬內(nèi)存分配/釋放,缺頁(yè)處理,內(nèi)存壓縮/回收內(nèi)存分配器等知識(shí),梳理
    的頭像 發(fā)表于 06-28 09:38 ?4050次閱讀

    glibc內(nèi)存管理存在的共性問(wèn)題及解決方法

    glibc內(nèi)存分配原理、內(nèi)存站崗問(wèn)題形成原因展開(kāi)討論,并對(duì)glibc緩存大量內(nèi)存(高達(dá)幾十個(gè) G
    的頭像 發(fā)表于 06-18 14:50 ?3169次閱讀

    什么是堆內(nèi)存?堆內(nèi)存是如何分配的?

    在一般的編譯系統(tǒng)中,堆內(nèi)存分配方向和棧內(nèi)存是相反的。當(dāng)棧內(nèi)存從高地址向低地址增長(zhǎng)的時(shí)候,堆內(nèi)存從低地址向高地址
    的頭像 發(fā)表于 07-05 17:58 ?9917次閱讀

    Glibc內(nèi)存管理之Ptmalloc2源代碼分析

    Glibc內(nèi)存管理之Ptmalloc2源代碼分析
    發(fā)表于 07-29 09:20 ?24次下載

    Linux內(nèi)存分配管理與內(nèi)存回收基本框架

    內(nèi)存對(duì)計(jì)算機(jī)系統(tǒng)來(lái)說(shuō)是一項(xiàng)非常重要的資源,直接影響著系統(tǒng)運(yùn)行的性能。最初的時(shí)候,系統(tǒng)是直接運(yùn)行在物理內(nèi)存上的,這存在著很多的問(wèn)題,尤其是安全問(wèn)題。后來(lái)出現(xiàn)了虛擬內(nèi)存,內(nèi)核和進(jìn)程都運(yùn)行在虛擬內(nèi)存
    的頭像 發(fā)表于 06-01 16:02 ?2429次閱讀

    jemalloc分配機(jī)制的介紹及其優(yōu)化實(shí)踐

    C/C++通過(guò)libc做內(nèi)存分配glibc中默認(rèn)的分配機(jī)制是ptmalloc。除此之外,還有眾多的不同側(cè)重的優(yōu)化,例如tcmalloc,jemalloc。
    的頭像 發(fā)表于 05-30 09:12 ?1123次閱讀
    jemalloc<b class='flag-5'>分配</b>機(jī)制的介紹及其優(yōu)化實(shí)踐

    glibc導(dǎo)致的堆外內(nèi)存泄露的排查過(guò)程

    本文記錄一次glibc導(dǎo)致的堆外內(nèi)存泄露的排查過(guò)程。
    的頭像 發(fā)表于 09-01 09:43 ?680次閱讀
    <b class='flag-5'>glibc</b>導(dǎo)致的堆外<b class='flag-5'>內(nèi)存</b>泄露的排查過(guò)程

    轉(zhuǎn)載 golang內(nèi)存分配

    . 線程擁有一定的 cache, 可用于無(wú)鎖分配. 同時(shí) Go 對(duì)于 GC 后回收內(nèi)存頁(yè), 并不是馬上歸還給操作系統(tǒng), 而是會(huì)延遲歸還, 用于滿足未來(lái)的內(nèi)存需求. ?? ? 在 1.
    的頭像 發(fā)表于 09-05 14:12 ?208次閱讀
    轉(zhuǎn)載 golang<b class='flag-5'>內(nèi)存</b><b class='flag-5'>分配</b>