导航

【内存管理】ION内存管理器(carveout heap预留内存)

Posted on 2021-05-25 23:57  yibuyibu01  阅读(2255)  评论(0编辑  收藏  举报

什么是carveout heap

  • carveout heap从代码中给的解释来看,是reserved预留的物理内存来实现的,这些内存buddy系统是没办法看到和管理到的
  • carveout heap中的内存通过自建通用内存分配器gen_pool,使用bitmap来管理申请和释放
  • 比如多数平台是在dts中配置保留的物理内存,将该内存专门用来作为carveout heap,ion_platform_heap结构体就是用来指示平台配置的

carvout heap创建

  • ion_carveout_heap_create()函数:
  1. ion_carveout_heap_create接口是ION提供的创建carveout heap的接口,不同平台需要主动调用该接口来创建heap
  2. 其实现主要是将平台预留的物理内存放入gen_pool中
struct ion_heap *ion_carveout_heap_create(struct ion_platform_heap *heap_data)
{
	struct ion_carveout_heap *carveout_heap;
	int ret;

	struct page *page;
	size_t size;
    //根据传递过来的base物理基地址转换成page(所有的物理内存都对应有一个page在表示)
	page = pfn_to_page(PFN_DOWN(heap_data->base));
	size = heap_data->size;
    //将该内存块做清零处理:通过将page映射到vmalloc虚拟地址端,调用memset执行清零。
	ret = ion_heap_pages_zero(page, size, pgprot_writecombine(PAGE_KERNEL));
	if (ret)
		return ERR_PTR(ret);

	carveout_heap = kzalloc(sizeof(*carveout_heap), GFP_KERNEL);
	if (!carveout_heap)
		return ERR_PTR(-ENOMEM);
    //创建内存池对象,PAGE_SHIFT表示pool中最小分配为4K大小
	carveout_heap->pool = gen_pool_create(PAGE_SHIFT, -1);
	if (!carveout_heap->pool) {
		kfree(carveout_heap);
		return ERR_PTR(-ENOMEM);
	}
	carveout_heap->base = heap_data->base;
    //将该内存块(base到base+size之间)加入到pool中,通过bitmap来表示使用和释放
	gen_pool_add(carveout_heap->pool, carveout_heap->base, heap_data->size,
		     -1);
	carveout_heap->heap.ops = &carveout_heap_ops;
	carveout_heap->heap.type = ION_HEAP_TYPE_CARVEOUT;
	carveout_heap->heap.flags = ION_HEAP_FLAG_DEFER_FREE;
    //返回ion_heap对象,该对象最后由平台自己add到ion dev的heaps链表中
	return &carveout_heap->heap;
}
  • gen_pool创建:gen_pool_create()函数

gen_pool看起来还挺简单,它通过chunk来管理不同内存块,min_alloc_order表示bitmap中一个bit代表多大内存,比如我们这里是PAGE_SHIFT=12,那么就是4K
algo是分配算法,其实就是bitmap的查找算法,其实就是调用bitmap_find_next_zero_area()函数
data目前没什么用,可能用来存放私有数据

struct gen_pool {
	spinlock_t lock;
	struct list_head chunks;	/* list of chunks in this pool */
	int min_alloc_order;		/* minimum allocation order */

	genpool_algo_t algo;		/* allocation function */
	void *data;

	const char *name;
};
  • chunk直译就是大块,其实就表示内存块

avail当前内存块的可用内存大小,按字节算
phys_addr按理说应该是跟start_addr一样,但从代码中被设置为-1
start_addr表示起始物理地址
end_addr结束物理地址
bit[0] 表示bitmap

/*
 *  General purpose special memory pool chunk descriptor.
 */
struct gen_pool_chunk {
	struct list_head next_chunk;	/* next chunk in pool */
	atomic_t avail;
	phys_addr_t phys_addr;		/* physical starting address of memory chunk */
	unsigned long start_addr;	/* start address of memory chunk */
	unsigned long end_addr;		/* end address of memory chunk (inclusive) */
	unsigned long bits[0];		/* bitmap for allocating memory chunk */
};
  • gen_pool创建
struct gen_pool *gen_pool_create(int min_alloc_order, int nid)
{
	struct gen_pool *pool;

	pool = kmalloc_node(sizeof(struct gen_pool), GFP_KERNEL, nid);
	if (pool != NULL) {
		spin_lock_init(&pool->lock);
		INIT_LIST_HEAD(&pool->chunks);
		pool->min_alloc_order = min_alloc_order;
		pool->algo = gen_pool_first_fit; //初始化查找算法,起始就是bitmap的查找
		pool->data = NULL;
		pool->name = NULL;
	}
	return pool;
}
  • 将内存加入到gen_pool中,中间会创建chunk来存放和管理该内存
  • gen_pool_add()->gen_pool_add_virt()函数:
int gen_pool_add_virt(struct gen_pool *pool, unsigned long virt, phys_addr_t phys,
		 size_t size, int nid)
{
	struct gen_pool_chunk *chunk;
    //根据一个bit代表多大内存,计算出当前内存需要多少个bit
	int nbits = size >> pool->min_alloc_order;
    //计算chunk结构体大小+需要表示内存大小的bit位数转换成long型变量,总共需要多少内存
	int nbytes = sizeof(struct gen_pool_chunk) +
				BITS_TO_LONGS(nbits) * sizeof(long);

    //申请chunk对象,使用nbytes
	chunk = kzalloc_node(nbytes, GFP_KERNEL, nid);
	if (unlikely(chunk == NULL))
		return -ENOMEM;

    //初始化chunk中的内存信息,包括内存起始地址,结束地址,avail可用内存大小(size是字节)
	chunk->phys_addr = phys;
	chunk->start_addr = virt;
	chunk->end_addr = virt + size - 1;
	atomic_set(&chunk->avail, size);

	spin_lock(&pool->lock);
    //最后将当前chunk加入到gen_pool链表中
	list_add_rcu(&chunk->next_chunk, &pool->chunks);
	spin_unlock(&pool->lock);

	return 0;
}
  • 创建完成

carvout heap分配

  • ion_carveout_heap_allocate()函数实现分配:
static int ion_carveout_heap_allocate(struct ion_heap *heap,
				      struct ion_buffer *buffer,
				      unsigned long size,
				      unsigned long flags)
{
	struct sg_table *table;
	phys_addr_t paddr;
	int ret;

	table = kmalloc(sizeof(*table), GFP_KERNEL);
	if (!table)
		return -ENOMEM;
    //我们看到通过ion方式申请到的内存都是通过sg_table来组织
	ret = sg_alloc_table(table, 1, GFP_KERNEL);
	if (ret)
		goto err_free;
    //关键函数,申请内存,这里申请过来的是物理地址,因为gen_pool中存放的就是物理地址,通过bitmap来管理
	paddr = ion_carveout_allocate(heap, size);
	if (paddr == ION_CARVEOUT_ALLOCATE_FAIL) {
		ret = -ENOMEM;
		goto err_free_table;
	}
    //将申请到的内存加入到sg_table中
    //生成的buffer最终都在ion_alloc总入口处转换成dma_buf,并最终转换成fd句柄返回给用户层
    sg_set_page(table->sgl, pfn_to_page(PFN_DOWN(paddr)), size, 0);
	buffer->sg_table = table;

	return 0;

err_free_table:
	sg_free_table(table);
err_free:
	kfree(table);
	return ret;
}
  • 调用ion_carveout_allocate()->gen_pool_alloc()->gen_pool_alloc_algo()真正实现分配:
unsigned long gen_pool_alloc_algo(struct gen_pool *pool, size_t size,
		genpool_algo_t algo, void *data)
{
	struct gen_pool_chunk *chunk;
	unsigned long addr = 0;
	int order = pool->min_alloc_order;
	int nbits, start_bit, end_bit, remain;

#ifndef CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG
	BUG_ON(in_nmi());
#endif

	if (size == 0)
		return 0;

    //假如一个bit代表4K的内存(当前是4K),总共需要多少个bit才能表示用户申请的内存size
	nbits = (size + (1UL << order) - 1) >> order;
	rcu_read_lock();
    //遍历当前gen_pool中所有的chunk内存块(一般ION都只创建一块),找到bitmap中连续nbits个bit为0的位
	list_for_each_entry_rcu(chunk, &pool->chunks, next_chunk) {
		if (size > atomic_read(&chunk->avail))
			continue;

		start_bit = 0;
        //计算当前chunk中末位bit的下标
		end_bit = chunk_size(chunk) >> order;
retry:
        //开始查找,algo算法实际在创建gen_pool时已经指定,其实就是在bits中查找满足要求的位
		start_bit = algo(chunk->bits, end_bit, start_bit,
				 nbits, data, pool);
		if (start_bit >= end_bit)
			continue;
        //对找到的满足要求的bits位置1,表示分配出去
		remain = bitmap_set_ll(chunk->bits, start_bit, nbits);
		if (remain) {
			remain = bitmap_clear_ll(chunk->bits, start_bit,
						 nbits - remain);
			BUG_ON(remain);
			goto retry;
		}
        //然后计算出分配到的内存实际的起始物理地址,start_addr+偏移地址
		addr = chunk->start_addr + ((unsigned long)start_bit << order);
		size = nbits << order;
        //更新当前chunk中可用的物理内存大小
		atomic_sub(size, &chunk->avail);
		break;
	}
	rcu_read_unlock();
    //返回申请到的内存起始物理地址
	return addr;
}
  • 分配完成

carvout heap释放

  • 释放主要调用ion_carveout_free()->gen_pool_free()函数:
void gen_pool_free(struct gen_pool *pool, unsigned long addr, size_t size)
{
	struct gen_pool_chunk *chunk;
	int order = pool->min_alloc_order;
	int start_bit, nbits, remain;

#ifndef CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG
	BUG_ON(in_nmi());
#endif

    //计算出需要释放的内存大小转换成bit代表的内存,总共需要多少个bit来表示
    //比如释放5K内存,由于order是4K,那么转换出来就是2个bit,也就是8K
    //不过,由于在分配时已经按order对齐,所以一般传递进来的内存应该也是order对齐的
	nbits = (size + (1UL << order) - 1) >> order;
	rcu_read_lock();
	list_for_each_entry_rcu(chunk, &pool->chunks, next_chunk) {
        //遍历所有的chunk,找到当前要释放的内存物理地址所属的chunk
        //通过物理地址范围确定所属chunk
		if (addr >= chunk->start_addr && addr <= chunk->end_addr) {
            //开始释放
			BUG_ON(addr + size - 1 > chunk->end_addr);
            //计算释放内存的地址对应的bitmap的起始bit
			start_bit = (addr - chunk->start_addr) >> order;
            //从起始bit的连续nbits个位置0,表示释放
			remain = bitmap_clear_ll(chunk->bits, start_bit, nbits);
			BUG_ON(remain);
            //计算释放的内存,并更新chunk中的avail可用内存大小
			size = nbits << order;
			atomic_add(size, &chunk->avail);
			rcu_read_unlock();
			return;
		}
	}
	rcu_read_unlock();
	BUG();
}
  • 释放完成