內存管理以內存池概述(轉)

原文連接:http://www.xiaoyaochong.net/wordpress/index.php/2013/08/10/%E5%BC%95%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E4%B9%8B%E5%86%85%E5%AD%98%E6%B1%A0%E6%A6%82%E8%BF%B0/php

在咱們編寫代碼的過程當中,不可避免的要和內存打交道,在申請釋放不太頻繁的狀況下,一般讓系統進行內存管理便可。可是,直接使用系統調用malloc/free、new/delete進行內存分配和釋放,存在必定的弊端:node

一、調用malloc/new,系統根據「最早匹配」、「最優匹配」或其餘算法在內存空閒塊表中查找一塊空閒內存,內存使用效率不高;linux

二、調用free/delete,系統可能須要進行空閒內存塊合併操做,這會帶來額外時間和空間上的開銷;nginx

三、頻繁使用時容易產生大量內存碎片,從而下降程序運行效率和穩定性,在以前ijoin項目中出現過,後來用google開源的tcmalloc替代glibc默認的內存分配方式;算法

四、容易出現內存泄漏現象,形成內存大小持續增長,甚至內存耗盡。數組

對於長期運行的後臺服務系統來講,出於性能和內存碎片等考慮,一般會考慮使用內存池來管理服務的內存分配,而不是簡單使用malloc/free,new/delete來進行動態內存分配。緩存

那麼,內存池是什麼?是在真正使用內存以前,先預先申請分配必定數量的、大小相等/不等的內存塊。當有新的內存需求時,就從申請好的內存塊中分出一部份內存塊,若內存塊不夠時再繼續申請新的內存。這樣作的一個顯著優勢是儘可能避免了內存碎片,內存分配效率獲得提高。在內存池上分配的內存不須要釋放,在內存池銷燬時會釋放從內存池分配出去的內存。數據結構

優勢:
一、加快內存分配速度(快於標準的malloc),內存塊夠用時,僅是大小判斷和指針偏移等簡單操做;
二、小塊內存的有效載荷高(沒有合併內存塊所需的指針),須要的額外信息少;
三、內存池上分配的內存一般不須要再單獨釋放,而是統一回收;
四、除了使用內存分配函數代替malloc,沒有使用上的其餘特殊約定。memcached

缺點:
一、若是內存池的生命週期比較長,可能給系統形成較大的內存壓力。
二、從內存池分配的內存,通常不能顯式釋放,形成某些內存得不到及時回收。wordpress

使用場景:
一、須要頻繁分配小塊內存。
二、內存使用有明確的生命週期。

那麼在咱們平常使用維護的應用系統中,是否也有相似使用內存池的例子呢?

在Ha3的SearcherCache裏面用到了內存池——ChainedFixedSizePool進行內存管理,經過維護一個chunkSize固定的鏈表節點或數組來進行內存的申請和釋放。在isearch/kingSo搜索引擎裏面也有使用內存池機制來進行統一初始化和回收以及快速內存申請——MemPool。這個實現版本相對SearcherCache裏面的ChainedFixedSizePool複雜些,它經過統計必定請求週期次數內,每種chunkSize知足請求週期內內存大小的命中次數,以預測常駐chunk的大小,使用的是一種自適應的調整策略,以使得常駐chunk能儘可能知足一個週期內的內存需求,儘可能避免在請求週期內向Linux內核從新申請內存。

像memcached,nginx都有使用內存池來管理內存分配,其實現的基本思想——Slab Allocator,其實就是一種很通用的內存池實現思路。Linux內核也使用了這種思想和其餘一些思想來構建一個在空間和時間上都具備高效性的內存分配器。

讓咱們先經過下面兩張google來的圖來簡單看下Linux的內存管理之道。

       以上兩張圖將32位Linux內存管理中的地址映射,虛擬地址管理,物理內存管理等主要數據結構和邏輯關係比較好的表達出來了。

以後,讓咱們來簡單瞭解下malloc,kmalloc,vmalloc,mmap內存分配的一些區別:

一、kmalloc和vmalloc用於分配內核空間的內存,malloc用於分配用戶空間的內存;

二、kmalloc保證分配的內存在物理上是連續的,vmalloc分配的是在虛擬地址空間上連續;

三、kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相對較大;

四、內存只有在要被DMA訪問的時候才須要物理上連續;

五、vmalloc比kmalloc要慢,kmalloc分配的物理地址與虛擬地址只有一個PAGE_OFFSET偏移,不須要藉助頁表機制。vmalloc函數使用虛擬地址,每次分配都須要對頁表進行設置,效率低;

六、malloc最終調用do_brk(),在用戶空間的堆棧中申請空間,不過do_brk作「批發」,malloc作「零售」。malloc用來給用戶態程序分配,處理的內存地址是虛擬地址,可以保證虛擬地址是連續的,但不能保證物理地址是連續;

七、mmap()將一個已經打開的文件的內容映射到Task的用戶空間,使得可以像訪問內存同樣訪問文件;

八、當須要分配一個不具備專用Slab隊列的數據結構而沒必要爲之使用整個頁面時,應該經過kmalloc分配,這些通常是較小而又不經常使用的數據結構;

九、若是數據結構大小接近一個頁面,則應該直接經過alloc_pages進行頁面分配;

十、函數vmalloc從內核空間分配一塊虛存以及相應的物理內存,相似於系統調用brk()。不過brk()是由進程在用戶空間分配的。

十一、由vmalloc分配的空間不會被kswapd換出, kswapd會掃描各個進程的用戶空間,可是看不到經過vmalloc分配到頁表項。

十二、經過kmalloc分配的數據結構,則kswapd先從各個Slab隊列中尋找和收集空閒不用的Slab,並釋放佔用的頁面,可是不會將尚在使用的Slab所佔據的頁面換出。

從上面可知,kmalloc實際上是經過Slab進行內存管理分配的。這幾個接口在系統中的位置以下圖:

下面就讓咱們一塊兒瞭解下Linux Slab機制。

Linux採用了Slab來管理小塊內存的分配與釋放。Slab是由 Jeff Bonwick 爲 SunOS 操做系統首次引入的一種算法。它的提出是基於如下因素考慮的:

一、內核函數常常反覆請求相同的數據類型。好比:建立進程時,會請求一塊內存來存放mm_struct結構;

二、不一樣的結構使用不一樣的分配方法能夠提升效率。一樣,若是進程在撤消的時候,內核不把mm_struct結構釋放掉,而是存放到一個緩衝區裏,之後如有請求mm_struct存儲空間的行爲就能夠直接從緩衝區中取得,而不需從新分配內存;

三、若是夥伴系統頻繁分配,釋放內存會影響系統的效率,所以,能夠把要釋放到的內存放到緩衝區中,直至超過一個閥值時才釋放至夥伴系統,這樣能夠在必定程度上減輕夥伴系統的壓力 ;
四、爲了減小「頁內碎片」的產生,一般能夠把小內存塊按照2的倍數組織在一塊兒,這一點和夥伴系統相似 。

Slab將緩存分爲兩種:一種是專用高速緩存,另一種是普通高速緩存。

一、專用高速緩存中用來存放內核使用的數據結構,例如:mm_struct, inode, vm_area_struct等。

二、普通高速緩存是指存放通常的數據,好比內核爲指針分配的一段內存。普通高速緩存將分配區分爲32*(2^0),32*(2^1),32*(2^2),…,32*(2^12)大小,共13個區域大小。另外,每一個大小均有兩個高速緩存,一個爲DMA高速緩存,一個是常規高速緩存,它們都在cache_sizes表中進行設定,並有對應的名字cache_names。

全部不一樣種類的高速緩存都經過雙向鏈表的方式組織在一塊兒,它的首結點是cache_chain 。cache_chain鏈的每一個元素都是一個 kmem_cache 結構的引用(稱爲一個 cache),它定義了一個要管理的給定大小的對象池。爲了有效地管理 Slab,根據已分配對象的數目,Slab 能夠有 3 種狀態,動態地處於緩衝區相應的隊列中:

一、Full 隊列,此時該 Slab 中沒有空閒對象。

二、Partial 隊列,此時該 Slab 中既有已分配的對象,也有空閒對象。

三、Empty 隊列,此時該 Slab 中全是空閒對象。

NUMA系統中,每一個節點都會擁有這 3 種 Slab 隊列,struct kmem_list3結構用於維護相關隊列。Slab 分配器優先從 Partial 隊列裏的 Slab 中分配對象。當 Slab 的最後一個已分配對象被釋放時,該 Slab 從 Partial 隊列轉移到 Empty 隊列;當 Slab 的最後一個空閒對象被分配時,該 Slab 從Partial 隊列轉移到Full 隊列裏。緩衝區中空閒對象總數不足時,則分配更多的 Slab;可是若是空閒對象比較富餘,Empty 隊列的部分 Slab 將被按期回收。爲了支持多處理器同時分配對象,緩衝區爲每一個處理器維護一個本地緩存array_cache。處理器直接從本地緩存中分配對象,從而避免了鎖的使用;當本地緩存爲空時,從 slab 中批量分配對象到本地緩存。

