1. 程式人生 > >Linux記憶體分配小結--malloc、brk、mmap

Linux記憶體分配小結--malloc、brk、mmap

http://blog.163.com/[email protected]/blog/static/132229655201210975312473/

http://blog.sina.com.cn/s/blog_7c60861501015vkk.html

Linux 的虛擬記憶體管理有幾個關鍵概念: 

1、每個程序都有獨立的虛擬地址空間,程序訪問的虛擬地址並不是真正的實體地址; 
2、虛擬地址可通過每個程序上的頁表(在每個程序的核心虛擬地址空間)與實體地址進行對映,獲得真正實體地址; 
3、如果虛擬地址對應實體地址不在實體記憶體中,則產生缺頁中斷,真正分配實體地址,同時更新程序的頁表;如果此時實體記憶體已耗盡,則根據記憶體替換演算法淘汰部分頁面至物理磁碟中。 

基於以上認識,進行了如下分析:
一、Linux 虛擬地址空間如何分佈?
Linux 使用虛擬地址空間,大大增加了程序的定址空間,由低地址到高地址分別為: 
1、只讀段:該部分空間只能讀,不可寫;(包括:程式碼段、rodata 段(C常量字串和#define定義的常量) )
2、資料段:儲存全域性變數、靜態變數的空間; 
3、堆 :就是平時所說的動態記憶體, malloc/new 大部分都來源於此。其中堆頂的位置可通過函式 brk 和 sbrk 進行動態調整。 
4、檔案對映區域 :動態庫、共享記憶體等對映物理空間的記憶體,一般是 mmap 函式所分配的虛擬地址空間。 
5、棧:用於維護函式呼叫的上下文空間,一般為 8M ,可通過 ulimit –s 檢視。 
6、核心虛擬空間:
使用者程式碼不可見的記憶體區域,由核心管理(頁表就存放在核心虛擬空間)。

下圖是 32 位系統典型的虛擬地址空間分佈(來自《深入理解計算機系統》)。


32 位系統有4G 的地址空間::

其中 0x08048000~0xbfffffff 是使用者空間,0xc0000000~0xffffffff 是核心空間,包括核心程式碼和資料、與程序相關的資料結構(如頁表、核心棧)等。另外,%esp 執行棧頂,往低地址方向變化;brk/sbrk 函式控制堆頂_edata往高地址方向變化


64位系統結果怎樣呢? 64 位系統是否擁有 2^64 的地址空間嗎? 
事實上, 64 位系統的虛擬地址空間劃分發生了改變: 
1、地址空間大小不是2^32,也不是2^64,而一般是2^48。

因為並不需要 2^64 這麼大的定址空間,過大空間只會導致資源的浪費。64位Linux一般使用48位來表示虛擬地址空間,40位表示實體地址,
這可通過 /proc/cpuinfo 來檢視 
address sizes   : 40 bits physical, 48 bits virtual 
2、其中,0x0000000000000000~0x00007fffffffffff 表示使用者空間, 0xFFFF800000000000~ 0xFFFFFFFFFFFFFFFF 表示核心空間,共提供 256TB(2^48) 的定址空間。
這兩個區間的特點是,第 47 位與 48~63 位相同,若這些位為 0 表示使用者空間,否則表示核心空間。 
3、使用者空間由低地址到高地址仍然是只讀段、資料段、堆、檔案對映區域和棧

二、malloc和free是如何分配和釋放記憶體?

如何檢視程序發生缺頁中斷的次數

         用ps -o majflt,minflt -C program命令檢視。

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

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

發成缺頁中斷後,執行了那些操作?

當一個程序發生缺頁中斷的時候,程序會陷入核心態,執行以下操作: 
1、檢查要訪問的虛擬地址是否合法 
2、查詢/分配一個物理頁 
3、填充物理頁內容(讀取磁碟,或者直接置0,或者啥也不幹) 
4、
建立對映關係(虛擬地址到實體地址) 
重新執行發生缺頁中斷的那條指令 
如果第3步,需要讀取磁碟,那麼這次缺頁中斷就是majflt,否則就是minflt。 

記憶體分配的原理

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

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

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

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

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


下面以一個例子來說明記憶體分配的原理:

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


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。

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


4、程序呼叫C=malloc(200K)以後,記憶體空間如圖4:       預設情況下,malloc函式分配記憶體,如果請求記憶體大於128K(可由M_MMAP_THRESHOLD選項調節),那就不是去推_edata指標了,而是利用mmap系統呼叫,從堆和棧的中間分配一塊虛擬記憶體       這樣子做主要是因為:: brk分配的記憶體需要等到高地址記憶體釋放以後才能釋放(例如,在B釋放之前,A是不可能釋放的,這就是記憶體碎片產生的原因,什麼時候緊縮看下面),而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所示。 三、既然堆內記憶體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>) 來修改這個臨界值。

四、如何檢視程序的缺頁中斷資訊? 
可通過以下命令檢視缺頁中斷資訊 
ps -o majflt,minflt -C <program_name> 
ps -o majflt,minflt -p <pid> 
其中:: majflt 代表 major fault ,指大錯誤;

           minflt 代表 minor fault ,指小錯誤。

這兩個數值表示一個程序自啟動以來所發生的缺頁中斷的次數。
其中 majflt 與 minflt 的不同是::

        majflt 表示需要讀寫磁碟,可能是記憶體對應頁面在磁碟中需要load 到實體記憶體中,也可能是此時實體記憶體不足,需要淘汰部分物理頁面至磁碟中。

五、C語言的記憶體分配方式與malloc


C語言跟記憶體分配方式
(1) 從靜態儲存區域分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。例如全域性變數,static變數。
(2) 在棧上建立。在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運
算內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限。
(3)從堆上分配,亦稱動態記憶體分配。程式在執行的時候用malloc或new申請任意多少的記憶體,程式設計師自己負責在何時用free或delete釋放記憶體。動態記憶體的生存期由我們決定,使用非常靈活,但問題也最多
     

      C語言跟記憶體申請相關的函式主要有 alloc,calloc,malloc,free,realloc,sbrk等.其中alloc是向棧申請記憶體,因此無需釋放. malloc分配的記憶體是位於堆中的,並且沒有初始化記憶體的內容,因此基本上malloc之後,呼叫函式memset來初始化這部分的記憶體空間.calloc則將初始化這部分的記憶體,設定為0. 而realloc則對malloc申請的記憶體進行大小的調整.申請的記憶體最終需要通過函式free來釋放. 而sbrk則是增加資料段的大小;
       malloc/calloc/free基本上都是C函式庫實現的,跟OS無關.C函式庫內部通過一定的結構來儲存當前有多少可用記憶體.如果程式 malloc的大小超出了庫裡所留存的空間,那麼將首先呼叫brk系統呼叫來增加可用空間,然後再分配空間.free時,釋放的記憶體並不立即返回給os, 而是保留在內部結構中. 可以打個比方: brk類似於批發,一次性的向OS申請大的記憶體,而malloc等函式則類似於零售,滿足程式執行時的要求.這套機制類似於緩衝.
使用這套機制的原因: 系統呼叫不能支援任意大小的記憶體分配(有的系統呼叫只支援固定大小以及其倍數的記憶體申請,這樣的話,對於小記憶體的分配會造成浪費; 系統呼叫申請記憶體代價昂貴,涉及到使用者態和核心態的轉換.
函式malloc()和calloc()都可以用來分配動態記憶體空間,但兩者稍有區別。

 
    在Linux系統上,程式被載入記憶體時,核心為使用者程序地址空間建立了程式碼段、資料段和堆疊段,在資料段與堆疊段之間的空閒區域用於動態記憶體分配。
      核心資料結構mm_struct中的成員變數start_code和end_code是程序程式碼段的起始和終止地址,start_data和 end_data是程序資料段的起始和終止地址,start_stack是程序堆疊段起始地址,start_brk是程序動態記憶體分配起始地址(堆的起始 地址),還有一個 brk(堆的當前最後地址),就是動態記憶體分配當前的終止地址。
C語言的動態記憶體分配基本函式是malloc(),在Linux上的基本實現是通過核心的brk系統呼叫。brk()是一個非常簡單的系統呼叫,只是簡單地改變mm_struct結構的成員變數brk的值。
      mmap系統呼叫實現了更有用的動態記憶體分配功能,可以將一個磁碟檔案的全部或部分內容對映到使用者空間中,程序讀寫檔案的操作變成了讀寫記憶體的操作。在 linux/mm/mmap.c檔案的do_mmap_pgoff()函式,是mmap系統呼叫實現的核心。do_mmap_pgoff()的程式碼,只是新建了一個vm_area_struct結構,並把file結構的引數賦值給其成員變數m_file,並沒有把檔案內容實際裝入記憶體。
Linux記憶體管理的基本思想之一,是隻有在真正訪問一個地址的時候才建立這個地址的物理對映。

相關推薦

Linux記憶體分配小結--mallocbrkmmap

http://blog.163.com/[email protected]/blog/static/132229655201210975312473/ http://blog.sina.com.cn/s/blog_7c60861501015vkk.html Linux 的虛擬記憶體管理有幾個關鍵

動態記憶體分配mallocfreecallocrealloc)

1. void*   malloc(size_t size);     //size_t   size表示的是記憶體的總大小 a、malloc所分配的是一塊連續的記憶體,以位元組為單位,並且不帶任何資訊 b、malloc實際分配的記憶體可能比請求的稍微多一點(什麼原因呢?思

C語言記憶體分配malloccallocrealloc

大家都知道程式在記憶體中的佈局分為:棧區、堆、靜態變數區、常量區等幾個部分。其中堆上的空間分配是由程式設計師自己來管理的,包括空間的申請和釋放。 我們這裡就說一下C標準庫中提供的幾個在堆上操作空間的函式: 1、void*malloc(unsigned size); 2

動態記憶體分配mallocrealloccalloc函式)

