memory pool 原理及使用

chipset: msm8x25

codebase: android4.1


一、	初始化:

int __init memory_pool_init(void)
{
	int i;

	alloc_root = RB_ROOT;
	mutex_init(&alloc_mutex);
	for (i = 0; i < ARRAY_SIZE(mpools); i++) {
		mutex_init(&mpools[i].pool_mutex);
		mpools[i].gpool = NULL;
	}

	return 0;
}


Mpools结构体如下,最多能存放8个,存放类型由平台自己决定:
#define MAX_MEMPOOLS 8
struct mem_pool mpools[MAX_MEMPOOLS];
struct mem_pool {
	struct mutex pool_mutex;
	struct gen_pool *gpool;
	unsigned long paddr;		//存放的是物理或者虚拟地址都可以。
	unsigned long size;			//pool 的size大小。
	unsigned long free;			//还有多少空闲部分可用。
	unsigned int id;
};

本平台定义的type如下:
enum {
	MEMTYPE_NONE = -1,
	MEMTYPE_SMI_KERNEL = 0,
	MEMTYPE_SMI,
	MEMTYPE_EBI0,
	MEMTYPE_EBI1,
	MEMTYPE_MAX,
};

下面函数是和平台相关,其中调用了kernel中的initialize_memory_pool函数,
当然自己使用的时候也可用按照这种写法:
static void __init initialize_mempools(void)
{
	struct mem_pool *mpool;
	int memtype;
	struct memtype_reserve *mt;

	//保留内存相关信息,其实type为MEMTYPE_EBI0部分才有size,
因为平台用的就是EBI1接口的DDR。
	mt = &reserve_info->memtype_reserve_table[0];
	for (memtype = 0; memtype < MEMTYPE_MAX; memtype++, mt++) {
		if (!mt->size)
			continue;
		//依次将平台所用到的保留内存信息保存到mpool中。
		mpool = initialize_memory_pool(mt->start, mt->size, memtype);
		if (!mpool)
			pr_warning("failed to create %s mempool\n",
				memtype_name[memtype]);
	}
}

好了,看公共的函数initialize_memory_pool:
struct mem_pool *initialize_memory_pool(unsigned long start,
	unsigned long size, int mem_type)
{
	int id = mem_type;

	//类型不符合或者size小于4k就返回
	if (id >= MAX_MEMPOOLS || size <= PAGE_SIZE || size % PAGE_SIZE)
		return NULL;

	mutex_lock(&mpools[id].pool_mutex);

	mpools[id].paddr = start;	//保留内存的虚拟地址,注意是虚拟地址。
	mpools[id].size = size;		//能使用的总size
	mpools[id].free = size;		//空闲size,一开始肯定和总size一样。
	mpools[id].id = id;
	mutex_unlock(&mpools[id].pool_mutex);

	pr_info("memory pool %d (start %lx size %lx) initialized\n",
		id, start, size);
	return &mpools[id];
}

二、	使用:
平台提供了两种接口供我们分配mempool:allocate_contiguous_ebi 和 allocate_contiguous_ebi_nomap, 区别只在于是否map。
void *allocate_contiguous_ebi(unsigned long size,
	unsigned long align, int cached)
{
	return allocate_contiguous_memory(size, get_ebi_memtype(),
		align, cached);
}
EXPORT_SYMBOL(allocate_contiguous_ebi);

unsigned long allocate_contiguous_ebi_nomap(unsigned long size,
	unsigned long align)
{
	return _allocate_contiguous_memory_nomap(size, get_ebi_memtype(),
		align, __builtin_return_address(0));
}
EXPORT_SYMBOL(allocate_contiguous_ebi_nomap);

static int get_ebi_memtype(void)
{
	/* on 7x30 and 8x55 "EBI1 kernel PMEM" is really on EBI0 */
	if (cpu_is_msm7x30() || cpu_is_msm8x55())
		return MEMTYPE_EBI0;
	//平台返回的是这个。
	return MEMTYPE_EBI1;
}
其实对应地就是调用了kernel的分配连续内存接口,就看allocate_contiguous_memory如何实现。

void *allocate_contiguous_memory(unsigned long size,
	int mem_type, unsigned long align, int cached)
{
	//叶框对齐
	unsigned long aligned_size = PFN_ALIGN(size);
	struct mem_pool *mpool;

	mpool = mem_type_to_memory_pool(mem_type);
	if (!mpool)
		return NULL;
	return __alloc(mpool, aligned_size, align, cached,
		__builtin_return_address(0));

}

