41、【OS】【Nuttx】【OSTest】内存监控:堆空间申请
背景
接之前 blog
39、【OS】【Nuttx】【OSTest】内存监控:用户堆成员)
40、【OS】【Nuttx】【OSTest】内存监控:用户堆内存函数)
分析了用户堆的相关成员,以及用户堆的相关内存函数,接下来继续看用户内存的初始化
用户堆空间申请
用户堆内存初始化之前,会先申请堆空间,这里 heap_start 是指针,up_allocate_heap 传入指针的地址进入,可获取到指向申请到的堆空间地址
SIM
在 sim 环境上,可看到准备申请的堆空间大小为 64 MB
sim 环境上分配空间的函数如下,这里有两个关键点:mmap 函数,host_uninterruptible
mmap
这里思考个问题:用户堆内存分配为什么不用 malloc,而是用 mmap?先来看看二者的区别:
- malloc:
1、标准库函数,主要用于动态分配小块或中等大小内存,通过系统调用(如 sbrk 或 mmap)来扩展堆空间,并管理这些内存块以供程序使用;
2、管理多个小块内存,通常从堆空间中分配内存,并且可以处理频繁的分配和释放操作;
3、控制选项较少,主要是分配和释放内存,无法直接设置内存区域的保护属性(如读、写、执行权限)
4、分配的内存是堆管理器分配的,需要维护复杂的内存分配,释放记录,可能会导致堆碎片问题,且频繁的分配和释放操作会导致较高的资源开销,适用于大多数需要动态分配内存的场景,尤其是分配较小的内存块 - mmap:
1、系统调用,在虚拟地址空间中创建新的内存映射,主要用于文件映射、共享内存和匿名内存映射等高级内存管理任务(这里用的就是匿名内存映射,分配纯粹的虚拟内存),可以将文件内容映射到内存中,或创建不与任何文件关联的大块内存区域
2、适合分配较大的连续内存块,且分配的内存块通常是页面对齐的(通常是4KB),适合需要长期持有或较少变更的内存区域
3、提供控制选项指定内存区域的保护属性(如 PROT_READ、PROT_WRITE、PROT_EXEC),可以选择是否与其他任务共享内存(如 MAP_SHARED 或 MAP_PRIVATE),或是进行匿名操作(如 MAP_AON)
4、直接在虚拟地址空间中创建一个新的内存映射区域,不依赖于堆管理器,
申请大块连续内存效率高,开销低,适合高级内存管理任务,如文件映射,共享内存
这里准备申请的堆空间是 64M,属于大块连续内存,当然 mmap 更合适
host_uninterruptible
该宏作用为屏蔽中断,这里其实有疑问,mmap 作为系统调用,操作系统内核会处理相关的同步和保护机制,以确保其正确性和安全性,在这里添加中断屏蔽是否属于画蛇添足?
来看下提交记录,两年前,有位 L 兄弟增加了中断屏蔽(后续又有老哥将其封装成了宏格式),之前一直是没有中断屏蔽的,直接用 mmap,不过其提交记录只有寥寥两句话 sim/mem: don’t let siwtch out when operated the host mem,也没有贴出更多详细信息,所以这里是否有必要添加中断屏蔽,值得商榷。
总而言之,sim 环境不是研究重点,更多应该是研究嵌入式环境下的内存分配,下面以 arm 为例,看下内存申请如何进行
arm common
来看下在 arm 中,其弱函数的定义,有几个关键点:link time 和 SRAM,这意味着可分配的堆空间大小,是在链接时确定的,并且分配在 sram 上,而不是 dram,因为 Nuttx 专为资源受限的嵌入式系统设计,从这一点就能看出来。
来看其弱函数的实现,有几个关键点:
- g_idle_topstack:堆的起始地址,等会儿分析
- 如果定义了 CONFIG_MM_KERNEL_HEAP,用户堆的起始地址需往上偏移 CONFIG_MM_KERNEL_HEAPSIZE,可参考上篇 blog 40、【OS】【Nuttx】【OSTest】内存监控:用户堆内存函数
- heao_size:极限终点为 CONFIG_RAM_END,可见堆空间大小也是预先配置好的,程序运行时分配的堆空间不能超过配置值
g_idle_toptask 的定义如下,该定义描述了在 arm 中内存的结构分布
结构分布如下:
堆的位置在 g_top_stack 的上面,那栈的位置呢?当然是 g_top_stack 的下面,所以栈越界会怎么样?会将堆空间的内容给踩掉,造成程序的未定义行为。此处可对比之前 blog 22、【OS】【Nuttx】【最小系统】任务创建 里面 SIM 环境中关于栈空间的分配
这篇就讲到这里,下篇继续