在 C (STM32F4) 中估算留有安全余量的可用 RAM
Estimating available RAM left with safety margin in C (STM32F4)
我目前正在使用 STM32CubeMx 和 Keil uVision 为 STM32F407 开发应用程序。我知道嵌入式系统中的动态内存分配大多是不鼓励的,但在互联网上我可以找到一些支持它的论据。
由于我的发明家灵魂,我想尝试这样做,但要安全地进行。假设我正在为传入的 UART 消息创建动态分配的 fifo,保存由消息本身及其长度组成的结构。但是我不想消耗所有堆大小,因此我想检查我还剩下多少:Me new (?) idea is尝试临时分配一些大块内存(比如 100 个字符)- 如果成功,我接受传入的消息,如果没有-这意味着我 运行 出局了堆并忽略 msg (或接受它并使最旧的出队)。检查后我当然释放了临时内存。
我想到了几个问题:
- 首先,它有意义吗?根据您的经验,您认为它可能有用且 安全?
- 我找不到关于在 ES 中到底共享 RAM 的确切信息(我知道堆、堆栈和易失性变量)所以我的问题是:提供 1 的答案不是 "hell no go home",您会为上述控制器选择多大的 临时内存检查器?
- 关于 micro 本身 - 它有 192kB RAM,但是在 Drivers\CMSIS\Device\ST\STM32F4xx\Source\Templates\arm\startup_stm32f407xx.s 文件中只有 512B+1024B 分配给堆和堆栈 - 不是非常少,剩下 190kB 用于 volatile 变量?将堆大小增加到 50kB 是否明智?如果是,我是直接在此文件中执行还是在其他地方执行更好?
可能对你们中的某些人来说 "safe dynamic memory" 和 "embedded" 合而为一 post 既令人震惊又令人眼花缭乱,但请记住,这是在试验和探索新的视野 :) 谢谢和问候。
Keil uVision 仅描述了 IDE。如果您使用的是 KEil MDK-ARM,这意味着 ARM 的 RealView 编译器,那么您可以使用 __heapstats()
function.
获得准确的堆信息
__heapstats()
有点奇怪,它不是简单地返回一个值,而是将堆信息输出到格式化的输出流,由传递给它的函数指针和文件描述符促进。输出函数必须有一个类似 fprintf()
的接口。你当然可以使用 fprintf()
,但这需要你有正确的 retargetted the stdio
例如:
typedef int (*__heapprt)(void *, char const *, ...);
__heapstats( (__heapprt)fprintf, stdout ) ;
例如输出:
4180 bytes in 1 free blocks (avge size 4180)
1 blocks 2^11+1 to 2^12
不幸的是,它并没有真正实现你所需要的,因为它输出的是文本。但是,您可以实现自己的函数来捕获内存中的数据并解析结果。您可能只需要捕获第一个十进制数字字符并丢弃其他任何内容,当然,可用内存量和最大可分配块不一定是同一回事。碎片由空闲块的数量及其平均大小指示。您也许可以保证至少能够分配一个平均大小的块。
嵌入式系统中动态分配的问题与处理内存耗尽有关,在实时系统中,使用默认 malloc/free 实现的分配和解除分配的时间不确定。在您的情况下,您最好使用固定块分配器。您可以通过创建内存块的静态数组(或通过在启动时从堆中动态分配它们)并在队列或链表或堆栈结构上放置指向每个块的指针来实现这样的分配器。要分配,您只需从 queue/list/stack 中删除一个指针,并释放您放回一个指针。当可用块结构为空时,内存耗尽。它完全是确定性的,因为它是 您的 实施可以很容易地监控性能和容量。
关于问题 3。您需要调整堆和系统堆栈大小以适合您的应用程序。我用过的大多数工具都有一个链接器脚本,可以自动分配所有可用内存,这些内存不是静态分配的,分配给堆栈或为其他目的保留给堆。然而,MDK-ARM 在默认链接器脚本中不这样做,而是分配一个固定大小的堆。
您可以使用链接器映射文件摘要来确定有多少 space 未使用并手动扩展堆。当静态分配的数据量可能增加时,我通常会这样做,留下少量未使用的 space 来进行维护。然而在某些时候;您最终 运行 内存不足,并且来自链接器的神秘错误消息可能不会明显表明您的堆太大了。可以覆盖默认的链接器脚本并提供您自己的链接器脚本,并且毫无疑问可以自动调整堆的大小——尽管我从来没有不厌其烦地尝试过。
好吧,我已经用动态堆免费 space 检查测试了我的想法并且它运行良好(虽然我没有执行长 - 运行 测试),但是@Clifford 回答和 this article 说服我放弃动态分配的想法。最终,我实现了自己的静态堆,其中包含页面(2d 数组)、占用页面指示器(页数大小的 0-1 数组)和由指向静态堆上 msg 的指针组成的结构 fifo(实际上只是索引数组)和消息长度(以确定它占据了多少个连续页面)。我收到的 95% 的消息应该只占用一页,5% - 2 或 3 页,所以碎片仍然是可能的,但至少我严格控制它并且它只影响分配给这个模块的内存部分代码(换句话说:碎片不会泄漏到代码的其他部分)。到目前为止,它没有任何问题,而且肯定更快,因为查找时间为 O(n*m),n - 页数,m - 可能的最长页面,但考虑到概率法则它下降到在)。此外,n 总是比内存中所有分配单元的数量小得多,因此查找的方式更少。
我目前正在使用 STM32CubeMx 和 Keil uVision 为 STM32F407 开发应用程序。我知道嵌入式系统中的动态内存分配大多是不鼓励的,但在互联网上我可以找到一些支持它的论据。
由于我的发明家灵魂,我想尝试这样做,但要安全地进行。假设我正在为传入的 UART 消息创建动态分配的 fifo,保存由消息本身及其长度组成的结构。但是我不想消耗所有堆大小,因此我想检查我还剩下多少:Me new (?) idea is尝试临时分配一些大块内存(比如 100 个字符)- 如果成功,我接受传入的消息,如果没有-这意味着我 运行 出局了堆并忽略 msg (或接受它并使最旧的出队)。检查后我当然释放了临时内存。
我想到了几个问题:
- 首先,它有意义吗?根据您的经验,您认为它可能有用且 安全?
- 我找不到关于在 ES 中到底共享 RAM 的确切信息(我知道堆、堆栈和易失性变量)所以我的问题是:提供 1 的答案不是 "hell no go home",您会为上述控制器选择多大的 临时内存检查器?
- 关于 micro 本身 - 它有 192kB RAM,但是在 Drivers\CMSIS\Device\ST\STM32F4xx\Source\Templates\arm\startup_stm32f407xx.s 文件中只有 512B+1024B 分配给堆和堆栈 - 不是非常少,剩下 190kB 用于 volatile 变量?将堆大小增加到 50kB 是否明智?如果是,我是直接在此文件中执行还是在其他地方执行更好?
可能对你们中的某些人来说 "safe dynamic memory" 和 "embedded" 合而为一 post 既令人震惊又令人眼花缭乱,但请记住,这是在试验和探索新的视野 :) 谢谢和问候。
Keil uVision 仅描述了 IDE。如果您使用的是 KEil MDK-ARM,这意味着 ARM 的 RealView 编译器,那么您可以使用 __heapstats()
function.
__heapstats()
有点奇怪,它不是简单地返回一个值,而是将堆信息输出到格式化的输出流,由传递给它的函数指针和文件描述符促进。输出函数必须有一个类似 fprintf()
的接口。你当然可以使用 fprintf()
,但这需要你有正确的 retargetted the stdio
例如:
typedef int (*__heapprt)(void *, char const *, ...);
__heapstats( (__heapprt)fprintf, stdout ) ;
例如输出:
4180 bytes in 1 free blocks (avge size 4180)
1 blocks 2^11+1 to 2^12
不幸的是,它并没有真正实现你所需要的,因为它输出的是文本。但是,您可以实现自己的函数来捕获内存中的数据并解析结果。您可能只需要捕获第一个十进制数字字符并丢弃其他任何内容,当然,可用内存量和最大可分配块不一定是同一回事。碎片由空闲块的数量及其平均大小指示。您也许可以保证至少能够分配一个平均大小的块。
嵌入式系统中动态分配的问题与处理内存耗尽有关,在实时系统中,使用默认 malloc/free 实现的分配和解除分配的时间不确定。在您的情况下,您最好使用固定块分配器。您可以通过创建内存块的静态数组(或通过在启动时从堆中动态分配它们)并在队列或链表或堆栈结构上放置指向每个块的指针来实现这样的分配器。要分配,您只需从 queue/list/stack 中删除一个指针,并释放您放回一个指针。当可用块结构为空时,内存耗尽。它完全是确定性的,因为它是 您的 实施可以很容易地监控性能和容量。
关于问题 3。您需要调整堆和系统堆栈大小以适合您的应用程序。我用过的大多数工具都有一个链接器脚本,可以自动分配所有可用内存,这些内存不是静态分配的,分配给堆栈或为其他目的保留给堆。然而,MDK-ARM 在默认链接器脚本中不这样做,而是分配一个固定大小的堆。
您可以使用链接器映射文件摘要来确定有多少 space 未使用并手动扩展堆。当静态分配的数据量可能增加时,我通常会这样做,留下少量未使用的 space 来进行维护。然而在某些时候;您最终 运行 内存不足,并且来自链接器的神秘错误消息可能不会明显表明您的堆太大了。可以覆盖默认的链接器脚本并提供您自己的链接器脚本,并且毫无疑问可以自动调整堆的大小——尽管我从来没有不厌其烦地尝试过。
好吧,我已经用动态堆免费 space 检查测试了我的想法并且它运行良好(虽然我没有执行长 - 运行 测试),但是@Clifford 回答和 this article 说服我放弃动态分配的想法。最终,我实现了自己的静态堆,其中包含页面(2d 数组)、占用页面指示器(页数大小的 0-1 数组)和由指向静态堆上 msg 的指针组成的结构 fifo(实际上只是索引数组)和消息长度(以确定它占据了多少个连续页面)。我收到的 95% 的消息应该只占用一页,5% - 2 或 3 页,所以碎片仍然是可能的,但至少我严格控制它并且它只影响分配给这个模块的内存部分代码(换句话说:碎片不会泄漏到代码的其他部分)。到目前为止,它没有任何问题,而且肯定更快,因为查找时间为 O(n*m),n - 页数,m - 可能的最长页面,但考虑到概率法则它下降到在)。此外,n 总是比内存中所有分配单元的数量小得多,因此查找的方式更少。