linux heap堆分配

來源:互聯網
上載者:User

標籤:

heap堆分配在使用者層面:malloc函數用於heap記憶體配置

void* malloc(size_t size);            

進程的虛擬記憶體地址布局:

 

對使用者來說,主要關注的空間是User Space。將User Space放大後,可以看到裡面主要分為如下幾段:

  • Code:這是整個使用者空間的最低地址部分,存放的是指令(也就是程式所編譯成的可執行機器碼)
  • Data:這裡存放的是初始化過的全域變數
  • BSS:這裡存放的是未初始化的全域變數
  • Heap:堆,這是我們本文重點關注的地方,堆自低地址向高地址增長,後面要講到的brk相關的系統調用就是從這裡分配記憶體
  • Mapping Area:這裡是與mmap系統調用相關的地區。大多數實際的malloc實現會考慮通過mmap分配較大塊的記憶體地區,本文不討論這種情況。這個地區自高地址向低地址增長
  • Stack:這是棧地區,自高地址向低地址增長

 

heap記憶體從低地址向高地址生長:malloc函數主要是用於虛擬記憶體線性地址的分配

另外需要注意的是,由於Linux是按頁進行記憶體映射的,所以如果break被設定為沒有按頁大小對齊,則系統實際上會在最後映射一個完整的頁,從而實際已映射的記憶體空間比break指向的地方要大一些。但是使用break之後的地址是很危險的(儘管也許break之後確實有一小塊可用記憶體地址)

進程所面對的虛擬記憶體地址空間,只有按頁映射到實體記憶體地址,才能真正使用。受實體儲存體容量限制,整個堆虛擬記憶體空間不可能全部映射到實際的實體記憶體。Linux維護一個break指標,這個指標指向堆空間的某個地址(線性地址空間)。從堆起始地址到break之間的地址空間為映射好的,可以供進程訪問;而從break往上,是未映射的地址空間,如果訪問這段空間則程式會報錯,即是經典的segmentation fault。

 

從作業系統角度來看,進程分配記憶體有兩種方式,分別由兩個系統調用完成:brk()和mmap()(不考慮共用記憶體)。

1、brk是將資料區段(.data)的最高地址指標_edata往高地址推;

2、mmap是在進程的虛擬位址空間中(堆和棧中間,稱為檔案對應地區的地方)找一塊閒置虛擬記憶體。

  這兩種方式分配的都是虛擬記憶體,沒有分配實體記憶體(不準確,系統調用會執行核心功能,分配記憶體),在第一次訪問已指派的虛擬位址空間的時候,發生缺頁中斷,作業系統負責分配實體記憶體,然後建立虛擬記憶體和實體記憶體之間的映射關係。

 

這兩種進程分配記憶體方式的區別:

1、對於大塊記憶體申請,glibc直接使用mmap系統調用為其劃分出另一塊虛擬位址,供進程單獨使用;在該塊記憶體釋放時,使用unmmap系統調用將這塊記憶體釋放(虛擬和實體記憶體都釋放),這個過程中間不會產生記憶體碎塊等問題。

2、針對小塊記憶體的申請,在程式啟動之後,進程會獲得一個heap底端的地址,進程每次進行記憶體申請時,glibc會將堆頂向上增長來擴充記憶體空間,也就是我們所說的堆地址向上增長。在對這些小塊記憶體進行操作時,便會產生記憶體碎塊的問題。實際上brk和sbrk系統調用,就是調整heap頂地址指標(break指標)。

(注意這裡所說的記憶體片段還是根據實體記憶體所說的)

由brk分配的heap堆記憶體是什麼時候釋放呢?

當glibc發現堆頂有連續的128k的空間是閒置時候,它就會通過brk或sbrk系統調用,來調整heap頂的位置,將佔用的記憶體返回給系統。這時,核心會通過刪除相應的線性區,來釋放佔用的實體記憶體。

下面我要講一個記憶體空洞的問題:

一個情境,堆頂有一塊正在使用的記憶體,而下面有很大的連續記憶體已經被釋放掉了,那麼這塊記憶體是否能夠被釋放?其對應的實體記憶體是否能夠被釋放?

很遺憾,不能。

這也就是說,只要堆頂的部分申請記憶體還在佔用,我在下面釋放的記憶體再多,都不會被返回到系統中,仍然佔用著實體記憶體。為什麼會這樣呢?

根源:這主要是與核心在處理堆的時候,過於簡單,它只能通過調整堆頂指標的方式來調整調整程式佔用的線性區;而又只能通過調整線性區的方式,來釋放記憶體。所以只要堆頂不減小,佔用的記憶體就不會釋放。

 

A和D之間的B已經通過free(B),但是此時C的實體記憶體和線性記憶體都沒有被釋放,只是被標記為已經釋放的空間,但是break指標沒有移動,edata==break?沒有回溯。在大多數malloc實現中,free函數釋放的記憶體並不直接歸還作業系統(也就是釋放實體記憶體),而是掛接到freelist數組中。  B對應的虛擬記憶體和實體記憶體都沒有釋放,因為只有一個_edata指標,如果往回推,那麼D這塊記憶體怎麼辦呢

當然,B這塊記憶體,是可以重用的,如果這個時候再來一個40K的請求(與之前B的大小相同),那麼malloc很可能就把B這塊記憶體返回回去了。 

所以如果下次有新的虛擬記憶體地址分配:首先會查看freelist數組中有沒有用過的但是被free的合適空間,如果有,就返還這個線性地址空間。如果沒有就從break指標位置開始分配

 

綜上:虛擬線性地址空間也有可能產生片段(這裡所說的片段就是由於free的記憶體的虛擬空間沒有釋放,導致下次分配虛擬空間時候,不能被使用),線性空間和實體記憶體是一起釋放的

記憶體片段和記憶體空洞都是一個意思

問題:既然堆內記憶體brk和sbrk不能直接釋放,為什麼不全部使用 mmap 來分配,munmap直接釋放呢? 
        既 然堆內片段不能直接釋放,導致疑似“記憶體泄露”問題,為什麼 malloc 不全部使用 mmap 來實現呢(mmap分配的記憶體可以會通過 munmap 進行 free ,實現真正釋放)?而是僅僅對於大於 128k 的大塊記憶體才使用 mmap ? 

其實,進程向 OS 申請和釋放地址空間的介面 sbrk/mmap/munmap 都是系統調用,頻繁調用系統調用都比較消耗系統資源的。並且, mmap 申請的記憶體被 munmap 後,重新申請會產生更多的缺頁中斷。例如使用 mmap 分配 1M 空間,第一次調用產生了大量缺頁中斷 (1M/4K 次 ) ,當munmap 後再次分配 1M 空間,會再次產生大量缺頁中斷。缺頁中斷是核心行為,會導致核心態CPU消耗較大。另外,如果使用 mmap 分配小記憶體,會導致地址空間的分區更多,核心的管理負擔更大。同時堆是一個連續空間,並且堆內片段由於沒有歸還 OS ,如果可重用片段,再次訪問該記憶體很可能不需產生任何系統調用和缺頁中斷,這將大大降低 CPU 的消耗。 因此, glibc 的 malloc 實現中,充分考慮了 sbrk 和 mmap 行為上的差異及優缺點,預設分配大塊記憶體 (128k) 才使用 mmap 獲得地址空間,也可通過 mallopt(M_MMAP_THRESHOLD, <SIZE>) 來修改這個臨界值。

 

linux heap堆分配

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.