Динамическое восстановление памяти после сбоя

Я работаю над встроенным процессором (400 МГц Intel PXA255 XScale), и мне показалось, что я видел один случай, когда памяти было недостаточно для выполнения «новой» операции. Программа не аварийно завершилась, поэтому я предположил, что другие потоки освободили память, и это было просто временным явлением. Это довольно критичный код, поэтому выход не является опцией, и удаленному пользователю необходимо вернуть какую-то ошибку.

Будет ли достаточно следующего исправления, чтобы решить проблему, или есть лучший способ? Прежде чем заменить каждое «новое» следующим кодом, я подумал, что спросить.

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

Сон помогает? Должен ли я установить максимальное количество попыток? Можно ли использовать статическую инициализацию везде?

ЗАКЛЮЧИТЕЛЬНОЕ ОБНОВЛЕНИЕ: Большое спасибо за полезные ответы, но оказывается, что произошла ошибка в проверке кода на предмет неудачного выделения памяти. Я буду помнить все эти ответы и заменять как можно больше malloc и new (особенно в коде обработки ошибок).

15.12.2008 02:02:48
9 ОТВЕТОВ
РЕШЕНИЕ

Есть несколько различных способов атаковать - обратите внимание, что инструкции инструмента будут немного отличаться в зависимости от используемой версии 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) был ограничен объем виртуальной памяти 32 МБ / процесс, что обычно вызывает массу забавных проблем фрагментации. В этом случае, даже если у вас достаточно свободной физической памяти, возможно, вам не хватает виртуальной памяти. Использование пользовательских распределителей блоков обычно является решением этой проблемы.

4. Вы выделяете очень большие блоки памяти? (> 2 МБ)

Что касается 3, вы можете просто исчерпать пространство виртуальной памяти процесса. Существуют приемы, в некоторой степени зависящие от версии ОС, для выделения памяти в общем виртуальном пространстве за пределами пространства процесса. Если у вас заканчивается виртуальная машина, но не физическая память, это может помочь.

5. Используете ли вы большое количество библиотек DLL?

Также относится к 3, в зависимости от версии ОС, библиотеки DLL также могут очень быстро сократить общий объем доступной виртуальной машины.

Дальнейшие прыжки с очков:

Обзор инструментов памяти CE

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

Средство управления мишенью окна ми

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

1
28.12.2008 08:00:56

Конечно, это будет зависеть от того, разумно ли вы ожидаете, что память станет доступной в течение 100 (миллисекунд?) Сна? Конечно, вы должны ограничить количество попыток.

Для меня здесь что-то не пахнет. Хммм ...

Встраиваемые системы, как правило, должны быть чрезвычайно детерминированными - возможно, вам следует рассмотреть всю систему и оценить потенциальную возможность ее сбоя заранее; а затем просто терпеть неудачу, это на самом деле происходит на практике.

0
15.12.2008 02:06:41

Исходя из вашего вопроса, я предполагаю, что ваша куча распределяется между несколькими потоками.

Если это не так, приведенный выше код не будет работать, потому что ничто не будет освобождено из кучи во время работы цикла.

Если куча является общей, то вышеописанное, вероятно, сработает. Однако, если у вас есть общая куча, то вызов «new», вероятно, приведет либо к спин-блокировке (похожей на ту, что у вас есть, но с использованием инструкций CAS), либо к блокировке на основе некоторых ресурсов ядра.

В обоих случаях петля, которую вы используете, уменьшит пропускную способность вашей системы. Это связано с тем, что вы либо будете подвергаться большему количеству переключений контекста, чем вам необходимо, или потребуется больше времени, чтобы ответить на событие «память теперь доступна».

Я хотел бы рассмотреть возможность переопределения операторов «новый» и «удалить». В случае сбоя new вы можете заблокировать (или заблокировать вращение какой-либо переменной счетчика) в ожидании освобождения памяти другим потоком, а затем удалить можно либо сигнализировать о заблокированном «новом» потоке, либо увеличить значение переменной счетчика с помощью CAS.

Это должно дать вам лучшую пропускную способность и быть немного более эффективным

