Zephyr内存管理

Zephyr内核提供了slab和heap两种动态内存分配方式,同时Zephyr也可以通过配置使用newlib中的malloc/free进行动态内存分配。对于实时操作系统来说,希望动态分配内存的执行时间是确定的,建议在实际开发中使用Zephyr内核提供的slab和heap来进行动态内存分配。Zephyr原本还有一个Pool的管理方式,现在已经决定将废弃k_mem_pool,目前最新的代码k_mem_pool虽然存在,但其后端仍然是使用heap实现。

本文先介绍说明Slab。

Slab

Slab分配器是一个Zephyr内核对象,通过Slab分配器可以从指定的内存区域动态分配内存块,Slab管理的内存块大小相同且固定,可以避免产生内存碎片,并能高效快速的分配和释放内存。Slab作为Zephyr内核对象,允许在分配不到内存块时进行等待,等到超时退出或者其它Slab用户释放内存为止。Slab常用于对内存需求是固定大小的情况。在一个系统中可以定义多个Slab,每个Slab能分配出来的内存块大小不一样,以满足不同功能模块的需求。

Slab实现

初始化Slab时,会先声明一片内存作为Slab的缓存区,Slab将这边缓存区划分为等大小的内存块,并使用一个单链表将这些内存块串联起来进行分配管理。缓存区内存的起始地址必须2的幂对齐,且要大于4.每个块大小为4的倍数个字节块,Slab中块的数量必须大于0。一个Slab缓存区内存的大小就是块大小剩余块数量。

struct k_mem_slabk_mem_slab_init
1
2
3
4
5
6
7
8
struct k_mem_slab {
_wait_q_t wait_q;
uint32_t num_blocks;
size_t block_size;
char *buffer;
char *free_list;
uint32_t num_used;
};
wait_qnum_blocksblock_sizebuffernum_blocks*block_sizefree_listnum_used
k_mem_slab_initfree_listbufferblock_sizenum_blocks

初始化slab

初始化slab有两种方式,一种是使用下面宏进行定义

1
2
3
4
5
6
#define K_MEM_SLAB_DEFINE(name, slab_block_size, slab_num_blocks, slab_align) \
char __noinit __aligned(WB_UP(slab_align)) \
_k_mem_slab_buf_##name[(slab_num_blocks) * WB_UP(slab_block_size)]; \
Z_STRUCT_SECTION_ITERABLE(k_mem_slab, name) = \
Z_MEM_SLAB_INITIALIZER(name, _k_mem_slab_buf_##name, \
WB_UP(slab_block_size), slab_num_blocks)
K_MEM_SLAB_DEFINE_k_mem_slab_buf_##nameslab_num_blocks*slab_block_sizestruct k_mem_slabnameZ_MEM_SLAB_INITIALIZERfree_list
1
2
3
4
5
6
7
8
9
10
11
#define Z_MEM_SLAB_INITIALIZER(obj, slab_buffer, slab_block_size, \
slab_num_blocks) \
{ \
.wait_q = Z_WAIT_Q_INIT(&obj.wait_q), \
.num_blocks = slab_num_blocks, \
.block_size = slab_block_size, \
.buffer = slab_buffer, \
.free_list = NULL, \
.num_used = 0, \
_OBJECT_TRACING_INIT \
}
free_listSYS_INIT(init_mem_slab_module, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_OBJECTS);init_mem_slab_modulestruct k_mem_slabbufferfree_list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int init_mem_slab_module(struct device *dev)
{
int rc = 0;

Z_STRUCT_SECTION_FOREACH(k_mem_slab, slab) { //遍历k_mem_slab
rc = create_free_list(slab); //创建free_list
if (rc < 0) {
goto out;
}
}

out:
return rc;
}


第二种初始化方式,在运行时使用k_mem_slab_init进行初始化, 下面两种方式是等效的
宏定义初始化