先看mem_type_to_memory_pool:
static struct mem_pool *mem_type_to_memory_pool(int mem_type)
{
	//取得mem_type对应的mpool.
	struct mem_pool *mpool = &mpools[mem_type];

	//这里只有MEMTYPE_EBI1对应的size有赋值,
所以其他的mpool都直接返回。
	if (!mpool->size)
		return NULL;

	mutex_lock(&mpool->pool_mutex);
	//初始化gpool
	if (!mpool->gpool)
		mpool->gpool = initialize_gpool(mpool->paddr, mpool->size);
	mutex_unlock(&mpool->pool_mutex);
	if (!mpool->gpool)
		return NULL;

	return mpool;
}

static struct gen_pool *initialize_gpool(unsigned long start,
	unsigned long size)
{
	struct gen_pool *gpool;

	//先创建gpool
	gpool = gen_pool_create(PAGE_SHIFT, -1);

	if (!gpool)
		return NULL;
	//添加gen pool 
	if (gen_pool_add(gpool, start, size, -1)) {
		gen_pool_destroy(gpool);
		return NULL;
	}

	return gpool;
}

struct gen_pool *gen_pool_create(int min_alloc_order, int nid)
{
	struct gen_pool *pool;
	//比较简单,分配gen_pool空间。
	pool = kmalloc_node(sizeof(struct gen_pool), GFP_KERNEL, nid);
	if (pool != NULL) {
		spin_lock_init(&pool->lock);
		INIT_LIST_HEAD(&pool->chunks);
		// min_alloc_order为PAGE_SHIFT =12.
		pool->min_alloc_order = min_alloc_order;
	}
	return pool;
}

static inline int gen_pool_add(struct gen_pool *pool, unsigned long addr,
			       size_t size, int nid)
{
	return gen_pool_add_virt(pool, addr, -1, size, nid);
}

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;
	//看意思是一个PAGE_SIZE作为一个bit来计算。
	int nbits = size >> pool->min_alloc_order;
	//nbits都存放在gen_pool_chunk的bits[0]数组中,用bitmap来管理。
	int nbytes = sizeof(struct gen_pool_chunk) +
				(nbits + BITS_PER_BYTE - 1) / BITS_PER_BYTE;

	//分配struct gen_pool_chunk空间。
	if (nbytes <= PAGE_SIZE)
		chunk = kmalloc_node(nbytes, __GFP_ZERO, nid);
	else
		chunk = vmalloc(nbytes);
	if (unlikely(chunk == NULL))
		return -ENOMEM;
	if (nbytes > PAGE_SIZE)
		memset(chunk, 0, nbytes);

	chunk->phys_addr = phys;	//保存物理地址,传进来的是-1,说明还没计算出来。
	chunk->start_addr = virt;	//其实这个值是虚拟或者物理地址都可以。如果是//物理地址,就调用allocate_contiguous_memory,会ioremap一次。否则使用//_allocate_contiguous_memory_nomap就可以了。
	chunk->end_addr = virt + size;    //chuank结束地址。
	atomic_set(&chunk->avail, size);  //保存当前chunk有效size到avail中。

	spin_lock(&pool->lock);
//以rcu的形式添加到pool的chunks列表中。

	list_add_rcu(&chunk->next_chunk, &pool->chunks);					 spin_unlock(&pool->lock);

	return 0;
}

再看__alloc,要动真格了:
static void *__alloc(struct mem_pool *mpool, unsigned long size,
	unsigned long align, int cached, void *caller)
{
	unsigned long paddr;
	void __iomem *vaddr;

	unsigned long aligned_size;
	int log_align = ilog2(align);

	struct alloc *node;

	aligned_size = PFN_ALIGN(size);
	//从gen pool去分配内存。
	paddr = gen_pool_alloc_aligned(mpool->gpool, aligned_size, log_align);
	if (!paddr)
		return NULL;

	node = kmalloc(sizeof(struct alloc), GFP_KERNEL);
	if (!node)
		goto out;

	//这里返回的肯定是物理内存,所以需要ioremap,调用、//_allocate_contiguous_memory_nomap那就不需要了。
	if (cached)
		vaddr = ioremap_cached(paddr, aligned_size);
	else
		vaddr = ioremap(paddr, aligned_size);

	if (!vaddr)
		goto out_kfree;

	node->vaddr = vaddr;
	//保留相对应参数到node节点中。
	node->paddr = paddr;
	node->len = aligned_size;
	node->mpool = mpool;
	node->caller = caller;
	//插入到红黑树中去管理。
	if (add_alloc(node))
		goto out_kfree;

	mpool->free -= aligned_size;

	return vaddr;
out_kfree:
	if (vaddr)
		iounmap(vaddr);
	kfree(node);
out:
	gen_pool_free(mpool->gpool, paddr, aligned_size);
	return NULL;
}

