【內存管理】CMA內存分配器(Contiguous Memory Allocator)


什么是CMA

參考這兩篇博文,寫得很好:
http://www.wowotech.net/memory_management/cma.html
https://www.cnblogs.com/LoyenWang/p/12182594.html

CMA的初始化創建

* 默認cma創建(dma_contiguous_default_area),兩種方式:

  1. 通過cmdline傳遞的參數"cma=",然后在kernel初始化階段解析參數,並調用start_kernel()->setup_arch()->arm64_memblock_init()->dma_contiguous_reserve()完成創建(android中一般不通過cmdline傳遞):
static phys_addr_t size_cmdline = -1;
static phys_addr_t base_cmdline;
static phys_addr_t limit_cmdline;
//解析cmdline傳遞的cma參數
static int __init early_cma(char *p)
{
	pr_debug("%s(%s)\n", __func__, p);
	size_cmdline = memparse(p, &p);
	if (*p != '@')
		return 0;
	base_cmdline = memparse(p + 1, &p);
	if (*p != '-') {
		limit_cmdline = base_cmdline + size_cmdline;
		return 0;
	}
	limit_cmdline = memparse(p + 1, &p);

	return 0;
}
early_param("cma", early_cma);
  1. 通過dts中配置cma節點,屬性中包含"shared-dma-pool"以及"linux,cma-default",在kernel初始化階段,通過調用start_kernel()->setup_arch()->arm64_memblock_init()->early_init_fdt_scan_reserved_mem()->fdt_init_reserved_mem()->__reserved_mem_init_node()完成對默認cma的創建和初始化:
static int __init __reserved_mem_init_node(struct reserved_mem *rmem)
{
	extern const struct of_device_id __reservedmem_of_table[];
	const struct of_device_id *i;
	//__reservedmem_of_table是初始化中的一個section段,通過RESERVEDMEM_OF_DECLARE定義的都會被鏈接到這個段中
	//參考:https://blog.csdn.net/rikeyone/article/details/79975138
	for (i = __reservedmem_of_table; i < &__rmem_of_table_sentinel; i++) {
		reservedmem_of_init_fn initfn = i->data;
		const char *compat = i->compatible;

		if (!of_flat_dt_is_compatible(rmem->fdt_node, compat))
			continue;

		if (initfn(rmem) == 0) {
			pr_info("initialized node %s, compatible id %s\n",
				rmem->name, compat);
			return 0;
		}
	}
	return -ENOENT;
}
//dma-contiguous.c文件中定義了該默認cma的創建回調。
//如果dts中沒有配置,那該回調也不會執行。
//參考:https://blog.csdn.net/rikeyone/article/details/79975138
RESERVEDMEM_OF_DECLARE(cma, "shared-dma-pool", rmem_cma_setup);
  1. 默認cma似乎在好些android平台上都沒有創建。

*其他CMA區創建

  • 其他CMA區域創建都應該類似默認cma一樣,通過RESERVEDMEM_OF_DECLARE接口定義一個結構體變量在__reservedmem_of_table段中,開機啟動時就會調用對應的initfn完成初始化,同時還需要在dts中配置對應的屬性節點。

  • 所有CMA的創建最終都會調用cma_init_reserved_mem()函數:

    1. 主要從cma全局數組cma_areas中分配一個cma實體並將傳遞過來的參數用於初始化該cam實體。
    2. 初始化參數包括,cma的name、起始頁框號base_pfn,總共頁數count,以及每個bit代表多少個頁2^(order_per_bit)。
    3. 更新全局變量totalcma_pages,記錄總的cma頁面數量,在meminfo中CmaTotal就是這個值。
int __init cma_init_reserved_mem(phys_addr_t base, phys_addr_t size,
				 unsigned int order_per_bit,
				 const char *name,
				 struct cma **res_cma)
{
	struct cma *cma;
	phys_addr_t alignment;

	/* Sanity checks */
    //判斷cma數量是否已經滿了,因為cma_areas數組指定了系統中總的cma數量,通過內核宏控制
	if (cma_area_count == ARRAY_SIZE(cma_areas)) {
		pr_err("Not enough slots for CMA reserved regions!\n");
		return -ENOSPC;
	}
    //判斷該cma內存區間是否與reversed中的某個區間是交叉的?為什么要這樣判斷?
	if (!size || !memblock_is_region_reserved(base, size))
		return -EINVAL;

	/* ensure minimal alignment required by mm core */
    //對齊方式按pageblock,也就是1024頁(4M)
	alignment = PAGE_SIZE <<
			max_t(unsigned long, MAX_ORDER - 1, pageblock_order);

	/* alignment should be aligned with order_per_bit */
    //判斷對齊方式alignment本身的大小與單個bit表示的內存大小,是否對齊
	if (!IS_ALIGNED(alignment >> PAGE_SHIFT, 1 << order_per_bit))
		return -EINVAL;
    //判斷base和size以aligment方式對齊后,得到的值是否還是原來的值,也就是判斷base和size是否基於alignment對齊
	if (ALIGN(base, alignment) != base || ALIGN(size, alignment) != size)
		return -EINVAL;

