1. 程式人生 > >malloc與free的底層實現

malloc與free的底層實現

1本節引言

本文引用了下面這篇文章,讀完下面,應該讀下上面兩篇文章,其中,《記憶體管理內幕》提供了一個簡單的malloc/free實現版本。看看它的free設計,相信有足夠的吸引力(gnu free版本遠比這複雜)


該篇文章基本把malloc與free的實現機制說清楚了。但是有些陷藏的東西沒說清楚。Malloc實際上有很多版本(DougLea Malloc/BSD Malloc/Hoard Malloc/)

下面這些內容,對原文作了整理。

原文出處:
http://blog.163.com/[email protected]/blog/static/132229655201210975312473/

malloc

的實現版本有很多,下面的內容講述基於GNU malloc的實現(DougLea Malloc的衍生版本)

2 記憶體分配原理

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

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

2.        mmap是在程序的虛擬地址空間中(堆和棧中間,稱為檔案對映區域的地方)找一塊空閒的虛擬記憶體。

這兩種方式分配的都是虛擬記憶體,沒有分配實體記憶體。在第一次訪問已分配的虛擬地址空間的時候,發生缺頁中斷,作業系統負責分配實體記憶體,然後建立虛擬記憶體和實體記憶體之間的對映關係。

3 malloc與free底層C實現

在標準C庫中,提供了malloc/free函式分配釋放記憶體,這兩個函式底層是由brk、mmap、,munmap這些系統呼叫實現的。

開啟glibc內部malloc/freemalloc.c實現,malloc/free的實現都是複雜的。       


4 malloc記憶體分配過程

下面以一個例子來說明記憶體分配的過程,分別是小於128KB的記憶體分配與大小於128K的記憶體分配 (GNUmalloc的版本)

小於128K記憶體分配

malloc小於128k的記憶體,使用brk分配記憶體,將_edata往高地址推(只分配虛擬空間,不對應實體記憶體(因此沒有初始化),第一次讀/寫資料時,引起核心缺頁中斷,核心才分配對應的實體記憶體,然後虛擬地址空間建立對映關係),如下圖(32位系統):


1.        程序啟動的時候,其(虛擬)記憶體空間的初始佈局如圖1所示

其中,mmap記憶體對映檔案是在堆和棧的中間(例如libc-2.2.93.so,其它資料檔案等),為了簡單起見,省略了記憶體對映檔案。

_edata指標(glibc裡面定義)指向資料段的最高地址。 

2.        程序呼叫A=malloc(30K)以後,記憶體空間如圖2

malloc函式會呼叫brk系統呼叫,將_edata指標往高地址推30K,就完成虛擬記憶體分配。

你可能會問:只要把_edata+30K就完成記憶體分配了?

事實是這樣的,_edata+30K只是完成虛擬地址的分配,A這塊記憶體現在還是沒有物理頁與之對應的,等到程序第一次讀寫A這塊記憶體的時候,發生缺頁中斷,這個時候,核心才分配A這塊記憶體對應的物理頁。也就是說,如果用malloc分配了A這塊內容,然後從來不訪問它,那麼,A對應的物理頁是不會被分配的。 

3.        程序呼叫B=malloc(40K)以後,記憶體空間如圖3。

大於128K記憶體分配

malloc大於128k的記憶體,使用mmap分配記憶體,在堆和棧之間找一塊空閒記憶體分配(對應獨立記憶體,而且初始化為0),如下圖:


4.        程序呼叫C=malloc(200K)以後,記憶體空間如圖4:

5.        預設情況下,malloc函式分配記憶體,如果請求記憶體大於128K(可由M_MMAP_THRESHOLD選項調節),那就不是去推_edata指標了,而是利用mmap系統呼叫,從堆和棧的中間分配一塊虛擬記憶體。這樣子做主要是因為:brk分配的記憶體需要等到高地址記憶體釋放以後才能釋放(例如,B釋放之前,A是不可能釋放的,這就是記憶體碎片產生的原因,什麼時候緊縮看下面),而mmap分配的記憶體可以單獨釋放。

當然,還有其它的好處,也有壞處,再具體下去,有興趣的同學可以去看glibc裡面malloc的程式碼了。 

6.        程序呼叫D=malloc(100K)以後,記憶體空間如圖5;