一、關於動態記憶體分配的定義(用時分配,不用釋放)所謂動態記憶體分配,就是指在程式執行的過程中動態地分配或者回收儲存空間的分配記憶體的方法。動態記憶體分配不象陣列等靜態記憶體分配方法那樣需要預先分配儲存

記憶體分配方式詳解(堆自由儲存區全域性/靜態儲存區和常量儲存區)

原文地址:https://blog.csdn.net/u013007900/article/details/79338653 參考文章:http://www.cnblogs.com/hanyonglu/archive/2011/04/12/2014212.html 一、資料結構中的棧和堆 雖

Linux_C++記憶體分配方式詳解——堆自由儲存區全域性/靜態儲存區和常量儲存區

棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清除的變數的儲存區。裡面的變數通常是區域性變數、函式引數等。在一個程序中,位於使用者虛擬地址空間頂部的是使用者棧,編譯器用它來實現函式的呼叫。和堆一樣,使用者棧在程式執行期間可以動態地擴充套件和收縮。   堆,就是那些

【C語言】記憶體分配函式malloc/ calloc/ realloc及記憶體釋放free

前言: 記憶體區域劃分與分配: 1、棧區(stack)——程式執行時由編譯器自動分配,存放函式的引數值,區域性變數的值等,程式結束時由編譯器自動釋放。 2、堆區(heap) —— 在記憶體開闢另一塊儲存區域。一般由程式設計師分配釋放, 若程式設計師不釋放,程式結束時可

Linux記憶體管理之malloc實現

程序虛擬地址空間由一個一個VMA來表示,這裡先接收VMA相關操作. 1.1 查詢VMA函式find_vma() struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr) 找到的vma結果需滿足條件:

【C語言】動態記憶體分配小結

為什麼存在動態記憶體分配? 我們已經掌握的記憶體開闢方式有: int val = 20;//在棧空間上開闢四個位元組 char arr[10];//在棧空間上開闢10個位元組的連續空間 但是上面開闢空間的方式有兩個特點: 1.空間開闢的大小是固定的 2.陣列在申明的時

C語言程式設計 學習筆記 動態記憶體分配malloc

如果輸入程式時,先告訴你個數,然後再輸入,要記錄每個資料(類似動態陣列) C99之前應該怎麼做呢? malloc()函式的作用就在此: int *a = (int*)malloc(n*sizeof(int)); malloc()函式的作用是向記憶體申請一個n*

記憶體分配malloc()和free())

C語言的一個特性是接近底層,對於硬體的控制能力比其他高階動態語言要強。同時,C語言賦予程式設計師更大的自由度,更信任程式設計師。在記憶體的分配與釋放上,我們知道非靜態變數(塊作用域,無連結,自動生存期)在程式進入到變數定義所在的地方(塊或函式內)時分配記憶體,在離開塊作用域時釋放。對於靜態變數,在程式載入到記