	/*
	 * Each reserved area must be initialised later, when more kernel
	 * subsystems (like slab allocator) are available.
	 */
	 //1. memblock是系統最初的內存管理器,分為memory type和reserved type,CMA最開始就屬於reserved type
	 //2. 運行到這里,就表示memblock已經建立,並且buddy還沒建立,CMA在buddy前建立OK
	 //3. CMA建立OK后,接着memblock中的memory type會釋放給buddy,reserved type則不會
	 //4. CMA作為特殊的reserved type,最終通過系統初始化調用cma_init_reserved_areas,將內存歸還給buddy

    //從cma_areas數組中分配一個cma對象
	cma = &cma_areas[cma_area_count];
	if (name) {
		cma->name = name;
	} else {
		cma->name = kasprintf(GFP_KERNEL, "cma%d\n", cma_area_count);
		if (!cma->name)
			return -ENOMEM;
	}
    
	cma->base_pfn = PFN_DOWN(base); //起始頁號
	cma->count = size >> PAGE_SHIFT; //總共頁面數
	cma->order_per_bit = order_per_bit; //一個bit代表的階數
	*res_cma = cma;
	cma_area_count++;
	totalcma_pages += (size / PAGE_SIZE); //totalcma_pages記錄總的cma頁面數量,在meminfo中CmaTotal就是這個值

	return 0;
}

到這里,只是完成對cma內存的保留和初始化,cma區最終還需要釋放給buddy。

CMA區域釋放給buddy

  • 釋放也是在kernel初始化過程中,會比cma的創建稍晚一些,是通過cma_init_reserved_areas接口完成的所有cma的初始化並將內存返還給buddy。

    • core_initcall(cma_init_reserved_areas)定義在kernel的init段中,通過start_kernel()->rest_init()創建內核線程kernel_init->kernel_init_freeable()->do_basic_setup()->do_initcalls()完成對各個init level的初始化。core init屬於level1。
  • cma_init_reserved_areas()函數,遍歷當前cma全局數組中已經分配的cma實體,通過調用cma_activate_area函數完成激活初始化,同時將內存釋放給buddy:

static int __init cma_init_reserved_areas(void)
{
	int i;

	for (i = 0; i < cma_area_count; i++) {
		int ret = cma_activate_area(&cma_areas[i]);

		if (ret)
			return ret;
	}

	return 0;
}
core_initcall(cma_init_reserved_areas);
  • cma_activate_area()函數:
    以pageblock為單位,設置migrate type為MIGRATE_CMA,然后將其整個pageblock包含的頁全部釋放給buddy,並更新整個系統的可用內存總數
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;
    //i代表有多少個page block,一般一個pageblock是1024頁
	unsigned i = cma->count >> pageblock_order;
	struct zone *zone;

    //cma也是通過bitmap來管理,每個bit代表多大,由order_per_bit決定。
    //默認的cma的order_per_bit為0,一個bit代表2^0個page。
    //分配bitmap
	cma->bitmap = kzalloc(bitmap_size, GFP_KERNEL);

	if (!cma->bitmap)
		return -ENOMEM;

	WARN_ON_ONCE(!pfn_valid(pfn));
	zone = page_zone(pfn_to_page(pfn));

    //以pageblock遍歷,
	do {
		unsigned j;

        //記錄當前pageblock的起始頁
		base_pfn = pfn;
        //判斷當前pageblock中的所有頁面是否滿足要求:合法的頁號、都在同一個zone中
		for (j = pageblock_nr_pages; j; --j, pfn++) {
			WARN_ON_ONCE(!pfn_valid(pfn));
			/*
			 * alloc_contig_range requires the pfn range
			 * specified to be in the same zone. Make this
			 * simple by forcing the entire CMA resv range
			 * to be in the same zone.
			 */
			if (page_zone(pfn_to_page(pfn)) != zone)
				goto not_in_zone;
		}
        //將當前pageblock初始化並釋放給buddy
		init_cma_reserved_pageblock(pfn_to_page(base_pfn));
	} while (--i);

	mutex_init(&cma->lock);

#ifdef CONFIG_CMA_DEBUGFS
	INIT_HLIST_HEAD(&cma->mem_head);
	spin_lock_init(&cma->mem_head_lock);
#endif

	return 0;

not_in_zone:
	pr_err("CMA area %s could not be activated\n", cma->name);
	kfree(cma->bitmap);
	cma->count = 0;
	return -EINVAL;
}
  • cma_activate_area()->init_cma_reserved_pageblock()函數設置pageblock類型並釋放內存給buddy:
