genalloc — 通用内存分配器

genalloc 是 linux 内核提供的通用内存分配器,源码位于 lib/genalloc.c。这个分配器为独立于内核以外的内存块提供分配方法,采用的是最先适配原则,android 最新的ION 内存管理器对 ION_HEAP_TYPE_CARVEOUT 类型的内存就是采用的这个分配器。

1、基础数据结构

首先看下分配器用到的几个数据结构,struct gen_pool 用来描述一个内存池:

struct gen_pool {
	rwlock_t lock;             /* 链表读写锁 */
	struct list_head chunks;   /* 内存池中内存块的链表 */
	int min_alloc_order;       /* 内存池最小分配单元的阶数,大小为 2^min_alloc_order */
};
在使用的时候需要向内存池中加入内存块,一个内存块即一大块连续的物理内存,用  struct gen_pool_chunk 来描述:

struct gen_pool_chunk {
	spinlock_t lock;              /* 操作内存块时用到的自旋锁 */
	struct list_head next_chunk;  /* 加入内存池的节点 */
	unsigned long start_addr;     /* 内存块的起始地址 */
	unsigned long end_addr;       /* 内存块的结束地址 */
	unsigned long bits[0];        /* 内存块的位图 */
};

2、函数接口及调用方法

genalloc 用到的函数接口有下面几个:

/* 创建一个内存池,主要工作是完成 struct gen_pool 的初始化 */
struct gen_pool *gen_pool_create(int min_alloc_order, int nid);
/* 向内存池中加入内存块,addr 为起始地址,size 为大小 */
int gen_pool_add(struct gen_pool *pool, unsigned long addr, size_t size, int nid);
/* 销毁一个内存池 */
void gen_pool_destroy(struct gen_pool *pool);
/* 内存池分配内存的函数 */
unsigned long gen_pool_alloc(struct gen_pool *pool, size_t size);
/* 内存池释放内存的函数 */
void gen_pool_free(struct gen_pool *pool, unsigned long addr, size_t size);
对通用内存分配器的一般使用方法如下:
/* 初始化内存池,需要创建以及加入内存块,参数为:起始地址、大小、最小分配阶数 */
static void *mm_init(uint32_t addr, uint32_t size, uint32_t order)
{
	struct gen_pool *pool;

	pool = gen_pool_create(order, 0);
	if (pool == NULL) {
		return NULL;
	}

	if (gen_pool_add(pool, addr, size, 0) != 0) {
		gen_pool_destroy(pool);

		return NULL;
	}

	return pool;
}

/* 销毁内存池 */
static void mm_exit(void *handle)
{
	gen_pool_destroy(handle);
}

/* 分配函数 */
static uint32_t mm_alloc(void *handle, uint32_t size)
{
	return gen_pool_alloc(handle, size);
}

/* 释放函数 */
static void mm_free(void *handle, uint32_t addr, uint32_t size)
{
	return gen_pool_free(handle, addr, size);
}

/* 提供给上一级内存管理器调用 */
struct xxx_mem_ops mm_ops = {
	.init = mm_init,
	.exit = mm_exit,
	.alloc = mm_alloc,
	.free = mm_free,
};
3、分配函数解析

genalloc 通过 gen_pool_alloc 函数来分配内存,下面我们分析一下这个函数的代码:

unsigned long gen_pool_alloc(struct gen_pool *pool, size_t size)
{
	struct list_head *_chunk;
	struct gen_pool_chunk *chunk;
	unsigned long addr, flags;
	int order = pool->min_alloc_order;
	int nbits, bit, start_bit, end_bit;

	if (size == 0)
		return 0;

	nbits = (size + (1UL << order) - 1) >> order;  /* 计算申请的内存需要几个连续的最小单元 */

	read_lock(&pool->lock);
	list_for_each(_chunk, &pool->chunks) {         /* 遍历内存池 */
		chunk = list_entry(_chunk, struct gen_pool_chunk, next_chunk);

		end_bit = (chunk->end_addr - chunk->start_addr) >> order;        /* 计算当前内存池长度 */
		end_bit -= nbits + 1;

		spin_lock_irqsave(&chunk->lock, flags);
		bit = -1;
		while (bit + 1 < end_bit) {  /* 循环查找最先适配的内存区 */
			bit = find_next_zero_bit(chunk->bits, end_bit, bit + 1);     /* 寻找为0的bit */
			if (bit >= end_bit)      /* 循环结束 */
				break;

			start_bit = bit;         /* 起始位置 */
			if (nbits > 1) {         /* 如果申请的内存大于一个最小单元,查找连续的nbits个单元 */
				bit = find_next_bit(chunk->bits, bit + nbits,bit + 1);
				if (bit - start_bit < nbits)
					continue;
			}

			addr = chunk->start_addr + ((unsigned long)start_bit << order);  /* 计算申请的内存的起始地址 */
			while (nbits--)
				__set_bit(start_bit++, chunk->bits);  /* 将申请到的单元全部标记为已用 */
			spin_unlock_irqrestore(&chunk->lock, flags);
			read_unlock(&pool->lock);
			return addr;
		}
		spin_unlock_irqrestore(&chunk->lock, flags);
	}
	read_unlock(&pool->lock);
	return 0;
}

因为是用的最先适配原则,所以逻辑比较简单,我们也可以根据自己的需求实现最适合分配器以及伙伴分配器。

附1、buddy 分配器

平台实现了一个 buddy 分配器,代码很精致:

struct buddy_unit {
	uint8_t			used:1;		/* 1 if allocated, 0 if free */
	uint8_t			order:7;	/* size of the region in buddy space */
};

struct buddy_pool {
	uint32_t		addr;		/* the start addr of the buddy area */
	uint32_t		size;		/* the total size of the buddy area */
	uint32_t		order;		/* the base order of each unit */

	uint32_t		total;		/* total units */
	uint32_t		free;		/* unused units */

	struct buddy_unit	*bitmap;	/* the bitmap of all buddy units */
	spinlock_t		lock;
};

struct buddy_pool *buddy_create(uint32_t addr, uint32_t size, uint32_t order)
{
	struct buddy_pool *pool;  /* buddy内存池 */
	uint32_t end;
	int index, uorder;

	/* 以 2^order = 2^15 = 32KB 为管理单元修正参数 */
	end = (addr + size) & ~((1 << order) - 1);  /* buddy区结束地址 */
	addr = addr & ~((1 << order) - 1);          /* buddy区起始地址 */

	if (end <= addr) {
		return NULL;
	}

	/* 实例化buddy内存池 */
	pool = kmalloc(sizeof(struct buddy_pool), GFP_KERNEL);
	if (pool == NULL) {
		return NULL;
	}

	pool->total = size >> order;  /* buddy区以32KB为单元的内存块总数 */

	/* 申请管理位图,每个单元占用一个字节 */
	pool->bitmap = kzalloc(sizeof(struct buddy_unit) * pool->total, GFP_KERNEL);
	if (pool->bitmap == NULL) {
		kfree(pool);
		return NULL;
	}

	/* 初始化各参数 */
	pool->free = pool->total;
	pool->addr = addr;
	pool->size = size;
	pool->order = order;
	spin_lock_init(&pool->lock);

	mdbg("create buddy: total=%d, order=%d\n", pool->total, pool->order);

	/* initialize the bitmap orders */
	index = 0;
	uorder = BUDDY_MAX_ORDER;  /* buddy区最大的分配阶数 */
	while (index < pool->total) {
		while (index + (1 << uorder) > pool->total) {
			uorder--;  /* e.g: 2^24 = 16MB, total = 2^9, uorder = 9 */
		}
		pool->bitmap[index].order = uorder; /* 只初始化buddy的第一个成员 */
		index += 1 << uorder;
	}

	return pool;
}

uint32_t buddy_alloc(struct buddy_pool *pool, uint32_t size)
{
	int i, order, buddy, index = -1;

	/* 获取申请内存长度的order */
	for (i = 0; i <= BUDDY_MAX_ORDER; i++) {
		if ((1 << (pool->order + i)) >= size) {
			break;
		}
	}
	if (i > BUDDY_MAX_ORDER) {
		minfo("size is too big: 0x%08x\n", size);
		return 0;
	}
	order = i;

	spin_lock(&pool->lock);
	i = 0;
	do {  /* 扫描管理位图 */
		mdbg("index = %d\n", index);
		/* 判断当前内存单元是否已经被申请 */
		if (pool->bitmap[i].used == 0) {  /* 如果没有被申请 */
			if (order == pool->bitmap[i].order) {  /* 如果当前order与申请的order相同则跳出循环 */
				/* matched order */
				index = i;  /* 将编号赋给index */
				break;
			} else if (order < pool->bitmap[i].order) {  /* 如果申请order小于当前order */
				/* get the best fit buddy unit */
				if (index == -1) {  /* 如果是第一次匹配 */
					index = i;
				} else if (pool->bitmap[i].order < pool->bitmap[index].order) {/* 当前order小于上次匹配的order */
					index = i;  /* 则将匹配order更新为当前值,这样可以保证是用的最小的buddy来分裂 */
				}
			}
		}
		i += 1 << pool->bitmap[i].order;  /* 移动到下一个可以分配单元 */
	} while (i < pool->total);

	/* no free buddy unit found */
	if (index == -1) {
		spin_unlock(&pool->lock);
		minfo("no buddy found!\n");

		return 0;
	}

	/* 如果申请order小于找到的order,则将找到的buddy分裂,直到相等 */
	while (order < pool->bitmap[index].order) {
		pool->bitmap[index].order--;  /* 将当前buddy的order减一 */
		/* Buddy# = Slot# ^ (1 << order) */
		buddy = index ^ (1 << pool->bitmap[index].order);       /* 找到分裂出来的buddy */
		pool->bitmap[buddy].order = pool->bitmap[index].order;  /* 将分裂出来的buddy的order也减一 */
	}

	pool->bitmap[index].used = 1;      /* 将找到的buddy标记为已用 */
	pool->bitmap[index].order = order; /* 重复赋值? */
	pool->free -= (1 << order);        /* 总的空闲单元数减去申请成功的单元数 */

	spin_unlock(&pool->lock);

	return pool->addr + (index << pool->order);  /* 返回申请到内存块的起始物理地址 */
}

你可能感兴趣的:(genalloc — 通用内存分配器)