做者:Younger Liu,linux
本做品採用知識共享署名-非商業性使用-相同方式共享 3.0 未本地化版本許可協議進行許可。git
原文地址:http://lwn.net/Articles/396657/算法
連續內存分配器(CMA - Contiguous Memory Allocator)是一個框架,容許創建一個平臺無關的配置,用於連續內存的管理。而後,設備所需內存都根據該配置進行分配。緩存
這個框架的主要做用不是分配內存,而是解析和管理內存配置,以及做爲在設備驅動程序和可插拔的分配器之間的中間組件。所以,它是與任何內存分配方法和分配策略沒有依賴關係的。架構
在嵌入式設備中,不少設備都沒有支持scatter-getter和IO map,都須要連續內存塊的操做。如設備:攝像機,硬件視頻解碼器,編碼器等。併發
這些設備每每須要較大的內存緩衝區(如:一個200萬像素的高清幀攝像機,須要超過6M的內存),該kmalloc內存分配機制對於這麼大的內存是沒有效果的。app
一些嵌入式設備對緩衝區有一些額外的要求,好比:在含有多個內存bank的設備中,要求只能在特定的bank中中分配內存;而還有一些要定內存邊界對齊的緩存區。框架
近來,嵌入式設備有了較大的發展(特別是V4L領域),而且這些驅動都有本身的內存分配代碼。它們衆多的大多數都是採用bootmem分配方法。CMA框架企圖採用統一的連續內存分配機制,併爲這些設備驅動提供簡單的API,並且是能夠定製化和模塊化的。ide
CMA主要設計目標是提供一個可定製的模塊化框架,而且是能夠配置的,以適應個別系統的須要。配置指定的內存區域,而後將這些內存分配給制定的設備。這些內存區域能夠共享給多個設備驅動,也能夠專門分配一個。這是經過如下方式實現的:模塊化
1. CMA的核心不是處理內存分配和空閒空間管理。專用分配器是用來處理內存分配和空閒內存管理的。所以,若是現有的解決方案不符合給定的系統,那麼能夠開發一種新的算法,這種算飯能夠很容易地插入到CMA框架中。
所提出的解決方案中包括一個最適算法(best-fit)的一個實現。
2. CMA容許運行時配置即將分配的內存區域。內存區域都是經由命令行給出的,因此能夠很容易地改變它,而不須要從新編譯內核。
每一個地區都有本身的大小,對齊標準,起始地址(物理地址)和對應該內存區域的內存分配算法。
這意味着同一時刻能夠有多中機制在運行,若是在一個平臺上同時運行多個不一樣的設備,這些設備具備不一樣的存儲器使用特性,那麼局能夠匹配最好的算法。
3. 當設備請求內存時,設備必須「自我介紹」,即附帶本身的信息以告知CMA。這樣CMA能夠知道誰分配內存。這容許系統架構師來指定哪一個移動設備應該使用哪些存儲區。
3a. 設備也能夠指定一個「類」內存區域,這使得系統更容易配置,進而一個單一的設備可能使用來自不一樣內存區域的內存。例如,一個視頻解碼器驅動程序可能要分配一些共享的緩衝區,那麼從第一個bank中分配一些,再從第二個bank中分配一些,能夠得到儘量高的內存吞吐量。
虛構一個使用了CMA的系統,來觀察一下其是如何使用和配置的。
有一個攜帶硬件視頻解碼器和攝像機的平臺,每一個設備在最壞的狀況下可能須要20M的內存。在該系統中,這兩個設備是不會同時使用的,而且內存是可能共享的。使用下面的兩個命令行:
cma=r=20M cma_map=video,camera=r
第一個CMA指令是分配20M的內存,而且內存分配器是有效的;第二個表示,名稱爲「video」和「camera」的兩個驅動從以前定義的內存區域中分配內存。
由於二者共享同一內存區域,相比於每一個設備保留20M的內存區域,使得系統節省了20M的內存空進。
可是隨着系統的發展和進化,平臺上可能同時運行視頻解碼器和攝像機,那麼20M的內存區域就不能知足須要了。那麼能夠經過命令快速解決:
cma=v=20M,c=20M cma_map=video=v;camera=c
從該解決方案中也能夠看出CMA是如何爲每個設備分配所需的私有內存池的。
分配機制也能經過一種類似的方式進行替換。在測試中發現,當給定的內存區域大小爲40M時,系統運行一段時間後,碎片會成爲一個問題。所以,爲了知足所須要求的緩存區大小,須要預留一個較大的緩存區。
可是不幸的是,你須要w設置一個新的分配算法——Neat Allocation Algorithm(簡寫na),這兩個設備對於內存有30M的需求:
cma=r=30M:na cma_map=video,camera=r
從上述示例能夠看出,當CMA提供的算法不知足要求時,如何配置本身的分配算法,而不須要修改CMA或重編內核。
如上圖所示,CMA有兩個參數「cma」和「cma_map」;其中「cma」指定要爲CMA保留的內存大小,參數「cma_map」用於指定該區域分配給爲哪個設備使用。
參數「cma」格式以下:
cma ::= "cma=" regions [ ';' ]
regions ::= region [ ';' regions ]
region ::= reg-name
'=' size
[ '@' start ]
[ '/' alignment ]
[ ':' [ alloc-name ] [ '(' alloc-params ')' ] ]
reg-name ::= a sequence of letters and digits
//內存區的名稱
size ::= memsize //內存區的大小
start ::= memsize //指望的內存區的起始地址
alignment ::= memsize //起始地址的對齊倍數
alloc-name ::= a non-empty sequence of letters and digits
// 將要使用的分配器的名稱
alloc-params ::= a sequence of chars other then ')' and ';'
// 分配器的參數
memsize ::= whatever memparse() accepts
參數"cma_map" 的格式以下:
cma-map ::= "cma_map=" rules [ ';' ]
rules ::= rule [ ';' rules ]
rule ::= patterns '=' regions
patterns ::= pattern [ ',' patterns ]
regions ::= reg-name [ ',' regions ]
// 與device相匹配的內存區的名稱
pattern ::= dev-pattern [ '/' kind-pattern ]
| '/' kind-pattern
// pattern request must match for this rule to
// apply to it; the first rule that matches is
// applied; if dev-pattern part is omitted
// value identical to the one used in previous
// pattern is assumed
dev-pattern ::= pattern-str
// pattern that device name must match for the
// rule to apply.
kind-pattern ::= pattern-str
// pattern that "kind" of memory (provided by
// device) must match for the rule to apply.
pattern-str ::= a non-empty sequence of characters with '?'
meaning any character and possible '*' at
the end meaning to match the rest of the
string
示例 (whitespace added for better readability):
cma = r1 = 64M // 64M區域
@512M // 開始於512M (或儘量接近)
/1M // 確保內存區域1M對齊
:foo(bar); // 使用帶參bar的分配器foo
r2 = 64M // 64M區域
/1M; // 確保內存區域1M對齊
// 使用第一個有效分配器
r3 = 64M // 64M區域
@512M // 開始於512M (或儘量接近)
:foo; // 使用不帶參的分配器foo
cma_map = foo = r1; // kind==NULL的foo設備使用區域r1
foo/quaz = r2; // OR:
/quaz = r2; // kind == "quaz"的設備foo使用區域r2
foo/* = r3; // OR:
/* = r3; // 任何kinde的設備foo都可使用區域r3
bar/* = r1,r2; // 任何kind的設備bar都可使用區域r1和r2
baz?/a* , baz?/b* = r3;
// 任何名如baz?且kind爲a或b的設備均可以區域r3,其中?表明任意字符
設備名稱來自device結構體。若是一個驅動沒有註冊爲device,那麼它是不能使用CMA的(一般,至少提供一個僞設備)。
不管設備什麼時候申請內存,內存的類別都是一個可選的參數。在不少場景下,這個參數是能夠忽略的,可是有時某些設備也可能須要。
好比:當前有兩個內存bank,因爲性能的緣由,這兩個內存bank設備都會使用,那麼此時,設備驅動須要爲這兩個不一樣的buffer定義兩個不一樣的名稱。
cma=a=32M@0,b=32M@512M cma_map=foo/a=a;foo/b=b
可是不管什麼時候,驅動分配內存都須要指定爲某一個內存區域:
buffer1 = cma_alloc(dev, 1 << 20, 0, "a");
buffer2 = cma_alloc(dev, 1 << 20, 0, "b");
固然,若是須要(好比,當指定的內存已經使用完),須要容許驅動從其餘的內存bank中分配。命令行參數能夠改成:
cma=a=32M@0,b=32M@512M cma_map=foo/a=a,b;foo/b=b,a
換句話說,若是同一個設備在某一個系統上只使用一個內存bank,命令行參數:
cma=r=64M cma_map=foo/*=r
驅動無需作出任何更改。
CMA框架爲設備提供了四個接口,分配接口cma_alloc():
unsigned long cma_alloc(const struct device *dev,
const char *kind,
unsigned long size,
unsigned long alignment);
若是須要,設備可能須要指定對齊規格(這個規格是內存chunk須要知足的),必須是2的冪次或0.而chunks通常至少一PAGE對齊的。(page大小通常爲4k)。
參數kind指定內存區域名稱,若是沒有指定,則採用NULL。調用示例:
addr = cma_alloc(dev, NULL, size, 0);
該函數返回的是物理地址,通常須要判斷返回值的有效性:
unsigned long addr = cma_alloc(dev, size);
if (IS_ERR_VALUE(addr))
return (int)addr;
/* Allocated */
(Make sure to include <linux/err.h> which contains the definition of the IS_ERR_VALUE() macro.)
釋放函數cma_put():
int cma_put(unsigned long addr);
參數爲內存塊的物理地址,實現機制是遞減引用計數,若是引用計數變爲0,則釋放該內存塊。絕大部分時候,用戶無需關注引用計數,只須要簡單地調用cma_put()就能夠了。
當該內存塊共享給其餘的驅動時,就須要調用cma_get()遞增引用計數:
int cma_put(unsigned long addr);
最後一個函數是cma_info(),返回分配給指定(dev, kind)的內存緩存區的描述信息。原型以下:
int cma_info(struct cma_info *info,
const struct device *dev,
const char *kind);
經過這個函數能夠獲取內存區的邊間,大小和對應於(dev, kind)內存區的個數。
爲CMA建立一個分配器須要實現四個函數。
前兩個是用來初始化和卸載分配器:
int cma_foo_init(struct cma_region *reg);
void cma_foo_done(struct cma_region *reg);
第一個函數在平臺初始化時調用。結構體cma_region記錄了緩存區的起始地址、大小,也會記錄經由命令行傳入的alloc_params域。
當調用函數cma_foo_done()時,均認爲改緩存區的內存已經所有釋放。
另外兩個函數式塊分配和釋放函數:
struct cma_chunk *cma_foo_alloc(struct cma_region *reg,
unsigned long size,
unsigned long alignment);
void cma_foo_free(struct cma_chunk *chunk);
每個函數,都是惟一線程訪問的。所以,分配器不須要擔憂併發。
當分配器已經實現後,剩下的就是註冊了,在文件"mm/cma-allocators.h"中定義以下語句:
CMA_ALLOCATOR("foo", foo)
第一個foo是命令行使用的分配器的名稱,第二個是函數名字。
在平臺初始化過程,會調用函數cma_regions_allocate():
void cma_regions_allocate(int (*alloc)(struct cma_region *reg));
會遍歷命令行提過全部的內存區併爲他們保留內存。只有一個參數cma_region用於保留內存。傳入NULL會調用cma_region_alloc()函數來經過bootmem來分配內存。
平臺也會提過cma_defaults()提供默認的cma和cma_map參數:
int cma_defaults(const char *cma, const char *cma_map)
在未來,CMA機制可以實現:CMA空閒區域可以用於page cache,文件系統buffer或設備交換區。如此,內存將再也不會被浪費。
由於CMA的內存是經由CMA框架分配和釋放的,因此CMA框架可以知道哪些內存分配了,哪些內存釋放了。所以,能夠跟蹤未使用的內存,使得CMA能夠將未使用的內存用於其餘目的,如page cache,IO緩存,交換緩存等等。
做者:Younger Liu,
本做品採用知識共享署名-非商業性使用-相同方式共享 3.0 未本地化版本許可協議進行許可。