Zephyr内存管理之Block

本文分析说明Zephyr的内存块分配器(Memory Blocks Allocator)机制。

Zephyr提供内存块分配器(Memory Blocks Allocator),通过该分配器可以从指定的内存区域动态分配到内存块:

  • 同一个分配器中分配到的内存块大小都一样
  • 可以同时分配和释放多个内存块
  • 分配到一组的块可能不连续
  • 管理区和内存区分离

和slab的区别, slab:

  • 一次只能分配到一个slab
  • 管理区在内存区中

内存块分配器 链接到标题

内存分配器实现在: zephyr/include/zephyr/sys/mem_blocks.h zephyr/lib/os/mem_blocks.c

定义内存分配器 链接到标题

由宏进行内存块内存分配的API有两个,都是通过定义全局变量申请的buf,管理器管理这些内部buf

//定义一个名为name的内存分配器,block的大小为blk_sz, 总计有num_blks个block,内存按照buf_align对齐
//可以跨文件使用
#define SYS_MEM_BLOCKS_DEFINE(name, blk_sz, num_blks, buf_align)

//定义一个名为name的内存分配器,block的大小为blk_sz, 总计有num_blks个block,内存按照buf_align对齐
//定义的内存块分配器为static不能跨文件使用
#define SYS_MEM_BLOCKS_DEFINE_STATIC(name, blk_sz, num_blks, buf_align)

以上两个宏都是由SYS_MEM_BLOCKS_DEFINE实现,STATIC的mbmod使用staic修饰,可以看到定义了一个未初始的全局变量,大小为num_blks * WB_UP(blk_sz

#define _SYS_MEM_BLOCKS_DEFINE(name, blk_sz, num_blks, balign, mbmod)	\
	mbmod uint8_t __noinit_named(sys_mem_blocks_buf_##name)		\
		__aligned(WB_UP(balign))				\
		_sys_mem_blocks_buf_##name[num_blks * WB_UP(blk_sz)];	\
	_SYS_MEM_BLOCKS_DEFINE_WITH_EXT_BUF(name, blk_sz, num_blks,	\
					   _sys_mem_blocks_buf_##name,	\
					   mbmod);

由宏为内存块分配器指定外部内存的API有两个,管理器管理外部内存

//定义一个名为name的内存分配器,block的大小为blk_sz, 总计有num_blks个block,block使用的内存由buf指定
//可以跨文件使用
#define SYS_MEM_BLOCKS_DEFINE_WITH_EXT_BUF(name, blk_sz, num_blks, buf)
//定义一个名为name的内存分配器,block的大小为blk_sz, 总计有num_blks个block,block使用的内存由buf指定
//定义的内存块分配器为static不能跨文件使用
#define SYS_MEM_BLOCKS_DEFINE_STATIC_WITH_EXT_BUF(name, blk_sz, num_blks, buf)

4中定义内存块分配器的管理结构都是通过_SYS_MEM_BLOCKS_DEFINE_WITH_EXT_BUF定义, 创建一个Bitmap和sys_mem_blocks_t

#define _SYS_MEM_BLOCKS_DEFINE_WITH_EXT_BUF(name, blk_sz, num_blks, buf, mbmod) \
	_SYS_BITARRAY_DEFINE(_sys_mem_blocks_bitmap_##name,		\
			     num_blks, mbmod);				\
	mbmod sys_mem_blocks_t name = {					\
		.num_blocks = num_blks,					\               //内存块的数量
		.blk_sz_shift = ilog2(blk_sz),				\           //内存块的大小,2的N次方,这里算log2,记录的就是N
		.buffer = buf,			        			\           //管理的内存
		.bitmap = &_sys_mem_blocks_bitmap_##name,		\       //管理内存的bitmap
	}

分配内存块 链接到标题

sys_mem_blocks_alloc分配的内存块之间可能不连续,sys_mem_blocks_alloc_contiguous分配的内存块之间是连续的,实现如下

int sys_mem_blocks_alloc(sys_mem_blocks_t *mem_block, size_t count,
			 void **out_blocks)
{
	int ret = 0;
	int i;

	__ASSERT_NO_MSG(mem_block != NULL);
	__ASSERT_NO_MSG(out_blocks != NULL);
	__ASSERT_NO_MSG(mem_block->bitmap != NULL);
	__ASSERT_NO_MSG(mem_block->buffer != NULL);

	if (count == 0) {
		/* Nothing to allocate */
		goto out;
	}
    //剩余内存块不足,直接退出
	if (count > mem_block->num_blocks) {
		/* Definitely not enough blocks to be allocated */
		ret = -ENOMEM;
		goto out;
	}

    //逐个分配内存块,内存块之间的地址可能不连续
	for (i = 0; i < count; i++) {
		void *ptr = alloc_blocks(mem_block, 1);

		if (ptr == NULL) {
			break;
		}

		out_blocks[i] = ptr;
	}

	//如果内存块数量未分配够,将已分配的释放并返回错误
	if (i < count) {
		(void)sys_mem_blocks_free(mem_block, i, out_blocks);
		ret = -ENOMEM;
	}

out:
	return ret;
}

int sys_mem_blocks_alloc_contiguous(sys_mem_blocks_t *mem_block, size_t count,
				   void **out_block)
{
	int ret = 0;

	__ASSERT_NO_MSG(mem_block != NULL);
	__ASSERT_NO_MSG(out_block != NULL);

	if (count == 0) {
		/* Nothing to allocate */
		*out_block = NULL;
		goto out;
	}

    //剩余内存块不足,直接退出
	if (count > mem_block->num_blocks) {
		/* Definitely not enough blocks to be allocated */
		ret = -ENOMEM;
		goto out;
	}

    //分配连续的内存块
	void *ptr = alloc_blocks(mem_block, count);

	if (ptr == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	*out_block = ptr;


out:
		return ret;
}

释放内存块 链接到标题

对应的sys_mem_blocks_free用于释放非连续性的内存块,sys_mem_blocks_free_contiguous用于释放连续性的内存块

int sys_mem_blocks_free(sys_mem_blocks_t *mem_block, size_t count,	void **in_blocks);

int sys_mem_blocks_free_contiguous(sys_mem_blocks_t *mem_block, void *block, size_t count);

调用的内部接口都是free_blocks,只是sys_mem_blocks_free每次都释放1个,而sys_mem_blocks_free_contiguous释放全部,过程和分配对应这里就不列出代码分析了

释放分配内部函数 链接到标题

从前面分析可以看到释放和分配用alloc_blocksfree_blocks实现,块分配器使用bitmap进行管理,这里进行代码分析

static void *alloc_blocks(sys_mem_blocks_t *mem_block, size_t num_blocks)
{
	size_t offset;
	int r;
	uint8_t *blk;
	void *ret = NULL;


	//block使用bitmap进行标记哪些用了哪些没用,因此在bitmap中查找是否有空闲的block,并做标记
    //返回的offset就是分配到block的index
	r = sys_bitarray_alloc(mem_block->bitmap, num_blocks, &offset);
	if (r == 0) {

		//通过index计算出block的起始地址
		blk = mem_block->buffer + (offset << mem_block->blk_sz_shift);

		ret = blk;
	}

	return ret;
}

static int free_blocks(sys_mem_blocks_t *mem_block, void *ptr, size_t num_blocks)
{
	size_t offset;
	uint8_t *blk = ptr;
	int ret = 0;

	//确认block在管理器管理的范围内
	if (blk < mem_block->buffer) {
		ret = -EFAULT;
		goto out;
	}

    //通过地址计算出block在bitmap内的index
	offset = (blk - mem_block->buffer) >> mem_block->blk_sz_shift;
	if (offset >= mem_block->num_blocks) {
		ret = -EFAULT;
		goto out;
	}

    //将bitmap内的标记清0
	ret = sys_bitarray_free(mem_block->bitmap, num_blocks, offset);


out:
	return ret;
}

强制分配 链接到标题

sys_mem_blocks_get从指定的块地址in_block开始强制分配连续的count个block,sys_mem_blocks_get指定了要从哪个block开始分配,而sys_mem_blocks_alloc则是由系统自行安排

int sys_mem_blocks_get(sys_mem_blocks_t *mem_block, void *in_block, size_t count)
{
	int ret = 0;
	int offset;

	__ASSERT_NO_MSG(mem_block != NULL);
	__ASSERT_NO_MSG(mem_block->bitmap != NULL);
	__ASSERT_NO_MSG(mem_block->buffer != NULL);

	if (count == 0) {
		/* Nothing to allocate */
		goto out;
	}

    //通过block size计算出在bitmap中的标记起始位置
	offset = ((uint8_t *)in_block - mem_block->buffer) >> mem_block->blk_sz_shift;

	if (offset + count > mem_block->num_blocks) {
		/* Definitely not enough blocks to be allocated */
		ret = -ENOMEM;
		goto out;
	}

    //将bitmap从offset开始后的count个标志位如果没有使用,着置位占用
	ret = sys_bitarray_test_and_set_region(mem_block->bitmap, count, offset, true);

	if (ret != 0) {
		ret = -ENOMEM;
		goto out;
	}

out:
	return ret;
}

检查block是否未分配 链接到标题

sys_mem_blocks_is_region_free用于检查指定in_block开始的count个block是否未分配

int sys_mem_blocks_is_region_free(sys_mem_blocks_t *mem_block, void *in_block, size_t count)
{
	bool result;
	size_t offset;

	__ASSERT_NO_MSG(mem_block != NULL);
	__ASSERT_NO_MSG(mem_block->bitmap != NULL);
	__ASSERT_NO_MSG(mem_block->buffer != NULL);

    //计算block在bitmap内的位置
	offset = ((uint8_t *)in_block - mem_block->buffer) >> mem_block->blk_sz_shift;

	__ASSERT_NO_MSG(offset + count <= mem_block->num_blocks);

    //检查从该位置开始count个标记是否为空闲
	result = sys_bitarray_is_region_cleared(mem_block->bitmap, count, offset);
	return result;
}

多块分配器 链接到标题

Zephyr提供一个多块分配器将块分配器编为一个组,分配块时从这一组块分配器中按照注册的sys_multi_mem_blocks_choice_fn_t选出块分配器进行分配。多块分配器使用下面结构体进行组织管理

#define MAX_MULTI_ALLOCATORS 8

struct sys_multi_mem_blocks {
	/* Number of allocators in this group */
	int num_allocators; //组内块分配器的数量
	sys_multi_mem_blocks_choice_fn_t choice_fn; //块分配器选择函数
	sys_mem_blocks_t *allocators[MAX_MULTI_ALLOCATORS];     //块分配器信息,最多支持8个块分配器
};

由于多块分配器的实现比较简单,下面就只说明API作用和实现方法 初始化多块分配器group,注册块分配器选择函数choice_fn, 主要是完成sys_multi_mem_blocksnum_allocatorschoice_fn的初始化

void sys_multi_mem_blocks_init(sys_multi_mem_blocks_t *group,
			       sys_multi_mem_blocks_choice_fn_t choice_fn);

将堆分配器alloc加入到多块分配器group中,主要是修改num_allocators,并将block信息拷贝进allocators

void sys_multi_mem_blocks_add_allocator(sys_multi_mem_blocks_t *group,
					sys_mem_blocks_t *alloc);

从多堆分配器group中分配配置为cfg,数量为count的block, out_blocks为分配到的block地址, blk_size为分配到block的大小。 该函数调用choice_fn选择器根据cfg选择出堆分配器,再从分配器中分配连续的count个block

int sys_multi_mem_blocks_alloc(sys_multi_mem_blocks_t *group,
			       void *cfg, size_t count,
			       void **out_blocks,
			       size_t *blk_size);

释放in_blocks开始连续count个块回多堆管理器group中,该函数会先对比要释放的block属于allocators中的哪一个多堆分配器,再进行释放

int sys_multi_mem_blocks_free(sys_multi_mem_blocks_t *group,
			      size_t count, void **in_blocks);

参考 链接到标题

https://docs.zephyrproject.org/3.4.0/kernel/memory_management/index.html