malloc 底層實現及原理
摘要:偶爾看到面試題會問到 malloc 的底層原理,今天就來記錄一下,畢竟學習要“知其所以然”,這樣才會胸有成竹。
註:下面分析均是基於 linux 環境下的 malloc 實現。步驟是:先總結結論,再逐步展開
結論
1)當開辟的空間小於 128K 時,調用 brk()函數,malloc 的底層實現是系統調用函數 brk(),其主要移動指針 _enddata(此時的 _enddata 指的是 Linux 地址空間中堆段的末尾地址,不是數據段的末尾地址)
2)當開辟的空間大於 128K 時,mmap()系統調用函數來在虛擬地址空間中(堆和棧中間,稱為“文件映射區域”的地方)找一塊空間來開辟。
具體內容
當一個進程發生缺頁中斷的時候,進程會陷入核心態,執行以下操作:
1)檢查要訪問的虛擬地址是否合法
2)查找/分配一個物理頁
3)填充物理頁內容(讀取磁盤,或者直接置0,或者什麽都不做)
4)建立映射關系(虛擬地址到物理地址的映射關系)
5)重復執行發生缺頁中斷的那條指令
如果第3布,需要讀取磁盤,那麽這次缺頁就是 majfit(major fault:大錯誤),否則就是 minflt(minor fault:小錯誤)
內存分配的原理
從操作系統角度看,進程分配內存有兩種方式,分別由兩個系統調用完成:brk 和 mmap (不考慮共享內存)
1)brk 是將數據段(.data)的最高地址指針 _edata 往高地址推
2)mmap 是在進程的虛擬地址空間中(堆和棧中間,稱為“文件映射區域”的地方)找一塊空閑的虛擬內存。
這兩種方式分配的都是虛擬內存,沒有分配物理內存。在第一次訪問已分配的虛擬地址空間的時候,發生缺頁中斷,操作系統負責分配物理內存,然後建立虛擬內存和物理內存之間的映射關系。
具體分配過程
情況一:malloc 小於 128K 的內存,使用 brk 分配
將_edata往高地址推(只分配虛擬空間,不對應物理內存(因此沒有初始化),第一次讀/寫數據時,引起內核缺頁中斷,內核才分配對應的物理內存,然後虛擬地址空間建立映射關系),如下圖:
1,進程啟動的時候,其(虛擬)內存空間的初始布局如圖1所示
2,進程調用A=malloc(30K)以後,內存空間如圖2:
malloc函數會調用brk系統調用,將_edata指針往高地址推30K,就完成虛擬內存分配
你可能會問:難道這樣就完成內存分配了?
事實是:_edata+30K只是完成虛擬地址的分配,A這塊內存現在還是沒有物理頁與之對應的,等到進程第一次讀寫A這塊內存的時候,發生缺頁中斷,這個時候,內核才分配A這塊內存對應的物理頁。也就是說,如果用malloc分配了A這塊內容,然後從來不訪問它,那麽,A對應的物理頁是不會被分配的。
3,進程調用B=malloc(40K)以後,內存空間如圖3
情況二:malloc 大於 128K 的內存,使用 mmap 分配(munmap 釋放)
4,進程調用C=malloc(200K)以後,內存空間如圖4
默認情況下,malloc函數分配內存,如果請求內存大於128K(可由M_MMAP_THRESHOLD選項調節),那就不是去推_edata指針了,而是利用mmap系統調用,從堆和棧的中間分配一塊虛擬內存
這樣子做主要是因為:
brk分配的內存需要等到高地址內存釋放以後才能釋放(例如,在B釋放之前,A是不可能釋放的,因為只有一個_edata 指針,這就是內存碎片產生的原因,什麽時候緊縮看下面),而mmap分配的內存可以單獨釋放。
當然,還有其它的好處,也有壞處,再具體下去,有興趣的同學可以去看glibc裏面malloc的代碼了。
5,進程調用D=malloc(100K)以後,內存空間如圖5
6,進程調用free(C)以後,C對應的虛擬內存和物理內存一起釋放
7,進程調用free(B)以後,如圖7所示
B對應的虛擬內存和物理內存都沒有釋放,因為只有一個_edata指針,如果往回推,那麽D這塊內存怎麽辦呢?當然,B這塊內存,是可以重用的,如果這個時候再來一個40K的請求,那麽malloc很可能就把B這塊內存返回回去了
8,進程調用free(D)以後,如圖8所示
B和D連接起來,變成一塊140K的空閑內存
9,默認情況下:
當最高地址空間的空閑內存超過128K(可由M_TRIM_THRESHOLD選項調節)時,執行內存緊縮操作(trim)。在上一個步驟free的時候,發現最高地址空閑內存超過128K,於是內存緊縮,變成圖9所示
參考博客:https://www.cnblogs.com/dongzhiquan/p/5621906.html
malloc 底層實現及原理