内存追踪
2026/7/16大约 6 分钟内核组件内核内存管理内存追踪调试
内存追踪
📚 本节导读
学习时长: 约 20 分钟
难度级别: ⭐⭐☆☆☆
前置知识: 4.4.1 堆内存管理、4.4.4 内存池
内存追踪是 OneOS 提供的内存调试工具,用于检测内存泄漏、定位内存越界和验证堆完整性。通过启用 OS_USING_MEM_TRACE 宏,可以在每个分配的内存块中记录分配该内存的任务信息,从而追踪内存的来源和归属。
🎯 学习目标
- 理解内存追踪的工作原理
- 掌握内存追踪 API 的使用方法
- 了解堆完整性检查的机制
- 掌握内存调试的最佳实践
一、配置
#define OS_USING_HEAP /* 必须:启用堆内存 */
#define OS_USING_MEM_TRACE /* 必须:启用内存追踪 */内存追踪功能依赖于堆内存管理,它会在每个内存块的头部额外记录分配该块的任务信息。
二、内存追踪原理
2.1 记录机制
启用 OS_USING_MEM_TRACE 后,分配内存时会在块头部记录当前任务的信息:
FIRSTFIT 算法:
未启用追踪的 chunk:
┌──────────┬───────────────┬──────────────────────────┐
│ LEFT_SIZE│ SIZE_AND_USED │ 用户数据 │
└──────────┴───────────────┴──────────────────────────┘
启用追踪的 chunk:
┌──────────┬───────────────┬──────────┬───────────────┐
│ LEFT_SIZE│ SIZE_AND_USED │ TASK_ID │ 用户数据 │
└──────────┴───────────────┴──────────┴───────────────┘BUDDY 算法:
未启用追踪的块:
┌──────┬──────┬──────────┬──────────┬──────────────┐
│ level│ used │ duplicate│magic_tag │ 用户数据 │
│(1B) │(1B) │ (1B) │ (1B) │ │
└──────┴──────┴──────────┴──────────┴──────────────┘
◄────── BLK_HEAD_TAG_SIZE (4B) ──────►
启用追踪的块:
┌──────┬──────┬──────────┬──────────┬──────────┬──────────────┐
│ level│ used │ duplicate│magic_tag │ TASK_ID │ 用户数据 │
│(1B) │(1B) │ (1B) │ (1B) │(ptr) │ │
└──────┴──────┴──────────┴──────────┴──────────┴──────────────┘
◄────── BLK_HEAD_SIZE (4B + sizeof(os_base_t)) ──►2.2 追踪输出
追踪功能会逐块输出所有已分配内存的信息:
mem_trace for memory addr: 0x20001000 ~ 0x20011000
Used Addr Size Task ID
---- ---------- ---------- --------
* 0x20001028 128 0x20001234
* 0x20001500 256 0x20001500
- 0x20001600 512 ← 空闲块
* 0x20001a00 1024 0x20001800*表示已用块-表示空闲块Task ID显示分配该内存的任务指针
三、API 详解
3.1 默认堆追踪
os_err_t os_default_heap_trace(void);- 功能:追踪默认堆中所有内存块的分配信息,输出每个块的地址、大小和分配任务
- 返回值:
OS_SUCCESS表示追踪成功,OS_FAILURE表示发现异常 - 注意:此函数仅在
OS_USING_MEM_TRACE启用时可用
3.2 自定义堆追踪
os_err_t os_heap_trace(os_heap_id heap_id);- 功能:追踪指定堆中所有内存块的分配信息
- 参数:
heap_id— 堆实例 ID - 返回值:
OS_SUCCESS表示追踪成功,OS_FAILURE表示发现异常
3.3 默认堆完整性检查
os_err_t os_default_heap_check(void);- 功能:检查默认堆的完整性,验证所有内存块的数据结构是否一致
- 返回值:
OS_SUCCESS表示堆正常,其他值表示发现错误 - 检查内容:
- 遍历所有 chunk,验证每个 chunk 的左右邻居关系
- 验证已用内存总量和空闲内存总量是否与记录一致
- 输出空闲块列表
3.4 自定义堆完整性检查
os_err_t os_heap_check(os_heap_id heap_id);- 功能:检查指定堆的完整性
- 参数:
heap_id— 堆实例 ID - 返回值:
OS_SUCCESS表示堆正常,其他值表示发现错误
四、Shell 命令
OneOS 提供了便捷的 Shell 命令用于内存调试:
| 命令 | 功能 | 依赖 |
|---|---|---|
show_mem | 显示默认堆使用情况 | OS_USING_HEAP |
show_heap | 显示所有堆信息 | OS_USING_HEAP |
check_mem | 检查默认堆完整性 | OS_USING_HEAP |
trace_mem | 追踪默认堆内存分配 | OS_USING_HEAP + OS_USING_MEM_TRACE |
show_mempool | 显示内存池信息 | OS_USING_MEM_POOL |
4.1 show_mem
show_mem输出示例:
Name Total Used Used rate Max Used Max rate Range
---------- ---------- ---------- ---------- ---------- ---------- -----
DEFAULT_HEAP 65536 12480 19.04% 16256 24.80% [0x20001000~0x20011000]4.2 check_mem
check_mem输出示例(正常):
mem_check for memory addr: 0x20001000 ~ 0x20011000
free block info:
bucketid chunkid chunk_size mem_size
3 128 16 0x00000038
5 230 32 0x00000078
memory addr : 0x20001000
memory total : 65536
memory used : 12480
memory max used : 16256
mem_check ok!输出示例(异常):
mem_check err:chunk:42, r_chunk:58, r_chunk's left:40
the addr:0x20001528 or 0x20001290 maybe overwrited! please check.4.3 trace_mem
trace_mem输出示例:
mem_trace for memory addr: 0x20001000 ~ 0x20011000
Used Addr Size Task ID
---- ---------- ---------- --------
* 0x20001028 128 0x20001300
* 0x20001500 256 0x20001300
- 0x20001600 512
* 0x20001a00 1024 0x20001800五、代码示例
5.1 使用内存追踪定位泄漏
#include <os_memory.h>
void task_with_leak(void)
{
void *buf1;
void *buf2;
buf1 = os_malloc(256);
buf2 = os_malloc(128);
/* 使用 buf1 和 buf2 ... */
/* 只释放了 buf1,忘记释放 buf2 → 内存泄漏! */
os_free(buf1);
}
void debug_memory(void)
{
/* 运行一段时间后,检查内存状态 */
os_meminfo_t info;
os_default_heap_info(&info);
os_kprintf("当前内存: 总计=%lu 已用=%lu 峰值=%lu\r\n",
info.mem_total, info.mem_used, info.mem_maxused);
/* 如果已用内存持续增长,可能存在泄漏 */
/* 使用追踪功能查看每个块的归属 */
#ifdef OS_USING_MEM_TRACE
os_default_heap_trace();
#endif
}5.2 定期检查堆完整性
void periodic_check_task(void)
{
os_err_t ret;
while (1)
{
os_task_sleep(1000); /* 每秒检查一次 */
ret = os_default_heap_check();
if (ret != OS_SUCCESS)
{
os_kprintf("堆完整性检查失败!错误码: %d\r\n", ret);
/* 发现异常后,可以进一步追踪 */
#ifdef OS_USING_MEM_TRACE
os_default_heap_trace();
#endif
}
}
}5.3 多堆追踪
void multi_heap_debug(void)
{
os_err_t ret;
/* 检查 SRAM 堆 */
ret = os_heap_check(sram_heap_id);
if (ret != OS_SUCCESS)
{
os_kprintf("SRAM 堆异常!\r\n");
}
/* 检查 SDRAM 堆 */
ret = os_heap_check(sdram_heap_id);
if (ret != OS_SUCCESS)
{
os_kprintf("SDRAM 堆异常!\r\n");
}
#ifdef OS_USING_MEM_TRACE
/* 追踪两个堆 */
os_heap_trace(sram_heap_id);
os_heap_trace(sdram_heap_id);
#endif
}六、内存调试最佳实践
6.1 开发阶段
| 实践 | 说明 |
|---|---|
| 启用 OS_USING_MEM_TRACE | 记录每个分配的任务信息,便于定位泄漏源 |
| 启用 OS_USING_MEMPOOL_CHECK_TAG | 检测内存池块的越界写入 |
| 定期调用 check_mem | 在关键路径上检查堆完整性 |
| 使用 Shell 命令 | show_mem、trace_mem、check_mem 辅助调试 |
6.2 常见问题排查
内存泄漏:
- 使用
show_mem查看mem_used是否持续增长 - 使用
trace_mem查看所有已分配块及其归属任务 - 确认每个
os_malloc/os_calloc都有对应的os_free
内存越界:
- 使用
check_mem检查堆完整性,定位越界写入位置 - 启用
OS_USING_MEMPOOL_CHECK_TAG检测内存池越界 - 出错的地址范围可以帮助定位越界的代码
Double-Free:
- 启用断言(
OS_ASSERT),FIRSTFIT 和 BUDDY 都会检测 double-free - 错误信息会明确指出 double-free 的地址
6.3 发布阶段
| 实践 | 说明 |
|---|---|
| 关闭 OS_USING_MEM_TRACE | 减少每个块的元数据开销 |
| 关闭 OS_USING_MEMPOOL_CHECK_TAG | 减少内存池块开销 |
| 保留 check_mem | 可定期调用作为运行时健康检查 |
📝 本节小结
- 内存追踪通过
OS_USING_MEM_TRACE启用,在每个块头部记录分配任务信息 os_default_heap_trace()/os_heap_trace()可输出所有已分配块的地址、大小和归属任务os_default_heap_check()/os_heap_check()遍历堆验证数据结构一致性,可检测越界写入- Shell 命令
show_mem、check_mem、trace_mem提供便捷的内存调试入口 - 开发阶段建议启用追踪和溢出检测,发布阶段可关闭以节省开销
- 内存泄漏排查:先看
mem_used趋势,再用trace_mem定位归属任务