2008-12-15 9 views
6

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

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

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

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

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

ответ

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

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

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

5. Вы используете большое количество DLL-файлов?

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

Далее спрыгнув точки:

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

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

Окно управления Target 'ми' инструмент

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

0

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

Мне что-то не пахнет прямо здесь. Hmmm ...

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

1

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

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

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

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

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

Это должно дать вам большую пропускную способность и быть немного более ЭФФЕКТИВНАЯ

1

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

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

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

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

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

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

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

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

+0

Проблема в том, что около 99% всего кода не имеют понятия, как грамотно обрабатывать неудачные выделения памяти. Приложения терпят неудачу таинственными способами. Никакая свободная память не приводит к остановке практически всех операционных систем. Печально, но верно. – Thorsten79 2008-12-29 09:52:09

2

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

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

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

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

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

1

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

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

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

1

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

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

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

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

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

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

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

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