1
K_MEM_SLAB_DEFINE(test_slab, TEST_BLOCK_SIZE, TEST_BLOCK_COUNT, sizeof(void *))


代码初始化

1
2
3
4
5
6
7
8
struct k_mem_slab test_slab;
static uint8_t __noinit __aligned(sizeof(void *))
test_slab_buf[(TEST_BLOCK_SIZE * TEST_BLOCK_COUNT)];

void thread()
{
k_mem_slab_init(&test_slab, test_slab_buf, TEST_BLOCK_SIZE, TEST_BLOCK_COUNT);
}


初始化函数如下

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
int k_mem_slab_init(struct k_mem_slab *slab, void *buffer,
size_t block_size, uint32_t num_blocks)
{
int rc = 0;
//slab内存初始化
slab->num_blocks = num_blocks;
slab->block_size = block_size;
slab->buffer = buffer;
slab->num_used = 0U;

//建立free list
rc = create_free_list(slab);
if (rc < 0) {
goto out;
}

//初始化wait q
z_waitq_init(&slab->wait_q);
SYS_TRACING_OBJ_INIT(k_mem_slab, slab);

z_object_init(slab);

out:
return rc;
}


分配slab内存

free_listfree_listNULL
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
int k_mem_slab_alloc(struct k_mem_slab *slab, void **mem, k_timeout_t timeout)
{
k_spinlock_key_t key = k_spin_lock(&lock);
int result;

if (slab->free_list != NULL) { //free_list不为NULL,有空闲内存块,直接分配
*mem = slab->free_list;
slab->free_list = *(char **)(slab->free_list);
slab->num_used++;
result = 0;
} else if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) { //没有空闲块且不等待,返回错误
/* don't wait for a free block to become available */
*mem = NULL;
result = -ENOMEM;
} else { //等待其它线程释放
result = z_pend_curr(&lock, key, &slab->wait_q, timeout);
if (result == 0) {
//等待成功获取到空闲内存块
*mem = _current->base.swap_data;
}
return result;
}

k_spin_unlock(&lock, key);

return result;

释放slab内存

wait_qfree_list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void k_mem_slab_free(struct k_mem_slab *slab, void **mem)
{
k_spinlock_key_t key = k_spin_lock(&lock);
//检查是否有线程在等待空闲的内存
struct k_thread *pending_thread = z_unpend_first_thread(&slab->wait_q);

if (pending_thread != NULL) { //如果有线程等待,将释放的内存块提供给等待线程
z_thread_return_value_set_with_data(pending_thread, 0, *mem);
z_ready_thread(pending_thread);
z_reschedule(&lock, key);
} else { //如果没有线程在等待,将释放的内存块加入到free_list中
**(char ***)mem = slab->free_list;
slab->free_list = *(char **)mem;
slab->num_used--;
k_spin_unlock(&lock, key);
}
}


Slab使用建议

Slab是一个内核对象,在分配时会存在等待资源的情况,释放时可能会从其它线程获取资源的情况,因此Slab分配和释放都有可能会引起线程的调度。
在需要定长内存的分配情况下,优先使用Slab。当从一个线程发送大量数据到另一个线程时,可以使用Slab,只发送内存块地址,可以避免不必要的数据拷贝动作。

参考

https://docs.zephyrproject.org/latest/reference/kernel/memory/slabs.html




Zephyr项目是一个由Linux基金会托管的协作项目,它是一个开放源码协作项目,将来自整个行业的领导者联合起来,构建一个最佳的小型、可伸缩、实时操作系统(RTOS),该系统针对跨多个架构的资源受限设备进行了优化。


联系关于Zephyr Project




Linux基金会是非营利性组织,是技术生态系统的重要组成部分。 

Linux基金会通过提供财务和智力资源、基础设施、服务、活动以及培训来支持创建永续开源生态系统。在共享技术的创建中,Linux基金会及其项目通过共同努力形成了非凡成功的投资。请长按以下二维码进行关注。