内存池
2026/7/16大约 6 分钟内核组件内核内存管理内存池
内存池
📚 本节导读
学习时长: 约 25 分钟
难度级别: ⭐⭐⭐☆☆
前置知识: 4.4 内存管理、C 语言链表
内存池(Memory Pool)是 OneOS 提供的另一种内存管理机制。与堆内存不同,内存池预先分配固定大小的内存块,分配和释放操作具有确定性时间,且无碎片问题。它特别适合高频、固定大小的对象分配场景,如网络数据包缓冲区、传感器数据缓存等。
🎯 学习目标
- 理解内存池与堆内存的区别和适用场景
- 掌握内存池的创建、分配、释放 API
- 了解内存池大小计算方法
- 掌握内存池溢出检测机制
- 能够使用 Shell 命令查看内存池状态
一、内存池概念
1.1 什么是内存池
内存池是一块预先分配好的内存区域,被划分为若干个大小相同的块(block)。分配和释放操作都在这些固定大小的块上进行。
┌──────────────────────────────────────────────────────────────┐
│ 内存池结构 │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 内存池控制块 (os_mempool_t) │ │
│ │ ┌──────────┬──────────┬──────────┬──────────────┐ │ │
│ │ │ blk_size │ total_num│ free_num │ free_list ──┐│ │ │
│ │ └──────────┴──────────┴──────────┴─────────────┘│ │ │
│ └──────────────────────────────────────────────────┼───┘ │
│ │ │
│ ┌──────────────────────────────────────────────────┘ │
│ ▼ │
│ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ 块 0 │──→│ 块 1 │──→│ 块 2 │──→ ... →│ 块 N-1│──→NULL│
│ └───────┘ └───────┘ └───────┘ └───────┘ │
│ 空闲链表头 (全部空闲块串联) │
│ │
│ 分配后: │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ 块 2 │──→ ... →│ 块 N-1│──→NULL (剩余空闲块) │
│ └───────┘ └───────┘ │
│ 空闲链表头 │
│ ┌───────┐ │
│ │ 块 0 │ (已分配,在用户手中) │
│ └───────┘ │
│ ┌───────┐ │
│ │ 块 1 │ (已分配,在用户手中) │
│ └───────┘ │
└──────────────────────────────────────────────────────────────┘1.2 内存池 vs 堆内存
| 特性 | 内存池 | 堆内存 |
|---|---|---|
| 分配大小 | 固定 | 可变 |
| 分配时间 | 确定性 O(1) | 非确定性 |
| 碎片 | 无 | 有 |
| 超时等待 | 支持 | 不支持 |
| 并发保护 | 自旋锁 | 信号量 |
| 适用场景 | 高频固定大小分配 | 通用动态分配 |
二、配置
#define OS_USING_MEM_POOL /* 必须:启用内存池 */
#define OS_USING_MEMPOOL_CHECK_TAG /* 可选:启用溢出检测 */三、核心 API
所有 API 定义在 kernel/include/os_memory.h 中,实现在 kernel/source/os_mem_pool.c。
3.1 静态定义
#define OS_MEM_POOL_DEFINE(name) os_mempool_dummy_t name与堆类似,内存池也支持静态定义控制块:
OS_MEM_POOL_DEFINE(my_pool_cb); /* 定义一个内存池控制块 */3.2 内存池大小计算
#define OS_MEMPOOL_SIZE(block_count, block_size) \
((OS_ALIGN_UP(block_size + OS_MEMPOOL_BLK_HEAD_SIZE, OS_ALIGN_SIZE)) * block_count)- block_count:需要的块数量
- block_size:每个块用户可用大小(字节)
- OS_MEMPOOL_BLK_HEAD_SIZE:块头部大小(开启溢出检测时为 4,否则为 0)
- OS_ALIGN_SIZE:平台对齐大小
计算示例:
/* 创建 10 个块,每个块 64 字节,无溢出检测 */
#define POOL_SIZE OS_MEMPOOL_SIZE(10, 64)
/* 结果:OS_ALIGN_UP(64 + 0, 4) * 10 = 64 * 10 = 640 字节 */
/* 创建 10 个块,每个块 64 字节,启用溢出检测 */
#define POOL_SIZE OS_MEMPOOL_SIZE(10, 64)
/* 结果:OS_ALIGN_UP(64 + 4, 4) * 10 = 68 * 10 = 680 字节 */3.3 初始化内存池
os_mempool_id os_mempool_init(os_mempool_dummy_t *mempool_cb,
const char *name,
void *start,
os_size_t size,
os_size_t blk_size);| 参数 | 类型 | 说明 |
|---|---|---|
mempool_cb | os_mempool_dummy_t * | 静态定义的内存池控制块指针 |
name | const char * | 内存池名称(用于调试) |
start | void * | 内存池起始地址(会自动对齐) |
size | os_size_t | 内存池总大小(字节) |
blk_size | os_size_t | 每个块用户可用大小(字节) |
| 返回值 | 说明 |
|---|---|
os_mempool_id | 内存池 ID,非空表示成功 |
OS_NULL | 初始化失败 |
3.4 反初始化内存池
os_err_t os_mempool_deinit(os_mempool_id mempool_id);- 功能:销毁内存池,唤醒所有等待该内存池的任务
- 返回值:
OS_SUCCESS表示成功
3.5 分配内存块
void *os_mempool_alloc(os_mempool_id mempool_id, os_tick_t timeout);| 参数 | 类型 | 说明 |
|---|---|---|
mempool_id | os_mempool_id | 内存池 ID |
timeout | os_tick_t | 等待超时时间(tick 数) |
| 返回值 | 说明 |
|---|---|
| 非空指针 | 分配成功,返回可用的内存块指针 |
OS_NULL | 分配失败(无空闲块或超时) |
timeout 取值:
| 值 | 含义 |
|---|---|
OS_NO_WAIT | 不等待,立即返回 |
0 ~ OS_TICK_MAX/2 | 等待指定 tick 数 |
OS_WAIT_FOREVER | 永久等待,直到有可用块 |
注意:在中断上下文或调度锁定时,只能使用
OS_NO_WAIT。
3.6 释放内存块
void os_mempool_free(os_mempool_id mempool_id, void *mem);- 功能:将之前分配的内存块归还给内存池
- 参数:
mem必须是从同一内存池中通过os_mempool_alloc分配的指针 - 注意:释放后如果有任务正在等待该内存池,会唤醒一个等待任务
3.7 获取内存池信息
void os_mempool_info(os_mempool_id mempool_id, os_mpinfo_t *info);- 功能:获取内存池的统计信息
- 参数:
info— 指向os_mpinfo_t结构体的指针
struct os_mpinfo
{
os_size_t blk_size; /* 用户可用的单个内存块大小 */
os_size_t blk_total_num; /* 内存块总数量 */
os_size_t blk_free_num; /* 空闲内存块数量 */
};四、代码示例
4.1 基本使用
#include <os_memory.h>
/* 静态定义内存池控制块 */
OS_MEM_POOL_DEFINE(sensor_pool_cb);
/* 计算内存池大小:20 个块,每个块 128 字节 */
#define SENSOR_POOL_SIZE OS_MEMPOOL_SIZE(20, 128)
/* 内存池数据区 */
static uint8_t sensor_pool_buf[SENSOR_POOL_SIZE];
static os_mempool_id sensor_pool_id;
void sensor_pool_init(void)
{
/* 初始化内存池 */
sensor_pool_id = os_mempool_init(&sensor_pool_cb,
"SENSOR_POOL",
sensor_pool_buf,
SENSOR_POOL_SIZE,
128);
if (sensor_pool_id)
{
os_kprintf("传感器内存池初始化成功\r\n");
}
}
void sensor_task(void)
{
os_mpinfo_t info;
void *sensor_data;
while (1)
{
/* 等待分配内存块(最多等待 100 ticks) */
sensor_data = os_mempool_alloc(sensor_pool_id, 100);
if (sensor_data)
{
/* 使用内存块存储传感器数据 */
/* ... 处理传感器数据 ... */
/* 使用完毕后释放 */
os_mempool_free(sensor_pool_id, sensor_data);
}
else
{
os_kprintf("内存池分配超时!\r\n");
}
/* 查看内存池状态 */
os_mempool_info(sensor_pool_id, &info);
os_kprintf("内存池: 块大小=%lu 总数=%lu 空闲=%lu\r\n",
info.blk_size, info.blk_total_num, info.blk_free_num);
}
}4.2 无等待分配(中断上下文)
void isr_handler(void)
{
void *pkt_buf;
/* 中断中只能使用 OS_NO_WAIT */
pkt_buf = os_mempool_alloc(pkt_pool_id, OS_NO_WAIT);
if (pkt_buf)
{
/* 快速处理数据包 */
/* ... */
os_mempool_free(pkt_pool_id, pkt_buf);
}
}4.3 永久等待分配
void data_consumer_task(void)
{
void *buf;
while (1)
{
/* 永久等待,直到有可用块 */
buf = os_mempool_alloc(data_pool_id, OS_WAIT_FOREVER);
/* 处理 buf 中的数据 */
os_mempool_free(data_pool_id, buf);
}
}五、Shell 命令
show_mempool # 显示所有内存池信息
show_mempool <mempool_address> # 显示指定内存池信息输出示例:
MemPool TotalCount FreeCount PerSize Block Task
---------- ---------- ---------- ---------- ----------
SENSOR_POOL 20 18 128 0
PKT_POOL 100 95 256 5: task1六、内部实现要点
6.1 内存对齐
初始化时,内存池会自动对齐起始地址和大小:
start_addr = (void *)OS_ALIGN_UP((os_ubase_t)start, OS_ALIGN_SIZE);
size = OS_ALIGN_DOWN((os_ubase_t)start + size - (os_ubase_t)start_addr, OS_ALIGN_SIZE);6.2 块大小计算
每个块的实际大小(包含头部)计算如下:
#ifdef OS_USING_MEMPOOL_CHECK_TAG
blk_size = OS_ALIGN_UP(blk_size + sizeof(struct mempool_check_tag), OS_ALIGN_SIZE);
#else
blk_size = OS_ALIGN_UP(blk_size, OS_ALIGN_SIZE);
#endif6.3 空闲链表
所有空闲块通过单向链表连接:
struct blk_head
{
struct blk_head *next; /* 指向下一个空闲块 */
};- 分配:从链表头部取一个块,
free_list = free_list->next - 释放:将块插入链表头部,
block->next = free_list; free_list = block
6.4 任务阻塞队列
当无空闲块时,分配任务会阻塞在内存池的 task_list_head 上。当有块被释放时,会唤醒一个等待任务。
📝 本节小结
- 内存池提供固定大小块的分配,分配和释放时间为 O(1),无碎片问题
- 通过
OS_MEM_POOL_DEFINE静态定义控制块,OS_MEMPOOL_SIZE计算所需内存大小 os_mempool_alloc支持超时等待(OS_NO_WAIT、指定 ticks、OS_WAIT_FOREVER)- 中断上下文只能使用
OS_NO_WAIT模式 - 启用
OS_USING_MEMPOOL_CHECK_TAG可在每个块头部添加魔数标记,检测内存越界 - 使用
show_mempoolShell 命令可查看内存池状态和阻塞任务