Что происходит, когда у микроконтроллеров заканчивается оперативная память?

Это может быть просто совпадением, но я заметил, что микроконтроллеры, которые я использовал, перезагружались, когда у них заканчивалась оперативная память (Atmega 328, если это зависит от оборудования). Это то, что делают микроконтроллеры, когда им не хватает памяти? Если нет, то что тогда происходит?

Почему как? Указатель стека конечно вслепую увеличивается до нераспределенного диапазона памяти (или перекатывается), но что происходит потом: есть ли какая-то защита, которая заставляет его перезагрузиться или это (помимо прочих эффектов) результат перезаписи критических данные (которые, как я предполагаю, отличаются от кода, который, как мне кажется, запускается непосредственно из флэш-памяти)?

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

Обновлять

Я должен указать, что меня особенно интересует фактический механизм повреждения памяти (является ли это результатом переноса SP -> зависит ли это от отображения памяти UC и т. д.)?

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

Ответы (4)

В общем, стек и куча врезаются друг в друга. В этот момент все становится беспорядочным.

В зависимости от MCU может (или произойдет) одна из нескольких вещей.

  1. Переменные повреждаются
  2. Стек поврежден
  3. Программа повреждена

Когда происходит 1, вы начинаете вести себя странно — вещи не делают то, что должны. Когда происходит 2, начинается ад. Если адрес возврата в стеке (если он есть) поврежден, то остается только гадать, куда вернется текущий вызов. В это время в основном MCU начнет делать случайные вещи. Когда 3 произойдет снова, кто знает, что произойдет. Это происходит только тогда, когда вы выполняете код вне оперативной памяти.

В общем, когда стек поврежден, все кончено. То, что происходит, зависит от MCU.

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

Если выделение памяти происходит, или если оно пытается, терпит неудачу и просто продолжается без выделенной памяти, то вы находитесь в сфере «кто знает?». MCU может в конечном итоге перезагрузиться из-за правильной комбинации событий (вызванные прерываниями, которые в конечном итоге приводят к перезагрузке чипа и т. д.), но нет никакой гарантии, что это произойдет.

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

Спасибо за ваш ответ, это отличное резюме эффектов. Возможно, мне следовало указать, что я хотел бы получить более подробную информацию о фактическом механизме этих повреждений: вся оперативная память выделена для стека и кучи, так что указатель стека переворачивается и перезаписывает более ранние переменные/адреса? Или это меньше зависит от отображения памяти каждого микро? При желании (это, вероятно, отдельная тема), мне было бы интересно узнать, как реализованы эти аппаратные обработчики.
Это в основном зависит от компилятора и используемой стандартной библиотеки C. Это также иногда зависит от того, как настроен компилятор (скрипты компоновщика и т. д.).
Не могли бы вы расширить это, возможно, с парой примеров?
Не на самом деле нет. Некоторые системы выделяют конечное пространство для разных сегментов, некоторые — нет. Некоторые используют скрипты компоновщика для определения сегментов, некоторые нет. Выберите интересующий вас микроконтроллер и изучите, как работает его распределение памяти.

Альтернативный взгляд: у микроконтроллеров не заканчивается память.

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

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

Общие правила программирования для микроконтроллеров избегают этого: например, вся память либо выделяется в стеке, либо распределяется статически (глобально); "new" или "malloc" запрещены. Так же как и рекурсия, так что можно проанализировать максимальную глубину вложенности подпрограмм и показать, что она помещается в доступный стек.

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

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

  • переписать его, уменьшить или
  • выберите больший процессор (они часто доступны с разными объемами памяти).

Одним из распространенных наборов правил для программирования микроконтроллеров является MISRA-C , принятый в автомобильной промышленности.

На мой взгляд, лучшей практикой является использование подмножества Ada SPARK-2014 . Ada на самом деле достаточно хорошо ориентируется на небольшие контроллеры, такие как AVR, MSP430 и ARM Cortex, и по своей сути обеспечивает лучшую модель для программирования микроконтроллеров, чем C. Но SPARK добавляет в программу аннотации в виде комментариев, которые описывают, что делает программа.

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

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

Сравнение МИСРА-С и СПАРК

