這是我2012年上半年寫(xiě)的文章,現(xiàn)在微信公眾號(hào)再次發(fā)表。
在我們使用ARM等嵌入式Linux系統(tǒng)的時(shí)候,一個(gè)頭疼的問(wèn)題是GPU,Camera,HDMI等都需要預(yù)留大量連續(xù)內(nèi)存,這部分內(nèi)存平時(shí)不用, 但是一般的做法又必須先預(yù)留著。目前,Marek Szyprowski和Michal Nazarewicz實(shí)現(xiàn)了一套全新的Contiguous Memory Allocator。通過(guò)這套機(jī)制,我們可以做到不預(yù)留內(nèi)存,這些內(nèi)存平時(shí)是可用的,只有當(dāng)需要的時(shí)候才被分配給Camera,HDMI等設(shè)備。下面分析 它的基本代碼流程。
聲明連續(xù)內(nèi)存
內(nèi)核啟動(dòng)過(guò)程中arch/arm/mm/init.c中的arm_memblock_init()會(huì)調(diào)用dma_contiguous_reserve(min(arm_dma_limit, arm_lowmem_limit));
該函數(shù)位于:drivers/base/dma-contiguous.c
其中的size_bytes定義為:
static const unsigned long size_bytes = CMA_SIZE_MBYTES * SZ_1M; 默認(rèn)情況下,CMA_SIZE_MBYTES會(huì)被定義為16MB,來(lái)源于CONFIG_CMA_SIZE_MBYTES=16->
由此可見(jiàn),連續(xù)內(nèi)存區(qū)域也是在內(nèi)核啟動(dòng)的早期,通過(guò)__memblock_alloc_base()拿到的。
另外:
drivers/base/dma-contiguous.c里面的core_initcall()會(huì)導(dǎo)致cma_init_reserved_areas()被調(diào)用:
cma_create_area()會(huì)調(diào)用cma_activate_area(),cma_activate_area()函數(shù)則會(huì)針對(duì)每個(gè)page調(diào)用:
init_cma_reserved_pageblock(pfn_to_page(base_pfn));
這個(gè)函數(shù)則會(huì)通過(guò)set_pageblock_migratetype(page, MIGRATE_CMA)將頁(yè)設(shè)置為MIGRATE_CMA類(lèi)型的:
同時(shí)其中調(diào)用的__free_pages(page, pageblock_order);最終會(huì)調(diào)用到__free_one_page(page, zone, order, migratetype);相關(guān)的page會(huì)被加到MIGRATE_CMA的free_list上面去:
list_add(&page->lru, &zone->free_area[order].free_list[migratetype]);
申請(qǐng)連續(xù)內(nèi)存
申請(qǐng)連續(xù)內(nèi)存仍然使用標(biāo)準(zhǔn)的arch/arm/mm/dma-mapping.c中定義的dma_alloc_coherent()和dma_alloc_writecombine(),這二者會(huì)間接調(diào)用drivers/base/dma-contiguous.c中的
->
->
int alloc_contig_range(unsigned long start, unsigned long end,
unsigned migratetype)
需要隔離page,隔離page的作用通過(guò)代碼的注釋可以體現(xiàn):
簡(jiǎn)單地說(shuō),就是把相關(guān)的page標(biāo)記為MIGRATE_ISOLATE,這樣buddy系統(tǒng)就不會(huì)再使用他們。
接下來(lái)調(diào)用__alloc_contig_migrate_range()進(jìn)行頁(yè)面隔離和遷移:
其中的函數(shù)migrate_pages()會(huì)完成頁(yè)面的遷移,遷移過(guò)程中通過(guò)傳入的__alloc_contig_migrate_alloc()申請(qǐng)新的page,并將老的page付給新的page:
其中的unmap_and_move()函數(shù)較為關(guān)鍵,它定義在mm/migrate.c中
通過(guò)unmap_and_move(),老的page就被遷移過(guò)去新的page。
接下來(lái)要回收page,回收page的作用是,不至于因?yàn)槟昧诉B續(xù)的內(nèi)存后,系統(tǒng)變得內(nèi)存饑餓:
->
->
釋放連續(xù)內(nèi)存
內(nèi)存釋放的時(shí)候也比較簡(jiǎn)單,直接就是:
arch/arm/mm/dma-mapping.c:
將page交還給buddy。
內(nèi)核內(nèi)存分配的migratetype
內(nèi)核內(nèi)存分配的時(shí)候,帶的標(biāo)志是GFP_,但是GFP_可以轉(zhuǎn)化為migratetype:
之后申請(qǐng)內(nèi)存的時(shí)候,會(huì)對(duì)比遷移類(lèi)型匹配的free_list:
另外,筆者也編寫(xiě)了一個(gè)測(cè)試程序,透過(guò)它隨時(shí)測(cè)試CMA的功能:
/*
* kernel module helper for testing CMA
*
* Licensed under GPLv2 or later.
*/
#include
#include
#include
#include
#include
#define CMA_NUM 10
static struct device *cma_dev;
static dma_addr_t dma_phys[CMA_NUM];
static void *dma_virt[CMA_NUM];
/* any read request will free coherent memory, eg.
* cat /dev/cma_test
*/
static ssize_t
cma_test_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
int i;
for (i = 0; i < CMA_NUM; i++) {??
if (dma_virt[i]) {
dma_free_coherent(cma_dev, (i + 1) * SZ_1M, dma_virt[i], dma_phys[i]);
_dev_info(cma_dev, "free virt: %p phys: %p\n", dma_virt[i], (void *)dma_phys[i]);
dma_virt[i] = NULL;
break;
}
}
return 0;
}
/*
* any write request will alloc coherent memory, eg.
* echo 0 > /dev/cma_test
*/
static ssize_t
cma_test_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int i;
int ret;
for (i = 0; i < CMA_NUM; i++) {??
if (!dma_virt[i]) {
dma_virt[i] = dma_alloc_coherent(cma_dev, (i + 1) * SZ_1M, &dma_phys[i], GFP_KERNEL);
if (dma_virt[i]) {
void *p;
/* touch every page in the allocated memory */
for (p = dma_virt[i]; p dma_virt[i] + (i + 1) * SZ_1M; p += PAGE_SIZE)??
*(u32 *)p = 0;
_dev_info(cma_dev, "alloc virt: %p phys: %p\n", dma_virt[i], (void *)dma_phys[i]);
} else {
dev_err(cma_dev, "no mem in CMA area\n");
ret = -ENOMEM;
}
break;
}
}
return count;
}
static const struct file_operations cma_test_fops = {
.owner = THIS_MODULE,
.read = cma_test_read,
.write = cma_test_write,
};
static struct miscdevice cma_test_misc = {
.name = "cma_test",
.fops = &cma_test_fops,
};
static int __init cma_test_init(void)
{
int ret = 0;
ret = misc_register(&cma_test_misc);
if (unlikely(ret)) {
pr_err("failed to register cma test misc device!\n");
return ret;
}
cma_dev = cma_test_misc.this_device;
cma_dev->coherent_dma_mask = ~0;
_dev_info(cma_dev, "registered.\n");
return ret;
}
module_init(cma_test_init);
static void __exit cma_test_exit(void)
{
misc_deregister(&cma_test_misc);
}
module_exit(cma_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
MODULE_DESCRIPTION("kernel module to help the test of CMA");
MODULE_ALIAS("CMA test");
申請(qǐng)內(nèi)存:
#echo0>/dev/cma_test
釋放內(nèi)存:
#cat/dev/cma_test
-
Linux
+關(guān)注
關(guān)注
87文章
11345瀏覽量
210403 -
分配器
+關(guān)注
關(guān)注
0文章
195瀏覽量
25832
原文標(biāo)題:宋寶華:Linux內(nèi)核的連續(xù)內(nèi)存分配器(CMA)——避免預(yù)留大塊內(nèi)存
文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論