1 前言
内存池是一个内核对象,它允许从指定的内存区域动态分配内存块。 内存池中的内存块可以具有任意大小,从而在应用程序需要为不同大小的数据结构分配存储空间时减少浪费的内存量。 内存池使用 “buddy memory allocation ”算法将较大的块有效地分割为较小的块,从而可以有效地分配和释放不同大小的块,同时限制内存碎片问题。
我正在学习 Zephyr,一个很可能会用到很多物联网设备上的操作系统,如果你也感兴趣,可点此查看帖子zephyr学习笔记汇总。
2 概念
可以定义任意数量的内存池。 每个内存池都由其内存地址引用。
内存池具有以下关键属性:
- 最小块大小,以字节为单位。它必须至少有4X字节长,其中X大于0。
- 最大块大小,以字节为单位。强制规定是最小块大小的4倍。也就是说,“最大块大小”必须等于“最小块大小”乘以4 ^ Y,其中Y大于或等于零。
- 最初可用的最大尺寸块的数量。这必须大于零。
- 为内存池的块提供内存的缓冲区。这必须至少为“最大块大小”乘以“最大大小块数”字节长。
内存池的缓冲区必须与N字节边界对齐,其中N是大于2的幂(即4,8,16,…)。 为确保缓冲区中的所有内存块与此边界相似,最小块大小必须也是N的倍数。
一个需要使用内存块的线程只是从内存池中分配它。 在成功分配之后,由线程提供的块描述符的数据字段指示存储器块的起始地址。 当线程完成一个内存块时,它必须将块释放回内存池,以便该块可以重新使用。
如果所需大小的块不可用,则线程可以选择等待一个可用的块。任何数量的线程可能同时在内存池中等待; 当合适的内存块变得可用时,它被赋予等待时间最长的最高优先级线程。
与堆不同,如果需要,可以定义多个内存池。 例如,不同的应用程序可以使用不同的内存池; 这可以避免一个应用程序劫持资源来分配所有可用的块。
2.1 内部操作
内存池的缓冲区是最大尺寸块的数组,块之间没有浪费的空间。 如果需要,这些“0级”块中的每一块都是四块,可以将其划分为四个相同大小的较小“级1”块。 同样,每个1级块本身就是一个四元组块,可以用类似的方式将其划分为4个较小的“2级”块,依此类推。 因此,内存池块可以递归地分区为四个直到获得最小大小的块,此时不会发生进一步的划分。
内存池通过一组块数据结构跟踪其缓冲区空间是如何分区的。 对于池所支持的每个分区级别有一个块集,或者对于每个块大小(对于另一种方式而言)。 块集使用四块状态数据结构的数组来跟踪其相关大小的所有空闲块。
当应用程序发出对内存块的请求时,内存池首先确定将满足请求的最小块的大小,并检查相应的块集。如果块集包含空闲块,则块将被标记为已使用,并且分配过程已完成。如果块集不包含空闲块,则内存池将尝试通过拆分较大尺寸的空闲块或合并较小尺寸的空闲块来自动创建一个;如果不能创建合适的块,则分配请求失败。
内存池的合并算法不能合并不同大小的相邻空闲块,如果它们属于不同的父四元组,它也不能合并相同大小的相邻空闲块。因此,使用内存池时仍然会遇到内存碎片问题。
当应用程序释放先前分配的内存块时,如果可能的话,它将与其三个“伙伴”块同步组合,并通过级别递归地提升。这是在不变的时间内完成的,而且很快,所以不需要手动的“碎片整理”管理。
3 操作
3.1 定义一个内存池
内存池使用 struct k_mem_pool 类型的变量来定义。 但是,由于内存池还需要许多可变大小的数据结构来表示其块集合及其四块的状态,因此内核不支持内存池的运行时定义。 内存池只能在编译时通过调用 K_MEM_POOL_DEFINE 来定义和初始化。
以下代码定义并初始化一个内存池,该内存池有3个每个4096字节的块,可以将其划分为小至64个字节的块,并对齐到4个字节的边界。 (也就是说,内存池支持的块大小为4096,1024,256和64个字节)。观察宏定义了所有的内存池数据结构及其缓冲区。
K_MEM_POOL_DEFINE(my_pool, 64, 4096, 3, 4);
3.2 分配内存块
内存块通过调用 k_mem_pool_alloc() 来分配。
以下代码构建在上面的示例上,并等待200毫秒以使200字节的内存块变为可用,然后用零填充它。 如果没有获得合适的块,则发出警告。
请注意,应用程序实际上会收到一个256字节的内存块,因为这是内存池支持的最接近的匹配大小。
struct k_mem_block block;
if (k_mem_pool_alloc(&my_pool, &block, 200, 100) == 0)) {
memset(block.data, 0, 200);
...
} else {
printf("Memory allocation time-out");
}
3.3 释放内存块
内存块通过调用 k_mem_pool_free() 来释放。
以下代码构建在上述示例上,并分配一个75字节的内存块,然后在不再需要时释放它。 (实际上分配的是256字节的内存块。)
struct k_mem_block block;
k_mem_pool_alloc(&my_pool, &block, 75, K_FOREVER);
... /* use memory block */
k_mem_pool_free(&block);
4 建议用法
使用内存池在可变大小的块中分配内存。
将大量数据从一个线程发送到另一个线程时使用内存池块,以避免不必要的数据复制。
5 配置选项
无
6 APIs
下列内存池API,都在 kernel.h 中提供了:
K_MEM_POOL_DEFINE
k_mem_pool_alloc()
k_mem_pool_free()