+1 это. Портирование malloc()(и его компаньон C++ new) на AVR — одна из худших вещей, которые могли бы сделать люди, работающие на arduino, и это привело к тому, что многие очень запутанные программисты с неработающим кодом как на их форуме, так и в обмене стеками arduino. Есть очень, очень мало ситуаций, когда mallocиспользование ATmega выгодно.
+1 за философию, -1 за реализм. Если бы все было запрограммировано должным образом, в этом вопросе не было бы необходимости. Вопрос заключался в том, что происходит, когда у микроконтроллеров заканчивается память. Как предотвратить их исчерпание памяти, это другой вопрос. С другой стороны, рекурсия — это мощный инструмент как для решения проблем, так и для исчерпания стека.
@PkP: я предварительно не соглашусь с -1 для реализма. Если встроенный кодировщик может думать "как я могу предотвратить...", а не "что происходит, когда...", то мы делаем успехи. Рекурсию, например, часто можно линеаризовать, на контроллере не так много задач, где необходима настоящая рекурсия. Теперь мы могли бы просто смириться с поражением и опасным программированием (и я согласен, что вам иногда приходится это делать: мы должны тщательно выбирать битвы), но давайте не будем делать это по умолчанию.
@ Брайан, поскольку я не идиот, я, очевидно, согласен с тобой. Мне просто нравится думать об этом с обратной точки зрения - мне нравится надеяться, что когда вы осознаете ужасные последствия исчерпания памяти (стека), вы будете искать способы предотвратить это. Таким образом, у вас есть реальный стимул к поиску хороших методов программирования, а не просто следованию хорошим советам по программированию... и когда вы столкнетесь с барьером памяти, вы, скорее всего, будете применять хорошие методы даже за счет удобства. Это просто точка зрения...
@PkP: слышу тебя громко и ясно. Я назвал это альтернативной точкой зрения, потому что на самом деле она не отвечает на вопрос!
Таким образом, у микроконтроллеров действительно заканчивается память. Я знаю, что они не должны этого делать, и я делаю все, чтобы избежать этого (начиная с mallocs(), я использую их с максимальным значением длины, чтобы сэкономить немного оперативной памяти, но наихудший сценарий такой же, как статически выделенный, поэтому мне интересно когда это на самом деле предпочтительнее), мне просто любопытно, и это тоже полезное знание для отладки в будущем. Я предпочитаю язык C (не поклонник Ады), знаете ли вы какие-нибудь бесплатные, более простые инструменты статического анализа кода (похоже, MISRA является частной собственностью)? На самом деле мне интересно: не зависит ли платформа/компоновщик для оценки ОЗУ?
Спасибо за ваш ответ, хотя, это должно было быть моим первым предложением!
"не фанат Ады" Я часто слышу это, но редко от кого-то с недавним опытом (2005 или 2012, не Ада-83!). Почему в вашем случае? Извините, я не эксперт по инструментам C, хотя слышал положительные отзывы о FRAMA-C.
Я не утверждаю, что у меня есть какие-то конкретные аргументы против этого, мне просто не нравится синтаксис и стиль кодирования, как у Паскаля. Я по-прежнему открыт для новых альтернатив, но на данный момент комбинация C, C++, VHDL и Python позволяет мне комфортно делать практически все, что я хочу.
Интересный. Одним из преимуществ Ады для меня является сходство с VHDL, что упрощает переключение между программным обеспечением и аппаратным обеспечением. (Конечно, я бы хотел, чтобы VHDL не был так ограничен после использования Ada некоторое время) Я все еще иногда использую C, но только если клиент достаточно платит.
@MisterMystère: у микроконтроллеров обычно не хватает памяти. Микроконтроллер, который имеет 4096 байт ОЗУ при первом включении, будет иметь 4096 байт навсегда. Возможно, что код может ошибочно попытаться получить доступ к несуществующим адресам или ожидать, что два разных метода вычисления адресов будут обращаться к разной памяти, когда это не так, но сам контроллер просто выполнит данные ему инструкции.

Мне очень нравится ответ Маженко, и я сам +1. Но я хочу уточнить это до острого момента:

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

Вы действительно не можете положиться ни на что, когда это происходит. Когда на машине заканчивается память стека, стек, скорее всего, повреждается. И когда это произойдет, все может случиться. Значения переменных, разливы, временные регистры — все повреждается, нарушая выполнение программы. If/then/elses может вычислить неправильно. Адреса возврата искажаются, заставляя программу переходить на случайные адреса. Любой код, который вы написали в программе, может выполняться. (Рассмотрите такой код: «если [условие], то {fire_all_missiles();}»). Кроме того, целая куча инструкций, которые вы не написали, может выполняться, когда ядро ​​переходит в неподключенную область памяти. Все ставки сделаны.

Спасибо за дополнение, мне особенно понравилась строка fire_all_missiles().

AVR сбросил вектор по нулевому адресу. Когда вы перезаписываете стек случайным мусором, вы в конечном итоге зацикливаетесь и перезаписываете некоторый адрес возврата, и он будет указывать «никуда»; затем, когда вы вернетесь из подпрограммы в это никуда, выполнение зациклится на адресе 0, где обычно находится обработчик перехода к сбросу.