几个月前,我开始用C语言开发用于实时系统的软件,用于空间应用程序以及具有C ++的微控制器。在这样的系统中有一个经验法则:永远不要创建堆对象(因此不要创建malloc / new),因为它会使程序变得不确定。当人们告诉我时,我无法验证这句话的正确性。那么,这是正确的说法吗?
对我来说,困惑是,据我所知,确定性意味着两次运行程序将导致精确,相同的执行路径。据我了解,这是多线程系统的一个问题,因为多次运行相同的程序可能会使不同的线程每次以不同的顺序运行。
几个月前,我开始用C语言开发用于实时系统的软件,用于空间应用程序以及具有C ++的微控制器。在这样的系统中有一个经验法则:永远不要创建堆对象(因此不要创建malloc / new),因为它会使程序变得不确定。当人们告诉我时,我无法验证这句话的正确性。那么,这是正确的说法吗?
对我来说,困惑是,据我所知,确定性意味着两次运行程序将导致精确,相同的执行路径。据我了解,这是多线程系统的一个问题,因为多次运行相同的程序可能会使不同的线程每次以不同的顺序运行。
rdrand
内置了一条指令,您可以从普通的用户空间进程中执行该指令。它提供了由AES调节的热噪声发生器的真正硬件随机性(除非NSA削弱了设计...)。当然,rdtsc
正如David指出的那样,它也是不确定性的,尤其是仅考虑单个过程时,但是在不同时钟域之间进行同步的优点是可以带来一些真正的不确定性。
Answers:
在实时系统的环境中,确定性比可重复的“执行路径”更多。另一个必需的属性是关键事件的时间是有限制的。在硬实时系统中,在其允许的时间间隔之外(该间隔开始之前或结束之后)发生的事件表示系统故障。
在这种情况下,使用动态内存分配可能会导致不确定性,尤其是在程序具有变化的分配,取消分配和重新分配模式的情况下。分配,重新分配和重新分配的时间可能会随时间而变化-因此,使整个系统的时间变得不可预测。
rand()
毫秒,并且时间限制大于RAND_MAX
,则系统是实时的。
如上所述,该评论不正确。
使用具有不确定行为的堆管理器会创建具有不确定行为的程序。但这是显而易见的。
不太明显的是存在具有确定性行为的堆管理器。也许最著名的示例是池分配器。它具有N * M字节的数组和available[]
N位的掩码。为了进行分配,它检查第一个可用条目(位测试,O(N),确定性上限)。要取消分配,它将设置可用位(O(1))。malloc(X)
会将X舍入到M的下一个最大值选择正确的池。
这可能不是很有效,特别是如果您对N和M的选择太高的话。如果选择得太低,程序可能会失败。但是N和M的限制可能会低于没有动态内存分配的等效程序的限制。
malloc()
而不是固定大小的块)本质上是非时间确定性的,或者可能导致无限制的碎片化。如图所示,例如,在“硬实时系统中的定时可预测内存分配”中[Herter 2014],存在可预测和高效的算法,但很少提及。我在这里为参与过的实时嵌入式系统实现了约500行代码中的一个:github.com/pavel-kirienko/o1heap
C11标准或n1570中的任何内容都没有说malloc
是确定性的(或不是)。在Linux上也没有其他文件,例如malloc(3)。顺便说一句,许多malloc
实现都是免费软件。
但是malloc
会(并且确实)会失败,并且它的性能未知(malloc
在我的桌面上进行一次典型的调用实际上只需要不到一微秒的时间,但是我可以想象到一个奇怪的情况,在负载非常大的情况下可能要花费更多的时间,可能要花费几毫秒的时间)电脑;阅读有关rash动的内容)。而且我的Linux桌面具有ASLR(地址空间布局随机化),因此两次运行同一程序会给出不同的malloc
-ed地址(在进程的虚拟地址空间中)。顺便说一句,这里是确定性的(在您需要详细说明的特定假设下),但是实际上没有用malloc
。
确定性意味着两次运行程序将导致精确,相同的执行路径
在大多数嵌入式系统中,这实际上是错误的,因为物理环境正在发生变化。例如,驱动火箭发动机的软件不能期望推力,阻力,风速等从一次发射到下一次发射完全相同。
(因此,令我惊讶的是,您相信或希望实时系统是确定性的;它们从未如此!也许您关心的是WCET,由于缓存的原因,它越来越难预测)
顺便说一句,一些“实时”或“嵌入式”系统正在实现自己的malloc
(或某些)变体。C ++程序可以具有其分配器-s,可由标准容器s使用。另请参见this和that,等等,等等。
嵌入式软件的高级层(例如自动驾驶汽车及其计划软件)肯定会使用堆分配,甚至可能使用垃圾收集技术(其中一些是“实时”的),但通常不被认为对安全性至关重要。
printf
用%p
的结果malloc
),并可能导致激烈的讨论
tl; dr:并不是动态内存分配本质上是不确定的(正如您根据相同的执行路径定义的那样);这通常会使您的程序变幻莫测。特别是,您无法预测在面对任意输入序列时分配器是否可能失败。
您可能有一个不确定的分配器。实际上,这在您的实时世界之外很普遍,在实时世界中,操作系统使用诸如地址布局随机化之类的东西。当然,这会使您的程序不确定。
但这不是一个有趣的情况,因此让我们假设一个完全确定性的分配器:相同的分配和释放顺序序列将始终在相同的位置产生相同的块,并且那些分配和释放将始终具有有限的运行时间。
现在您的程序可以是确定性的:相同的一组输入将导致完全相同的执行路径。
问题在于,如果要响应输入来分配和释放内存,则无法预测分配是否会失败(并且失败不是一种选择)。
首先,您的程序可能会泄漏内存。因此,如果需要无限期运行,最终分配将失败。
但是,即使您可以证明没有泄漏,您也需要知道,从来没有一个输入序列会比可用内存占用更多的内存。
但是,即使您可以证明程序永远不会需要的内存超过可用内存,分配器也可能会根据分配和释放的顺序来分割内存,从而最终无法找到连续的块来满足分配,即使总体上有足够的可用内存。
很难证明没有输入序列会导致病理性分裂。
您可以设计分配器以保证不会出现碎片(例如,仅分配一个大小的块),但是这对调用者造成了实质性的约束,并可能由于浪费而增加了所需的内存量。而且,调用者必须仍然证明没有泄漏,并且无论输入顺序如何,总内存需求都有令人满意的上限。这种负担如此之大,以至于设计系统实际上更加简单,因此它不使用动态内存分配。
实时系统的处理方法是,程序必须严格满足某些计算和内存限制,而与执行路径无关(根据输入的不同,执行路径可能仍然有很大差异)。那么,在这种情况下,使用通用动态内存分配(例如malloc / new)意味着什么?这意味着开发人员在某个时候无法确定确切的内存消耗,并且无法判断所得程序是否能够满足内存和计算能力的要求。
是的,这是正确的。对于您提到的那种应用程序,必须详细说明可能发生的所有事情。程序必须根据规范处理最坏的情况,并且要准确地留出足够多的内存。“我们不知道获得多少输入”的情况不存在。最坏的情况是用固定数字指定的。
您的程序必须具有确定性,从某种意义上讲,它可以处理最坏情况下的所有事情。
堆的主要目的是允许几个不相关的应用程序共享RAM内存,例如在PC中,RAM中运行的程序/进程/线程的数量不确定。在实时系统中不存在这种情况。
另外,随着段的增加或删除,堆本质上是不确定的。
此处更多信息:https : //electronics.stackexchange.com/a/171581/6102
从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://zh.wikipedia.org/wiki/完整性(操作系统)
和这里:
这是:
我不是正式方法的专家,但是此验证的要求之一可能是消除内存分配所需时间的不确定性。在RTOS中,所有事件之间的精确间隔都是毫秒。动态内存分配始终在所需的时序方面存在问题。
从数学上来说,您真的需要从关于时间和内存量的最基本的假设中证明一切正常。
如果您想到堆内存的替代方案: 静态内存。地址是固定的,分配的大小是固定的。内存中的位置是固定的。因此,很容易就内存充足性,可靠性,可用性等进行推理。
例如第一级或第二级触发闪烁器设备的数据值或其统计不确定性分布会受到一些影响,这些影响可能源于您可能需要等待的不可复制的时间量malloc/free
。
最糟糕的方面是,它们与硬件的物理现象无关,而与内存的状态(及其历史记录)无关。
在这种情况下,您的目标是从受这些错误影响的数据中重建事件的原始序列。重建/猜测的序列也会受到错误的影响。这种迭代并非总是会收敛于一个稳定的解决方案。并不是说那将是正确的;您的数据不再独立...您将面临逻辑短路的风险...
您说“当人们告诉我时,我无法验证此声明的正确性”。
我将尝试给您一个纯粹的假设情况/案例研究。
让我们想象一下,您在系统上使用CCD或处理一些第一级和第二级闪烁器触发器,这些触发器必须节省资源(您在太空中)。
将设置采集速率,以使背景x%
为MAXBINCOUNT
。
有一个爆发,您的计数出现尖峰,而bin计数器溢出。
我全部想要:您切换到最大采集速率,然后完成缓冲区。
在完成额外的缓冲区的同时,您将释放/分配更多的内存。
你会怎么做?
注意:
现在,信号maxbincount
以您的硬件允许的最大采集速率在附近变化,并且事件比平时更长。
您完成了空间并要求更多...同时,您遇到了上述相同的问题。
溢出和系统性峰计算出时间序列中的低估还是空洞?
通过硬件,您收到的数据量超出了库存或传输的范围。
您必须在时间或空间上对数据进行聚类(2x2、4x4,... 16x16 ... 256x256 ...像素缩放...)。
前一个问题的不确定性可能会影响误差分布。
对于CCD设置,您的边框像素的计数接近。maxbincount
(取决于您希望看到的更好的“位置”)。
现在您可以在CCD上淋浴或在一个大点上进行淋浴,而计数总数相同但统计不确定性不同(等待时间引入的部分)...
因此,例如,在期望有洛伦兹轮廓的情况下,您可以使用高斯一个(Voigt)获得其卷积,或者如果第二个在肮脏的高斯中真正占主导地位,则可以进行卷积...
总会有一个权衡。程序的运行环境及其执行的任务应成为决定是否应使用HEAP的基础。
当您要在多个函数调用之间共享数据时,堆对象是有效的。您只需要传递指针,因为可以全局访问堆。也有缺点。某些功能可能会释放此内存,但其他地方也可能存在一些引用。
如果堆内存在工作完成后仍未释放,并且程序继续分配更多的内存,则HEAP有时会耗尽内存并影响程序的确定性。
.data
与.bss
如何不同?