文章目錄
-
-
- 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的過程,一旦搜尋到,就調用
向夥伴系統申請記憶體。需要注意的是,CMA記憶體配置設定過程是一個比較“重”的操作,可能涉及頁面遷移、頁面回收等操作,是以不适合用于atomic context。alloc_contig_range
-
釋放連續記憶體
配置設定連續記憶體的逆過程,除了bitmap的操作之外,最重要的就是調用
,将指定的pages傳回夥伴系統。free_contig_range