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
结束地址    +------------------------+

关键特点:

  1. 起始和结束都有guard node防止越界

  2. guard node永远标记为已分配

  3. 中间区域初始化为一个大的空闲块

  4. 所有内存块都按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)

主要流程:

  1. 参数检查和大小对齐

  2. 获取互斥锁

  3. 处理延迟释放列表

  4. 搜索合适的空闲块

  5. 分割空闲块(如果太大)

  6. 更新块状态和统计信息

  7. 释放互斥锁

关键代码分析:

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

这就解释了为什么:

  1. alignsize 计算时只加 MM_ALLOCNODE_OVERHEAD(不是 MM_SIZEOF_ALLOCNODE)

  2. 但返回地址时要跳过完整的 MM_SIZEOF_ALLOCNODE

  3. 实际可用空间仍然满足用户请求的 size 大小

这种设计既保证了内存对齐,又优化了内存使用效率。

3.2 内存释放(mm_free)

主要流程:

  1. 参数检查

  2. 获取互斥锁

  3. 标记块为空闲

  4. 尝试与相邻块合并

  5. 更新统计信息

  6. 释放互斥锁

重要细节:

延迟释放机制
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)

实现要点:

  1. 计算需要的额外空间

  2. 分配更大的块

  3. 调整到所需对齐

  4. 处理剩余空间

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 内存池管理

当配置启用内存池时:

  1. 支持固定大小块的快速分配

  2. 可设置内存池阈值

  3. 整合多个内存池

  4. 自动扩展能力

4.3 调试功能

内存泄漏检测
  • 记录分配的进程ID

  • 跟踪分配序号

  • 支持调用栈回溯

内存破坏检测
  • 检查块头完整性

  • 验证相邻块关系

  • 块大小检查

使用统计
  • 总内存使用量

  • 当前使用量

  • 最大使用量

5. 性能优化

5.1 延迟释放机制

优点:

  1. 减少碎片产生

  2. 提高吞吐量

  3. 支持批量处理

实现:

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 内存对齐优化

  1. 自然对齐减少访问开销

  2. 支持特殊对齐需求

  3. 避免非对齐访问异常

5.3 锁优化

  1. 采用细粒度锁

  2. 中断上下文处理

  3. 支持递归锁定

6. 使用建议

6.1 最佳实践

合理使用内存池
  • 频繁分配的小块使用内存池

  • 设置合适的阈值

  • 监控内存池使用情况

选择适当的分配策略
  • 批量分配减少碎片

  • 利用内存对齐特性

  • 合理使用重分配

调试和监控
  • 开启内存检查

  • 跟踪内存使用

  • 分析内存泄漏

6.2 注意事项

内存对齐
  • 遵循系统对齐要求

  • 注意特殊硬件约束

  • 避免非对齐访问

多线程安全
  • 正确使用互斥锁

  • 避免死锁情况

  • 注意中断上下文

性能考虑
  • 减少碎片产生

  • 避免频繁小块分配

  • 合理使用内存池

7. 总结

NuttX MM Heap 内存管理器是一个功能完备、性能优秀的实现,其主要优势:

核心特性
  • 多区域内存管理

  • 完善的调试支持

实现亮点
  • 优秀的内存布局

  • 高效的算法实现

  • 灵活的扩展机制

实用功能
  • 延迟释放机制

  • 内存池支持

  • 调试工具集

理解其实现原理对于:

  • 系统内存优化

  • 性能调优

  • 问题诊断     都有重要的参考价值。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