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缓存区内存的大小就是块大小剩余块数量。
每个Slab都将维护一个struct k_mem_slab
,在k_mem_slab_init
中对其进行初始化:
1 | struct k_mem_slab { |
wait_q
: 在slab无空闲块时,申请slab内存的thread将被放入该wait_qnum_blocks
: slab管理的block总数block_size
:slab管理的block大小buffer
: slab的缓存区,大小为num_blocks*block_size
free_list
: slab空闲链表,slab中空闲的块将以单链表的的形式串接起来num_used
:slab中已经被分配的block数量
k_mem_slab_init
中将建立slab的free_list
,将buffer
按照block_size
大小分割为num_blocks
块。每个块的最低4字节保存指向下一个块的指针,从高地址开始依次顺序的将各个块链成一个单链表,如下图(1)。
初始化slab
初始化slab有两种方式,一种是使用下面宏进行定义
1 |
|
通过宏K_MEM_SLAB_DEFINE
定义slab,会定义一个全局数组_k_mem_slab_buf_##name
,大小为slab_num_blocks*slab_block_size
,在定义一个全局的struct k_mem_slab
变量name
,该变量被放到名为k_mem_slab的section中,并通过宏Z_MEM_SLAB_INITIALIZER
对该结构体变量进行初始化赋值,这里除了free_list
无法赋值外,其它的字段都可以直接初始化:
1 |
|
free_list
将在slab模块初始化时SYS_INIT(init_mem_slab_module, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_OBJECTS);
在init_mem_slab_module
中对k_mem_slab section进行遍历取出每个定义好的struct k_mem_slab
的全局变量,对其buffer
进行分割,建立出free_list
。1
2
3
4
5
6
7
8
9
10
11
12
13
14static 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
8struct 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
25int 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_list
指向的块分配给申请者,果分配内存时发现free_list
为NULL
说明slab内的块已经被分配完,将根据timeout进行等待。如图中(2)(3),代码分析如下:
1 | int k_mem_slab_alloc(struct k_mem_slab *slab, void **mem, k_timeout_t timeout) |
释放slab内存
释放slab内存时,会先检查slab内的wait_q
是否有其它线程在等待分配内存块,如果有则将要释放的内存块提供给等待的线程。如果没有线程等待,释放的内存块将会被加入到free_list
.如图中(4)(5),代码分析如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17void 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