动态内存分配失败恢复

时间:2008-12-15 02:02:48

标签: c++ embedded windows-ce new-operator viper

我正在研究嵌入式处理器(400 MHz Intel PXA255 XScale),我认为我看到一个没有足够内存来满足“新”操作的情况。该程序没有崩溃,所以我假设其他线程已经释放了他们的记忆,这只是一个短暂的事情。这是一些非常关键的代码,因此退出不是一个选项,需要将某种错误返回给远程用户。

以下小修补程序是否足以解决问题,还是有更好的方法?在用以下代码替换每个'new'之前,我想我会问。

char someArr[];
do{ 
    someArr = new char[10]; 
    Sleep(100); // no justification for choosing 100 ms
} while ( someArr == NULL );

睡眠有帮助吗?我应该设置一些最大重试次数吗?是否可以在任何地方使用静态初始化?

最终更新:非常感谢您提供了有用的响应,但事实证明,在检查内存分配失败的代码中存在错误。我会记住所有这些答案,尽可能多地替换malloc和new(特别是在错误处理代码中)。

9 个答案:

答案 0 :(得分:15)

您正试图通过本地推理解决全球问题。全局问题是整个设备对操作系统和所有应用程序具有有限数量的RAM(以及可能的后备存储)。为了确保不超过此数量的RAM,您有以下几种选择:

  • 每个进程在固定数量的RAM中运行,以便在启动时按每个进程确定;程序员做了推理以确保一切都适合。所以,是的,可以静态分配所有内容。这只是很多工作,每次更改系统配置时,都必须重新考虑分配

  • 进程知道自己的内存使用情况和需求,并不断建议彼此需要多少内存。他们合作,因此他们不会耗尽内存。这假设系统中的至少一些进程可以调整它们自己的存储器要求(例如,通过改变内部高速缓存的大小)。 Alonso和Appel写了paper about this approach

  • 每个进程都知道内存可能会耗尽,并且可以故障转移到消耗最少内存量的状态。通常,这种策略是通过内存异常来实现的。异常在main()中或附近处理,并且内存不足事件实质上从头开始重新启动程序。如果内存响应用户请求而增长,则此故障转移模式可以工作;如果程序的内存需求增长与用户的内容无关,那么可能导致颠簸。

上面的提案与所有方案都不匹配。相反,您希望其他一些流程可以解决问题,最终会显示您需要的内存。你可能会很幸运。你可能没有。

如果您希望系统可靠地运行,那么根据共享有限内存的需要,您最好重新考虑系统上运行的每个进程的设计。它可能比你预期的更重要,但如果你理解这个问题,你就可以做到这一点。祝你好运!

答案 1 :(得分:2)

其他答案中有很多好东西,但我确实认为值得补充的是,如果所有线程都进入类似的循环,那么程序将会死锁。

对这种情况的“正确”答案可能是对程序的不同部分有严格的限制,以确保它们不会过度消耗内存。这可能需要重写程序所有部分的主要部分。

下一个最佳解决方案是进行一些回调,其中失败的分配尝试可以告诉程序的其余部分需要更多内存。也许该程序的其他部分可以比通常更积极地释放一些缓冲区,或释放用于缓存搜索结果的内存,或者其他东西。这将需要程序其他部分的新代码。但是,这可以逐步完成,而不是要求在整个程序中重写。

另一种解决方案是让程序使用互斥锁保护大(临时)内存请求。听起来你有信心,如果你以后可以再试一次,记忆将会很快发布。我建议您使用互斥锁进行可能消耗大量内存的操作,这将允许在另一个线程释放所需内存时立即唤醒线程。否则,即使内存立即释放,你的线程也会休眠十分之一秒。

您也可以尝试sleep(0),它只会将控制权移交给任何其他准备运行的线程。如果所有其他线程都进入休眠状态,这将允许您的线程立即重新获得控制权,而不必等待其100毫秒的句子。但是如果至少有一个线程仍然需要运行,你仍然需要等到它放弃控制权。这在Linux机器上通常是10毫秒,最后我检查过。我不知道其他平台。如果调度程序已自愿进入休眠状态,则您的线程在调度程序中的优先级也可能较低。

答案 2 :(得分:1)

正如其他人所提到的,理想情况下,您可以通过前期设计和软件架构来避免这个问题,但我现在假设这不是一个选项。

正如另一篇文章所提到的那样,将逻辑包装在一些实用程序函数中会很好,这样你就不会在所有地方编写内存不足的代码了。

要解决真正的问题,您尝试使用共享资源,内存,但无法使用,因为该共享资源正被系统中的另一个线程使用。理想情况下,您要做的是等待系统中的其他线程之一释放您需要的资源,然后获取该资源。如果你有办法拦截所有的分配和免费调用,你可以设置一些东西,以便分配线程被阻塞直到内存可用,并且释放在内存可用时发出分配线程的信号。但我会假设这太过分了。

考虑到无法完全重新构建系统或重写内存分配器的限制,那么我认为您的解决方案是最实用的,只要您(以及您团队中的其他人),了解这些限制以及它将导致的问题。

现在,为了改进您的具体方法,您可能需要测量工作负载以查看内存分配和释放的频率。这样可以更好地计算重试间隔应该是什么。

其次,您希望尝试增加每次迭代的超时时间,以减少该线程在系统上的负载。

最后,如果线程在经过一些迭代后无法取得进展,你肯定应该有一些错误/恐慌情况。这将让您至少看到如果所有线程都在等待系统中的另一个线程释放内存时可能遇到的潜在实时锁定情况。您可以根据经验显示的工作简单地选择一些迭代,或者您可以更聪明地了解它并跟踪有多少线程等待内存,如果最终导致所有线程出现恐慌。

注意:这显然不是一个完美的解决方案,因为其他海报提到了一个更全局的应用程序整体视图,需要正确解决问题,但上面是一个实用的应该在短期内发挥作用的技术。

答案 3 :(得分:1)

您使用C ++。因此,您可以使用一些C ++实用程序来使您的生活更轻松。例如,为什么不使用new_handler?

void my_new_handler() {
    // make room for memory, then return, or throw bad_alloc if
    // nothing can be freed.
}

int main() {
    std::set_new_handler(&my_new_handler);

    // every allocation done will ask my_new_handler if there is
    // no memory for use anymore. This answer tells you what the
    // standard allocator function does: 
    // https://stackoverflow.com/questions/377178
}

在new_handler中,您可以向所有应用程序发送信号,以便他们知道某些应用程序需要内存,然后稍等一下,让其他应用程序有时间来完成内存请求。重要的是,您做某事而不是默默地希望获取可用内存。如果仍然没有足够的内存,新运算符将再次调用您的处理程序,因此您不必担心所有应用程序是否已经释放所需的内存。如果您需要知道new_handler中所需的内存大小,还可以重载operator new 。请参阅我的other answer了解如何执行此操作。这样,你有一个中心位置来处理内存问题,而不是许多与此有关的地方。

答案 4 :(得分:1)

有几种不同的攻击方法 - 请注意,根据您使用的Windows CE / Windows Mobile版本,工具说明会略有不同。

要回答的一些问题:

<强> 1。您的应用程序是否会泄漏内存,导致内存不足?

<强> 2。您的应用程序是否仅在某些阶段使用过多内存,导致内存不足?

可以使用Windows CE AppVerifier工具调查

1和2,它可以为您的产品提供详细的内存记录工具。其他堆包装工具也可以提供类似的信息(可能性能更高),具体取决于您的产品设计。

http://msdn.microsoft.com/en-us/library/aa446904.aspx

第3。您是否在此过程中经常分配和释放内存?

在操作系统版本6.0之前的Windows CE(不要与Windows Mobile 6.x混淆)有32MB /进程虚拟内存限制,这往往会导致很多有趣的碎片问题。在这种情况下,即使您有足够的物理内存空间,也可能会耗尽虚拟内存。使用自定义块分配器通常可以缓解此问题。

<强> 4。你在分配非常大的内存块吗? (&gt; 2MB)

与3相关,你可能只是耗尽进程虚拟内存空间。有一些技巧,在某种程度上取决于操作系统版本,在进程空间之外的共享VM空间中分配内存。如果您的VM耗尽,但物理RAM耗尽,这可能会有所帮助。

<强> 5。您使用的是大量DLL吗?

还与3相关,根据操作系统版本,DLL也可能非常快地减少总可用VM。

进一步跳出积分:

CE内存工具概述

http://blogs.msdn.com/ce_base/archive/2006/01/11/511883.aspx

目标控制窗口'mi'工具

http://msdn.microsoft.com/en-us/library/aa450013.aspx

答案 5 :(得分:1)

根据您的问题,我假设您的堆在多个线程之间共享。

如果不是,那么上面的代码将不起作用,因为在循环运行时不会从堆中释放任何内容。

如果堆是共享的,那么上面的内容可能会有效。但是,如果你有一个共享堆,然后调用“新”可能会导致两种自旋锁(类似的循环,你有一个,但使用CAS指令),否则将基于一些内核资源块。

在这两种情况下,您拥有的循环都会降低系统的吞吐量。这是因为您将需要更多的上下文切换,或者需要更长的时间来响应“内存现在可用”事件。

我会考虑覆盖“新”和“删除”运算符。当新的失败时你可以阻止(或旋转锁定某种类型的计数器变量)等待另一个线程释放内存,然后删除可以发出阻塞的“新”线程信号或使用CAS增加计数器变量。

这应该会给你更好的吞吐量并且更有效率

答案 6 :(得分:1)

几点:

  • 嵌入式程序通常在启动时分配所有内存或仅使用静态内存来避免这种情况。
  • 除非设备上还有其他内容可以定期释放内存,否则您的解决方案可能不会有效。
  • Viper我有64MB RAM,我认为它们不到32MB,你的应用程序使用了多少内存?

答案 7 :(得分:1)

我认为最明智的做法是使用内存的静态分配,这样你就可以了解发生了什么。动态内存分配是桌面编程的一个坏习惯,不适用于受限资源的计算机(除非您花费大量时间和精力创建一个良好的托管和受控内存管理系统)。

另外,检查设备中的操作系统的功能(假设它有一个,像这样的高端ARM设备往往运行操作系统)具有处理内存的功能。

答案 8 :(得分:0)

当然,这取决于你是否有合理的期望在100(毫秒?)睡眠中可用的内存?当然,你应该限制它尝试的次数。

对我来说,这里闻不到什么。嗯...

嵌入式系统通常需要具有极高的确定性 - 也许您应该检查整个系统,并了解此事先发生故障的可能性;然后只是努力实践它实际上发生了。