【C語言】動態記憶體分配malloc,realloc,calloc,free)的基本理解和區別

#include<Windows.h> #include<stdio.h> #include<malloc.h> int main() { int* p = NULL; printf("%x\n", p); p = (int*)malloc(sizeof(int)*

關於記憶體分配malloc()和calloc()的區別

動態分配記憶體空間,較為熟悉的是malloc(),但有時也會用calloc()。兩者有何區別呢? 先寫一下兩者的常規用法示例吧。 void *malloc(size_t size); void *calloc(size_t count,size_t size); 可見

記憶體分配malloc, new, HeapAlloc, VirtualAlloc

VC程式設計精粹需要進行記憶體的動態分配和釋放操作,本文總結常用的VC對記憶體的操作方法並比較他們之間的區別,以便於讀者能夠加深對他們的理解並根據專案的實際情況選用適合自己的方案。 1、GlobalAlloc() The GlobalAlloc function allocates the specifi

C語言中malloccallocrealloc動態記憶體分配的理解

void* realloc(void* ptr, unsigned newsize); void* malloc(unsigned size); void* calloc(size_t numElements, size_t sizeOfElement); 都在stdlib.h函式庫內 它們的返回值都是請求系

linux 高階記憶體頁框管理:永久核心對映臨時核心對映以及非連續記憶體分配

摘要:高階記憶體頁框的核心對映分為三種情況:永久核心對映、臨時核心對映和非連續記憶體對映。那麼這三者有什麼區別和聯絡呢?臨時核心對映如何保證不會被阻塞呢?本文主要為你解答這些疑問,並詳細探討高階記憶體對映的前兩種方式。 1.高階記憶體的區域劃分 核心將高階記憶體

linuxbrkmmapmalloc和new的區別

答:brk是系統呼叫,主要工作是實現虛擬記憶體到記憶體的對映,可以讓程序的堆指標增長一定的大小,邏輯上消耗掉一塊虛擬地址空間,malloc向OS獲取的記憶體大小比較小時,將直接通過brk呼叫獲取虛擬地址。 mmap是系統呼叫,也是實現虛擬記憶體到記憶體的對映,可以讓程序

常見動態記憶體分配malloc()/free()new/delete使用方法及常見錯誤

1.動態記憶體分配的幾種方式 ①從靜態儲存區分配記憶體,記憶體在編譯時已經分配好了,這塊記憶體在整個程式執行期間都存在,比如全域性變數 ②從棧上分配記憶體,函式體結束時,棧記憶體自動銷燬,比如區域性變數 ③從堆上開闢記憶體,比如malloc()/ne

#linux 記憶體磁碟程序網路分析

1 記憶體 [root@localhost ~]# free -m total used free shared buffers cached Mem: 15951 13825

Java虛擬機器自動記憶體管理機制物件建立及記憶體分配

  1、物件是如何建立: 步驟:    (1)、虛擬機器遇到new <類名>的指令---->根據new的引數是否在常量池中定位一個類的符號引用    (2)、檢測該符號引用代表的類是否已經被載入、解析、和初始化。(如果沒有則