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 | static int init_mem_slab_module(struct device *dev) |
第二种初始化方式,在运行时使用k_mem_slab_init进行初始化, 下面两种方式是等效的
宏定义初始化
1 | K_MEM_SLAB_DEFINE(test_slab, TEST_BLOCK_SIZE, TEST_BLOCK_COUNT, sizeof(void *)) |
代码初始化
1 | struct k_mem_slab test_slab; |
初始化函数如下
1 | int k_mem_slab_init(struct k_mem_slab *slab, void *buffer, |
分配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 | void k_mem_slab_free(struct k_mem_slab *slab, void **mem) |
Slab使用建议
Slab是一个内核对象,在分配时会存在等待资源的情况,释放时可能会从其它线程获取资源的情况,因此Slab分配和释放都有可能会引起线程的调度。
在需要定长内存的分配情况下,优先使用Slab。当从一个线程发送大量数据到另一个线程时,可以使用Slab,之发送内存块地址,可以避免不必要的数据拷贝动作。
参考
https://docs.zephyrproject.org/latest/reference/kernel/memory/slabs.html