NuttX(Vela) Heap 源码学习
NuttX MM Heap 是一个完整的堆内存管理器实现,提供了高效且灵活的内存分配和释放功能。本文将深入分析其实现原理和源码细节。
1. 基本概念
1.1 主要特性
-
支持多内存区域管理
-
支持内存对齐分配
-
提供内存延迟释放机制
-
包含完整的内存保护和调试功能
-
支持多线程安全
1.2 核心数据结构
内存块头部(struct mm_allocnode_s)
struct mm_allocnode_s {
size_t preceding; // 前一个块的大小
size_t size; // 块大小(包含header)和状态标志
#if CONFIG_MM_BACKTRACE >= 0
pid_t pid; // 分配该块的进程ID
unsigned long seqno; // 分配序号
#endif
#if CONFIG_MM_BACKTRACE > 0
void *backtrace[CONFIG_MM_BACKTRACE]; // 分配时的调用栈
#endif
};
空闲块节点(struct mm_freenode_s)
struct mm_freenode_s {
size_t preceding; // 前一个块的大小
size_t size; // 块大小和状态标志
FAR struct mm_freenode_s *flink; // 下一个空闲块
FAR struct mm_freenode_s *blink; // 前一个空闲块
};
堆管理结构(struct mm_heap_s)
struct mm_heap_s {
struct nxmutex_s mm_lock; // 互斥锁
size_t mm_heapsize; // 堆总大小
size_t mm_curused; // 当前使用量
#if CONFIG_MM_REGIONS > 1
int mm_nregions; // 内存区域数量
#endif
struct mm_allocnode_s *mm_heapstart[CONFIG_MM_REGIONS]; // 各区域起始地址
struct mm_allocnode_s *mm_heapend[CONFIG_MM_REGIONS]; // 各区域结束地址
struct mm_freenode_s mm_nodelist[MM_NFREELIST]; // 空闲块链表数组,按大小分类
#ifdef CONFIG_MM_HEAP_MEMPOOL
FAR struct mempool_multiple_s *mm_mpool; // 内存池管理器
size_t mm_threshold; // 内存池阈值
#endif
struct mm_delaynode_s *mm_delaylist[ ]; // 延迟释放列表
#if CONFIG_MM_FREE_DELAYCOUNT_MAX > 0
size_t mm_delaycount[ ]; // 延迟释放计数
#endif
};
1.3 重要宏定义
/* 内存对齐相关 */
#define MM_GRAN_MASK (MM_GRAN - 1)
#define MM_ALIGN_UP(a) (((a) + MM_GRAN_MASK) & ~MM_GRAN_MASK)
#define MM_ALIGN_DOWN(a) ((a) & ~MM_GRAN_MASK)
/* 内存块标志位 */
#define MM_ALLOC_BIT (1 << 0) // 块已分配标志
#define MM_PREVFREE_BIT (1 << 1) // 前一个块空闲标志
#define MM_MASK_BIT (MM_ALLOC_BIT | MM_PREVFREE_BIT)
/* 内存块操作宏 */
#define MM_SIZEOF_NODE(n) ((n)->size & ~MM_MASK_BIT)
#define MM_NODE_IS_ALLOC(n) (((n)->size & MM_ALLOC_BIT) != 0)
#define MM_NODE_IS_FREE(n) (((n)->size & MM_ALLOC_BIT) == 0)
2. 内存布局
2.1 初始化内存布局
当调用 mm_initialize 初始化堆时,整体内存布局如下:
起始地址 +------------------------+
| struct mm_heap_s | <-- 堆控制结构
+------------------------+
| guard node (allocated) | <-- 起始guard node
+------------------------+
| free node | <-- 可用内存区域
| (user data) |
| |
+------------------------+
| guard node (allocated) | <-- 结束guard node
结束地址 +------------------------+
关键特点:
-
起始和结束都有guard node防止越界
-
guard node永远标记为已分配
-
中间区域初始化为一个大的空闲块
-
所有内存块都按MM_GRAN对齐
2.2 内存块的组织形式
物理相邻块通过size和preceding字段关联:
块A +------------------------+
| preceding = 0 |
| size = 0x100 |
+------------------------+
| 数据 |
+------------------------+
块B | preceding = 0x100 |
| size = 0x200 |
+------------------------+
| 数据 |
+------------------------+
空闲块通过双向链表连接:
-
使用flink和blink指针
-
按大小排序组织
-
支持快速插入和删除
2.3 空闲块链表管理
mm_heap_s 结构体中的 mm_nodelist 数组是整个内存管理系统的核心,它实现了高效的空闲块管理:
分类管理
-
mm_nodelist 是一个大小为 MM_NFREELIST 的数组
-
每个数组元素是一个双向链表的头节点
-
不同大小的空闲块被分配到不同的链表中
大小分类方式
// 计算空闲块应该放入哪个链表
ndx = mm_size2ndx(size)
-
小块内存使用精确匹配
-
大块内存使用对数分类
-
保证快速查找合适大小的块
空闲块管理
-
分配时从对应链表取出合适的块
-
释放时插入到对应大小的链表
-
合并时会从原链表移除并加入新链表
性能保证
-
分配和释放都是 O(1) 操作
-
避免遍历整个空闲链表
-
减少内存碎片
3. 核心算法实现
3.1 内存分配(mm_malloc)
主要流程:
-
参数检查和大小对齐
-
获取互斥锁
-
处理延迟释放列表
-
搜索合适的空闲块
-
分割空闲块(如果太大)
-
更新块状态和统计信息
-
释放互斥锁
关键代码分析:
FAR void *mm_malloc(FAR struct mm_heap_s *heap, size_t size)
{
/* 调整size确保最小块大小和对齐要求 */
if (size < MM_MIN_CHUNK - MM_ALLOCNODE_OVERHEAD)
{
size = MM_MIN_CHUNK - MM_ALLOCNODE_OVERHEAD;
}
alignsize = MM_ALIGN_UP(size + MM_ALLOCNODE_OVERHEAD);
DEBUGVERIFY(mm_lock(heap));
/* 处理延迟释放列表 */
free_delaylist(heap, false);
/* 找到合适的空闲块 */
node = mm_search_suitable_node(heap, alignsize);
if (node)
{
ret = mm_split_chunk(heap, node, alignsize);
}
mm_unlock(heap);
return ret;
}
3.1.1 内存分配大小计算
在 mm_malloc 实现中,一个关键步骤是计算实际需要分配的内存大小。这涉及以下几个重要概念:
请求大小的调整
if (size < MM_MIN_CHUNK - MM_ALLOCNODE_OVERHEAD)
{
size = MM_MIN_CHUNK - MM_ALLOCNODE_OVERHEAD;
}
对齐大小的计算
alignsize = MM_ALIGN_UP(size + MM_ALLOCNODE_OVERHEAD);
这里涉及几个关键点:
MM_ALLOCNODE_OVERHEAD 的特殊性
-
MM_ALLOCNODE_OVERHEAD = MM_SIZEOF_ALLOCNODE - sizeof(mmsize_t)
-
减去 mmsize_t (preceding 字段大小)的原因是它复用了前一个块的尾部空间
-
这种设计可以节省每个块的 sizeof(mmsize_t) 字节空间
内存布局示意图
前一个块 | 当前块 | 后一个块
---------+-------------------------------------+----------
... | preceding | size | 用户数据 | ...
^ ^
| |
| +-- 实际返回给用户的地址(ret)
+-- 块的起始地址(node)
大小计算过程
-
用户请求大小:size
-
加上块头开销:size + MM_ALLOCNODE_OVERHEAD
-
进行对齐:MM_ALIGN_UP(size + MM_ALLOCNODE_OVERHEAD)
-
最终用户得到大小:alignsize - MM_ALLOCNODE_OVERHEAD
这就解释了为什么:
-
alignsize 计算时只加 MM_ALLOCNODE_OVERHEAD(不是 MM_SIZEOF_ALLOCNODE)
-
但返回地址时要跳过完整的 MM_SIZEOF_ALLOCNODE
-
实际可用空间仍然满足用户请求的 size 大小
这种设计既保证了内存对齐,又优化了内存使用效率。
3.2 内存释放(mm_free)
主要流程:
-
参数检查
-
获取互斥锁
-
标记块为空闲
-
尝试与相邻块合并
-
更新统计信息
-
释放互斥锁
重要细节:
延迟释放机制
void mm_free(FAR struct mm_heap_s *heap, FAR void *mem)
{
if (CONFIG_MM_FREE_DELAYCOUNT_MAX > 0)
{
/* 添加到延迟释放列表 */
mm_delayfree(heap, mem, true);
}
else
{
/* 直接释放 */
mm_delayfree(heap, mem, false);
}
}
块合并过程
/* 向前合并空闲块 */
if (MM_PREVNODE_IS_FREE(node))
{
prev = (FAR struct mm_freenode_s *)
((FAR char *)node - node->preceding);
node = merge_nodes(heap, prev, node);
}
/* 向后合并空闲块 */
next = (FAR struct mm_freenode_s *)
((FAR char *)node + nodesize);
if (MM_NODE_IS_FREE(next))
{
node = merge_nodes(heap, node, next);
}
3.3 内存重分配(mm_realloc)
主要策略:
缩小内存块:
-
分割当前块
-
释放多余部分
扩大内存块:
-
尝试使用相邻空闲块扩展
-
如果不行就分配新块并复制
优化处理:
-
原地扩展避免复制
-
利用前后空闲块
-
维持内存对齐
3.4 内存对齐分配(mm_memalign)
实现要点:
-
计算需要的额外空间
-
分配更大的块
-
调整到所需对齐
-
处理剩余空间
4. 高级特性
4.1 多内存区域支持
通过mm_addregion函数添加新内存区域:
void mm_addregion(FAR struct mm_heap_s *heap,
FAR void *heapstart, size_t heapsize)
{
/* 创建区域的guard nodes */
heap->mm_heapstart[idx] = (FAR struct mm_allocnode_s *)heapbase;
heap->mm_heapstart[idx]->size = MM_SIZEOF_ALLOCNODE | MM_ALLOC_BIT;
/* 创建可用区域节点 */
node = (FAR struct mm_freenode_s *)(heapbase + MM_SIZEOF_ALLOCNODE);
node->size = heapsize - 2 * MM_SIZEOF_ALLOCNODE;
/* 创建结束guard node */
heap->mm_heapend[idx] = (FAR struct mm_allocnode_s *)
(heapend - MM_SIZEOF_ALLOCNODE);
heap->mm_heapend[idx]->size = MM_SIZEOF_ALLOCNODE |
MM_ALLOC_BIT | MM_PREVFREE_BIT;
}
4.2 内存池管理
当配置启用内存池时:
-
支持固定大小块的快速分配
-
可设置内存池阈值
-
整合多个内存池
-
自动扩展能力
4.3 调试功能
内存泄漏检测
-
记录分配的进程ID
-
跟踪分配序号
-
支持调用栈回溯
内存破坏检测
-
检查块头完整性
-
验证相邻块关系
-
块大小检查
使用统计
-
总内存使用量
-
当前使用量
-
最大使用量
5. 性能优化
5.1 延迟释放机制
优点:
-
减少碎片产生
-
提高吞吐量
-
支持批量处理
实现:
static void add_delaylist(FAR struct mm_heap_s *heap, FAR void *mem)
{
FAR struct mm_delaynode_s *tmp = mem;
tmp->flink = heap->mm_delaylist[this_cpu()];
heap->mm_delaylist[this_cpu()] = tmp;
}
5.2 内存对齐优化
-
自然对齐减少访问开销
-
支持特殊对齐需求
-
避免非对齐访问异常
5.3 锁优化
-
采用细粒度锁
-
中断上下文处理
-
支持递归锁定
6. 使用建议
6.1 最佳实践
合理使用内存池
-
频繁分配的小块使用内存池
-
设置合适的阈值
-
监控内存池使用情况
选择适当的分配策略
-
批量分配减少碎片
-
利用内存对齐特性
-
合理使用重分配
调试和监控
-
开启内存检查
-
跟踪内存使用
-
分析内存泄漏
6.2 注意事项
内存对齐
-
遵循系统对齐要求
-
注意特殊硬件约束
-
避免非对齐访问
多线程安全
-
正确使用互斥锁
-
避免死锁情况
-
注意中断上下文
性能考虑
-
减少碎片产生
-
避免频繁小块分配
-
合理使用内存池
7. 总结
NuttX MM Heap 内存管理器是一个功能完备、性能优秀的实现,其主要优势:
核心特性
-
多区域内存管理
-
完善的调试支持
实现亮点
-
优秀的内存布局
-
高效的算法实现
-
灵活的扩展机制
实用功能
-
延迟释放机制
-
内存池支持
-
调试工具集
理解其实现原理对于:
-
系统内存优化
-
性能调优
-
问题诊断 都有重要的参考价值。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



所有评论(0)