Почему у нас не должно быть динамически выделенной памяти с разным размером во встроенной системе
Я слышал, что во встроенной системе мы должны использовать некоторые предварительно выделенные фрагменты памяти фиксированного размера (например, систему памяти друзей?). Может ли кто-нибудь дать мне подробное объяснение, почему? Спасибо,
3 ответа
Во встроенных системах у вас очень ограниченная память. Поэтому, если вы иногда теряете только один байт памяти (потому что вы выделяете его, но не освобождаете его), это довольно быстро израсходует системную память (1 ГБ ОЗУ со скоростью утечки 1/ час займет ее время. Если у вас 4кб ОЗУ, не так долго)
По сути, поведение по избеганию динамической памяти состоит в том, чтобы избежать влияния ошибок в вашей программе. Поскольку статическое выделение памяти является полностью детерминированным (в то время как динамическое выделение памяти - нет), используя только статическое выделение памяти, можно противодействовать таким ошибкам. Одним из важных факторов для этого является то, что встроенные системы часто используются в критически важных приложениях. Несколько часов простоя могут стоить миллионы или может произойти авария.
Кроме того, в зависимости от распределителя динамической памяти, неопределенность также может занимать неопределенное количество времени, что может привести к большему количеству ошибок, особенно в системах, полагающихся на сжатые сроки (спасибо Clifford за упоминание этого). Этот тип ошибки часто трудно протестировать и воспроизвести, потому что он основан на очень специфическом пути выполнения.
Кроме того, встроенные системы обычно не имеют MMU, поэтому нет ничего лучше защиты памяти. Если вам не хватает памяти и ваш код для обработки этого условия не работает, вы можете в конечном итоге выполнить любую память как инструкцию (могут случиться плохие вещи! Однако этот случай только косвенно связан с динамическим выделением памяти).
Как упоминал Hao Shen, фрагментация также представляет опасность. Может ли это произойти, зависит от вашего конкретного варианта использования, но во встроенных системах довольно легко потерять 50% ОЗУ из-за фрагментации. Вы можете избежать фрагментации, только если выделите фрагменты, которые всегда имеют одинаковый размер.
Производительность также играет роль (зависит от варианта использования - спасибо Hao Shen). Статически распределенная память выделяется компилятором, тогда как malloc()
и аналогичные должны работать на устройстве и, следовательно, потреблять процессорное время (и мощность).
Многие встроенные ОС (например, ChibiOS) поддерживают какой-то динамический распределитель памяти. Но его использование только увеличивает вероятность возникновения непредвиденных проблем.
Обратите внимание, что эти аргументы часто можно обойти, используя меньшие статически распределенные пулы памяти. Это нереальное решение, так как в этих пулах все еще может не хватать памяти, но оно затронет только небольшую часть системы.
Как отмечает Stefano Sanfilippo, в некоторых системах даже не хватает ресурсов для поддержки динамического распределения памяти.
Примечание. Большинство стандартов кодирования, включая стандарт кодирования JPL и DO-178B (для критического кода авионики - спасибо [Stephano Sanfilippo]) ( Stefano Sanfilippo), запрещают использование malloc.
Я также предполагаю, что стандарт MISRA C запрещает malloc()
из-за этого сообщения на форуме - однако у меня нет доступа к самому стандарту.
Основные причины не использовать динамическое выделение динамической памяти здесь в основном:
а) детерминизм и, коррелированные, б) фрагментация памяти.
Утечки памяти, как правило, не являются проблемой в этих небольших встроенных приложениях, поскольку они будут обнаружены очень рано при разработке / тестировании.
Фрагментация памяти, однако, может стать недетерминированной, вызывая (в лучшем случае) ошибки нехватки памяти в случайные моменты времени и указывает на приложение в поле.
Также может быть нетривиальным предсказать фактическое максимальное использование памяти приложением во время разработки с динамическим выделением, тогда как объем статически выделенной памяти известен во время компиляции, и абсолютно тривиально проверить, может ли эта память быть предоставлена аппаратное обеспечение или нет.
Выделение памяти из пула блоков фиксированного размера имеет несколько преимуществ по сравнению с динамическим распределением памяти. Это предотвращает фрагментацию кучи и является более детерминированным.
При динамическом распределении памяти фрагменты памяти динамического размера выделяются из кучи фиксированного размера. Распределения не обязательно освобождаются в том же порядке, в котором они размещены. Со временем это может привести к ситуации, когда свободные части кучи распределяются между выделенными частями кучи. По мере того, как происходит такая фрагментация, выполнение запросов на выделение большего объема памяти становится более трудным. Если сделан запрос на выделение большого объема памяти, и в куче нет непрерывного свободного раздела, достаточно большого, то выделение будет неудачным. Кучи может иметь достаточно общей свободной памяти, но если она все фрагментирована и нет смежного раздела, выделение будет неудачным. Возможность сбоя malloc() из-за фрагментации кучи нежелательна во встроенных системах.
Одним из способов борьбы с фрагментацией является объединение меньших выделений памяти в более крупные непрерывные секции по мере их освобождения. Это может быть сделано различными способами, но все они требуют времени и могут сделать систему менее детерминированной. Например, если диспетчер памяти сканирует кучу, когда освобождается выделение памяти, то количество времени, которое требуется для выполнения free(), может варьироваться в зависимости от того, какие типы памяти соседствуют с выделяемым выделением. Это недетерминировано и нежелательно во многих встроенных системах.
Выделение из пула фрагментов фиксированного размера не вызывает фрагментации. До тех пор, пока существует несколько свободных чанков, выделение не завершится неудачей, потому что каждый чанк имеет правильный размер. Кроме того, выделение и освобождение от пула фрагментов фиксированного размера проще. Таким образом, функции allocate и free могут быть записаны как детерминированные.