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_sz
n_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