1
15.12.2008 02:16:43

Несколько моментов:

  • Встроенные программы часто выделяют всю память при запуске или используют только статическую память, чтобы избежать подобных ситуаций.
  • Если на устройстве не работает что-то еще, что освобождает память на регулярной основе, ваше решение вряд ли будет эффективным.
  • Viper у меня имеет 64 МБ ОЗУ, я не думаю, что они имеют менее 32 МБ, сколько памяти использует ваше приложение?
1
15.12.2008 02:35:59

Вы пытаетесь решить глобальную проблему с помощью локальных рассуждений. Глобальная проблема заключается в том, что все устройство имеет ограниченный объем оперативной памяти (и, возможно, резервное хранилище) для операционной системы и всех приложений. Чтобы убедиться, что этот объем оперативной памяти не превышен, у вас есть несколько вариантов:

  • Каждый процесс работает с фиксированным объемом ОЗУ, который определяется для каждого процесса во время запуска; программист рассуждает, чтобы убедиться, что все подходит. Так что, да, можно все статически распределить . Это просто большая работа, и каждый раз, когда вы меняете конфигурацию вашей системы, вам приходится пересматривать распределение .

  • Процессы знают об использовании и потребностях своей памяти и постоянно консультируют друг друга о том, сколько памяти им нужно. Они сотрудничают, поэтому у них не заканчивается память . Это предполагает, что, по крайней мере, некоторые процессы в системе могут регулировать свои собственные требования к памяти (например, путем изменения размера внутреннего кэша). Алонсо и Аппель написали статью об этом подходе .

  • Каждый процесс осознает, что память может быть исчерпана и может перейти в состояние, в котором он потребляет минимальный объем памяти . Часто эта стратегия реализуется с помощью исключения нехватки памяти. Исключение обрабатывается в функции main () или рядом с ней, и событие нехватки памяти фактически перезапускает программу с нуля. Этот режим отработки отказа может работать, если память увеличивается в ответ на пользовательские запросы; если требования к памяти программы растут независимо от действий пользователя, это может привести к перегрузке.

Ваше предложение выше не соответствует ни одному из сценариев. Вместо этого вы надеетесь, что какой-то другой процесс решит проблему, и в итоге появится нужная вам память. Тебе может повезти. Вы не могли бы.

Если вы хотите, чтобы ваша система работала надежно, вам следует пересмотреть структуру каждого процесса, работающего в системе, в свете необходимости совместного использования ограниченной памяти. Это может быть большая работа, чем вы ожидали, но если вы понимаете проблему, вы можете сделать это. Удачи!

15
15.12.2008 02:44:28
Проблема всего этого в том, что около 99% всего кода не имеет понятия, как правильно обрабатывать неудачные выделения памяти. Приложения терпят неудачу таинственными способами. Отсутствие свободной памяти приводит к полной остановке практически всех операционных систем. Грустно, но правда.
Thorsten79 29.12.2008 09:52:09

В других ответах есть много хорошего, но я думаю, что стоит добавить, что если все потоки попадут в один и тот же цикл, то программа будет заблокирована.

«Правильный» ответ на эту ситуацию, вероятно, состоит в том, чтобы иметь строгие ограничения для различных частей программы, чтобы гарантировать, что они не потребляют слишком много памяти. Для этого, вероятно, потребуется переписать основные разделы во всех частях программы.

Следующим лучшим решением было бы иметь некоторый обратный вызов, при котором неудачная попытка выделения может сказать остальной части программы, что требуется больше памяти. Возможно, другие части программы могут освобождать некоторые буферы более агрессивно, чем обычно, или освобождать память, используемую для кэширования результатов поиска, или что-то еще. Это потребует нового кода для других частей программы. Однако это можно сделать постепенно, а не требовать перезаписи всей программы.

Другое решение состоит в том, чтобы программа защищала большие (временные) запросы памяти с помощью мьютекса. Похоже, вы уверены, что память скоро будет освобождена, если вы можете просто повторить попытку позже. Я предлагаю вам использовать мьютекс для операций, которые могут потреблять много памяти, это позволит потоку немедленно разбудить, когда другой поток освободит необходимую память. В противном случае ваш поток будет спать в течение одной десятой секунды, даже если память немедленно освобождается.

