堆内存管理
堆内存管理
📚 本节导读
学习时长: 约 30 分钟
难度级别: ⭐⭐⭐☆☆
前置知识: 4.4 内存管理、C 语言动态内存(malloc/free)
堆内存(Heap)是 OneOS 中最灵活的内存分配方式。它允许在运行时按需分配任意大小的内存块,并在使用完毕后释放。OneOS 不仅提供了简洁的默认堆 API(与标准 C 库风格一致),还支持多堆实例的管理,方便在不同内存区域进行独立的内存管理。
🎯 学习目标
- 掌握默认堆的初始化与内存区域添加方法
- 熟练使用
os_malloc、os_free、os_realloc、os_calloc等默认堆 API - 理解多堆机制,能创建和管理自定义堆实例
- 掌握堆内存统计信息的获取方法
- 了解堆完整性检查功能
一、堆内存概念
1.1 什么是堆
堆是一块由操作系统管理的内存区域,程序可以在运行时从该区域动态申请和释放任意大小的内存块。与栈(Stack)不同,堆内存的生命周期由程序员显式控制,不受函数调用栈的限制。
┌──────────────────────────────────────────────────────────────┐
│ 系统内存布局 │
│ │
│ 高地址 │
│ ┌──────────┐ │
│ │ 栈 │ ← 函数调用、局部变量(自动分配/释放) │
│ │ ↓ │ │
│ │ │ │
│ │ ↑ │ │
│ │ 堆 │ ← 动态分配(手动分配/释放,由 OneOS 管理) │
│ ├──────────┤ │
│ │ BSS段 │ ← 未初始化全局变量 │
│ ├──────────┤ │
│ │ DATA段 │ ← 已初始化全局变量 │
│ ├──────────┤ │
│ │ TEXT段 │ ← 代码 │
│ └──────────┘ │
│ 低地址 │
└──────────────────────────────────────────────────────────────┘1.2 OneOS 堆架构
OneOS 的堆内存管理采用分层架构:
┌──────────────────────────────────────┐
│ 应用层 API │
│ os_malloc / os_free / os_realloc │
│ os_calloc / os_aligned_malloc │
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ 默认堆 (Default Heap) │
│ os_default_heap_init / add │
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ 多堆管理层 (Multi-Heap) │
│ os_heap_init / os_heap_alloc ... │
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ 内存算法层 (Algorithm) │
│ FIRSTFIT / BUDDY │
└──────────────────────────────────────┘1.3 堆控制块(os_heap_t)字段详解
堆的控制块 os_heap_t(即 struct os_heap)定义在 kernel/source/os_prototypes.h 中:
来源:kernel/source/os_prototypes.h
struct os_heap
{
struct heap_mem *h_mem;
uint8_t object_inited;
char name[OS_NAME_MAX + 1];
os_list_node_t resource_node; /* Node in resource list */
};
typedef struct os_heap os_heap_t;各字段含义如下:
| 字段 | 类型 | 说明 |
|---|---|---|
h_mem | struct heap_mem * | 内存区域链表头:指向该堆的第一个内存区域(heap_mem),一个堆可有多个内存区域 |
object_inited | uint8_t | 是否已初始化(OS_KOBJ_INITED / OS_KOBJ_DEINITED) |
name | char[OS_NAME_MAX+1] | 堆名称(调试/Shell 显示用) |
resource_node | os_list_node_t | 资源链表节点,把堆挂到全局堆资源链表(防重复创建/系统查询) |
概念对应表:
| 堆概念 | 对应字段 |
|---|---|
| 堆包含的内存区域 | h_mem(指向第一个 heap_mem) |
| 生命周期 | object_inited |
| 堆标识 | name |
| 系统管理/防重复 | resource_node(挂到 gs_os_heap_resource_list_head) |
关键点:os_heap_t 是堆的顶层控制块,但它不直接存内存块——真正管理内存块的是它指向的 h_mem(heap_mem 内存区域对象)。一个堆通过 os_heap_add 可挂多个内存区域(h_mem 链表)。
1.4 内存区域控制块(struct heap_mem)字段详解
每个堆可包含多个内存区域,每个区域由一个 struct heap_mem 描述,定义在 kernel/source/os_prototypes.h 中:
来源:kernel/source/os_prototypes.h
struct heap_mem
{
void *(*k_alloc)(struct heap_mem *h_mem, os_size_t size);
void *(*k_aligned_alloc)(struct heap_mem *h_mem, os_size_t align, os_size_t size);
void (*k_free)(struct heap_mem *h_mem, void *mem);
void *(*k_realloc)(struct heap_mem *h_mem, void *mem, os_size_t newsize);
void (*k_deinit)(struct heap_mem *h_mem);
os_err_t (*k_mem_check)(struct heap_mem *h_mem);
#ifdef OS_USING_MEM_TRACE
os_err_t (*k_mem_trace)(struct heap_mem *h_mem);
#endif
os_size_t (*k_ptr_to_size)(struct heap_mem *h_mem, void *mem);
void *header; /* Pointer to current memory's start address. */
struct heap_mem *next; /* Pointer to next memory's heap_mem object. */
os_size_t mem_total;
os_size_t mem_used;
os_size_t mem_maxused;
dummy_semaphore_t sem_id;
enum os_mem_alg alg;
};各字段含义如下:
| 字段 | 类型 | 说明 |
|---|---|---|
k_alloc | 函数指针 | 该区域算法的分配函数(如 FIRSTFIT 的 k_firstfit_alloc) |
k_aligned_alloc | 函数指针 | 该区域算法的对齐分配函数 |
k_free | 函数指针 | 该区域算法的释放函数 |
k_realloc | 函数指针 | 该区域算法的重分配函数 |
k_deinit | 函数指针 | 该区域算法的反初始化函数 |
k_mem_check | 函数指针 | 该区域算法的完整性检查函数 |
k_mem_trace | 函数指针 | 内存追踪函数(仅 OS_USING_MEM_TRACE 时存在) |
k_ptr_to_size | 函数指针 | 根据指针返回原块大小(os_heap_realloc 用) |
header | void * | 该内存区域的起始地址(对齐后) |
next | struct heap_mem * | 下一个内存区域指针(链接同堆的多个区域) |
mem_total | os_size_t | 该区域总大小(字节) |
mem_used | os_size_t | 该区域已用大小(字节) |
mem_maxused | os_size_t | 该区域历史峰值(字节) |
sem_id | dummy_semaphore_t | 该区域并发保护的信号量 |
alg | enum os_mem_alg | 该区域使用的内存算法(FIRSTFIT/BUDDY) |
概念对应表:
| 内存区域概念 | 对应字段 |
|---|---|
| 区域起始地址 | header |
| 区域大小 | mem_total |
| 已用/峰值 | mem_used / mem_maxused |
| 同堆下一个区域 | next |
| 区域算法 | alg |
| 区域并发保护 | sem_id |
| 算法操作接口 | k_alloc/k_free/k_realloc/k_mem_check 等函数指针 |
关键点 —— 字段与多堆 API 的对应:
os_heap_add用alg查alg_init_table,调用对应算法的mem_init把该区域的k_alloc/k_free等函数指针绑定到具体算法实现(如 FIRSTFIT)。os_heap_alloc遍历h_mem链表,调用每个区域的k_alloc——函数指针多态让统一接口支持不同算法。header/next把同一堆的多个内存区域串成链表(os_heap_add尾接)。
二、配置依赖
使用堆内存管理需要以下配置。约束来自项目源码 kernel/include/os_memory.h 的编译检查(与项目一致):
#ifdef OS_USING_HEAP
#ifndef OS_USING_SEMAPHORE
#error "Macro OS_USING_SEMAPHORE must be set."
#endif
#if !defined(OS_USING_ALG_FIRSTFIT) && !defined(OS_USING_ALG_BUDDY)
#error "ERROR! At least one memory algorithm must be choose."
#endif配置项说明:
| 配置宏 | 是否必须 | 说明 |
|---|---|---|
OS_USING_HEAP | 必须 | 启用堆内存管理,编译进默认堆 + 多堆代码 |
OS_USING_SEMAPHORE | 必须 | 未定义时编译报错 Macro OS_USING_SEMAPHORE must be set(堆并发保护依赖信号量) |
OS_USING_ALG_FIRSTFIT | 至少选其一 | FIRSTFIT 内存算法 |
OS_USING_ALG_BUDDY | 至少选其一 | BUDDY 内存算法 |
OS_USING_MEM_TRACE | 可选 | 内存追踪(泄漏检测),额外提供 os_default_heap_trace()/os_heap_trace() |
OS_USING_SHELL | 可选 | 提供 show_mem/show_heap/check_mem/trace_mem Shell 命令 |
关键点:OS_USING_HEAP、OS_USING_SEMAPHORE、OS_USING_ALG_* 三者是硬约束——不满足会在编译期 #error。这保证了堆实现依赖的信号量、算法一定存在。
三、堆内存关键宏定义
堆内存实现和 API 中使用了多个宏,理解它们有助于看懂后续的源码分步详解。这些宏分布在不同的头文件中,集中讲解如下。
3.1 OS_HEAP_DEFINE(name) —— 定义堆控制块变量
来源:kernel/include/os_memory.h
#define OS_HEAP_DEFINE(name) os_heap_dummy_t name- 展开为一条变量声明:
os_heap_dummy_t name。 os_heap_dummy_t是给用户看的占位类型(typedef struct dummy_heap),内存布局与内核真正的os_heap_t一致。- 用于静态定义一个堆控制块变量(默认堆的
gs_default_heap_control_block、用户多堆的自定义控制块都由此宏产生)。 - 用法示例:
OS_HEAP_DEFINE(g_my_heap); /* 展开为:os_heap_dummy_t g_my_heap; */3.2 OS_TYPE_CONVERT(type, obj) —— 类型转换
来源:kernel/source/os_kernel_internal.h
#define OS_TYPE_CONVERT(type, obj) ((type)(obj))- 本质是 C 语言的强转:
((type)(obj))。 - 用于把用户侧占位类型(如
os_heap_dummy_t *)转换为内核真实类型(如os_heap_t *)。 - 示例(
os_heap_init中):
heap = OS_TYPE_CONVERT(os_heap_t *, heap_cb); /* 等价于 (os_heap_t *)heap_cb */- 也用于返回值的类型转换,如
return OS_TYPE_CONVERT(os_heap_id, heap)(把os_heap_t*转成os_heap_id句柄)。
3.3 OS_ALIGN_UP(size, align) —— 向上对齐
来源:kernel/include/os_stddef.h
/* Return the most contiguous size aligned at specified width. OS_ALIGN_UP(13, 4) would return 16. */
#define OS_ALIGN_UP(size, align) (((size) + (align)-1) & ~((align)-1))- 把
size向上取整到align的整数倍。 - 原理:
(size + align - 1) & ~(align - 1)——先加align-1再按align边界掩码取整(要求align为 2 的幂)。
具体演算(OS_ALIGN_UP(13, 4)):
| 步骤 | 值 | 说明 |
|---|---|---|
size | 13 = 0b1101 | 原始大小 |
align - 1 | 3 = 0b0011 | 掩码 |
size + align - 1 | 16 = 0b10000 | 加 align-1 |
~(align - 1) | 0b...1100 | 按 align 边界掩码 |
16 & 0b...1100 | 16 = 0b10000 | 结果为 16 |
用途:os_heap_add 里 start = OS_ALIGN_UP(start_addr, OS_ALIGN_SIZE)——把内存区域起始地址向上对齐。
3.4 OS_ALIGN_DOWN(size, align) —— 向下对齐
来源:kernel/include/os_stddef.h
/* Return the down number of aligned at specified width. OS_ALIGN_DOWN(13, 4) would return 12. */
#define OS_ALIGN_DOWN(size, align) ((size) & ~((align)-1))- 把
size向下取整到align的整数倍。 - 原理:
size & ~(align - 1)——直接按align边界掩码截断低位(要求align为 2 的幂)。
具体演算(OS_ALIGN_DOWN(13, 4)):
| 步骤 | 值 | 说明 |
|---|---|---|
size | 13 = 0b1101 | 原始大小 |
~(align - 1) | 0b...1100 | 按 align 边界掩码 |
13 & 0b...1100 | 12 = 0b1100 | 结果为 12 |
用途:os_heap_add 里 size = OS_ALIGN_DOWN(start_addr + size - start, OS_ALIGN_SIZE)——把对齐后的区域大小向下取整到对齐边界。
3.5 内存算法枚举 enum os_mem_alg —— 选择堆内存算法
来源:kernel/include/os_dummy.h
enum os_mem_alg
{
#ifdef OS_USING_ALG_FIRSTFIT
OS_MEM_ALG_FIRSTFIT,
#endif
#ifdef OS_USING_ALG_BUDDY
OS_MEM_ALG_BUDDY,
#endif
OS_MEM_ALG_DEFAULT /*Use default memory algorithm */
};各枚举值含义:
| 枚举值 | 说明 |
|---|---|
OS_MEM_ALG_FIRSTFIT | 首次适应算法(仅当配置 OS_USING_ALG_FIRSTFIT 时存在) |
OS_MEM_ALG_BUDDY | 伙伴算法(仅当配置 OS_USING_ALG_BUDDY 时存在) |
OS_MEM_ALG_DEFAULT | 默认算法,实际指向第一个注册的算法(值恒为最后一个) |
关键点:
- 枚举值依赖配置宏——未启用某算法则对应枚举不存在。
OS_MEM_ALG_DEFAULT始终存在,且排在最后;os_heap_add中if (OS_MEM_ALG_DEFAULT == alg) alg = (enum os_mem_alg)0把它转为第一个算法(通常是 FIRSTFIT)。alg_init_table(算法注册表)按配置顺序初始化,os_heap_add遍历该表找到alg对应算法并调用其mem_init。
这些宏与源码的对应关系:
| 宏/枚举 | 用在哪段源码 |
|---|---|
OS_HEAP_DEFINE | 定义默认堆控制块、用户多堆控制块 |
OS_TYPE_CONVERT | os_heap_init/os_heap_add 等的参数和返回值转换 |
OS_ALIGN_UP | os_heap_add 对齐起始地址 |
OS_ALIGN_DOWN | os_heap_add 对齐区域大小 |
OS_MEM_ALG_* | os_heap_add 选择算法、os_malloc 等 |
四、默认堆 API
默认堆是 OneOS 提供的一个全局堆实例,其 API 风格与标准 C 库类似,使用最为便捷。所有 API 定义在 kernel/include/os_memory.h 中。
4.1 初始化默认堆
void os_default_heap_init(void);| 参数 | 说明 |
|---|---|
| 返回值 | 无 |
- 功能:初始化默认堆控制块,创建名为
"DEFAULT_HEAP"的堆实例 - 调用时机:在系统启动阶段,添加内存区域之前调用
- 注意:此函数只初始化控制块,不分配实际内存区域
完整源码(kernel/source/os_memory.c,与项目一致):
void os_default_heap_init(void)
{
(void)memset(&gs_default_heap_control_block, 0, sizeof(os_heap_dummy_t));
gs_default_heap = os_heap_init(&gs_default_heap_control_block, "DEFAULT_HEAP");
if (!gs_default_heap)
{
OS_KERN_LOG(KERN_ERROR, MEM_TAG, "default memory heap init failed!");
}
}分步详解:
第一步:清零默认堆控制块(无条件执行)
(void)memset(&gs_default_heap_control_block, 0, sizeof(os_heap_dummy_t));进入本步的条件:调用 os_default_heap_init() 时无条件执行。把静态定义的默认堆控制块 gs_default_heap_control_block 清零,为复用做准备(gs_default_heap_control_block 是 OS_HEAP_DEFINE(gs_default_heap_control_block) 定义的静态变量)。
第二步:初始化默认堆(无条件执行)—— 调用 os_heap_init
gs_default_heap = os_heap_init(&gs_default_heap_control_block, "DEFAULT_HEAP");
if (!gs_default_heap)
{
OS_KERN_LOG(KERN_ERROR, MEM_TAG, "default memory heap init failed!");
}进入本步的条件:第一步完成。
os_heap_init(&gs_default_heap_control_block, "DEFAULT_HEAP"):创建名为"DEFAULT_HEAP"的堆实例,返回堆 ID 存入全局gs_default_heap(os_heap_init详解见 6.2)。- 子分支:条件
gs_default_heap == OS_NULL(初始化失败,如控制块已在资源链表)→ 打印错误日志default memory heap init failed!。
关键点:os_default_heap_init 只初始化控制块、不添加实际内存。真正的内存区域由 os_default_heap_add 在之后添加。默认堆的控制块是静态全局变量,不动态分配。
4.2 添加内存区域
os_err_t os_default_heap_add(void *start_addr, os_size_t size, enum os_mem_alg alg);| 参数 | 类型 | 说明 |
|---|---|---|
start_addr | void * | 内存区域的起始地址 |
size | os_size_t | 内存区域大小(字节) |
alg | enum os_mem_alg | 内存管理算法,可选 OS_MEM_ALG_FIRSTFIT、OS_MEM_ALG_BUDDY 或 OS_MEM_ALG_DEFAULT |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 添加成功 |
OS_INVAL | 参数无效或内存区域重叠 |
OS_NOMEM | 内存区域太小 |
- 功能:向默认堆添加一块可用内存区域,可以多次调用以组合多个不连续的内存区域
- 注意:
OS_MEM_ALG_DEFAULT会使用配置表中第一个注册的算法(即OS_MEM_ALG_FIRSTFIT或OS_MEM_ALG_BUDDY)
完整源码(kernel/source/os_memory.c,与项目一致):
os_err_t os_default_heap_add(void *start_addr, os_size_t size, enum os_mem_alg alg)
{
return os_heap_add(gs_default_heap, start_addr, size, alg);
}分步详解:
第一步:转发到多堆 API(无条件执行)—— 调用 os_heap_add
return os_heap_add(gs_default_heap, start_addr, size, alg);进入本步的条件:调用 os_default_heap_add() 时无条件执行。它只是 os_heap_add 的封装,把堆 ID 固定为默认堆 gs_default_heap,其余参数原样转发。完整实现和分步详解见 6.3 os_heap_add。
关键点:os_default_heap_add 与 os_heap_add 功能完全相同,区别仅在于操作的堆实例——前者固定操作默认堆 gs_default_heap,后者通过 heap_id 指定任意堆。理解 os_heap_add 即理解 os_default_heap_add。
4.3 分配内存
os_malloc() —— 从默认堆分配内存
void *os_malloc(os_size_t size);| 参数 | 说明 |
|---|---|
size | 请求的内存大小(字节) |
| 返回值 | 成功返回内存指针,失败返回 OS_NULL |
- 功能:从默认堆中分配
size字节的内存 - 注意:分配的内存内容是未初始化的
完整源码(kernel/source/os_memory.c,与项目一致):
void *os_malloc(os_size_t size)
{
return os_heap_alloc(gs_default_heap, size);
}分步详解:
第一步:转发到多堆 API(无条件执行)—— 调用 os_heap_alloc
return os_heap_alloc(gs_default_heap, size);进入本步的条件:调用 os_malloc() 时无条件执行。os_malloc 只是 os_heap_alloc 的封装,把堆 ID 固定为默认堆 gs_default_heap。完整实现见 6.4 os_heap_alloc(遍历堆的内存区域,逐个尝试算法分配)。
关键点:os_malloc 等价于标准 C 的 malloc,但分配的是 OneOS 默认堆的内存。失败返回 OS_NULL(不抛异常),调用方必须检查返回值。
os_aligned_malloc() —— 分配对齐内存
void *os_aligned_malloc(os_size_t align, os_size_t size);| 参数 | 说明 |
|---|---|
align | 对齐字节数,必须是 2 的幂 |
size | 请求的内存大小(字节) |
| 返回值 | 成功返回对齐后的内存指针,失败返回 OS_NULL |
- 功能:从默认堆中分配内存,返回地址按
align字节对齐
完整源码(kernel/source/os_memory.c,与项目一致):
void *os_aligned_malloc(os_size_t align, os_size_t size)
{
return os_heap_aligned_alloc(gs_default_heap, align, size);
}分步详解:
第一步:转发到多堆 API(无条件执行)—— 调用 os_heap_aligned_alloc
return os_heap_aligned_alloc(gs_default_heap, align, size);进入本步的条件:调用 os_aligned_malloc() 时无条件执行。os_aligned_malloc 只是 os_heap_aligned_alloc 的封装,堆 ID 固定为默认堆。完整实现见 6.4(遍历内存区域调用 k_aligned_alloc 算法分配对齐内存)。
关键点:适合需要特定对齐的场景(如 DMA 缓冲区)。align 必须是 2 的幂,否则对齐行为不确定。
os_calloc() —— 分配并清零内存
void *os_calloc(os_size_t count, os_size_t size);| 参数 | 说明 |
|---|---|
count | 元素个数 |
size | 每个元素大小(字节) |
| 返回值 | 成功返回内存指针,失败返回 OS_NULL |
- 功能:分配
count * size字节的内存,并将内存初始化为零
完整源码(kernel/source/os_memory.c,与项目一致):
void *os_calloc(os_size_t count, os_size_t size)
{
void *ptr;
ptr = os_heap_alloc(gs_default_heap, count * size);
if (ptr)
{
(void)memset(ptr, 0, count * size);
}
return ptr;
}分步详解:
第一步:分配内存(无条件执行)—— 调用 os_heap_alloc
void *ptr;
ptr = os_heap_alloc(gs_default_heap, count * size);进入本步的条件:调用 os_calloc() 时无条件执行。os_heap_alloc(gs_default_heap, count * size) 从默认堆分配 count * size 字节,返回指针 ptr。
第二步:分配成功则清零(条件:ptr != OS_NULL)—— memset 置零
if (ptr)
{
(void)memset(ptr, 0, count * size);
}
return ptr;进入本步的条件:第一步返回的 ptr != OS_NULL(分配成功)。
- 子分支 A:条件
ptr != OS_NULL→memset(ptr, 0, count * size)把内存清零。 - 子分支 B:条件
ptr == OS_NULL(分配失败)→ 跳过清零,直接返回OS_NULL。
关键点:os_calloc = os_malloc(count*size) + memset 0。与标准 calloc 一致,适合需要零初始化数组的场景。
4.4 释放内存
void os_free(void *ptr);| 参数 | 说明 |
|---|---|
ptr | 要释放的内存指针 |
| 返回值 | 无 |
- 功能:释放之前通过
os_malloc、os_realloc、os_calloc或os_aligned_malloc分配的内存 - 注意:释放后指针不再有效,不应继续使用
完整源码(kernel/source/os_memory.c,与项目一致):
void os_free(void *ptr)
{
os_heap_free(gs_default_heap, ptr);
}分步详解:
第一步:转发到多堆 API(无条件执行)—— 调用 os_heap_free
os_heap_free(gs_default_heap, ptr);进入本步的条件:调用 os_free() 时无条件执行。os_free 只是 os_heap_free 的封装,堆 ID 固定为默认堆。完整实现见 6.4 os_heap_free(遍历内存区域,找到 ptr 所属区域并调用 k_free 释放)。
关键点:ptr 必须是由 OneOS 堆 API 分配的有效指针。释放非法/重复/未由堆分配的指针会触发 OS_ASSERT_EX 断言(见 6.4 os_heap_free)。释放后指针置为悬空,不应再使用。
4.5 重分配内存
void *os_realloc(void *ptr, os_size_t size);| 参数 | 说明 |
|---|---|
ptr | 原内存指针(若为 OS_NULL,行为等同于 os_malloc) |
size | 新的大小(字节),若为 0,行为等同于 os_free |
| 返回值 | 成功返回新内存指针,失败返回 OS_NULL(原内存保持不变) |
- 功能:调整已分配内存块的大小
- 注意:重分配可能移动内存位置,返回的指针可能与原指针不同
完整源码(kernel/source/os_memory.c,与项目一致):
void *os_realloc(void *ptr, os_size_t size)
{
return os_heap_realloc(gs_default_heap, ptr, size);
}分步详解:
第一步:转发到多堆 API(无条件执行)—— 调用 os_heap_realloc
return os_heap_realloc(gs_default_heap, ptr, size);进入本步的条件:调用 os_realloc() 时无条件执行。os_realloc 只是 os_heap_realloc 的封装,堆 ID 固定为默认堆。完整实现见 6.4 os_heap_realloc(若 ptr 为 OS_NULL 则等同分配;否则在所属区域重分配;原区域失败则尝试其他区域并拷贝)。
关键点:os_realloc 兼容三种语义——ptr==OS_NULL 时等同 os_malloc(size)、size==0 时等同 os_free(ptr)、否则调整大小。失败时返回 OS_NULL 且原内存保持不变。
4.6 获取堆信息
void os_default_heap_info(os_meminfo_t *info);| 参数 | 说明 |
|---|---|
info | 指向 os_meminfo_t 结构体的指针,用于接收统计信息 |
| 返回值 | 无 |
- 功能:获取默认堆的统计信息(总大小/已用/峰值)
完整源码(kernel/source/os_memory.c,与项目一致):
void os_default_heap_info(os_meminfo_t *info)
{
OS_ASSERT(info);
os_heap_info(gs_default_heap, info);
}分步详解:
第一步:参数校验(条件:info != OS_NULL)—— 不满足则断言崩溃
OS_ASSERT(info);进入本步的条件:调用 os_default_heap_info() 时无条件执行。info 是传出参数(函数要写它),必须非空。
第二步:转发到多堆 API(无条件执行)—— 调用 os_heap_info
os_heap_info(gs_default_heap, info);进入本步的条件:第一步断言通过。os_heap_info 遍历默认堆所有内存区域,累加 mem_total/mem_used/mem_maxused 到 info(完整实现见 6.4)。
4.7 堆完整性检查
os_err_t os_default_heap_check(void);| 参数 | 说明 |
|---|---|
| 返回值 | OS_SUCCESS 表示堆正常,其他值表示发现错误 |
- 功能:检查默认堆的完整性,验证所有内存块的数据结构是否一致
完整源码(kernel/source/os_memory.c,与项目一致):
os_err_t os_default_heap_check(void)
{
return os_heap_check(gs_default_heap);
}分步详解:
第一步:转发到多堆 API(无条件执行)—— 调用 os_heap_check
return os_heap_check(gs_default_heap);进入本步的条件:调用 os_default_heap_check() 时无条件执行。os_default_heap_check 只是 os_heap_check 的封装,堆 ID 固定为默认堆。完整实现见 6.4 os_heap_check(遍历内存区域,调用 k_mem_check 校验每个区域的数据结构一致性)。
关键点:堆完整性检查用于调试内存越界/损坏——检测内存块的头部/尾部标记是否被改写。若返回非 OS_SUCCESS,说明堆被破坏,通常是缓冲区越界或野指针写入导致。
五、默认堆使用示例
#include <os_memory.h>
/* 在系统启动时初始化堆 */
void board_memory_init(void)
{
/* 初始化默认堆控制块 */
os_default_heap_init();
/* 添加 64KB 堆内存区域,使用默认算法 */
os_default_heap_add((void *)0x20000000, 64 * 1024, OS_MEM_ALG_DEFAULT);
}
/* 应用代码中使用堆内存 */
void app_task(void)
{
os_meminfo_t info;
char *buf1;
char *buf2;
int *arr;
/* 1. 分配普通内存 */
buf1 = (char *)os_malloc(128);
if (buf1)
{
os_kprintf("分配 128 字节成功,地址: %p\r\n", buf1);
memset(buf1, 0, 128); /* 使用 buf1(清空后写入数据) */
}
/* 2. 分配对齐内存 */
buf2 = (char *)os_aligned_malloc(64, 256);
if (buf2)
{
os_kprintf("分配 64 字节对齐的 256 字节成功,地址: %p\r\n", buf2);
snprintf(buf2, 256, "aligned buffer"); /* 使用 buf2 */
}
/* 3. 分配零初始化内存 */
arr = (int *)os_calloc(10, sizeof(int));
if (arr)
{
/* arr[0] ~ arr[9] 已被初始化为 0 */
os_kprintf("calloc 10 个 int 成功\r\n");
}
/* 4. 重分配内存 */
buf1 = (char *)os_realloc(buf1, 512);
if (buf1)
{
os_kprintf("重分配为 512 字节成功\r\n");
}
/* 5. 查看堆内存使用情况 */
os_default_heap_info(&info);
os_kprintf("堆: 总计=%lu 已用=%lu 峰值=%lu\r\n",
info.mem_total, info.mem_used, info.mem_maxused);
/* 6. 释放内存 */
os_free(buf1);
os_free(buf2);
os_free(arr);
/* 7. 检查堆完整性 */
if (os_default_heap_check() == OS_SUCCESS)
{
os_kprintf("堆完整性检查通过\r\n");
}
}六、多堆管理
除了默认堆,OneOS 还支持创建多个自定义堆实例,适用于需要将不同内存区域独立管理的场景(例如:将 SRAM 和 SDRAM 分别作为独立的堆来管理)。
6.1 静态定义堆
#define OS_HEAP_DEFINE(name) os_heap_dummy_t name- 功能:静态定义一个堆控制块变量
- 示例:
OS_HEAP_DEFINE(my_sdram_heap);定义一个名为my_sdram_heap的堆控制块
宏定义详解(kernel/include/os_memory.h,与项目一致):
#define OS_HEAP_DEFINE(name) os_heap_dummy_t name- 展开为一条变量声明:
os_heap_dummy_t name。 os_heap_dummy_t是给用户看的占位类型(typedef struct dummy_heap),内存布局与内核真正的os_heap_t一致。- 用户用它定义一个全局变量
name,这块静态内存将来被内核当作堆控制块使用。
关键点:静态定义的堆控制块是静态内存,不依赖 OS_USING_HEAP 之外的动态分配。多堆的每个堆都需要一个独立的 OS_HEAP_DEFINE 变量。
6.2 创建堆
os_heap_id os_heap_init(os_heap_dummy_t *heap_cb, const char *name);| 参数 | 类型 | 说明 |
|---|---|---|
heap_cb | os_heap_dummy_t * | 静态定义的堆控制块指针 |
name | const char * | 堆名称(用于调试和 Shell 显示) |
| 返回值 | 说明 |
|---|---|
os_heap_id | 堆实例 ID,非空表示成功 |
OS_NULL | 创建失败(堆已存在) |
完整源码(kernel/source/os_memory.c,与项目一致):
os_heap_id os_heap_init(os_heap_dummy_t *heap_cb, const char *name)
{
os_list_node_t *pos;
os_heap_t *item_heap;
os_heap_t *heap;
os_err_t ret;
heap = OS_TYPE_CONVERT(os_heap_t *, heap_cb);
OS_ASSERT(heap);
ret = OS_SUCCESS;
os_spin_lock(&gs_os_heap_resource_list_lock);
os_list_for_each(pos, &gs_os_heap_resource_list_head)
{
item_heap = os_list_entry(pos, os_heap_t, resource_node);
if (item_heap == heap)
{
os_spin_unlock(&gs_os_heap_resource_list_lock);
OS_KERN_LOG(KERN_ERROR, MEM_TAG, "The heap(addr: %p) already exist", item_heap);
ret = OS_INVAL;
heap = OS_NULL;
break;
}
}
if (OS_SUCCESS == ret)
{
os_list_add_tail(&gs_os_heap_resource_list_head, &heap->resource_node);
os_spin_unlock(&gs_os_heap_resource_list_lock);
if (OS_NULL != name)
{
strncpy(&heap->name[0], name, OS_NAME_MAX);
heap->name[OS_NAME_MAX] = '\0';
}
else
{
heap->name[0] = '\0';
}
heap->h_mem = OS_NULL;
heap->object_inited = OS_KOBJ_INITED;
}
return OS_TYPE_CONVERT(os_heap_id, heap);
}分步详解:
第一步:准备与参数校验(条件:heap_cb 非空)—— 不满足则断言崩溃
heap = OS_TYPE_CONVERT(os_heap_t *, heap_cb);
OS_ASSERT(heap);
ret = OS_SUCCESS;进入本步的条件:调用 os_heap_init() 时无条件执行。heap_cb 经强转后必须非空(用户传入的静态控制块)。
第二步:防重复创建检查(条件:资源链表遍历)—— 已存在则返回 OS_INVAL
os_spin_lock(&gs_os_heap_resource_list_lock);
os_list_for_each(pos, &gs_os_heap_resource_list_head)
{
item_heap = os_list_entry(pos, os_heap_t, resource_node);
if (item_heap == heap)
{
os_spin_unlock(&gs_os_heap_resource_list_lock);
OS_KERN_LOG(KERN_ERROR, MEM_TAG, "The heap(addr: %p) already exist", item_heap);
ret = OS_INVAL;
heap = OS_NULL;
break;
}
}进入本步的条件:第一步断言通过。加堆资源链表锁 gs_os_heap_resource_list_lock,遍历整个堆资源链表。
- 子分支:条件
item_heap == heap(发现同一控制块地址已创建过,即重复创建)→ 释放锁、打日志The heap already exist、ret = OS_INVAL、heap = OS_NULL,break。
为什么需防重复:堆控制块由用户静态提供、地址固定。同一个 heap_cb 地址被 os_heap_init 两次,会重复登记进资源链表、重复初始化同一块内存,造成链表错乱。
第三步:登记并初始化(条件:未重复创建,ret == OS_SUCCESS)—— 登记链表 + 复制名称 + 初始化字段
if (OS_SUCCESS == ret)
{
os_list_add_tail(&gs_os_heap_resource_list_head, &heap->resource_node);
os_spin_unlock(&gs_os_heap_resource_list_lock);
if (OS_NULL != name)
{
strncpy(&heap->name[0], name, OS_NAME_MAX);
heap->name[OS_NAME_MAX] = '\0';
}
else
{
heap->name[0] = '\0';
}
heap->h_mem = OS_NULL;
heap->object_inited = OS_KOBJ_INITED;
}
return OS_TYPE_CONVERT(os_heap_id, heap);进入本步的条件:第二步未发现重复(ret == OS_SUCCESS)。
os_list_add_tail(...):把堆的resource_node登记进堆资源链表。- 子分支:条件
name != OS_NULL→strncpy复制名称(最多OS_NAME_MAX字符,末尾补'\0');否则(name == OS_NULL)→name[0] = '\0'(空名)。 h_mem = OS_NULL:初始无内存区域。object_inited = OS_KOBJ_INITED:标记已初始化。
最终返回值:return OS_TYPE_CONVERT(os_heap_id, heap)——成功返回堆 ID;重复创建时 heap == OS_NULL,返回 OS_NULL。
6.3 添加内存区域
os_err_t os_heap_add(os_heap_id heap_id, void *start_addr, os_size_t size, enum os_mem_alg alg);| 参数 | 类型 | 说明 |
|---|---|---|
heap_id | os_heap_id | 堆实例 ID |
start_addr | void * | 内存区域起始地址 |
size | os_size_t | 内存区域大小(字节) |
alg | enum os_mem_alg | 内存管理算法 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 添加成功 |
OS_INVAL | 参数无效或内存区域重叠 |
OS_NOMEM | 内存区域太小 |
参数与 os_default_heap_add 相同,但需要指定 heap_id。
完整源码(kernel/source/os_memory.c,与项目一致):
os_err_t os_heap_add(os_heap_id heap_id, void *start_addr, os_size_t size, enum os_mem_alg alg)
{
os_size_t start;
struct heap_mem *h_mem;
struct heap_mem *h_mem_new;
os_bool_t mem_inited = OS_FALSE;
os_size_t i;
os_list_node_t *pos;
os_heap_t *item_heap;
os_heap_t *heap;
struct heap_mem *h_mem_temp;
os_size_t h_mem_temp_start;
os_size_t h_mem_temp_end;
os_err_t ret;
heap = OS_TYPE_CONVERT(os_heap_t *, heap_id);
OS_ASSERT(OS_NULL != heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);
OS_ASSERT(alg <= OS_MEM_ALG_DEFAULT);
if (OS_MEM_ALG_DEFAULT == alg)
{
alg = ((enum os_mem_alg)0);
}
ret = OS_SUCCESS;
start = OS_ALIGN_UP((os_size_t)(char *)start_addr, OS_ALIGN_SIZE);
OS_ASSERT(((os_size_t)(char *)start_addr + size) > start);
size = OS_ALIGN_DOWN((os_size_t)(char *)start_addr + size - start, OS_ALIGN_SIZE);
if (size > sizeof(struct heap_mem))
{
os_spin_lock(&gs_os_heap_resource_list_lock);
/* Search each memheap. */
os_list_for_each(pos, &gs_os_heap_resource_list_head)
{
item_heap = os_list_entry(pos, os_heap_t, resource_node);
/* Every memheap may contains multiple memory. Check each memory. */
h_mem_temp = item_heap->h_mem;
while (OS_NULL != h_mem_temp)
{
h_mem_temp_start = (os_size_t)h_mem_temp;
h_mem_temp_end = (os_size_t)(char *)h_mem_temp->header + h_mem_temp->mem_total;
OS_ASSERT(h_mem_temp_start < h_mem_temp_end);
if (!((start >= h_mem_temp_end) || (h_mem_temp_start >= (start + size))))
{
OS_KERN_LOG(KERN_ERROR, MEM_TAG, "memory is overlaped, please check !");
ret = OS_INVAL;
break;
}
h_mem_temp = h_mem_temp->next;
}
if (OS_SUCCESS != ret)
{
os_spin_unlock(&gs_os_heap_resource_list_lock);
break;
}
}
}
else
{
OS_KERN_LOG(KERN_ERROR, MEM_TAG, "memory is too small !");
ret = OS_NOMEM;
}
if (OS_SUCCESS == ret)
{
h_mem_new = (struct heap_mem *)start;
(void)memset((void *)h_mem_new, 0, sizeof(struct heap_mem));
start += sizeof(struct heap_mem);
size -= sizeof(struct heap_mem);
for (i = 0; i < (sizeof(alg_init_table) / sizeof(struct alg_init_func)); i++)
{
if (alg == alg_init_table[i].mem_alg)
{
alg_init_table[i].mem_init(h_mem_new, (void *)(char *)start, size);
mem_inited = OS_TRUE;
break;
}
}
if (OS_TRUE == mem_inited)
{
if (OS_NULL != heap->h_mem)
{
h_mem = heap->h_mem;
while (OS_NULL != h_mem->next)
{
h_mem = h_mem->next;
}
h_mem->next = h_mem_new;
}
else
{
heap->h_mem = h_mem_new;
}
}
else
{
OS_KERN_LOG(KERN_ERROR, MEM_TAG, "memory algorithm %d not support, please check !", (int32_t)alg);
ret = OS_INVAL;
}
os_spin_unlock(&gs_os_heap_resource_list_lock);
}
return ret;
}分步详解:
第一步:准备与参数校验(条件:堆非空/已初始化/算法合法)—— 不满足则断言崩溃
heap = OS_TYPE_CONVERT(os_heap_t *, heap_id);
OS_ASSERT(OS_NULL != heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);
OS_ASSERT(alg <= OS_MEM_ALG_DEFAULT);
if (OS_MEM_ALG_DEFAULT == alg)
{
alg = ((enum os_mem_alg)0);
}进入本步的条件:调用 os_heap_add() 时无条件执行(入口硬性校验)。
heap != OS_NULL、object_inited == OS_KOBJ_INITED:堆已初始化。alg <= OS_MEM_ALG_DEFAULT:算法合法。- 子条件:
alg == OS_MEM_ALG_DEFAULT→ 把算法改为(enum os_mem_alg)0(即OS_MEM_ALG_FIRSTFIT,因为alg_init_table第一个注册的通常是 FIRSTFIT)。
第二步:对齐起始地址和大小(无条件执行)—— 地址对齐 + 大小对齐
ret = OS_SUCCESS;
start = OS_ALIGN_UP((os_size_t)(char *)start_addr, OS_ALIGN_SIZE);
OS_ASSERT(((os_size_t)(char *)start_addr + size) > start);
size = OS_ALIGN_DOWN((os_size_t)(char *)start_addr + size - start, OS_ALIGN_SIZE);进入本步的条件:第一步断言通过。
start = OS_ALIGN_UP(start_addr, OS_ALIGN_SIZE):起始地址向上对齐到对齐边界。OS_ASSERT((start_addr + size) > start):校验对齐后地址不越界(防起始地址+大小溢出)。size = OS_ALIGN_DOWN(start_addr + size - start, OS_ALIGN_SIZE):对齐后的内存区域大小(去掉对齐损失,向下取整到对齐边界)。
第三步:检查内存区域是否重叠(条件:size > sizeof(struct heap_mem))—— 重叠则返回 OS_INVAL,太小返回 OS_NOMEM
if (size > sizeof(struct heap_mem))
{
os_spin_lock(&gs_os_heap_resource_list_lock);
os_list_for_each(pos, &gs_os_heap_resource_list_head)
{
item_heap = os_list_entry(pos, os_heap_t, resource_node);
h_mem_temp = item_heap->h_mem;
while (OS_NULL != h_mem_temp)
{
h_mem_temp_start = (os_size_t)h_mem_temp;
h_mem_temp_end = (os_size_t)(char *)h_mem_temp->header + h_mem_temp->mem_total;
OS_ASSERT(h_mem_temp_start < h_mem_temp_end);
if (!((start >= h_mem_temp_end) || (h_mem_temp_start >= (start + size))))
{
OS_KERN_LOG(KERN_ERROR, MEM_TAG, "memory is overlaped, please check !");
ret = OS_INVAL;
break;
}
h_mem_temp = h_mem_temp->next;
}
if (OS_SUCCESS != ret)
{
os_spin_unlock(&gs_os_heap_resource_list_lock);
break;
}
}
}
else
{
OS_KERN_LOG(KERN_ERROR, MEM_TAG, "memory is too small !");
ret = OS_NOMEM;
}进入本步的条件:第二步完成。size > sizeof(struct heap_mem)(对齐后大小能容纳一个堆管理头 heap_mem)。
- 加堆资源链表锁,遍历所有堆的所有内存区域:
- 计算每个已存在区域的范围
[h_mem_temp_start, h_mem_temp_end); - 条件
start >= h_mem_temp_end || h_mem_temp_start >= start+size(新区间与旧区间不重叠)→ 继续检查下一个; - 否则(重叠)→ 打日志
memory is overlaped,ret = OS_INVAL,break。
- 计算每个已存在区域的范围
- 条件
size <= sizeof(struct heap_mem)(区域太小,连管理头都放不下)→ret = OS_NOMEM。
第四步:调用算法初始化内存区域(条件:ret == OS_SUCCESS)—— 查找算法并初始化
if (OS_SUCCESS == ret)
{
h_mem_new = (struct heap_mem *)start;
(void)memset((void *)h_mem_new, 0, sizeof(struct heap_mem));
start += sizeof(struct heap_mem);
size -= sizeof(struct heap_mem);
for (i = 0; i < (sizeof(alg_init_table) / sizeof(struct alg_init_func)); i++)
{
if (alg == alg_init_table[i].mem_alg)
{
alg_init_table[i].mem_init(h_mem_new, (void *)(char *)start, size);
mem_inited = OS_TRUE;
break;
}
}
}进入本步的条件:第三步未发现重叠/太小(ret == OS_SUCCESS)。
- 把
start处作为h_mem_new(新内存区域管理头),清零,start前进一个heap_mem头大小、size减去头大小。 - 遍历算法注册表
alg_init_table:- 条件
alg == alg_init_table[i].mem_alg→ 调用对应算法的mem_init(如k_firstfit_mem_init)初始化该区域,mem_inited = OS_TRUE,break。
- 条件
第五步:挂接到堆的内存区域链表(条件:算法初始化成功)—— 头插或尾接
if (OS_TRUE == mem_inited)
{
if (OS_NULL != heap->h_mem)
{
h_mem = heap->h_mem;
while (OS_NULL != h_mem->next)
{
h_mem = h_mem->next;
}
h_mem->next = h_mem_new;
}
else
{
heap->h_mem = h_mem_new;
}
}
else
{
OS_KERN_LOG(KERN_ERROR, MEM_TAG, "memory algorithm %d not support, please check !", (int32_t)alg);
ret = OS_INVAL;
}
os_spin_unlock(&gs_os_heap_resource_list_lock);进入本步的条件:第四步完成。
- 子分支 A:条件
mem_inited == OS_TRUE(算法初始化成功):- 条件
heap->h_mem != OS_NULL(已有内存区域)→ 遍历到链表末尾,把h_mem_new接到尾部(一个堆可组合多个内存区域); - 条件
heap->h_mem == OS_NULL(首个区域)→heap->h_mem = h_mem_new。
- 条件
- 子分支 B:条件
mem_inited == OS_FALSE(算法未找到)→ 打日志memory algorithm not support,ret = OS_INVAL。 - 释放锁。
os_heap_add 的 if-else 条件走向总结:
os_heap_add() 条件走向总结:
├── OS_ASSERT(heap 非空 / 已初始化 / alg 合法)任一失败 → 断言崩溃
├── if (alg == OS_MEM_ALG_DEFAULT) → alg = (enum os_mem_alg)0(FIRSTFIT)
├── 地址/大小对齐
├── if (size > sizeof(struct heap_mem))(够放管理头)
│ ├── 遍历所有堆的内存区域检查重叠
│ │ ├── 若不重叠 → 继续检查
│ │ └── 若重叠 → 返回 OS_INVAL
│ └── 若 ret == OS_SUCCESS
│ ├── 用 h_mem_new 作为管理头,按 alg 查算法表
│ │ ├── 找到算法 → mem_init 初始化,mem_inited = OS_TRUE
│ │ └── 未找到 → 返回 OS_INVAL
│ ├── if (mem_inited) → 挂到 h_mem 链表(尾接或头插)→ 返回 OS_SUCCESS
│ └── else → 返回 OS_INVAL
└── else(size 太小)→ 返回 OS_NOMEM6.4 多堆分配与释放
void *os_heap_alloc(os_heap_id heap_id, os_size_t size);
void *os_heap_aligned_alloc(os_heap_id heap_id, os_size_t align, os_size_t size);
void os_heap_free(os_heap_id heap_id, void *ptr);
void *os_heap_realloc(os_heap_id heap_id, void *ptr, os_size_t size);
void os_heap_info(os_heap_id heap_id, os_meminfo_t *info);
os_err_t os_heap_check(os_heap_id heap_id);这些 API 与默认堆 API 功能相同,但需要指定 heap_id 来操作特定的堆实例。
os_heap_alloc() —— 从指定堆分配内存
完整源码(kernel/source/os_memory.c,与项目一致):
void *os_heap_alloc(os_heap_id heap_id, os_size_t size)
{
os_heap_t *heap;
struct heap_mem *h_mem;
void *ptr;
heap = OS_TYPE_CONVERT(os_heap_t *, heap_id);
OS_ASSERT(heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);
ptr = OS_NULL;
h_mem = heap->h_mem;
while (h_mem && h_mem->k_alloc)
{
ptr = h_mem->k_alloc(h_mem, size);
if (ptr)
{
break;
}
h_mem = h_mem->next;
}
return ptr;
}分步详解:
第一步:参数校验(条件:堆非空/已初始化)—— 不满足则断言崩溃
OS_ASSERT(heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);进入本步的条件:调用 os_heap_alloc() 时无条件执行。堆非空、已初始化。
第二步:遍历内存区域逐个尝试分配(条件:存在 h_mem 且支持 k_alloc)—— 任一成功即返回
ptr = OS_NULL;
h_mem = heap->h_mem;
while (h_mem && h_mem->k_alloc)
{
ptr = h_mem->k_alloc(h_mem, size);
if (ptr)
{
break;
}
h_mem = h_mem->next;
}
return ptr;进入本步的条件:第一步断言通过。
- 条件
h_mem != OS_NULL且h_mem->k_alloc != OS_NULL(有内存区域且该区域算法支持分配)→ 调用h_mem->k_alloc(h_mem, size)尝试分配。 - 子分支:条件
ptr != OS_NULL(该区域分配成功)→break返回; - 否则(该区域内存不足)→
h_mem = h_mem->next尝试下一个内存区域。 - 全部区域都失败 → 返回
OS_NULL。
关键点:一个堆可有多个内存区域(os_heap_add 多次添加),分配时依次尝试每个区域,任一区域成功即可。这是多区域堆的实现方式。
os_heap_aligned_alloc() —— 从指定堆分配对齐内存
完整源码(kernel/source/os_memory.c,与项目一致):
void *os_heap_aligned_alloc(os_heap_id heap_id, os_size_t align, os_size_t size)
{
struct heap_mem *h_mem;
void *ptr;
os_heap_t *heap;
heap = OS_TYPE_CONVERT(os_heap_t *, heap_id);
OS_ASSERT(heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);
ptr = OS_NULL;
h_mem = heap->h_mem;
while (h_mem && h_mem->k_aligned_alloc)
{
ptr = h_mem->k_aligned_alloc(h_mem, align, size);
if (ptr)
{
break;
}
h_mem = h_mem->next;
}
return ptr;
}分步详解:
第一步:参数校验(条件:堆非空/已初始化)—— 不满足则断言崩溃
OS_ASSERT(heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);进入本步的条件:调用 os_heap_aligned_alloc() 时无条件执行。堆非空、已初始化。
第二步:遍历内存区域尝试对齐分配(条件:存在 h_mem 且支持 k_aligned_alloc)
ptr = OS_NULL;
h_mem = heap->h_mem;
while (h_mem && h_mem->k_aligned_alloc)
{
ptr = h_mem->k_aligned_alloc(h_mem, align, size);
if (ptr)
{
break;
}
h_mem = h_mem->next;
}
return ptr;进入本步的条件:第一步断言通过。与 os_heap_alloc 逻辑相同,但调用每个区域的 k_aligned_alloc(h_mem, align, size) 进行对齐分配(align 必须为 2 的幂)。
os_heap_free() —— 从指定堆释放内存
完整源码(kernel/source/os_memory.c,与项目一致):
void os_heap_free(os_heap_id heap_id, void *ptr)
{
struct heap_mem *h_mem;
os_heap_t *heap;
heap = OS_TYPE_CONVERT(os_heap_t *, heap_id);
OS_ASSERT(heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);
OS_ASSERT(ptr);
h_mem = heap->h_mem;
while (h_mem)
{
if ((ptr >= h_mem->header) && ((os_size_t)ptr <= ((os_size_t)h_mem->header + h_mem->mem_total)))
{
if (h_mem->k_free)
{
h_mem->k_free(h_mem, ptr);
}
break;
}
h_mem = h_mem->next;
}
OS_ASSERT_EX(h_mem, "unexpected heap or mem addr (invalid addr: %p ?)", ptr);
}分步详解:
第一步:参数校验(条件:堆非空/已初始化/ptr 非空)—— 不满足则断言崩溃
OS_ASSERT(heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);
OS_ASSERT(ptr);进入本步的条件:调用 os_heap_free() 时无条件执行。堆非空、已初始化、ptr 非空。
第二步:定位 ptr 所属内存区域并释放(条件:找到 ptr 在哪个 h_mem 范围内)
h_mem = heap->h_mem;
while (h_mem)
{
if ((ptr >= h_mem->header) && ((os_size_t)ptr <= ((os_size_t)h_mem->header + h_mem->mem_total)))
{
if (h_mem->k_free)
{
h_mem->k_free(h_mem, ptr);
}
break;
}
h_mem = h_mem->next;
}
OS_ASSERT_EX(h_mem, "unexpected heap or mem addr (invalid addr: %p ?)", ptr);进入本步的条件:第一步断言通过。
- 遍历堆的内存区域,条件
ptr落在该区域[header, header+mem_total]内 → 调用h_mem->k_free(h_mem, ptr)释放该区域内的块,break。 - 遍历完未找到(
h_mem == OS_NULL,ptr不是本堆分配的内存)→OS_ASSERT_EX断言崩溃。
关键点:os_heap_free 不能释放不是本堆分配的指针——ptr 必须来自 os_heap_alloc/os_heap_aligned_alloc 等本堆分配函数,否则触发断言。
os_heap_realloc() —— 重分配指定堆内存
完整源码(kernel/source/os_memory.c,与项目一致):
void *os_heap_realloc(os_heap_id heap_id, void *ptr, os_size_t size)
{
struct heap_mem *h_mem;
struct heap_mem *h_mem_new;
os_size_t oldsize;
void *ptr_new;
os_heap_t *heap;
heap = OS_TYPE_CONVERT(os_heap_t *, heap_id);
OS_ASSERT(heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);
h_mem = OS_NULL;
ptr_new = OS_NULL;
if (!ptr)
{
ptr_new = os_heap_alloc((os_heap_id)heap, size);
}
else
{
/* Find the original h_mem. */
h_mem = heap->h_mem;
while (h_mem)
{
if ((ptr >= h_mem->header) && (((os_size_t)ptr - (os_size_t)h_mem->header) <= h_mem->mem_total))
{
break;
}
h_mem = h_mem->next;
}
/* Realloc in the original h_mem. */
if (h_mem && h_mem->k_realloc)
{
ptr_new = h_mem->k_realloc(h_mem, ptr, size);
}
OS_ASSERT_EX(h_mem, "unexpected heap or mem addr (invalid addr: %p ?)", ptr);
}
if ((!ptr_new) && h_mem && (h_mem->k_free))
{
/* Alloc in the other h_mem. */
h_mem_new = heap->h_mem;
while (h_mem_new)
{
if ((h_mem_new != h_mem) && (h_mem_new->k_alloc))
{
ptr_new = h_mem_new->k_alloc(h_mem_new, size);
if (ptr_new)
{
oldsize = h_mem->k_ptr_to_size(h_mem, ptr);
memcpy(ptr_new, ptr, oldsize > size ? size : oldsize);
h_mem->k_free(h_mem, ptr);
break;
}
}
h_mem_new = h_mem_new->next;
}
}
return ptr_new;
}分步详解:
第一步:参数校验(条件:堆非空/已初始化)—— 不满足则断言崩溃
OS_ASSERT(heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);进入本步的条件:调用 os_heap_realloc() 时无条件执行。堆非空、已初始化。
第二步:ptr 为空则直接分配(条件:ptr == OS_NULL)—— 等同 os_heap_alloc
if (!ptr)
{
ptr_new = os_heap_alloc((os_heap_id)heap, size);
}进入本步的条件:ptr == OS_NULL(无原内存块)→ os_heap_alloc 直接分配 size 字节,ptr_new = 分配结果(等同 malloc 语义)。
第三步:在所属内存区域重分配(条件:ptr != OS_NULL)—— 查找原区域并重分配
else
{
h_mem = heap->h_mem;
while (h_mem)
{
if ((ptr >= h_mem->header) && (((os_size_t)ptr - (os_size_t)h_mem->header) <= h_mem->mem_total))
{
break;
}
h_mem = h_mem->next;
}
if (h_mem && h_mem->k_realloc)
{
ptr_new = h_mem->k_realloc(h_mem, ptr, size);
}
OS_ASSERT_EX(h_mem, "unexpected heap or mem addr (invalid addr: %p ?)", ptr);
}进入本步的条件:ptr != OS_NULL(有原内存块)。
- 遍历内存区域,找到
ptr所属的h_mem。 - 条件
h_mem存在且支持k_realloc→ 调用h_mem->k_realloc(h_mem, ptr, size)在原区域尝试调整大小,结果存ptr_new。 - 找不到
h_mem(ptr不是本堆的)→OS_ASSERT_EX断言崩溃。
第四步:原区域失败则迁移到其他区域(条件:ptr_new == OS_NULL 且原区域支持释放)—— 拷贝并释放原块
if ((!ptr_new) && h_mem && (h_mem->k_free))
{
h_mem_new = heap->h_mem;
while (h_mem_new)
{
if ((h_mem_new != h_mem) && (h_mem_new->k_alloc))
{
ptr_new = h_mem_new->k_alloc(h_mem_new, size);
if (ptr_new)
{
oldsize = h_mem->k_ptr_to_size(h_mem, ptr);
memcpy(ptr_new, ptr, oldsize > size ? size : oldsize);
h_mem->k_free(h_mem, ptr);
break;
}
}
h_mem_new = h_mem_new->next;
}
}
return ptr_new;进入本步的条件:ptr_new == OS_NULL(原区域重分配失败,如空间不足)且原区域支持释放 k_free。
- 遍历其他内存区域(
h_mem_new != h_mem)尝试k_alloc; - 条件分配成功 →
oldsize = k_ptr_to_size取原块大小,memcpy(ptr_new, ptr, min(oldsize, size))拷贝数据,h_mem->k_free释放原块,break。
关键点:os_heap_realloc 支持三种情况——ptr==NULL(等同 malloc)、在原区域原地重分配、原区域失败则迁移到其他区域并拷贝数据。失败返回 OS_NULL 且原内存不变。
os_heap_info() —— 获取指定堆统计信息
完整源码(kernel/source/os_memory.c,与项目一致):
void os_heap_info(os_heap_id heap_id, os_meminfo_t *info)
{
struct heap_mem *h_mem;
os_heap_t *heap;
heap = OS_TYPE_CONVERT(os_heap_t *, heap_id);
OS_ASSERT(heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);
OS_ASSERT(info);
(void)memset(info, 0, sizeof(os_meminfo_t));
h_mem = heap->h_mem;
while (h_mem)
{
info->mem_total += h_mem->mem_total;
info->mem_used += h_mem->mem_used;
info->mem_maxused += h_mem->mem_maxused;
h_mem = h_mem->next;
}
}分步详解:
第一步:参数校验(条件:堆非空/已初始化/info 非空)—— 不满足则断言崩溃
OS_ASSERT(heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);
OS_ASSERT(info);进入本步的条件:调用 os_heap_info() 时无条件执行。info 是传出参数,必须非空。
第二步:清零并累加各区域统计(无条件执行)—— 遍历所有内存区域
(void)memset(info, 0, sizeof(os_meminfo_t));
h_mem = heap->h_mem;
while (h_mem)
{
info->mem_total += h_mem->mem_total;
info->mem_used += h_mem->mem_used;
info->mem_maxused += h_mem->mem_maxused;
h_mem = h_mem->next;
}进入本步的条件:第一步断言通过。
memset(info, 0, ...):清零info(防脏数据累加)。- 遍历所有内存区域,累加各区域的
mem_total/mem_used/mem_maxused到info。
关键点:多区域堆的统计是所有区域累加之和。
os_heap_check() —— 检查指定堆完整性
完整源码(kernel/source/os_memory.c,与项目一致):
os_err_t os_heap_check(os_heap_id heap_id)
{
struct heap_mem *h_mem;
os_err_t ret;
os_heap_t *heap;
heap = OS_TYPE_CONVERT(os_heap_t *, heap_id);
OS_ASSERT(heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);
ret = OS_SUCCESS;
h_mem = heap->h_mem;
while (h_mem)
{
if (h_mem->k_mem_check)
{
ret = h_mem->k_mem_check(h_mem);
if (ret != OS_SUCCESS)
{
break;
}
}
h_mem = h_mem->next;
}
return ret;
}分步详解:
第一步:参数校验(条件:堆非空/已初始化)—— 不满足则断言崩溃
OS_ASSERT(heap);
OS_ASSERT(OS_KOBJ_INITED == heap->object_inited);进入本步的条件:调用 os_heap_check() 时无条件执行。堆非空、已初始化。
第二步:遍历各区域调用算法校验(条件:存在 h_mem 且支持 k_mem_check)—— 任一失败即返回错误
ret = OS_SUCCESS;
h_mem = heap->h_mem;
while (h_mem)
{
if (h_mem->k_mem_check)
{
ret = h_mem->k_mem_check(h_mem);
if (ret != OS_SUCCESS)
{
break;
}
}
h_mem = h_mem->next;
}
return ret;进入本步的条件:第一步断言通过。
- 遍历内存区域,条件支持
k_mem_check→ 调用h_mem->k_mem_check(h_mem)校验该区域。 - 子分支:条件
ret != OS_SUCCESS(某区域校验失败)→break,直接返回错误。 - 全部区域正常 → 返回
OS_SUCCESS。
6.5 多堆使用示例
#include <os_memory.h>
/* 静态定义两个堆控制块 */
OS_HEAP_DEFINE(sram_heap_cb);
OS_HEAP_DEFINE(sdram_heap_cb);
static os_heap_id sram_heap_id;
static os_heap_id sdram_heap_id;
void multi_heap_init(void)
{
/* 创建 SRAM 堆 */
sram_heap_id = os_heap_init(&sram_heap_cb, "SRAM_HEAP");
os_heap_add(sram_heap_id, (void *)0x20000000, 64 * 1024, OS_MEM_ALG_FIRSTFIT);
/* 创建 SDRAM 堆 */
sdram_heap_id = os_heap_init(&sdram_heap_cb, "SDRAM_HEAP");
os_heap_add(sdram_heap_id, (void *)0xC0000000, 8 * 1024 * 1024, OS_MEM_ALG_BUDDY);
}
void multi_heap_task(void)
{
os_meminfo_t info;
char *fast_buf;
char *large_buf;
/* 从 SRAM 堆分配小块内存 */
fast_buf = (char *)os_heap_alloc(sram_heap_id, 256);
/* 从 SDRAM 堆分配大块内存 */
large_buf = (char *)os_heap_alloc(sdram_heap_id, 1024 * 1024);
/* 查看各堆使用情况 */
os_heap_info(sram_heap_id, &info);
os_kprintf("SRAM堆: 总计=%lu 已用=%lu 峰值=%lu\r\n",
info.mem_total, info.mem_used, info.mem_maxused);
os_heap_info(sdram_heap_id, &info);
os_kprintf("SDRAM堆: 总计=%lu 已用=%lu 峰值=%lu\r\n",
info.mem_total, info.mem_used, info.mem_maxused);
/* 释放内存 */
os_heap_free(sram_heap_id, fast_buf);
os_heap_free(sdram_heap_id, large_buf);
}七、Shell 命令
OneOS 提供了 Shell 命令用于调试堆内存:
| 命令 | 功能 | 说明 |
|---|---|---|
show_mem | 显示默认堆使用情况 | 包括总大小、已用、使用率、峰值、地址范围 |
show_heap | 显示所有堆信息 | 遍历所有已注册的堆实例 |
check_mem | 检查默认堆完整性 | 验证内存块结构一致性 |
📝 本节小结
核心要点回顾
本节围绕堆内存管理展开,核心要点如下:
1. 默认堆 API 与标准 C 库的对应关系
| OneOS API | 等价 C 库 | 功能 |
|---|---|---|
os_malloc | malloc | 分配内存 |
os_calloc | calloc | 分配并清零 |
os_free | free | 释放内存 |
os_realloc | realloc | 重分配内存 |
os_aligned_malloc | posix_memalign | 对齐分配 |
2. 初始化流程(三步)
os_default_heap_init():初始化默认堆控制块(创建"DEFAULT_HEAP")。os_default_heap_add():添加实际内存区域(可多次,组合多区域)。- 之后才能用
os_malloc等分配。
3. 多堆机制
- 用
OS_HEAP_DEFINE(name)静态定义堆控制块。 os_heap_init创建堆(有防重复检查),os_heap_add添加区域。- 分配/释放需指定
heap_id(os_heap_alloc/os_heap_free等)。
4. 分配 API 的内部机制
os_heap_alloc遍历堆的所有内存区域,逐个尝试k_alloc,任一成功即返回。os_heap_realloc支持原地重分配、失败则迁移到其他区域并拷贝数据。os_heap_free需定位ptr所属区域再释放,非本堆指针会断言。
5. 配置硬约束
OS_USING_HEAP+OS_USING_SEMAPHORE+ 至少一种算法(OS_USING_ALG_FIRSTFIT/OS_USING_ALG_BUDDY),不满足编译报#error。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
os_malloc 返回 OS_NULL 未检查 | 必须检查返回值,否则解引用空指针 |
未 os_default_heap_init 就分配 | 先初始化控制块,再 os_default_heap_add 添加内存区域 |
同一个 heap_cb 重复 os_heap_init | 返回 OS_NULL(防重复检查),应复用已创建的堆 |
os_heap_add 区域重叠 | 返回 OS_INVAL,应检查区域地址不重叠 |
os_heap_add 区域太小 | 返回 OS_NOMEM(至少 sizeof(struct heap_mem)) |
| 释放非本堆分配的指针 | os_heap_free 会断言崩溃,必须释放本堆 os_heap_alloc 返回的指针 |
os_aligned_malloc 的 align 不是 2 的幂 | 对齐行为不确定,应保证 2 的幂 |
忽略 os_heap_check 返回值 | 非 OS_SUCCESS 说明堆被破坏(越界/野指针),需排查 |
下一步
本节掌握了堆内存管理的默认堆与多堆 API。接下来请学习下一小节:
- 4.4.2 FIRSTFIT 算法:了解堆默认算法——首次适应算法的原理与实现