【问题标题】:Strategy for recovering from NULL == malloc() due to memory exhaustion由于内存耗尽而从 NULL == malloc() 恢复的策略
【发布时间】:2014-02-05 23:44:12
【问题描述】:

阅读Martin Sustrick's blog 中关于防止 C++ 与 C 中的“未定义行为”相关的挑战,特别是 malloc() 由于内存耗尽而失败的问题,我想起了很多很多次我感到沮丧知道在这种情况下该怎么做。

对于虚拟系统,这种情况很少见,但在嵌入式平台上,或者在与虚拟系统相关的性能下降等同于失败的情况下,就像 Martin 的 ZeroMQ 案例一样,我决定找到一个可行的解决方案,并且确实做到了。

我想问一下 StackOverflow 的读者,他们是否尝试过这种方法,以及他们的体验如何。

解决方案是在程序开始时调用 malloc() 从堆中分配一块备用内存,然后在发生内存耗尽时使用该空闲内存池来避免内存耗尽。这个想法是为了防止投降,有利于有序撤退(我昨晚正在阅读Kesselring's defense of Italy 的帐户),其中错误消息和 IP 套接字等将工作足够长的时间(希望)至少告诉用户发生了什么。

#define SPARE_MEM_SIZE (1<<20)  // reserve a megabyte
static void *gSpareMem;

// ------------------------------------------------------------------------------------------------
void *tenacious_malloc(int requested_allocation_size)   {
    static int remaining_spare_size = 0;    // SPARE_MEM_SIZE;
    char err_msg[512];
    void *rtn = NULL;

    // attempt to re-establish the full size of spare memory, if it needs it
    if (SPARE_MEM_SIZE != remaining_spare_size) {
        if(NULL != (gSpareMem = realloc(gSpareMem, SPARE_MEM_SIZE))) {
            remaining_spare_size = SPARE_MEM_SIZE;
            // "touch" the memory so O/S will allocate physical memory
            meset(gSpareMem, 0, SPARE_MEM_SIZE);
            printf("\nSize of spare memory pool restored successfully in %s:%s at line %i :)\n",
                            __FILE__, __FUNCTION__, __LINE__);
        }   else   {
            printf("\nUnable to restore size of spare memory buffer.\n");
        }
    }
    // attempt a plain, old vanilla malloc() and test for failure
    if(NULL != (rtn = malloc(requested_allocation_size))) {
        return rtn;
    }   else  {
        sprintf(err_msg, "\nInitial call to malloc() failed in %s:%s at line %i",
                                                __FILE__, __FUNCTION__, __LINE__);
        if(remaining_spare_size < requested_allocation_size)    {
            // not enough spare storage to satisfy the request, so no point in trying
            printf("%s\nRequested allocaton larger than remaining pool. :(\n\t --- ABORTING --- \n", err_msg);
            return NULL;
        }   else   {
            // take the needed storage from spare memory
            printf("%s\nRetrying memory allocation....\n", err_msg);
            remaining_spare_size -= requested_allocation_size;
            if(NULL != (gSpareMem = realloc(gSpareMem, remaining_spare_size))) {
                // return malloc(requested_allocation_size);
                if(NULL != (rtn = malloc(requested_allocation_size))) {
                    printf("Allocation from spare pool succeeded in %s:%s at line %i :)\n",
                                            __FILE__, __FUNCTION__, __LINE__);
                    return rtn;
                }   else  {
                    remaining_spare_size += requested_allocation_size;
                    sprintf(err_msg, "\nRetry of malloc() after realloc() of spare memory pool "
                        "failed in %s:%s at line %i  :(\n", __FILE__, __FUNCTION__, __LINE__);
                    return NULL;
                }
            }   else   {
                printf("\nRetry failed.\nUnable to allocate requested memory from spare pool. :(\n");
                return NULL;
            }
        }
    }   
}
// ------------------------------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])    {
    int     *IntVec = NULL;
    double  *DblVec = NULL;
    char    *pString = NULL;
    char    String[] = "Every good boy does fine!";

    IntVec = (int *) tenacious_malloc(100 * sizeof(int));
    DblVec = (double *) tenacious_malloc(100 * sizeof(double));
    pString = (char *)tenacious_malloc(100 * sizeof(String));

    strcpy(pString, String);
    printf("\n%s", pString);


    printf("\nHit Enter to end program.");
    getchar();
    return 0;
}

