背景
接上篇blog
41、【OS】【Nuttx】【OSTest】内存监控:堆空间申请
分析了堆空间的申请,下面分析堆管理器如何初始化申请后的堆空间
用户堆空间初始化
回到 umm_initialize 函数,之前 blog 40、【OS】【Nuttx】【OSTest】内存监控:用户堆内存函数 介绍过其相关一些概念
mm_initialize_pool 只看 mm_initialize 即可,因为 这里 init 指针传的空指针
根据申请的内存区域初始化堆结构
堆结构如下所示,主要由3部分组成:堆管理器(mm_heap_s 类型),守卫节点(mm_heapstart,mm_heapend),实际分配节点(mm_allocnode_s 类型)
mm_heap_s
堆管理器的头部信息如下
- mm_lock:互斥锁,用来控制对堆的访问,确保在多线程环境下堆的操作是线程安全的
- mm_heapsize:堆的总大小,记录堆的最大容量
- mm_maxused:曾经使用过的最大内存,帮助了解堆的峰值情况
- mm_curused:已使用内存,反映了当前时刻堆的实际占用情况
- mm_heapstart:守卫节点,防止堆溢出,指向第一个节点,标志堆开始
- mm_heapend:守卫节点,防止堆溢出,指向最后一个节点,标志堆结束
- mm_nodelist:当前未被使用的内存块,数组中的元素提供了不同的入口点,以加速查找空闲内存块的过程,注意,这里 MM_NNODES 表示的是不同大小类别的数量,维护的是不同尺寸内存块类型的总数,不是空闲节点总数
- mm_delaylist:延迟释放列表,由于某些原因不能立即释放内存的节点会被暂时存储在该列表中
mm_nodelist 维护内存块如下图所示
如图,mm_initialize 主要为 mm_heap_s 上下文初始化,比较简单,下面主要来分看看 mm_addregion
mm_addregion
如定义描述,region 是堆里面的连续的内存区域,由守卫节点和实际分配节点组成,在某些场景下,一个堆里面可以有多个 region,这里只分析对应一个 region 的情况
这里面比较有意思的一点,在这里 heapbase 对齐的时候额外加了俩节点的空间
改动可以追溯到2022年8月份的提交
可以看到在2022年前,这里还只是对 heapstart 对齐,并没有额外俩节点的操作,如果这里注释没看懂,继续往前追溯
在早些时间 2022 年5月,有过一次提交,但后面回退了,并有了 8 月份那次提交
可以看出,5月份的提交想对 heapbase 做好字节对齐
在嵌入式系统中,字节对齐是非常重要的概念,涉及到数据结构如何在内存中布局以及处理器如何访问这些数据:
- 当数据按照总线宽度进行对齐时,处理器可以更高效地进行读写操作。比如对于一个32位的整数,在32位系统上对其存储意味着它的起始地址是4的倍数。这样,处理器可以在一次总线处理中完成对该整数的读写操作。如果不按对齐方式存储,可能需要两次或更多次的总线处理来完成同样的操作,降低效率
- 某些 ARM 处理器要求32位数据的起始地址必须是4的倍数。如果数据没有正确对齐,会触发硬件异常,如 HardFault
这里 SIZEOF_MM_ALLOCNODE 为 mm_allocnode_s 的数据类型大小,从其定义可以看出这里实现了字节对齐。但为了实现 MM_MIN_CHUNK 对齐,可以看到 W 兄尝试将 SIZEOF_MM_ALLOCNODE 替换成 MM_MIN_CHUNK
但这样不是最佳的办法,因为首先 MM_MIN_CHUNK 是需要对齐最小的 2 的幂,假设 mm_allocnode_s 数据类型的大小为 257 字节,那么就需要对齐到 512 字节,这里面有最大 49% 的字节是浪费掉去做对齐的
而且,这样做只能对齐到 heapstart,无法覆盖到给用户分配数据的区域,此时用户拿到的数据仍可能是非字节对齐的,如下所示
为了解决该问题,8月份该 MR被回退了,并提供了新的解决方案,此时也不再使用 MM_MIN_CHUNK 进行字节对齐,并将MM字节对齐的区域覆盖到分配给用户的地址,相较之前确实是个 better solution,不过这里描述有点问题,不是进行的 MM_MIN_CHUNK 对齐,而是 MM_ALIGN_UP 对齐
改动也可以看出这里非 MM_MIN_CHUNK 对齐,而是 MM_ALIGN_UP,这样不会浪费空间
示意图如下