Linux中動態內存的分配與回收(heap, buddy system, stab)【轉】


轉自:https://blog.csdn.net/yyf_it/article/details/52203620

1.malloc 和free的原理(http://m.blog.csdn.net/article/details?id=39496057)

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

1、brk是將數據段(.data)的最高地址指針_edata往高地址推;

2、mmap是在進程的虛擬地址空間中(堆和棧中間,稱為文件映射區域的地方)找一塊空閑的虛擬內存。

這兩種方式分配的都是虛擬內存,沒有分配物理內存。在第一次訪問已分配的虛擬地址空間的時候,發生缺頁中斷,操作系統負責分配物理內存,然后建立虛擬內存和物理內存之間的映射關系。

在標准C庫中,提供了malloc/free函數分配釋放內存,這兩個函數底層是由brk,mmap,munmap這些系統調用實現的

情況一、malloc小於128k的內存,使用brk分配內存,將_edata往高地址推(只分配虛擬空間,不對應物理內存(因此沒有初始化),第一次讀/寫數據時,引起內核缺頁中斷,內核才分配對應的物理內存,然后虛擬地址空間建立映射關系)

情況二、malloc大於128k的內存,使用mmap分配內存,在堆和棧之間找一塊空閑內存分配(對應獨立內存,而且初始化為0)


這樣子做主要是因為::
brk分配的內存需要等到高地址內存釋放以后才能釋放(例如,在B釋放之前,A是不可能釋放的,這就是內存碎片產生的原因,什么時候緊縮看下面),而mmap分配的內存可以單獨釋放。
B對應的虛擬內存和物理內存都沒有釋放,因為只有一個_edata指針,如果往回推,那么D這塊內存怎么辦呢?
當然,B這塊內存,是可以重用的,如果這個時候再來一個40K的請求,那么malloc很可能就把B這塊內存返回回去了。
當最高地址空間的空閑內存超過128K(可由M_TRIM_THRESHOLD選項調節)時,執行內存緊縮操作(trim)

 

內存池


C/C++中直接使用系統調用malloc/free、new/delete進行內存分配和釋放,有以下弊端:

調用malloc/new,系統需要根據“最先匹配”、“最優匹配”或其他算法在內存空閑塊表中查找一塊空閑內存,調用free/delete,系統可能需要合並空閑內存塊,這些會產生額外開銷
頻繁使用時會產生大量內存碎片,從而降低程序運行效率
容易造成內存泄漏
內存池(memory pool)是代替直接調用malloc/free、new/delete進行內存管理的常用方法,當我們申請內存空間時,首先到我們的內存池中查找合適的內存塊,而不是直接向操作系統申請,優勢在於:

比malloc/free進行內存申請/釋放的方式快
不會產生或很少產生堆碎片
可避免內存泄漏
在啟動的時候,一個”內存池”(Memory Pool)分配一塊很大的內存,並將會將這個大塊(block)分成較小的塊(smaller chunks)。每次你從內存池申請內存空間時,它會從先前已經分配的塊(chunks)中得到,而不是從操作系統。
http://blog.csdn.net/shawngucas/article/details/6574863

 

LINUX內核內存管理kmalloc,vmalloc

在設備驅動程序或者內核模塊中動態開辟內存,不是用malloc,而是kmalloc ,vmalloc,釋放內存用的是kfree,vfree,kmalloc函數返回的是虛擬地址(線性地址). kmalloc特殊之處在於它分配的內存是物理上連續的,這對於要進行DMA的設備十分重要. 而用vmalloc分配的內存只是線性地址連續,物理地址不一定連續,不能直接用於DMA。

vmalloc函數的工作方式類似於kmalloc,只不過它分配的內存虛擬地址是連續的,而物理地址則無需連續。通過vmalloc獲得的頁必須一個一個地進行映射,效率不高, 因此,只在不得已(一般是為了獲得大塊內存)時使用。vmalloc函數返回一個指針,指向邏輯上連續的一塊內存區,其大小至少為size。在發生錯誤 時,函數返回NULL。vmalloc可能睡眠,因此,不能從中斷上下文中進行調用,也不能從其它不允許阻塞的情況下調用。要釋放通過vmalloc所獲 得的內存,應使用vfree函數

vmalloc和kmalloc區別大概可總結為:

      1,vmalloc分配的一般為高端內存,只有當內存不夠的時候才分配低端內存;kmallco從低端內存分配。

      2,vmalloc分配的物理地址一般不連續,而kmalloc分配的地址連續,兩者分配的虛擬地址都是連續的;

      3,vmalloc分配的一般為大塊內存,而kmaooc一般分配的為小塊內存,(一般不超過128k);

 

slab分配器是Linux內存管理中非常重要和復雜的一部分,其工作是針對一些經常分配並釋放的對象,如進程描述符等,這些對象的大小一般比較小,如果直接采用伙伴系統來進行分配和釋放,不僅會造成大量的內碎片,而且處理速度也太慢。而slab分配器是基於對象進行管理的,相同類型的對象歸為一類(如進程描述符就是一類),每當要申請這樣一個對象,slab分配器就從一個slab列表中分配一個這樣大小的單元出去,而當要釋放時,將其重新保存在該列表中,而不是直接返回給伙伴系統。slab分配對象時,會使用最近釋放的對象內存塊,因此其駐留在CPU高速緩存的概率較高。

用於描述和管理cache的數據結構是struct kmem_cache

 

還要介紹的一個數據結構就是struct array_cache。struct kmem_cache中定義了一個struct array_cache指針數組,數組的元素個數對應了系統的CPU數,和伙伴系統中的每CPU頁框高速緩存類似,該結構用來描述每個CPU的本地高速緩存,它可以減少SMP系統中對於自旋鎖的競爭。在每個array_cache的末端都用一個指針數組記錄了slab中的空閑對象,分配對象時,采用LIFO方式,也就是將該數組中的最后一個索引對應的對象分配出去,以保證該對象還駐留在高速緩存中的可能性。實際上,每次分配內存都是直接與本地CPU高速緩存進行交互,只有當其空閑內存不足時,才會從kmem_list中的slab中引入一部分對象到本地高速緩存中,而kmem_list中的空閑對象也不足了,那么就要從伙伴系統中引入新的頁來建立新的slab了,這一點也和伙伴系統的每CPU頁框高速緩存很類似。

轉載linux中內存分配

伙伴系統算法

  在實際應用中,經常需要分配一組連續的頁框,而頻繁地申請和釋放不同大小的連續頁框,必然導致在已分配頁框的內存塊中分散了許多小塊的 空閑頁框這樣,即使這些頁框是空閑的,其他需要分配連續頁框的應用也很難得到滿足

  為了避免出現這種情況,Linux內核中引入了伙伴系統算法(buddy system)把所有的空閑頁框分組為11個 塊鏈表,每個塊鏈表分別包含大小為1,2,4,8,16,32,64,128,256,512和1024個連續頁框的頁框塊最大可以申請1024個連 續頁框,對應4MB大小的連續內存每個頁框塊的第一個頁框的物理地址是該塊大小的整數倍

  假設要申請一個256個頁框的塊,先從256個頁框的鏈表中查找空閑塊,如果沒有,就去512個 頁框的鏈表中找,找到了則將頁框塊分為2個256個 頁框的塊,一個分配給應用,另外一個移到256個頁框的鏈表中如果512個頁框的鏈表中仍沒有空閑塊,繼續向1024個頁 框的鏈表查找,如果仍然沒有,則返回錯誤

  頁框塊在釋放時,會主動將兩個連續的頁框塊合並為一個較大的頁框塊

  1.2.slab分 配器

  slab分配器源於 Solaris 2.4 的 分配算法,工作於物理內存頁框分配器之上,管理特定大小對象的緩存,進行快速而高效的內存分配

  slab分配器為每種使用的內核對象建立單獨的緩沖區Linux 內核已經采用了伙伴系統管理物理內存頁框,因此 slab分配器直接工作於伙伴系 統之上每種緩沖區由多個 slab 組成,每個 slab就是一組連續的物理內存頁框,被划分成了固定數目的對象根據對象大小的不同,缺省情況下一個 slab 最多可以由 1024個頁框構成出於對齊 等其它方面的要求,slab 中分配給對象的內存可能大於用戶要求的對象實際大小,這會造成一定的 內存浪費

  2.常用內存分配函數

  2.1.__get_free_pages

  unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

  __get_free_pages函數是最原始的內存分配方式,直接從伙伴系統中獲取原始頁框,返回值為第一個頁框的起始地址__get_free_pages在實現上只是封裝了alloc_pages函 數,從代碼分析,alloc_pages函數會分配長度為1<<order的 連續頁框塊order參數的最大值由include/Linux/Mmzone.h文 件中的MAX_ORDER宏決定,在默認的2.6.18內 核版本中,該宏定義為10也就是說在理論上__get_free_pages函 數一次最多能申請1<<10 * 4KB也就是4MB的 連續物理內存但是在實際應用中,很可能因為不存在這么大量的連續空閑頁框而導致分配失敗在測試中,order為10時分配成功,order為11則返回錯誤

  2.2.kmem_cache_alloc

  struct kmem_cache *kmem_cache_create(const char *name, size_t size,

  size_t align, unsigned long flags,

  void (*ctor)(void*, struct kmem_cache *, unsigned long),

  void (*dtor)(void*, struct kmem_cache *, unsigned long))

  void *kmem_cache_alloc(struct kmem_cache *c, gfp_t flags)

  kmem_cache_create/ kmem_cache_alloc是基於slab分配器的一種內存分配方式,適用於反復分配釋放同一大小內存塊的場合首先用kmem_cache_create創建一個高速緩存區域,然后用kmem_cache_alloc從 該高速緩存區域中獲取新的內存塊 kmem_cache_alloc一次能分配的最大內存由mm/slab.c文件中的MAX_OBJ_ORDER宏 定義,在默認的2.6.18內核版本中,該宏定義為5, 於是一次最多能申請1<<5 * 4KB也就是128KB的 連續物理內存分析內核源碼發現,kmem_cache_create函數的size參數大於128KB時會調用BUG()測試結果驗證了分析結果,用kmem_cache_create分 配超過128KB的內存時使內核崩潰

  2.3.kmalloc

  void *kmalloc(size_t size, gfp_t flags)

  kmalloc是內核中最常用的一種內存分配方式,它通過調用kmem_cache_alloc函 數來實現kmalloc一次最多能申請的內存大小由include/Linux/Kmalloc_size.h的 內容來決定,在默認的2.6.18內核版本中,kmalloc一 次最多能申請大小為131702B也就是128KB字 節的連續物理內存測試結果表明,如果試圖用kmalloc函數分配大於128KB的內存,編譯不能通過

  2.4.vmalloc

  void *vmalloc(unsigned long size)

  前面幾種內存分配方式都是物理連續的,能保證較低的平均訪問時間但是在某些場合中,對內存區的請求不是很頻繁,較高的內存訪問時間也 可以接受,這是就可以分配一段線性連續,物理不連續的地址,帶來的好處是一次可以分配較大塊的內存圖3-1表 示的是vmalloc分配的內存使用的地址范圍vmalloc對 一次能分配的內存大小沒有明確限制出於性能考慮,應謹慎使用vmalloc函數在測試過程中, 最大能一次分配1GB的空間

  Linux內核部分內存分布

  2.5.dma_alloc_coherent

  void *dma_alloc_coherent(struct device *dev, size_t size,

  ma_addr_t *dma_handle, gfp_t gfp)

  DMA是一種硬件機制,允許外圍設備和主存之間直接傳輸IO數據,而不需要CPU的參與,使用DMA機制能大幅提高與設備通信的 吞吐量DMA操作中,涉及到CPU高速緩 存和對應的內存數據一致性的問題,必須保證兩者的數據一致,在x86_64體系結構中,硬件已經很 好的解決了這個問題, dma_alloc_coherent和__get_free_pages函數實現差別不大,前者實際是調用__alloc_pages函 數來分配內存,因此一次分配內存的大小限制和后者一樣__get_free_pages分配的內 存同樣可以用於DMA操作測試結果證明,dma_alloc_coherent函 數一次能分配的最大內存也為4M

  2.6.ioremap

  void * ioremap (unsigned long offset, unsigned long size)

  ioremap是一種更直接的內存“分配”方式,使用時直接指定物理起始地址和需要分配內存的大小,然后將該段 物理地址映射到內核地址空間ioremap用到的物理地址空間都是事先確定的,和上面的幾種內存 分配方式並不太一樣,並不是分配一段新的物理內存ioremap多用於設備驅動,可以讓CPU直接訪問外部設備的IO空間ioremap能映射的內存由原有的物理內存空間決定,所以沒有進行測試

  2.7.Boot Memory

  如果要分配大量的連續物理內存,上述的分配函數都不能滿足,就只能用比較特殊的方式,在Linux內 核引導階段來預留部分內存

  2.7.1.在內核引導時分配內存

  void* alloc_bootmem(unsigned long size)

  可以在Linux內核引導過程中繞過伙伴系統來分配大塊內存使用方法是在Linux內核引導時,調用mem_init函數之前 用alloc_bootmem函數申請指定大小的內存如果需要在其他地方調用這塊內存,可以將alloc_bootmem返回的內存首地址通過EXPORT_SYMBOL導 出,然后就可以使用這塊內存了這種內存分配方式的缺點是,申請內存的代碼必須在鏈接到內核中的代碼里才能使用,因此必須重新編譯內核,而且內存管理系統 看不到這部分內存,需要用戶自行管理測試結果表明,重新編譯內核后重啟,能夠訪問引導時分配的內存塊

  2.7.2.通過內核引導參數預留頂部內存

  在Linux內核引導時,傳入參數“mem=size”保留頂部的內存區間比如系統有256MB內 存,參數“mem=248M”會預留頂部的8MB內存,進入系統后可以調用ioremap(0xF800000,0x800000)來申請這段內存

  3.幾種分配函數的比較

 


分配原理

最大內存

其他

__get_free_pages

直接對頁框進行操作

4MB

適用於分配較大量的連續物理內存

kmem_cache_alloc

基於slab機制實現

128KB

適合需要頻繁申請釋放相同大小內存塊時使用

kmalloc

基於kmem_cache_alloc實現

128KB

最常見的分配方式,需要小於頁框大小的內存時可以使用

vmalloc

建立非連續物理內存到虛擬地址的映射

 

物理不連續,適合需要大內存,但是對地址連續性沒有要求的場合

dma_alloc_coherent

基於__alloc_pages實現

4MB

適用於DMA操 作

ioremap

實現已知物理地址到虛擬地址的映射

 

適用於物理地址已知的場合,如設備驅動

alloc_bootmem

在啟動kernel時,預留一段內存,內核看不見

 

小於物理內存大小,內存管理要求較高

 


1.kmalloc
kmalloc內存分配和malloc相似,除非被阻塞否則他執行的速度非常快,而且不對獲得空間清零.
說明:在用kmalloc申請函數后,要清零用memset()函數對申請的內存進行清零。

2. kamlloc函數原型:
#include
Void *kmalloc(size_t size, int flags);
(1)第一個參數是要分配的塊的大小
(2)第二個參數是分配標志(flags),他提供了多種kmalloc的行為。
(3)第三個最常用的GFP_KERNEL;

A.表示內存分配(最終總是調用get_free_pages來實現實際的分配;這就是GFP前綴的由來)是代表運行在內核空間的進程執行的。使用GFP_KERNEL容許kmalloc在分配空閑內存時候如果內存不足容許把當前進程睡眠以等待。因此這時分配函數必須是可重入的。如果在進程上下文之外如:中斷處理程序、tasklet以及內核定時器中這種情況下current進程不該睡眠,驅動程序該使用GFP_ATOMIC.
B.GFP_ATOMIC
用來從中斷處理和進程上下文之外的其他代碼中分配內存. 從不睡眠.
C.GFP_KERNEL
內核內存的正常分配. 可能睡眠.
D.GFP_USER
用來為用戶空間頁來分配內存; 它可能睡眠.
E.GFP_HIGHUSER
如同 GFP_USER, 但是從高端內存分配, 如果有. 高端內存在下一個子節描述.
F.GFP_NOFS,GFP_NOIO
這個標志功能如同 GFP_KERNEL, 但是它們增加限制到內核能做的來滿足請求. 一個 GFP_NOFS 分配不允許進行任何文件系統調用, 而 GFP_NOIO 根本不允許任何 I/O 初始化. 它們主要地用在文件系統和虛擬內存代碼, 那里允許一個分配睡眠, 但是遞歸的文件系統調用會是一個壞注意.
上面列出的這些分配標志可以是下列標志的相或來作為參數, 這些標志改變這些分配如何進行:
__GFP_DMA
這個標志要求分配在能夠 DMA 的內存區. 確切的含義是平台依賴的並且在下面章節來解釋.
__GFP_HIGHMEM
這個標志指示分配的內存可以位於高端內存.
__GFP_COLD
正常地, 內存分配器盡力返回\"緩沖熱\"的頁 -- 可能在處理器緩沖中找到的頁. 相反, 這個標志請求一個\"冷\"頁, 它在一段時間沒被使用. 它對分配頁作 DMA 讀是有用的, 此時在處理器緩沖中出現是無用的. 一個完整的對如何分配 DMA 緩存的討論看\"直接內存存取\"一節在第 1 章.
__GFP_NOWARN
這個很少用到的標志阻止內核來發出警告(使用 printk ), 當一個分配無法滿足.
__GFP_HIGH
這個標志標識了一個高優先級請求, 它被允許來消耗甚至被內核保留給緊急狀況的最后的內存頁.
__GFP_REPEAT
__GFP_NOFAIL
__GFP_NORETRY
這些標志修改分配器如何動作, 當它有困難滿足一個分配. __GFP_REPEAT 意思是\" 更盡力些嘗試\" 通過重復嘗試 -- 但是分配可能仍然失敗. __GFP_NOFAIL 標志告訴分配器不要失敗; 它盡最大努力來滿足要求. 使用 __GFP_NOFAIL 是強烈不推薦的; 可能從不會有有效的理由在一個設備驅動中使用它. 最后, __GFP_NORETRY 告知分配器立即放棄如果得不到請求的內存.
Ø 內存區段
__GFP_DMA和__GFP_HIGHMEM的使用與平台相關,Linux把內存分成3個區段:可用於DMA的內存、常規內存、以及高端內存。X86平台上ISA設備DMA區段是內存的前16MB,而PCI設備無此限制。
內存區后面的機制在 mm/page_alloc.c 中實現, 而內存區的初始化在平台特定的文件中, 常常在 arch 目錄樹的 mm/init.c。
3.kamlloc的使用方法:
Linux 處理內存分配通過創建一套固定大小的內存對象池. 分配請求被這樣來處理, 進入一個持有足夠大的對象的池子並且將整個內存塊遞交給請求者. 驅動開發者應當記住的一件事情是, 內核只能分配某些預定義的, 固定大小的字節數組.
如果你請求一個任意數量內存, 你可能得到稍微多於你請求的, 至多是 2 倍數量. 同樣, 程序員應當記住 kmalloc 能夠處理的最小分配是 32 或者 64 字節, 依賴系統的體系所使用的頁大小. kmalloc 能夠分配的內存塊的大小有一個上限. 這個限制隨着體系和內核配置選項而變化. 如果你的代碼是要完全可移植, 它不能指望可以分配任何大於 128 KB. 如果你需要多於幾個 KB, 但是, 有個比 kmalloc 更好的方法來獲得內存。在設備驅動程序或者內核模塊中動態開辟內存,不是用malloc,而是kmalloc ,vmalloc,或者用get_free_pages直接申請頁。釋放內存用的是kfree,vfree,或free_pages. kmalloc函數返回的是虛擬地址(線性地址). kmalloc特殊之處在於它分配的內存是物理上連續的,這對於要進行DMA的設備十分重要. 而用vmalloc分配的內存只是線性地址連續,物理地址不一定連續,不能直接用於DMA.
  注意kmalloc最大只能開辟128k-16,16個字節是被頁描述符結構占用了。
  內存映射的I/O口,寄存器或者是硬件設備的RAM(如顯存)一般占用F0000000以上的地址空間。在驅動程序中不能直接訪問,要通過kernel函數vremap獲得重新映射以后的地址。
  另外,很多硬件需要一塊比較大的連續內存用作DMA傳送。這塊內存需要一直駐留在內存,不能被交換到文件中去。但是kmalloc最多只能開辟大小為32XPAGE_SIZE的內存,一般的PAGE_SIZE=4kB,也就是128kB的大小的內存。
3.kmalloc和vmalloc的區別
• vmalloc()與 kmalloc()都可用於分配內存
• kmalloc()分配的內存處於3GB~high_memory之 間,這段內核空間與物理內存的映射一一對應
•vmalloc()分配的內存在 VMALLOC_START~4GB之間,這段非連續內 存區映射到物理內存也可能是非連續的
• 在內核空間中調用kmalloc()分配連續物理空間,而調用vmalloc()分配非物理連續空間。
• 把kmalloc()所分配內核空間中的地址稱為內核邏輯地址
• 把vmalloc()分配的內核空間中的地址稱 為內核虛擬地址
• vmalloc()在分配過程中須更新內核頁表
總結:
1.kmalloc和vmalloc分配的是內核的內存,malloc分配的是用戶的內存
2.kmalloc保證分配的內存在物理上是連續的, kmalloc()分配的內存在0xBFFFFFFF-0xFFFFFFFF以上的內存中,driver一般是用它來完成對DS的分配,更適合於類似設備驅動的程序來使用;
3.vmalloc保證的是在虛擬地址空間上的連續,vmalloc()則是位於物理地址非連續,虛地址連續區,起始位置由VMALLOL_START來決定,一般作為交換區、模塊的分配。
3.kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相對較大(因為vmalloc還可以處理交換空間)。
4.內存只有在要被DMA訪問的時候才需要物理上連續,vmalloc比kmalloc要慢
5.vmalloc使用的正確場合是分配一大塊,連續的,只在軟件中存在的,用於緩沖的內存區域。不能在微處理器之外使用。
6.vmalloc 中調用了 kmalloc (GFP—KERNEL),因此也不能應用於原子上下文。
7.kmalloc和 kfree管理內核段內分配的內存,這是真實地址已知的實際物理內存塊。
8.vmalloc對應於vfree,分配連續的虛擬內存,但是物理上不一定連續。
9.kmalloc分配內存是基於slab,因此slab的一些特性包括着色,對齊等都具備,性能較好。物理地址和邏輯地址都是連續的
————————————————
版權聲明:本文為CSDN博主「見賢思齊,見不賢而內自省」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/yyf_it/article/details/52203620


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM