sys_pool为Zephyr中Pool的内存分配管理算法,支持创建用户和内核两种模式的Pool,在Zephyr内存管理之Heap一文中已提到过在早期的Zephyr中内核使用Pool进行动态内存管理,k_malloc/k_free等函数都是使用的Pool管理内存。在最新的Zephyr中已经通过默认配置CONFIG_MEM_POOL_HEAP_BACKEND=y,将内核的动态内存管理变为Heap来实现。虽然Pool已经从Zephyr的Kernel中移除,但Zephyr的内置的minimal stdlib动态分配内存和Lvgl的porting依然使用的是Pool,因此分析Pool的内存管理原理还是有实际意义。
Pool管理的是一片固定大小的连续内存区域,用户可以在Pool管理的内存区域中动态分配指定长度的内存。sys_pool采用伙伴系统内存内存管理算法,sys_pool本身时是线程安全的管理算法。其代码在下列几个位置:
- include/sys/mempool_base.h : sys_pool基础的数据结构,宏和函数
- lib/os/mempool.h :sys_pool对外的数据结构和函数
- lib/os/mempool.c :sys_pool实现代码
Block
使用伙伴管理系统的Pool中内存以Block的形式进行管理,从Pool中分配内存就是取出一个可用的Block,在初始化Block时会指定最大的Block和最先的Block,每次分配内存的过程就是从较大的Block向较小的Block进行分割,直到分割出来的大小和分配内存大小一致。从Pool中能够分配最大内存不能超过最大的Block,分配的内存小于最小的Block就会给与最小的Block。在Zephyr中伙伴系统的数量是4,也就是说一次分割会将大的Block分为4个小的Block,然后取出其中一个Block进行分配或者进行下一次分割。

在Pool中最大的Block处于level 0,每分割一次level增加1,在每个level中对block依次编号,一个block的位置和大小可以由level和block编号唯一确认。一个Block的数据结构如下:
| 1 | struct sys_mem_pool_block { | 
pool 指向block所属的pool控制块。level block的level,占4bit,最大为15,最大的block最多可以被分为$4^{15}=65536$个blockblock block的编号,占28bit
任意一个被分配的block的最开始都会放置一个struct sys_mem_pool_block用于管理该block,从该结构体之后的内存才是分配出的可用内存。Pool中空闲的block会在最开始放置sys_dnode_t,将该Block放入空闲链表。

Pool管理
Pool定义
在使用Pool前需要先定义Pool,定义Pool主要是完成Pool内存的分配和Pool管理结构的分配,在mempool.h中提供了下面宏定义一个Pool:
| 1 | 
name, pool的名字ignored, 忽略字段无意义minsz, pool中最小的block size,长度必须是4的倍数且大于0maxsz, pool中最大的block size,长度必须是minsz的倍数,且倍数是4的幂(1,4,16,64,…)nmax, 最大block的数量, pool管理的内存空间maxsz*nmaxalign, pool buffer起始地址对齐,必须是2的幂,且必须 要大于等于4section, pool放置的section名
使用SYS_MEM_POOL_DEFINE定义Pool代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	char __aligned(WB_UP(align)) Z_GENERIC_SECTION(section)		\
		_mpool_buf_#
				  + _MPOOL_BITS_SIZE(maxsz, minsz, nmax)]; \
	struct sys_mem_pool_lvl Z_GENERIC_SECTION(section)		\
		_mpool_lvls_#
	Z_GENERIC_SECTION(section) struct sys_mem_pool name = {		\
		.base = {						\
			.buf = _mpool_buf_##name,			\
			.max_sz = WB_UP(maxsz),				\
			.n_max = nmax,					\
			.n_levels = Z_MPOOL_LVLS(maxsz, minsz),		\
			.levels = _mpool_lvls_##name,			\
			.flags = SYS_MEM_POOL_USER			\
		}							\
	};
- 定义一个align对齐的全局数组作为Pool buffer。该数组前maxsz*nmax字节为Pool内存分配区,的该数组的末尾还有一段bitmap用于标记block是否被使用。
- 根据内存分配区的大小计算出block level总数n_levels,定义一个含有n_levels个struct sys_mem_pool_lvl的结构体数组,用于管理不同level的block。
- 定义一个结构体变量struct sys_mem_pool,该结构体变量用于管理和存储Pool的信息。
level
一个Pool的level由其定义时的maxsz和minsz决定,maxsz通过4分裂变为minsz的次数就是一个Pool的level数,例如maxsz每次分成等分的4块,连续分裂3次后变为minsz,即maxsz/4/4/4 = minsz,那么level就是3。
Pool为每个level的block提供一个被名为struct sys_mem_pool_lvl的管理结构,该结构中包含一个free_list和一个bitmap,对应level的空闲的block将加入到free_list,当block被使用时会从free_list中取出,被取出的block在bitmap中对应的bit将被置1表示已经被使用。
| 1 | struct sys_mem_pool_lvl { | 
bits:block使用情况的bitmap,当level管理的block小于等于32个时使用bits存储bitmapbits_p:bits_p和bits共用4字节空间, 当level管理的block大于32个的时候bits已经无法直接存储,因此当作bits_p用,放置一个指针,指向在Pool buffer末尾更大的bitmap。某个level的block bitmap能够在bits中直接存储,这个level被称作inline level。free_list:对应level下的空闲block将会加入到该双向链表中进行管理
Pool buffer管理结构
Pool buffer的管理结构如下,定义的时候将对其中的struct sys_mem_pool_base进行初始化
| 1 | struct sys_mem_pool { | 
base:用于存储和管理Pool的信息mutex:内存分配和释放操作时上锁,保证sys_pool操作的线程安全。
| 1 | struct sys_mem_pool_base { | 
buf:指向Pool buffermax_sz:最大block的大小n_max:最大block的数量,实际管理的pool buffer大小就是n_max*max_szn_levels:Pool的levelmax_inline_level:最大的inline level,超过该max_inline_level的level bitmap存放在pool buffer后面的bitmap。levels:指向struct sys_mem_pool_lvl管理结构数组。flags:Pool属性,SYS_MEM_POOL_KERNEL和SYS_MEM_POOL_USER可选,当选择kernel时在pool内存管理操作时会锁irq保护。由于目前Pool不再被用于内核,而使用SYS_MEM_POOL_DEFINE定义的pool都将使用SYS_MEM_POOL_USER。
Pool初始化
Pool初始化主要是完成free_list的建立和max_inline_level的确认,代码如下:
| 1 | static inline void sys_mem_pool_init(struct sys_mem_pool *p) | 
分配内存
从Pool中分配内存,就是从Pool中找到和要分配内存大小匹配的Block,如果没有匹配的Block就从大的Block分裂出来匹配的Block,分配内存代码分析如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31void *sys_mem_pool_alloc(struct sys_mem_pool *p, size_t size)
{
	struct sys_mem_pool_block *blk;
	uint32_t level, block;
	char *ret;
	
	//多线程保护
	sys_mutex_lock(&p->mutex, K_FOREVER);
	//分配的内存以block管理,在最开始放置block header信息,后面跟随的是可用内存
	size += WB_UP(sizeof(struct sys_mem_pool_block));
	
	//按照计算的size分配block
	if (z_sys_mem_pool_block_alloc(&p->base, size, &level, &block,
				      (void **)&ret)) {
		ret = NULL;
		goto out;
	}
	//更新分配的block信息
	blk = (struct sys_mem_pool_block *)ret;
	blk->level = level;
	blk->block = block;
	blk->pool = p;
	
	//刨除block header部分内容,返回实际可用的内存地址
	ret += WB_UP(sizeof(struct sys_mem_pool_block));
out:
	sys_mutex_unlock(&p->mutex);
	return ret;
}
| 1 | int z_sys_mem_pool_block_alloc(struct sys_mem_pool_base *p, size_t size, | 
分割block的流程如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19static void *block_break(struct sys_mem_pool_base *p, void *block, int l,
				size_t *lsizes)
{
	int i, bn;
    //计算出要分割的block的Block number
	bn = block_num(p, block, lsizes[l]);
    //将该block的在bitmap内的bit置1, 被分割的block就是已被使用的block
	set_alloc_bit(p, l + 1, 4*bn);
    //只使用分割后的第一个block,后面3个block被放入free_list内备用
	for (i = 1; i < 4; i++) {
		int lsz = lsizes[l + 1];
		void *block2 = (lsz * i) + (char *)block;
		sys_dlist_append(&p->levels[l + 1].free_list, block2);
	}
	return block;
}
释放内存
释放内存时,即使将Block退回到Pool,如果其伙伴block空闲就进行合并,不空闲就放入free_list,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22void sys_mem_pool_free(void *ptr)
{
	struct sys_mem_pool_block *blk;
	struct sys_mem_pool *p;
	if (ptr == NULL) {
		return;
	}
	//通过内存计算出block的地址
	ptr = (char *)ptr - WB_UP(sizeof(struct sys_mem_pool_block));
	
	//取block
	blk = (struct sys_mem_pool_block *)ptr;
	p = blk->pool;
	//多线程保护
	sys_mutex_lock(&p->mutex, K_FOREVER);
	//释放对应的block
	z_sys_mem_pool_block_free(&p->base, blk->level, blk->block);
	sys_mutex_unlock(&p->mutex);
}
| 1 | void z_sys_mem_pool_block_free(struct sys_mem_pool_base *p, uint32_t level, | 
| 1 | static void *block_alloc(struct sys_mem_pool_base *p, int l, size_t lsz) | 
| 1 | static void *block_break(struct sys_mem_pool_base *p, void *block, int l, | 
Pool的使用
本小结简单分析Zephyr的内置的minimal stdlib如何使用Pool,代码参考lib/libc/minimal/source/stdlib/malloc.c, 为了简化分析,本文只分析非用户模式下的情况
1.定义Pool
非用户模式下Pool的buffer放到.data段中,管理的内存大小通过CONFIG_MINIMAL_LIBC_MALLOC_ARENA_SIZE配置1
2
3
4
SYS_MEM_POOL_DEFINE(z_malloc_mem_pool, NULL, 16,
		    CONFIG_MINIMAL_LIBC_MALLOC_ARENA_SIZE, 1, 4, POOL_SECTION);
2.初始化Pool
在系统初始化时,通过malloc_prepare初始化Pool,优先顺序属于APPLICATION级别的,这也说明后面的malloc/free也只能在驱动和kernel初始化完成后才能使用1
2
3
4
5
6
7
8
9
10static int malloc_prepare(struct device *unused)
{
	ARG_UNUSED(unused);
	sys_mem_pool_init(&z_malloc_mem_pool);
	return 0;
}
SYS_INIT(malloc_prepare, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
3. malloc/free
malloc/free非常简单,直接封装pool的分配和释放函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16void *malloc(size_t size)
{
	void *ret;
	ret = sys_mem_pool_alloc(&z_malloc_mem_pool, size);
	if (ret == NULL) {
		errno = ENOMEM;
	}
	return ret;
}
void free(void *ptr)
{
	sys_mem_pool_free(ptr);
}
参考
https://docs.zephyrproject.org/latest/reference/kernel/memory/pools.html
