我几个月前开始使用C语言为空间应用程序和使用C++语言的微控制器开发实时系统软件。在这类系统中,有一个经验法则,就是永远不要创建堆对象(因此没有malloc/new),因为这会使程序不确定性。当别人告诉我这个经验法则时,我无法验证其正确性。那么,这是一个正确的说法吗?
对我来说纠结的地方在于,据我所知,确定性意味着再次运行程序将导致完全相同的执行路径。就我的理解而言,在多线程系统中会出现这个问题,因为每次运行相同的程序可能会按不同的顺序运行不同的线程。
我几个月前开始使用C语言为空间应用程序和使用C++语言的微控制器开发实时系统软件。在这类系统中,有一个经验法则,就是永远不要创建堆对象(因此没有malloc/new),因为这会使程序不确定性。当别人告诉我这个经验法则时,我无法验证其正确性。那么,这是一个正确的说法吗?
对我来说纠结的地方在于,据我所知,确定性意味着再次运行程序将导致完全相同的执行路径。就我的理解而言,在多线程系统中会出现这个问题,因为每次运行相同的程序可能会按不同的顺序运行不同的线程。
在实时系统的背景下,确定性不仅仅是可重复的“执行路径”而已。另一个必须具备的特性是关键事件的时间限制。在硬实时系统中,发生在其允许时间间隔之外的事件(无论是在该间隔开始之前还是之后)都代表着系统故障。
在这种情况下,使用动态内存分配可能会导致不确定性,特别是如果程序具有变化的内存分配、释放和重新分配模式。内存分配、释放和重新分配的时间可以随时间变化而变化,因此使整个系统的时间变得不可预测。
rand()
毫秒,并且时间限制超过了RAND_MAX
,那么该系统就是实时的。 - MSalters如所述,该评论是不正确的。
使用具有非确定性行为的堆管理器会创建具有非确定性行为的程序。但这是显而易见的。
稍微不那么明显的是具有确定性行为的堆管理器的存在。可能最著名的示例是池分配器。它具有N * M字节的数组和N位的available[]
掩码。要进行分配,它会检查第一个可用条目(位测试,O(N),确定性上限)。要进行释放,它设置可用位(O(1))。 malloc(X)
将X舍入到下一个最大的M值以选择正确的池。
这可能不是很有效率,特别是如果您选择的N和M值过高。如果您选择得太低,程序可能会失败。但是,与没有动态内存分配的等效程序相比,N和M的限制可以更低。
malloc()
而不是固定大小的块)本质上是非时间确定性的或可能导致无限制的碎片化。正如“Timing-Predictable Memory Allocation In Hard Real-Time Systems” [Herter 2014]所示,可预测和高效的算法存在,但它们很少被提及。我在这里实现了一个约500行的实时嵌入式系统的算法:https://github.com/pavel-kirienko/o1heap - Pavel KirienkoC11标准或n1570文件中都未对malloc
的确定性(或非确定性)进行规定。而且,像Linux上的malloc(3)等一些其他文档也没有提及。顺便说一句,许多malloc
实现都是免费软件。
但是malloc
可能失败(确实有这种情况),而且其性能不确定(在我的台式计算机上,典型的malloc
调用实际上只需要不到一微秒的时间,但我可以想象,在非常繁忙的计算机上可能需要更长时间,例如几毫秒;请阅读关于抖动的内容)。另外,我的Linux台式机具有ASLR(地址空间布局随机化),因此两次运行同一个程序会给出不同的malloc
分配的地址(在进程的虚拟地址空间中)。顺便说一句,这里有一个确定性(在需要详细说明的特定假设下)但实际上没有什么用处的malloc
实现。
确定性意味着两次运行程序将导致完全相同的执行路径
这在大多数嵌入式系统中实际上是错误的,因为物理环境正在发生变化;例如,驱动火箭引擎的软件不能期望从一次发射到下一次发射推力、阻力、风速等都是完全相同的。
所以我感到惊讶的是你相信或希望实时系统是确定性的; 它们从来不是!也许您关心 WCET,由于 高速缓存 的影响,这越来越难以预测)
顺便提一下,一些“实时”或“嵌入式”系统正在实现自己的malloc
(或其变体)。C ++程序可以使用其分配器,它可以被标准的容器使用。还可以参考这篇文章和那篇文章等等.....
嵌入式软件的高级层(例如自动驾驶汽车及其规划软件)肯定正在使用堆分配,甚至可能是垃圾收集技术(其中一些是“实时的”),但通常不被认为具有安全性关键性。
malloc
结果的%p
调试printf
),可能会引起激烈讨论。 - Basile Starynkevitch您可以设计分配器来保证没有内存碎片(例如,分配仅限于一个大小的块),但这会对调用者造成重大约束并可能增加由于浪费而需要的内存量。而且调用者仍然必须证明没有泄漏,并且无论输入序列如何,都存在可满足的总内存上限。这个负担非常重,实际上更简单的方法是设计系统不使用动态内存分配。
实时系统的问题在于,程序必须严格符合特定的计算和内存限制,无论采取哪种执行路径(这可能仍然会因输入而有很大不同)。那么,在这种情况下使用通用动态内存分配(例如malloc / new)意味着什么?这意味着开发人员在某个时刻无法确定确切的内存消耗,并且不可能告诉是否结果程序将能够满足内存和计算能力要求。
是的,这是正确的。对于您提到的那种应用程序,必须详细说明可能发生的每种情况。根据规范,程序必须处理最坏情况,并准确地留出那么多内存,既不多也不少。不存在“我们不知道会得到多少输入”的情况。最坏情况是用固定数字指定的。
您的程序必须是确定性的,以便能够处理最坏情况。
堆的目的是允许几个不相关的应用程序共享RAM内存,例如在PC上,运行的程序/进程/线程数量不是确定的。这种情况在实时系统中不存在。
此外,堆是非确定性的,因为随着时间的推移,段会被添加或删除。
例如,第一或第二级触发闪烁器设备可能从不可重复的malloc/free等待时间中导致数据值或其统计不确定性分布的某些影响。
最糟糕的方面是,它们既不与物理现象相关,也不与硬件相关,而是与内存状态(及其历史)有关。
在这种情况下,您的目标是从受这些错误影响的数据中重构原始事件序列。 重构/猜测的序列也将受到错误的影响。 并不总是能够收敛到稳定的解决方案;这并不意味着它会是正确的;您的数据不再独立... 您面临逻辑短路的风险...
您说:"当人们告诉我时,我无法验证此声明的正确性"。
我将尝试给您一个纯粹的假设情况/案例研究。
让我们想象一下,您处理具有 CCD 或某些第一和第二级闪烁体触发器的系统,该系统必须节约资源(您位于太空中)。
采集率将设置为使背景处于MAXBINCOUNT
的x%
。
出现了一次突发,您的计数器出现了尖峰,并且bin计数器溢出。
我需要全部:您切换到最大采集速率并完成缓冲区。
同时,你去释放/分配更多内存,等待结束额外的缓冲区。
你会怎么做?
请注意:
现在相反,信号在允许您硬件的最大采集速率下围绕maxbincount
变化,并且事件比通常更长。
你用完空间并请求更多...同时,您遇到了上述相同的问题。
溢出和系统峰计数低估或时间序列中的空洞?
从您的硬件中,您收到的数据比您可以存储或传输的数据更多。
您必须按时间或空间(2x2、4x4、... 16x16 ... 256x256 ...像素缩放...)对数据进行聚类。
前面问题中的不确定性可能会影响误差分布。
有一些CCD设置,您可以在其中获得边框像素计数接近maxbincount
的计数(这取决于您希望在哪里看到更好)。
现在,您可以在CCD上淋浴或单个大斑点上拥有相同数量的计数,但具有不同的统计不确定性(由等待时间引入的部分)...
例如,您期望获得洛伦兹曲线,您可以获得其与高斯曲线的卷积(Voigt),或者如果第二个真的是主导的,则为dirty高斯...
介绍GHS的Integrity RTOS:
https://www.ghs.com/products/rtos/integrity.html
和LynxOS:
LynxOS和Integrity RTOS是太空应用、导弹、飞机等领域使用的软件之一,因为其他许多软件未获得当局(例如FAA)的批准或认证。
https://www.ghs.com/news/230210r.html
为了满足太空应用的严格标准,Integrity RTOS实际上提供了形式化验证,即经过数学证明的逻辑,证明他们的软件按照规范运行。
其中这些标准,引用自这里:
https://en.wikipedia.org/wiki/Integrity_(operating_system)
和这里:
Green Hills Integrity Dynamic memory allocation
是这样的:
我不是形式方法的专家,但也许验证的要求之一是消除内存分配所需时间上的不确定性。在RTOS中,所有事件都精确地计划在几毫秒之后发生。而动态内存分配总是有时间上的问题。
从最基本的关于时间和内存量的假设开始,您真的需要证明所有工作都正常运行。
如果考虑堆内存的替代方案:静态内存。地址固定,分配的大小固定。内存中的位置固定。因此,非常容易推断内存的充足性、可靠性、可用性等。
rdrand
的指令,您可以从一个普通的用户空间进程中执行它。 它从热噪声发生器提供真正的硬件随机性,并使用AES进行条件处理(除非NSA削弱了设计…)。 当然,正如David指出的那样,rdtsc
也是不确定的,特别是考虑到仅一个进程,但他提出了不同时钟域之间的同步给出了一些真正的不确定性。 - Peter Cordes