void __init init_cma_reserved_pageblock(struct page *page)
{
	unsigned i = pageblock_nr_pages;
	struct page *p = page;

	do {
        //清除頁描述flag中的PG_Reserved標志位
		__ClearPageReserved(p);
        //設置page->_refcount = 0
		set_page_count(p, 0);
	} while (++p, --i);
    //設置pageblock的遷移類型為MIGRATE_CMA
	set_pageblock_migratetype(page, MIGRATE_CMA);

	if (pageblock_order >= MAX_ORDER) {
		i = pageblock_nr_pages;
		p = page;
		do {
			set_page_refcounted(p);
			__free_pages(p, MAX_ORDER - 1);
			p += MAX_ORDER_NR_PAGES;
		} while (i -= MAX_ORDER_NR_PAGES);
	} else {
        //設置page->_refcount = 1
		set_page_refcounted(page);
        //釋放pages到buddy中,以pageblock釋放,order為10
		__free_pages(page, pageblock_order);
	}
    //調整對應zone中的managed_pages可管理頁面數,即加上一個pageblock數量
    //調整總的內存數量totalram_pages,即加上一個pageblock數量
	adjust_managed_page_count(page, pageblock_nr_pages);
}

CMA的分配

  • CMA分配通過統一接口cma_alloc函數,會從bitmap中先查找滿足要求的連續bit,然后通過alloc_contig_range實現分配,成功后的頁面會從buddy總摘出來:
struct page *cma_alloc(struct cma *cma, size_t count, unsigned int align,
		       gfp_t gfp_mask)
{
	unsigned long mask, offset;
	unsigned long pfn = -1;
	unsigned long start = 0;
	unsigned long bitmap_maxno, bitmap_no, bitmap_count;
	struct page *page = NULL;
	int ret = -ENOMEM;

	if (!cma || !cma->count)
		return NULL;

	pr_debug("%s(cma %p, count %zu, align %d)\n", __func__, (void *)cma,
		 count, align);

	if (!count)
		return NULL;

	mask = cma_bitmap_aligned_mask(cma, align);
	offset = cma_bitmap_aligned_offset(cma, align);
	bitmap_maxno = cma_bitmap_maxno(cma);
	bitmap_count = cma_bitmap_pages_to_bits(cma, count);

	if (bitmap_count > bitmap_maxno)
		return NULL;

	for (;;) {
		mutex_lock(&cma->lock);
        //1. 從cma->bitmap中查找連續bitmap_count個為0的bit
		bitmap_no = bitmap_find_next_zero_area_off(cma->bitmap,
				bitmap_maxno, start, bitmap_count, mask,
				offset);
		if (bitmap_no >= bitmap_maxno) {
			mutex_unlock(&cma->lock);
			break;
		}
        //2. 將查找到的連續bit設置為1,表示內存被分配占用
		bitmap_set(cma->bitmap, bitmap_no, bitmap_count);
		/*
		 * It's safe to drop the lock here. We've marked this region for
		 * our exclusive use. If the migration fails we will take the
		 * lock again and unmark it.
		 */
		mutex_unlock(&cma->lock);
        //3. 計算分配的起始頁的頁號
		pfn = cma->base_pfn + (bitmap_no << cma->order_per_bit);
		mutex_lock(&cma_mutex);
        //4. 分配從起始頁開始的連續count個頁,分配的migrate type為CMA類型
		ret = alloc_contig_range(pfn, pfn + count, MIGRATE_CMA,
					 gfp_mask);
		mutex_unlock(&cma_mutex);
        //5. 分配成功,就返回起始page
		if (ret == 0) {
			page = pfn_to_page(pfn);
			break;
		}

		cma_clear_bitmap(cma, pfn, count);
		if (ret != -EBUSY)
			break;

		pr_debug("%s(): memory range at %p is busy, retrying\n",
			 __func__, pfn_to_page(pfn));
		/* try again with a bit different memory target */
		start = bitmap_no + mask + 1;
	}

	trace_cma_alloc(pfn, page, count, align);

	if (ret && !(gfp_mask & __GFP_NOWARN)) {
		pr_info("%s: alloc failed, req-size: %zu pages, ret: %d\n",
			__func__, count, ret);
		cma_debug_show_areas(cma);
	}

	pr_debug("%s(): returned %p\n", __func__, page);
	return page;
}

CMA的釋放

  • 釋放操作也很清晰,通過cma_release函數實現,會將頁面釋放回buddy系統,並將cma的bitmap相應bit清零:
bool cma_release(struct cma *cma, const struct page *pages, unsigned int count)
{
	unsigned long pfn;

	if (!cma || !pages)
		return false;

	pr_debug("%s(page %p)\n", __func__, (void *)pages);

	pfn = page_to_pfn(pages);

	if (pfn < cma->base_pfn || pfn >= cma->base_pfn + cma->count)
		return false;

	VM_BUG_ON(pfn + count > cma->base_pfn + cma->count);
	//釋放回buddy
	free_contig_range(pfn, count);
	//清零bit位,表示對應cma內存可用
	cma_clear_bitmap(cma, pfn, count);
	trace_cma_release(pfn, pages, count);

	return true;
}

CMA與buddy

后續補充


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM