Zephyr内存管理之Block

Creative Commons
本作品采用知识共享署名

本文分析说明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

1
2
3
4
5
6
7
//定义一个名为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

1
2
3
4
5
6
7
#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有两个,管理器管理外部内存

1
2
3
4
5
6
//定义一个名为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

1
2
3
4
5
6
7
8
9
#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分配的内存块之间是连续的,实现如下

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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用于释放连续性的内存块

1
2
3
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进行管理,这里进行代码分析

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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则是由系统自行安排

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
31
32
33
34
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是否未分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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选出块分配器进行分配。多块分配器使用下面结构体进行组织管理

1
2
3
4
5
6
7
8
#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的初始化

1
2
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

1
2
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

1
2
3
4
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中的哪一个多堆分配器,再进行释放

1
2
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