40、【OS】【Nuttx】OSTest分析(4):内存监控(二)
背景
接上篇 blog
39、【OS】【Nuttx】OSTest分析(4):内存监控(一)
分析了用户堆的相关成员,下面来分析下用户堆的初始化
用户堆初始化
上篇 blog 提到了用户堆的关键指针 g_mmheap,该指针指向了已经初始化好的内存区域,下面来看 g_mmheap 是如何初始化的
调试控制台输入
-exec watch g_mmheap
运行程序,可见用户堆的初始化函数为 umm_initialize
调用栈如下
来看下 umm_initialize 的定义,该函数用来初始化用户空间的堆内存。这里提到了三种构建模式:CONFIG_BUILD_FLAT,CONFIG_BUILD_PROTECTED 和 CONFIG_BUILD_KERNEL,三种模式各有优缺点,下面来分析下:
-
CONFIG_BUILD_FLAT:平坦构建,在该模式下,内核和用户程序共享同一个地址空间,意味着没有用户态和内核态之间的区分,所有代码都在相同的权限级别上运行,而由于只有一个地址空间,内存管理相对简单,不需要复杂的上下文切换和虚拟内存管理机制,可以提高性能,减少复杂性。在这种场景下,函数调用可以直接进行,不需要通过系统调用接口或其他中间层。比如,标准库函数 malloc 和 free 可以直接调用
如果未启用 CONFIG_MM_KERNEL_HEAP ,则内核和用户共享一个堆空间;
启用 CONFIG_MM_KERNEL_HEAP 后,内核和用户分别有独立的内核堆(不受保护)和用户堆空间可以使用
Flat Build 适合资源有限的嵌入式设备,因为不需要额外硬件支持(如MMU),能有效利用有限的内存和处理能力,在如 ARM Cortex-M 等内核等资源受限芯片受欢迎。
-
CONFIG_BUILD_PROTECTED:该模式下,系统包含两个独立的地址空间,一个用于受保护的内核态,另一个用于不受保护的应用态
和 CONFIG_BUILD_FLAT 类似,如果不启用 CONFIG_MM_KERNEL_HEAP ,内核和用户共享一个堆空间(物理空间),启用 CONFIG_MM_KERNEL_HEAP 后,内核和用户分别有独立的内核堆(受保护)和用户堆空间可以使用
-
CONFIG_BUILD_KERNEL:每个用户进程都有自己独立的堆,提供了最高的安全性和隔离性,且堆空间根据需要动态调整,对内存管理有严格要求的应用,需要具体的硬件支持,如 MMU
-
除了 umm_initialize 之外,用户态的其他内存函数如下,在内核模式下,必须使用特殊的分配器来管理从内核访问的内存(下面会介绍),如果不在内核模式,内核态和用户态对象使用标准分配器(如malloc, free等,在stdlib.h中声明),红框圈起来下面画红线的注释可忽略,下面会分析,大致意思是如果在 flat 模式,内存函数直接使用 stdlib.h;而如果在 kernel 模式,用户将使用在 userspace.h 的函数定义
该注释的添加可以追溯到 2013 年这位 p 兄弟的提交(考古了)
提交描述如下,可见里面此时 kmalloc.h 里面关于 kumm 的内存分配,当没配置 kernel 模式时,使用 malloc,配置 kernel 模式后,使用 umm_malloc :
Remove user_map.h; replace with a header at the beginning of the user-space blob. User work queue no started by os_brinup() on behalf of the application
umm_malloc 的定义如下,通过函数指针实现,具体的函数定义这里就不细看了,因为后面另外一位老兄又删掉了这些用户的内存函数
到了 2015 年,另一位 g 兄弟应该是发现了某些 bug,又将这些用户内存函数删掉了
提交描述如下,大致意思是重新设计了保护 protected 模式,原来的用户内存函数效率比较低,每次需要穿越进入到内核态取 PID,而且有个致命缺点:当任务退出时,此时的内存操作可能会导致程序崩溃(具体实现没看,代码比较久远了),当前的实现是将这些用户的操作函数都删掉了,该接口只输出用户堆结构,然后由内核的内存函数来操作,以避免不必要的系统调用,提升性能,更重要的是,防止任务退出时可能导致的程序崩溃:
Protected mode: Redesign how the user space heap is accessed from the kernel code. It used to call memory management functions in user space via function pointers in the userspace interface. That is inefficient because the first thing that those memory management functions do is to trap back into the kernel to get the current PID. Worse, that operation can be fatal is certain fragile situations such as when a task is exitting.
The solution is to remove all of the memory management function calls from the interface. Instead, the interface exports the userspace heap structure and then kernel size implementations of those memory management functions will operate on the userspace heap structure. This avoids the unnecessary system calls and, more importantly, failures do to freeing memory when a test exits.
所以这位 g 兄弟把这些接口都删掉了
综上,后面这段注释属于历史遗留没删掉的
查看这位 g 兄弟之前操作,确实是忘记删了…
这次就分析到这里,下次继续