[內存管理]連續內存分配器(CMA)概述

做者:Younger Liu,linux

本做品採用知識共享署名-非商業性使用-相同方式共享 3.0 未本地化版本許可協議進行許可。git

原文地址:http://lwn.net/Articles/396657/算法

1.    簡介

連續內存分配器(CMA - Contiguous Memory Allocator)是一個框架,容許創建一個平臺無關的配置,用於連續內存的管理。而後,設備所需內存都根據該配置進行分配。緩存

這個框架的主要做用不是分配內存,而是解析和管理內存配置,以及做爲在設備驅動程序和可插拔的分配器之間的中間組件。所以,它是與任何內存分配方法和分配策略沒有依賴關係的。架構

2.    爲何須要?

在嵌入式設備中,不少設備都沒有支持scatter-getter和IO map,都須要連續內存塊的操做。如設備:攝像機,硬件視頻解碼器,編碼器等。併發

這些設備每每須要較大的內存緩衝區(如:一個200萬像素的高清幀攝像機,須要超過6M的內存),該kmalloc內存分配機制對於這麼大的內存是沒有效果的。app

一些嵌入式設備對緩衝區有一些額外的要求,好比:在含有多個內存bank的設備中,要求只能在特定的bank中中分配內存;而還有一些要定內存邊界對齊的緩存區。框架

近來,嵌入式設備有了較大的發展(特別是V4L領域),而且這些驅動都有本身的內存分配代碼。它們衆多的大多數都是採用bootmem分配方法。CMA框架企圖採用統一的連續內存分配機制,併爲這些設備驅動提供簡單的API,並且是能夠定製化和模塊化的。ide

 

3.    設計

CMA主要設計目標是提供一個可定製的模塊化框架,而且是能夠配置的,以適應個別系統的須要。配置指定的內存區域,而後將這些內存分配給制定的設備。這些內存區域能夠共享給多個設備驅動,也能夠專門分配一個。這是經過如下方式實現的:模塊化

1. CMA的核心不是處理內存分配和空閒空間管理。專用分配器是用來處理內存分配和空閒內存管理的。所以,若是現有的解決方案不符合給定的系統,那麼能夠開發一種新的算法,這種算飯能夠很容易地插入到CMA框架中。

所提出的解決方案中包括一個最適算法(best-fit)的一個實現。

2. CMA容許運行時配置即將分配的內存區域。內存區域都是經由命令行給出的,因此能夠很容易地改變它,而不須要從新編譯內核。

每一個地區都有本身的大小,對齊標準,起始地址(物理地址)和對應該內存區域的內存分配算法。

這意味着同一時刻能夠有多中機制在運行,若是在一個平臺上同時運行多個不一樣的設備,這些設備具備不一樣的存儲器使用特性,那麼局能夠匹配最好的算法。

3. 當設備請求內存時,設備必須「自我介紹」,即附帶本身的信息以告知CMA。這樣CMA能夠知道誰分配內存。這容許系統架構師來指定哪一個移動設備應該使用哪些存儲區。

3a. 設備也能夠指定一個「類」內存區域,這使得系統更容易配置,進而一個單一的設備可能使用來自不一樣內存區域的內存。例如,一個視頻解碼器驅動程序可能要分配一些共享的緩衝區,那麼從第一個bank中分配一些,再從第二個bank中分配一些,能夠得到儘量高的內存吞吐量。

 

4.    使用場景

虛構一個使用了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或重編內核。

 

5.    技術細節

5.1           命令行參數

    如上圖所示,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,其中?表明任意字符

 

5.2           設備與內存類別

    設備名稱來自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

    驅動無需作出任何更改。

 

5.3           API接口

    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)內存區的個數。

 

5.4           分配操做

爲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是命令行使用的分配器的名稱,第二個是函數名字。

 

5.5           平臺集成

在平臺初始化過程,會調用函數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)

   

6.    展望

在未來,CMA機制可以實現:CMA空閒區域可以用於page cache,文件系統buffer或設備交換區。如此,內存將再也不會被浪費。

    由於CMA的內存是經由CMA框架分配和釋放的,因此CMA框架可以知道哪些內存分配了,哪些內存釋放了。所以,能夠跟蹤未使用的內存,使得CMA能夠將未使用的內存用於其餘目的,如page cache,IO緩存,交換緩存等等。

做者:Younger Liu,

本做品採用知識共享署名-非商業性使用-相同方式共享 3.0 未本地化版本許可協議進行許可。

相關文章
相關標籤/搜索