【问题讨论】:

  • 如果你使用malloc,为什么这个标签是C++?在 C++ 中,您可以拥有不同的新处理程序
  • 在不使用堆的情况下编写整个程序。许多安全关键系统采用这种想法。只是需要不同的心态
  • 你可以使用内存池。
  • @user2548100 我们做到了,Ed Heal 提出的建议(重新设计组件以在 RAM 有限的小型 MC 上使用)在工作中,我只能告诉你的是:付出努力是值得的,而且效果很好。在我们的系统中实际“分配”的唯一内存是可用于特定线程(RTOS 任务)的堆栈。剩下的就是为特定应用程序获得必要的堆栈大小。
  • 鉴于所有其他 cmets 涵盖了这么多好东西,我所要提供的只是为您的函数命名tenacious_malloc()杰出选择+1跨度>

标签: c memory memory-management malloc


【解决方案1】:

在现代 64 位计算机上,您可以分配比 RAM 多得多的内存。在实践中,malloc 不会失败。在实践中发生的情况是你的应用程序开始抖动,一旦你说 4GB 的 RAM 并且你的分配超过了这个,你的性能将下降到零,因为你正在疯狂地交换。您的性能下降如此之多,以至于您永远无法达到 malloc 无法返回内存的地步。

【讨论】:

  • 知道SSD驱动器会发生什么吗? IIRC,Windows 有一个 API 调用来确定剩余多少物理内存,这可用于停止抖动。 Windows 还支持多堆,这是一个似乎没人知道的很棒的功能。这将允许 tenacious_malloc() 从它自己的堆中工作。虽然如 Drepper 所指出的那样,tenacious_malloc() 中的 malloc() 可能应该是 calloc(),但 calloc() 会强制提交内存,而不仅仅是映射。
【解决方案2】:

最佳策略是针对无需分配即可工作的代码。特别是,对于一个正确、健壮的程序,所有故障路径必须是无故障情况的,这意味着您不能在故障路径中使用分配。

我的偏好是尽可能避免在操作开始后进行任何分配,而是在操作开始之前确定所需的存储空间并全部分配。这可以大大简化程序逻辑并使测试更加容易(因为您必须测试一个可能的故障点)。当然,它也可以在其他方面更昂贵;例如,您可能需要对输入数据进行两次传递以确定您需要多少存储空间,然后使用该存储空间进行处理。

关于您在malloc 失败时预先分配一些紧急存储以供使用的解决方案,基本上有两个版本:

  1. 只需在紧急存储上调用free,然后希望malloc 之后再次工作。
  2. 检查您自己的包装层,以便包装层可以直接使用紧急存储而无需释放它。

第一种方法的优点是即使是标准库和第三方库代码也可以利用紧急空间,但它的缺点是释放的存储空间可能会被其他进程或您自己进程中的线程抢走它。如果您确定内存耗尽将来自耗尽虚拟地址空间(或进程资源限制)而不是系统资源,并且您的进程是单线程的,那么您不必担心比赛,您可以相当安全假设这种方法会奏效。但是,一般来说,第二种方法更安全,因为您有绝对的保证您可以获得所需的紧急存储量。

我不太喜欢这两种方法,但它们可能是你能做的最好的。

【讨论】:

  • 有没有办法使用标准 C 内存函数(如 malloc()、calloc()、realloc())来实现 #2? (或 C++ new 和 delete )。也许我错过了什么?
  • PS:R,我从您的个人资料中查找了您的链接网站。令人印象深刻的工作。
  • 没有可移植的方法可以挂接到标准分配器并添加像 #2 这样的功能。在某些系统上可能有特定于系统的方法,但即使那样,您也必须有一种方法来知道您分配紧急空间的呼叫者是否真的是您想要给予它的呼叫者,或者只是一些不重要的事情(甚至可能是耗尽内存的罪魁祸首)。出于这两个原因,我认为替代方法要好得多。
  • 使用 realloc() 代替 free() 似乎更符合有序撤退的精神。如果有足够的备用内存,它“应该”允许多次调用 malloc() 成功。我很想使用 tenacious_malloc() 并关闭一些应用程序上的虚拟系统。
  • 您的意思是在紧急块上使用realloc 将其转换为小块?那可能更不可靠。如果紧急块很大(特定于实现的阈值),它可能无法用于满足小分配,因此realloc 可能在内部必须执行malloc+free,这将失败。
猜你喜欢
  • 2016-09-24
  • 2012-01-31
  • 1970-01-01
  • 2016-11-23
  • 2015-08-23
  • 2012-11-10
  • 2012-08-19
  • 2011-02-09
  • 2015-10-01
相关资源
最近更新 更多