Вы также можете попробовать sleep (0), который просто передаст управление любому другому потоку, готовому к запуску. Это позволит вашему потоку немедленно восстановить управление, если все остальные потоки перейдут в спящий режим, вместо того, чтобы ждать 100-миллисекундное предложение. Но если хотя бы один поток все еще хочет работать, вам все равно придется ждать, пока он не сдаст контроль. Это обычно 10 миллисекунд на компьютерах с Linux, последний раз я проверял. Я не знаю о других платформах. Ваш поток может также иметь более низкий приоритет в планировщике, если он добровольно перешел в спящий режим.

2
20.12.2008 05:10:36

Во-вторых, самое разумное - использовать статическое распределение памяти, чтобы вы имели представление о том, что происходит. Динамическое распределение памяти - это плохая привычка при программировании на рабочем столе, которая не подходит для машин с ограниченными ресурсами (если вы не тратите немало времени и усилий на создание хорошей управляемой и контролируемой системы управления памятью).

Кроме того, проверьте, какие функции ОС на вашем устройстве (при условии, что оно имеет одно высокопроизводительное устройство ARM, подобное этому, как правило, для запуска ОС) для обработки памяти.

1
17.12.2008 08:36:48

Вы используете 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, если вам нужно знать объем памяти, необходимый в new_handler. Смотрите мой другой ответ о том, как это сделать. Таким образом, у вас есть одно центральное место для решения проблем с памятью, а не во многих местах, связанных с этим.

1
23.05.2017 10:33:14

Как уже упоминали другие, в идеале, вы бы избежали этой проблемы с помощью предварительного проектирования и архитектуры программного обеспечения, но я предполагаю, что на данный момент это не вариант.

Как упоминается в другом посте, было бы хорошо обернуть логику в некоторые служебные функции, чтобы в конечном итоге вы не писали код нехватки памяти.

Чтобы решить реальную проблему, вы пытаетесь использовать общий ресурс, память, но не можете, потому что этот общий ресурс используется другим потоком в системе. В идеале вы хотели бы подождать, пока один из других потоков в системе освободит нужный вам ресурс, а затем получить этот ресурс. Если у вас был способ перехвата всех распределенных и свободных вызовов, вы могли бы что-то настроить так, чтобы поток распределения блокировался до тех пор, пока не освободилась память, и освобождение сигнализировало поток распределения, когда память была доступна. Но я собираюсь предположить, что это просто слишком много работы.

Учитывая ограничения невозможности полностью перестроить систему или переписать распределитель памяти, тогда я думаю, что ваше решение является наиболее практичным, если вы (и другие члены вашей команды) понимаете ограничения, и проблемы, которые это вызовет в будущем.

Теперь, чтобы улучшить ваш конкретный подход, вы можете измерить рабочие нагрузки, чтобы увидеть, как часто выделяется и освобождается память. Это даст вам лучший способ рассчитать, каким должен быть интервал повторения.

Во-вторых, вы хотите попытаться увеличить время ожидания для каждой итерации, чтобы уменьшить нагрузку на этот поток в системе.

Наконец, у вас определенно должно быть время ошибки / паники, если поток не может продвинуться после некоторого количества итераций. Это позволит вам, по крайней мере, увидеть потенциальный случай блокировки в реальном времени, с которым вы можете столкнуться, если все потоки ожидают освобождения памяти другим потоком в системе. Вы можете просто выбрать количество итераций, основываясь на том, что эмпирически показано, как работает, или вы можете стать умнее и следить за тем, сколько потоков зависло в ожидании памяти, и если это в конечном итоге приводит к панике всех потоков.

Примечание . Очевидно, что это не идеальное решение, и поскольку другие авторы упоминали, что для правильного решения проблемы необходим более глобальный взгляд на приложение в целом, но вышеприведенный практический метод должен работать в краткосрочной перспективе.

1
29.12.2008 11:46:31