天天看點

【ARM Linux 記憶體管理入門及漸進 3 - CMA】

文章目錄

      • 1.1.1 曆史背景
      • 1.1.2 device tree reserved memory node
      • 1.1.3 CMA 結構描述符
      • 1.1.4 配置CMA記憶體
      • 1.1.5 reusable 屬性
      • 1.1.6 per device CMA
      • 1.1.7 cmdline cma
      • 1.1.8 memblock 與 CMA的關系
    • 1.2 CMA 工作流程
      • 1.2.1 準備知識
      • 1.2.2 初始化 CMA area

1.1.1 曆史背景

曆史背景

1.1.2 device tree reserved memory node

一開始,CMA area 的概念是全局的,通過核心配置參數和指令行參數,核心可以定位到 Global CMA area 在記憶體中的起始位址和大小(這裡的Global的意思是針對所有的driver而言的)。并在初始化的時候,調用

dma_contiguous_reserve

函數,将指定的 memory region 保留給 Global CMA area 使用。

人性是貪婪的,驅動亦然,很快,有些驅動想吃獨食,不願意和其他驅動共享CMA,是以出現兩種 CMA area:

  • Global CMA area給大家共享,
  • per device CMA 可以給指定的一個或者幾個驅動使用。

這時候,指令行參數不是那麼合适了,是以引入了 device tree 中的 r

eserved memory

node 的概念。當然,為了相容,核心仍然支援CMA的 command line 參數。

1.1.3 CMA 結構描述符

在CMA子產品中,

struct cma

資料結構用來抽象一個CMA area,具體定義如下:

struct cma { 
    unsigned long   base_pfn; 
    unsigned long   count; 
    unsigned long   *bitmap; 
    /* Order of pages represented by one bit */ 
    unsigned int order_per_bit; 
    struct mutex    lock; 
};
           

cma 子產品使用 bitmap來管理其記憶體的配置設定,0表示free,1表示已經配置設定。具體記憶體管理的機關和struct cma中的order_per_bit成員相關:

如果 order_per_bit 等于0,表示按照一個一個page來配置設定和釋放,

如果 order_per_bit等于 1,表示按照2個page組成的block來配置設定和釋放,以此類推。

struct cma 中的bitmap成員就是管理該cma area記憶體的bit map。

count 成員說明了該cma area記憶體有多少個page。它和order_per_bit一起決定了bitmap指針指向記憶體的大小。

base_pfn 定義了該CMA area的起始 page frame number,base_pfn和count一起定義了該CMA area在記憶體在的位置。

我們前面說過了,CMA子產品需要管理若幹個CMA area,有gloal的,有per device的,代碼如下:

每一個

struct cma

抽象了一個CMA area,辨別了一個實體位址連續的memory area。

調用

cma_alloc

配置設定的連續記憶體就是從CMA area中獲得的。

具體有多少個CMA area是編譯時決定了,而具體要配置多少個CMA area是和系統設計相關,你可以為特定的驅動準備一個CMA area,也可以隻建立一個通用的CMA area,供多個驅動使用(本文重點描述這個共用的CMA area)。

1.1.4 配置CMA記憶體

如上文提到的,配置CMA記憶體區有兩種方法:

  • 一種是通過dts的reserved memory;
  • 一種是通過command line參數和核心配置參數。

device tree 中可以包含

reserved-memory

node,在該節點的 child node中,可以定義各種保留記憶體的資訊。compatible屬性是

shared-dma-pool

的那個節點是專門用于建立 global CMA area的,而其他的child node都是for per device CMA area的。

Global CMA area 的初始化可以參考定義如下:

具體的setup過程倒是比較簡單,從device tree中可以擷取該memory range的起始位址和大小,調用

cma_init_reserved_mem

函數即可以注冊一個CMA area。需要補充說明的是:CMA對應的

reserved memory

節點必須有

reusable

屬性,不能有

no-map

的屬性。

1.1.5 reusable 屬性

reusable 屬性的 reserved memory 有這樣的特性:即在驅動不使用這些記憶體的時候,OS可以使用這些記憶體(當然有限制條件),而當驅動從這個CMA area配置設定memory的時候,OS可以reclaim這些記憶體,讓驅動可以使用它。

no-map屬性和位址映射相關,如果沒有no-map屬性,那麼OS會為這段memory建立位址映射,象其他普通記憶體一樣。但是有no-map屬性的往往是專用于某個裝置驅動,在驅動中會進行io remap,如果OS已經對這段位址進行了mapping,而驅動又一次mapping,這樣就有不同的虛拟位址mapping到同一個實體位址上去,在某些ARCH上(ARMv6之後的cpu),會造成不可預知的後果。而CMA這個場景,reserved memory必須要mapping好,這樣才能用于其他記憶體配置設定場景,例如page cache。

1.1.6 per device CMA

per device CMA area的注冊過程和各自具體的驅動相關,但是最終會

dma_declare_contiguous

這個接口函數,為一個指定的裝置而注冊CMA area,這裡就不詳述了。

1.1.7 cmdline cma

通過指令行參數也可以建立cma area。我們可以通過

cma=nn[MG]@[start[MG][-end[MG]]]

這樣指令行參數來指明Global CMA area在整個實體記憶體中的位置。

在初始化過程中,核心會解析這些指令行參數,擷取CMA area的位置(起始位址,大小),并調用

cma_declare_contiguous

接口函數向CMA子產品進行注冊(當然,和device tree傳參類似,最終也是調用

cma_init_reserved_mem

接口函數)。除了指令行參數,通過核心配置(CMA_SIZE_MBYTES和CMA_SIZE_PERCENTAGE)也可以确定CMA area的參數。