分析关键函数gen_pool_alloc_aligned:
unsigned long gen_pool_alloc_aligned(struct gen_pool *pool, size_t size,
				     unsigned alignment_order)
{
	struct gen_pool_chunk *chunk;
	unsigned long addr = 0, align_mask = 0;
	int order = pool->min_alloc_order;
	int nbits, start_bit = 0, remain;

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

	if (size == 0)
		return 0;

	if (alignment_order > order)
		align_mask = (1 << (alignment_order - order)) - 1;
	
	//获取当前申请size所对应的nbits数量。
	nbits = (size + (1UL << order) - 1) >> order;

	rcu_read_lock();
	//在当前pool的chunks列表上依次查询
	list_for_each_entry_rcu(chunk, &pool->chunks, next_chunk) {
		unsigned long chunk_size;
		if (size > atomic_read(&chunk->avail))
			continue;
		//本chunk所以拥有的总chunk size.
		chunk_size = (chunk->end_addr - chunk->start_addr) >> order;

retry:
		//寻找未被使用区域的start bit位置
		start_bit = bitmap_find_next_zero_area_off(chunk->bits, chunk_size,
						       0, nbits, align_mask,
						       chunk->start_addr);
		//如果超出chunk size,那么再看下一个chunk。
		if (start_bit >= chunk_size)
			continue;
		//没超出那就设置nbits的大小表示这部分内存已经被使用了
		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;
		}

		//获取当前申请size对应的address,这里为物理地址。
		addr = chunk->start_addr + ((unsigned long)start_bit << order);
		size = nbits << pool->min_alloc_order;
		//计算还有多少size可以供其他进程申请。
		atomic_sub(size, &chunk->avail);
		break;
	}
	rcu_read_unlock();
	return addr;
}

对于bitmap如何使用,这里就不具体追踪了,看函数名知道大概就可以了。



最后,我们看下_allocate_contiguous_memory_nomap,其实和上面的区别在于是否remap.
unsigned long _allocate_contiguous_memory_nomap(unsigned long size,
	int mem_type, unsigned long align, void *caller)
{
	unsigned long paddr;
	unsigned long aligned_size;

	struct alloc *node;
	struct mem_pool *mpool;
	int log_align = ilog2(align);

	mpool = mem_type_to_memory_pool(mem_type);
	if (!mpool || !mpool->gpool)
		return 0;

	aligned_size = PFN_ALIGN(size);
	paddr = gen_pool_alloc_aligned(mpool->gpool, aligned_size, log_align);
	if (!paddr)
		return 0;

	node = kmalloc(sizeof(struct alloc), GFP_KERNEL);
	if (!node)
		goto out;

	node->paddr = paddr;

	/* We search the tree using node->vaddr, so set
	 * it to something unique even though we don't
	 * use it for physical allocation nodes.
	 * The virtual and physical address ranges
	 * are disjoint, so there won't be any chance of
	 * a duplicate node->vaddr value.
	 */
	//区别就在于这一步,因为这个函数传进来的就是虚拟地址,所以我们没必要再ioremap了,直接使用。
	node->vaddr = (void *)paddr;
	node->len = aligned_size;
	node->mpool = mpool;
	node->caller = caller;
	if (add_alloc(node))
		goto out_kfree;

	mpool->free -= aligned_size;
	return paddr;
out_kfree:
	kfree(node);
out:
	gen_pool_free(mpool->gpool, paddr, aligned_size);
	return 0;
}


Mempool还是比较简单的,后续的ION的使用我们就会看到它使用了mempool了。


2013/01/23





你可能感兴趣的:(memory pool 原理及使用)