Slab分配器把每一次請求的內存稱之爲對象。根據對象放置的位置又分爲外置式和內置式Slab,由對象是否超過 1/8 個物理內存頁框的大小決定。外置式Slab頁面不含Slab管理的對象,所有用來存儲Slab對象自己。內置式Slab,Slab管理的對象與Slab對象自己存儲在一塊兒。

     Slab分配器經過着色機制將被管理的對象起始地址與cache line對齊。可是,Slab中的對象大小不肯定,設置着色區的目的就是將Slab中第一個對象的起始地址日後推到與緩衝行對齊的位置。由於一個緩衝區中有多個Slab,所以,應該把每一個緩衝區中的各個Slab着色區的大小盡可能安排成不一樣的大小,這樣可使得在不一樣的Slab中,處於同一相對位置的對象,讓它們在高速緩存中的起始地址相互錯開,這樣就能夠改善高速緩存的存取效率。

與傳統的內存管理模式相比,Slab緩存分配器提供了不少優勢。首先,內核一般依賴於對小對象的分配,它們會在系統生命週期內進行無數次分配。Slab 緩存分配器經過對相似大小的對象進行緩存而提供這種功能,從而避免了常見的碎片問題。Slab 分配器還支持通用對象的初始化,從而避免了爲同一目而對一個對象重複進行初始化。最後,Slab 分配器還能夠支持硬件緩存對齊和着色,這容許不一樣緩存中的對象佔用相同的緩存行,從而提升緩存的利用率並得到更好的性能。在2.6.18內核系統中,能夠經過cat /proc/Slabinfo能夠查看當前系統上的Slab統計信息。

隨着大規模多處理器系統和 NUMA系統的普遍應用,Slab分配器逐漸暴露出自身的嚴重不足:

一、較多複雜的隊列管理。在 Slab 分配器中存在衆多的隊列,例如針對處理器的本地對象緩存隊列,Slab 中空閒對象隊列,每一個 Slab 處於一個特定狀態的隊列中,甚至緩衝區控制結構也處於一個隊列之中。有效地管理這些不一樣的隊列是一件費力且複雜的工做。

二、Slab 管理數據和隊列的存儲開銷比較大。每一個 Slab 須要一個 struct Slab 數據結構和一個管理全部空閒對象的 kmem_bufctl_t(4 字節的無符號整數)的數組。當對象體積較少時,kmem_bufctl_t 數組將形成較大的開銷(好比對象大小爲32字節時,將浪費 1/8 的空間)。爲了使得對象在硬件高速緩存中對齊和使用着色策略,還必須浪費額外的內存。同時,緩衝區針對節點和處理器的隊列也會浪費很多內存。測試代表在一個 1000 節點/處理器的大規模 NUMA 系統中,數 GB 內存被用來維護隊列和對象的引用。

三、緩衝區內存回收比較複雜。

四、對 NUMA 的支持很是複雜。Slab 對 NUMA 的支持基於物理頁框分配器,沒法細粒度地使用對象,所以不能保證處理器級緩存的對象來自同一節點。

五、冗餘的 Partial 隊列。Slab 分配器針對每一個節點都有一個 Partial 隊列,隨着時間流逝,將有大量的 Partial Slab 產生,不利於內存的合理使用。

六、性能調優比較困難。針對每一個 Slab 能夠調整的參數比較複雜,並且分配處理器本地緩存時,不得不使用自旋鎖。

七、調試功能比較難於使用。

爲了解決以上 Slab 分配器的不足之處,內核開發人員 Christoph Lameter 在 Linux 內核 2.6.22 版本中引入一種新的解決方案:Slub 分配器。Slub 分配器特色是簡化設計理念,同時保留 Slab 分配器的基本思想:每一個緩衝區由多個小的 Slab 組成,每一個 Slab 包含固定數目的對象。Slub 分配器簡化了kmem_cache,Slab 等相關的管理數據結構,摒棄了Slab 分配器中衆多的隊列概念,並針對多處理器、NUMA 系統進行優化,從而提升了性能和可擴展性並下降了內存的浪費。爲了保證內核其它模塊可以無縫遷移到 Slub 分配器,Slub 還保留了原有 Slab 分配器全部的接口 API 函數。

以上咱們由簡單到複雜的瞭解了內存池管理的基本思想,Linux的內存管理也是隨着硬件的升級換代在不斷髮展變化之中,引入更優的管理策略,剪裁過於複雜的邏輯和數據結構以及過多的參數,面向相對統一的管理對象,調優調試更加友好(也體現了簡單便是美),以充分挖掘和發揮新硬件的性能。

附:

linux內核分析———SLAB原理及實現 

Linux內存管理Slab分配器

Linux Slab 分配器剖析

Linux內存管理之Slab機制(系列) 

Linux SLUB 分配器詳解

linux slub分配器淺析

Linux Slub分配器

linux 內核 內存管理 slub算法(系列) 

相關文章
相關標籤/搜索