7.        程序呼叫free(C)以後,C對應的虛擬記憶體和實體記憶體一起釋放

8.        程序呼叫free(B)以後,如圖7所示:

B對應的虛擬記憶體和實體記憶體都沒有釋放,因為只有一個_edata指標,如果往回推,那麼D這塊記憶體怎麼辦呢?當然,B這塊記憶體,是可以重用的,如果這個時候再來一個40K的請求,那麼malloc很可能就把B這塊記憶體返回回去了。 

9.        程序呼叫free(D)以後,如圖8所示

B和D連線起來,變成一塊140K的空閒記憶體。

10.    記憶體緊縮操作(trim
預設情況下:當最高地址空間的空閒記憶體超過128K(可由M_TRIM_THRESHOLD選項調節)時,執行記憶體緊縮操作(trim)。在上一個步驟free的時候,發現最高地址空閒記憶體超過128K,於是記憶體緊縮,變成圖9所示。

5 缺頁中斷

用ps -o majflt,minflt -C program命令檢視缺頁中斷的次數。

majflt代表majorfault,中文名叫大錯誤,minflt代表minor fault,中文名叫小錯誤。

這2個數值表示一個程序自啟動以來所發生的缺頁中斷的次數。

發生缺頁中斷後,執行了哪些操作?

當一個程序發生缺頁中斷的時候,程序會陷入核心態,執行以下操作: 

1.        檢查要訪問的虛擬地址是否合法 

2.        查詢/分配一個物理頁 

3.        填充物理頁內容(讀取磁碟,或者直接置0,或者啥也不幹) 

4.        建立對映關係(虛擬地址到實體地址) 

5.        重新執行發生缺頁中斷的那條指令 

如果第3步,需要讀取磁碟,那麼這次缺頁中斷就是majflt,否則就是minflt。 

6 附錄 

DougLea Malloc

相關推薦

mallocfree底層實現

1本節引言 本文引用了下面這篇文章,讀完下面,應該讀下上面兩篇文章,其中,《記憶體管理內幕》提供了一個簡單的malloc/free實現版本。看看它的free設計,相信有足夠的吸引力(gnu free版本遠比這複雜) 該篇文章基本把malloc與free的實現機制說清

【Linux】mallocfree底層的簡單實現!!!

從作業系統角度來看,程序分配記憶體有兩種方式,分別由兩個系統呼叫完成:brk和mmap(當然在這裡是不考慮共享記憶體) brk是將資料段(.data)的最高地址指標_edata往高地址推; mmap是在程序的虛擬地址空間中(堆和棧中間,稱為檔案對映區域的地方

Unix系統編程()mallocfree實現

原因 編程錯誤 alloc 系統編程 OS 內存分配 continued 我們 如何 盡管malloc和free所提供的內存分配接口比之brk和sbrk要容易許多,但在使用時仍然容易犯下各種編程錯誤。 理解malloc和free的實現,將使我們洞悉產生這些錯誤的原因

二維陣列mallocfree

特別適用於C語言動態字串陣列操作 #include <stdio.h> typedef unsigned int wchar; #define LENGTH 10 //需求:字串陣列的二維陣列動態開闢 //name[n][LENGTH]  。。。。。。。。。

mallocfree函式原型

這兩個和記憶體相關的函式都在標頭檔案stdlib.h中,所以在應用的時候,需要包含該檔案。 #include<stdlib.h> C語言中的記憶體分配函式: void* malloc(size_t size); malloc向系統申請分配製定size個位元組的記

04 . Docker安全Docker底層實現

Docker安全 --- `Docker安全性時,主要考慮三個方面` ```shell # 1. 由核心的名字空間和控制組機制提供的容器內在安全![](https://img2020.cnblogs.com/blog/1871335/202006/1871335-20200614153954679-167

Java中常見數據結構:listmap -底層如何實現

增強for 繼續 lin lec -- 什麽 num nsvalue index 1:集合 2 Collection(單列集合) 3 List(有序,可重復) 4 ArrayList 5

內存管理 malloc free實現

結構體 設置 表結構 hub art http turn ubun prev libc 中提供非常好用的 malloc free 功能,如果自己實現一個,應該怎麽做。 要實現 malloc free 需要有 可以分配內存使用的堆,和記錄內存使用情況的鏈表。 如下圖所示,堆

(轉)HashMap底層實現原理/HashMapHashTable區別/HashMapHashSet區別

eem 實現原理 ger 銀行 索引 target 聲明 到你 們的 ①HashMap的工作原理 HashMap基於hashing原理,我們通過put()和get()方法儲存和獲取對象。當我們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算has

基礎 | Java序列化反序列化的底層實現

在深拷貝與淺拷貝中,提到可以採用「序列化與反序列化」的方式來實現深拷貝,今天主要來填一下序列化的坑。 其中,序列化是一種物件持久化的手段,普遍應用於網路傳輸和遠端方法呼叫(RMI)等場景中,建議關注。 什麼是Java序列化和反序列化? 參考答案: 在Java中

Python-資料結構演算法(十一、字典(對映)——基於兩種不同的底層實現

保證一週更兩篇吧,以此來督促自己好好的學習!程式碼的很多地方我都給予了詳細的解釋,幫助理解。好了,幹就完了~加油! 宣告:本python資料結構與演算法是imooc上liuyubobobo老師java資料結構的python改寫,並添加了一些自己的理解和新的東西,liuyubobobo

C++之new、delete mallocfree

在C/C++程式設計中經常會申請記憶體,而對記憶體的申請釋放操作有兩套方法: new、delete 與malloc、free。 1. 區別 (1). new、delete是c++中的操作符,malloc、free是C中的一個函式,它們都可用於申請動態記憶體和釋放記憶體。 (2)

jdk動態代理cglib程式碼實現--SpringAop底層原理

動態代理分為兩類:基於介面的代理和基於繼承的代理 兩類實現的代表是:JDK代理 與 CGlib代理 cglib實現動態代理: 1、定義目標物件: public class RealSubject { //目標物件RealSubject,cglib不

Redis系列-生產應用篇-分散式鎖(5)-單程序Redis分散式鎖的Java實現(Redisson使用底層實現)-原子鎖類

Redisson單程序Redis分散式樂觀鎖的使用與實現 本文基於Redisson 3.7.5 4. 原子鎖類 Redisson中實現了兩種原子鎖類:RAtomicLong和RAtomicDouble,還有RLongAdder和RDoubleAdder RA

MFC(2):底層實現視窗、MFC視窗建立訊息對映、windows字符集和嚮導的使用

=====================從底層去實現一個視窗=============== 程式入口是WinMain函式 視窗的建立步驟: -->  設計(WNDCLASS)  -->  註冊(RegisterClass)  -->

Java中常見資料結構:listmap -底層如何實現

1:集合 2 Collection(單列集合) 3 List(有序,可重複) 4 ArrayList 5 底層資料結構是陣列,查詢快,增刪慢 6 執行緒不安全,效率高

Dubbo架構底層實現

一、Dubbo的設計角色 (1)系統角色 Provider: 暴露服務的服務提供方。 Consumer: 呼叫遠端服務的服務消費方。 Registry: 服務註冊與發現的註冊中心。1 Monitor: 統計服務的呼叫次調和呼叫時間的監控中心。 Container:

呼叫malloc()函式之後,核心發生了什麼?附malloc()和free()實現的原始碼

         特此宣告:本文參照了另外一篇文章和一個帖子,再結合自己的理解總結了malloc()函式的實現機制。     我們經常會在C程式中呼叫malloc()函式動態分配一塊連續的記憶體空間並使用它們。那麼,這些使用者空間發生的事會引發核心空間什麼樣的反應呢? ma

HashMap底層實現原理/HashMapHashTable區別/HashMapHashSet區別

①HashMap的工作原理 HashMap基於hashing原理,我們通過put()和get()方法儲存和獲取物件。當我們將鍵值對傳遞給put()方法時,它呼叫鍵物件的hashCode()方法來計算hashcode,讓後找到bucket位置來儲存值物件。當獲取物件時,通

C語言動態記憶體分配:(一)malloc/free實現malloc實際分配/釋放的記憶體

一、malloc/free概述 malloc是在C語言中用於在程式執行時在堆中進行動態記憶體分配的庫函式。free是進行記憶體釋放的庫函式。 1、函式原型 #include <stdlib.h> void *malloc( size_t size