1.1.8 memblock 與 CMA的關系

記憶體管理子系統進行初始化的時候,首先是memblock掌控全局的,這時候需要确定整個系統的的記憶體布局,簡單說就是了解整個memory的分布情況,哪些是memory block是memory type,哪些memory block是reserved type。毫無疑問,CMA area對應的當然是reserved type。最先進行的是memory type的記憶體塊的建立,可以參考如下代碼:

setup_arch--->setup_machine_fdt--->early_init_dt_scan
		--->early_init_dt_scan_nodes--->memblock_add
           

随後會建立reserved type的memory block,可以參考如下代碼:

setup_arch--->arm64_memblock_init
		--->early_init_fdt_scan_reserved_mem
				--->__fdt_scan_reserved_mem--->memblock_reserve
           

完成上面的初始化之後,memblock子產品已經通過device tree建構了整個系統的記憶體全貌:哪些是普通記憶體區域,哪些是保留記憶體區域。

對于那些reserved memory,我們還需要進行初始化,代碼如下:

setup_arch--->arm64_memblock_init
	--->early_init_fdt_scan_reserved_mem
		--->fdt_init_reserved_mem
			--->__reserved_mem_init_node
           

上面的代碼會scan核心中的一個特定的section(還記得前面

RESERVEDMEM_OF_DECLARE

的定義嗎?),如果比對就會調用相應的初始化函數,而對于Global CMA area而言,這個初始化函數就是

rmem_cma_setup

。當然,如果有需要,具體的驅動也可以定義自己的CMA area,初始化的思路都是一樣的。

1.2 CMA 工作流程

1.2.1 準備知識

如果想要了解CMA是如何運作的,你可能需要知道一點點關于migrate types和pageblocks的知識。當從夥伴系統請求記憶體的時候,我們需要提供了一個gfp_mask的參數。它有很多的功能,不過在CMA這個場景,它用來指定請求頁面的遷移類型(migrate type)。

migrate type有很多中,其中有一個是

MIGRATE_MOVABLE

類型,被标記為

MIGRATE_MOVABLE

的page說明該頁面上的資料是可以遷移的。也就是說,如果需要,我們可以配置設定一個新的page,copy資料到這個new page上去,釋放這個page。而完成這樣的操作對系統沒有任何的影響。我們來舉一個簡單的例子:對于核心中的data section,其對應的page不是是movable的,因為一旦移動資料,那麼核心子產品就無法通路那些頁面上的全局變量了。而對于page cache這樣的頁面,其實是可以搬移的,隻要讓指針指向新的page就OK了。

夥伴系統不會跟蹤每一個page frame的遷移類型,實際上它是按照pageblock為機關進行管理的,memory zone中會有一個bitmap,指明該zone中每一個pageblock的migrate type。在處理記憶體配置設定請求的時候,一般會首先從和請求相同migrate type(gfp_mask)的pageblocks中配置設定頁面。如果配置設定不成功,不同migrate type的pageblocks中也會考慮,甚至可能改變pageblock的migrate type。這意味着一個non-movable頁面請求也可以從migrate type是movable的pageblock中配置設定。這一點CMA是不能接受的,是以我們引入了一個新的migrate type:MIGRATE_CMA。這種遷移類型具有一個重要性質:隻有可移動的頁面可以從MIGRATE_CMA的pageblock中配置設定。

1.2.2 初始化 CMA area

static int __init cma_activate_area(struct cma *cma)
{ 
    int bitmap_size = BITS_TO_LONGS(cma_bitmap_maxno(cma)) 
    					* sizeof(long); 
    unsigned long base_pfn = cma->base_pfn, pfn = base_pfn; 
    unsigned i = cma->count >> pageblock_order; 
    struct zone *zone; -------(1)
    cma->bitmap = kzalloc(bitmap_size, GFP_KERNEL); --配置設定記憶體
    zone = page_zone(pfn_to_page(pfn)); --找到page對應的memory zone
    do {-----(2) 
        unsigned j;
        base_pfn = pfn; 
        for (j = pageblock_nr_pages; j; --j, pfn++) {------(3) 
            if (page_zone(pfn_to_page(pfn)) != zone) 
                goto err; 
        } 
        init_cma_reserved_pageblock(pfn_to_page(base_pfn));--(4) 
    } while (--i);
    mutex_init(&cma->lock);
    return 0;
}
           

(1)CMA area有一個bitmap來管理各個page的狀态,這裡

bitmap_size

給出了bitmap需要多少的記憶體。i變量表示該CMA area有多少個pageblock。

(2)周遊該CMA area中的所有的pageblock。

(3)確定CMA area中的所有page都是在一個memory zone内,同時累加了pfn,進而得到下一個pageblock的初始page frame number。

(4)将該pageblock導入到夥伴系統,并且将migrate type設定為

MIGRATE_CMA

  • 配置設定連續記憶體

    cma_alloc用來從指定的CMA area上配置設定count個連續的page frame,按照align對齊。具體的代碼就不再分析了,比較簡單,實際上就是從bitmap上搜尋free page的過程,一旦搜尋到,就調用

    alloc_contig_range

    向夥伴系統申請記憶體。需要注意的是,CMA記憶體配置設定過程是一個比較“重”的操作,可能涉及頁面遷移、頁面回收等操作,是以不适合用于atomic context。
  • 釋放連續記憶體

    配置設定連續記憶體的逆過程,除了bitmap的操作之外,最重要的就是調用

    free_contig_range

    ,将指定的pages傳回夥伴系統。

繼續閱讀