6

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

以下小修复是否足以解决问题,还是有更好的方法?在用以下代码替换每个“新”之前,我想我会问。

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

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

最后更新:非常感谢您提供的有用回复,但事实证明在检查内存分配失败的代码中存在错误。不过,我会牢记所有这些答案,并尽可能多地替换 malloc 和 new (尤其是在错误处理代码中)。

4

9 回答 9

15

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

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

  • 进程知道他们自己的内存使用和需求,并不断地相互告知他们需要多少内存。他们合作,所以他们不会耗尽内存。这假设系统中至少有一些进程可以调整它们自己的内存需求(例如,通过改变内部高速缓存的大小)。Alonso 和 Appel 写了一篇关于这种方法的论文。

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

您上面的建议与任何情况都不匹配。 相反,您希望其他一些过程可以解决问题,并且最终会出现您需要的内存。你可能会走运。你可能不会。

如果您希望您的系统可靠地工作,您最好重新考虑系统上运行的每个进程的设计,因为需要共享有限的内存。这可能比您预期的要大,但是如果您了解问题所在,则可以这样做。祝你好运!

于 2008-12-15T02:44:28.717 回答
2

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

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

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

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

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

于 2008-12-15T03:31:16.377 回答
1

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

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

如果堆是共享的,那么上面的方法可能会起作用。但是,如果您有一个共享堆,那么调用“new”可能会导致自旋锁(与您拥有的类似的循环,但使用 CAS 指令),或者它会基于某些内核资源而阻塞。

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

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

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

于 2008-12-15T02:16:43.813 回答
1

几点:

  • 嵌入式程序通常在启动时分配所有内存或仅使用静态内存以避免此类情况。
  • 除非设备上运行其他东西定期释放内存,否则您的解决方案可能不会有效。
  • 我拥有的 Viper 有 64MB RAM,我认为它们的内存不会少于 32MB,您的应用程序使用了多少内存?
于 2008-12-15T02:21:11.947 回答
1

我认为最明智的做法是使用静态分配内存,这样您就知道发生了什么。动态内存分配是桌面编程的一个坏习惯,不适合资源受限的机器(除非您花费相当多的时间和精力来创建一个良好的托管和受控内存管理系统)。

此外,检查您设备中的操作系统(假设它有一个像这样的高端 ARM 设备倾向于运行操作系统)具有哪些功能来处理内存。

于 2008-12-17T08:36:48.460 回答
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 。有关如何做到这一点,请参阅我的其他答案。这样,您就有了一个中心位置处理内存问题,而不是很多地方都关心这个。

于 2008-12-28T07:28:30.457 回答
1

有几种不同的方法来解决这个问题 - 请注意,工具说明会有所不同,具体取决于您使用的 Windows CE / Windows Mobile 版本。

需要回答的一些问题:

1.您的应用程序是否存在内存泄漏,导致这种低内存情况?

2. 您的应用程序是否只是在某些阶段使用了过多的内存,从而导致了这种内存不足的情况?

1 和 2 可以使用 Windows CE AppVerifier 工具进行调查,该工具可以为您的产品提供详细的内存记录工具。其他堆包装工具也可以提供类似的信息(并且可能具有更高的性能),具体取决于您的产品设计。

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

3、你在这个过程中分配和释放内存是否非常频繁?

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

4. 您是否分配了非常大的内存块?(> 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

于 2008-12-28T08:00:56.320 回答
1

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

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

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

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

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

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

最后,如果线程在经过一定次数的迭代后无法取得进展,您肯定会遇到一些错误/恐慌情况。如果所有线程都在等待系统中的另一个线程释放内存,这将至少让您看到可能遇到的潜在活锁情况。您可以简单地根据经验证明有效的方式选择一些迭代,或者您可以更聪明地了解它并跟踪有多少线程被卡住等待内存,以及是否最终导致所有线程恐慌。

注意:这显然不是一个完美的解决方案,正如其他发帖者所提到的,需要对整个应用程序进行更全面的了解才能正确解决问题,但以上是一种实用的技术,应该在短期内发挥作用。

于 2008-12-29T11:46:31.887 回答
0

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

对我来说,这里没有什么味道。嗯……

嵌入式系统通常需要具有极高的确定性——也许您应该审查整个系统并提前了解其失败的可能性;然后努力失败,它实际上在实践中发生。

于 2008-12-15T02:06:41.163 回答