內(nèi)存管理—— malloc 細(xì)節(jié)

古裝美女

1. 應(yīng)用程序內(nèi)存布局

在 Linux 系統(tǒng)中,應(yīng)用程序的內(nèi)存被分為若干個邏輯段,如圖:


應(yīng)用程序內(nèi)存布局

其中,各個分段的意義是:

  • 代碼段:程序編譯后的可執(zhí)行代碼(指令)存放區(qū)域,在編譯時確定了,同一個程序在不同機(jī)器上、在同一個機(jī)器上的不同次運(yùn)行,同一個方法的入口地址都是確定的
  • 數(shù)據(jù)段:存放程序已初始化的靜態(tài)常量和全局變量
  • BSS 段:存放未初始化的靜態(tài)常量和全局變量
  • 堆:動態(tài)分配的內(nèi)存區(qū)域,從低地址向高地址增長,大小不固定
  • 棧:存放局部變量、函數(shù)調(diào)用上下文等,棧的大小在程序啟動時就固定了,一般是 8MB,系統(tǒng)提供了參數(shù)可以修改
    在上述分段中,文件映射段和堆的內(nèi)存是由程序動態(tài)分配的,通常使用 C 標(biāo)準(zhǔn)庫的 mallocmmap 方法來執(zhí)行

2. malloc 是如何分配內(nèi)存的

2.1 malloc 概述

實(shí)際上,malloc 是 C 標(biāo)準(zhǔn)庫函數(shù),而不是系統(tǒng)調(diào)用,mmapbrk 是系統(tǒng)調(diào)用,malloc 申請內(nèi)存時有兩種方式:

  • 方式一:通過系統(tǒng)調(diào)用 brk 從堆分配內(nèi)存,具體方法是將堆空間的最高地址指針往高地址擴(kuò)展,擴(kuò)充堆區(qū)的大小
  • 方式二:通過系統(tǒng)調(diào)用 mmap 從文件映射區(qū)分配內(nèi)存,具體方法是在文件映射區(qū)中找一塊足夠的空間,進(jìn)行分配
    需要注意的是,此兩種方式分配的都是 虛擬內(nèi)存,并沒有分配物理內(nèi)存,那么什么時候進(jìn)行物理內(nèi)存的分配呢?在第一次訪問虛擬空間時,查找頁表失敗,產(chǎn)生缺頁中斷,會進(jìn)行物理分配的內(nèi)存,并建立虛擬內(nèi)存地址和物理內(nèi)存地址的映射關(guān)系(生成頁表項(xiàng))

C 標(biāo)準(zhǔn)庫中提供 malloc / free 函數(shù)分配和釋放內(nèi)存,這兩個函數(shù)底層由 brk / mmap /unmap 等系統(tǒng)調(diào)用實(shí)現(xiàn)。

分別什么情況用 brkmmap 呢?
malloc 源碼中定義了一個閾值 M_MMAP_THRESHOLD,默認(rèn)為 128K

  • 當(dāng)分配的值小于該閾值時,調(diào)用 brk
  • 否則,調(diào)用 mmap

2.2 一個例子

2.2.1 首先看看 brk 內(nèi)存分配

圖1-圖3
  • 程序啟動后,虛擬內(nèi)存空間初始布局如圖1 所示
  • 程序執(zhí)行 A = malloc(30K) 后,執(zhí)行系統(tǒng)調(diào)用 brk,將堆頂指針王高地址增加 30K,得到圖2所示的內(nèi)存布局。注意,此時只是完成了虛擬內(nèi)存的分配,對應(yīng)的物理內(nèi)存還沒分配,頁表項(xiàng)也沒創(chuàng)建,等到程序第一次讀取 A 這塊內(nèi)存時,發(fā)生缺頁中斷,內(nèi)核才會分配物理內(nèi)存并建立對應(yīng)頁表項(xiàng)
  • 程序執(zhí)行 B = malloc(40K) 后,同樣的堆頂指針往高地址增加,如圖3 所示

2.2.2 當(dāng) mmap 分配較大內(nèi)存

圖3-圖6
  • 程序執(zhí)行 C = malloc(200K),待分配的值超過了閾值,使用 mmap 分配,在堆和棧中間找一塊空閑內(nèi)存,并初始化為0,如圖4所示。這樣做的一個重要原因是,使用brk移動堆頂?shù)刂返姆绞椒峙鋬?nèi)存,只有高地址的內(nèi)存被釋放后,才能釋放低地址的內(nèi)存,對于內(nèi)存釋放的順序有依賴,如圖4中,必須先釋放 B ,才能釋放 A,對于大塊內(nèi)存分配如果使用此種方式,將造成很多大塊內(nèi)存無法按需釋放,而 mmap 分配的內(nèi)存無依賴,可以單獨(dú)釋放

  • 程序執(zhí)行 D=malloc(100k) 后,內(nèi)存空間如圖5所示

  • 程序調(diào)用 free(C) 釋放內(nèi)存,將 C 對應(yīng)的 虛擬內(nèi)存和物理內(nèi)存一起釋放,得到圖6所示

2.2.3 內(nèi)存的釋放

圖7-圖9
  • 調(diào)用 free(B) 后的內(nèi)存布局如圖7,B 對應(yīng)的虛擬內(nèi)存和物理內(nèi)存都沒有釋放,因?yàn)橹挥幸粋€棧頂指針,由于 D 內(nèi)存的存在,無法回推。當(dāng)然,B 部分的內(nèi)存是可重用的,此時如果來一個 40K 的分配請求,很可能就把 B 給分配返回了。
  • 程序執(zhí)行 free(D) 后,內(nèi)存布局如圖 8 所示,B和D構(gòu)成了一塊 140K 的空閑內(nèi)存
  • 系統(tǒng)默認(rèn)當(dāng)高地址空間的空閑內(nèi)存超過 128K(由 M_TRIM_THRESHOLD 選項(xiàng)調(diào)節(jié))時,會自動執(zhí)行內(nèi)存緊縮操作 (trim),在上一步 free(D)完成后,進(jìn)行內(nèi)存緊縮,內(nèi)存布局變成圖9所示,棧頂?shù)刂方档停尫艑?yīng)的虛擬內(nèi)存和物理內(nèi)存

3. 總結(jié)

malloc 細(xì)節(jié)包括以下幾點(diǎn):

  • 當(dāng)分配請求超過閾值時,使用 mmap 進(jìn)行分配
  • 分配請求小于閾值時,使用 brk 分配
  • 分配時并沒有立即分配物理地址,只是分配了虛擬地址,第一次訪問時才建立頁表項(xiàng),分配物理地址
  • 釋放 mmap 分配的地址時,可以立即釋放
  • 釋放 brk 分配的內(nèi)存時,不會立即釋放,但可以重用,執(zhí)行釋放時會檢查堆頂指針附近的最大空閑塊,如果超過閾值,則會執(zhí)行內(nèi)存緊縮策略,真正釋放物理地址
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容