Это может быть просто совпадением, но я заметил, что микроконтроллеры, которые я использовал, перезагружались, когда у них заканчивалась оперативная память (Atmega 328, если это зависит от оборудования). Это то, что делают микроконтроллеры, когда им не хватает памяти? Если нет, то что тогда происходит?
Почему как? Указатель стека конечно вслепую увеличивается до нераспределенного диапазона памяти (или перекатывается), но что происходит потом: есть ли какая-то защита, которая заставляет его перезагрузиться или это (помимо прочих эффектов) результат перезаписи критических данные (которые, как я предполагаю, отличаются от кода, который, как мне кажется, запускается непосредственно из флэш-памяти)?
Я не уверен, что это должно быть здесь или в Stack Overflow, пожалуйста, дайте мне знать, если это следует переместить, хотя я почти уверен, что аппаратное обеспечение играет в этом роль.
Я должен указать, что меня особенно интересует фактический механизм повреждения памяти (является ли это результатом переноса SP -> зависит ли это от отображения памяти UC и т. д.)?
В общем, стек и куча врезаются друг в друга. В этот момент все становится беспорядочным.
В зависимости от MCU может (или произойдет) одна из нескольких вещей.
Когда происходит 1, вы начинаете вести себя странно — вещи не делают то, что должны. Когда происходит 2, начинается ад. Если адрес возврата в стеке (если он есть) поврежден, то остается только гадать, куда вернется текущий вызов. В это время в основном MCU начнет делать случайные вещи. Когда 3 произойдет снова, кто знает, что произойдет. Это происходит только тогда, когда вы выполняете код вне оперативной памяти.
В общем, когда стек поврежден, все кончено. То, что происходит, зависит от MCU.
Возможно, попытка выделить память в первую очередь не удалась, поэтому повреждения не произошло. В этом случае MCU может вызвать исключение. Если обработчик исключений не установлен, то чаще всего MCU просто останавливается (эквивалент while (1);
. Если обработчик установлен, то он может перезагрузиться без ошибок.
Если выделение памяти происходит, или если оно пытается, терпит неудачу и просто продолжается без выделенной памяти, то вы находитесь в сфере «кто знает?». MCU может в конечном итоге перезагрузиться из-за правильной комбинации событий (вызванные прерываниями, которые в конечном итоге приводят к перезагрузке чипа и т. д.), но нет никакой гарантии, что это произойдет.
Однако, как правило, с высокой вероятностью, если он включен, внутренний сторожевой таймер (если он существует) отключается и перезагружает чип. Когда программа полностью уходит в самоволку из-за такого рода сбоя, инструкции по сбросу таймера, как правило, не запускаются, поэтому он истечет время ожидания и сбрасывается.
Альтернативный взгляд: у микроконтроллеров не заканчивается память.
По крайней мере, при правильном программировании. Программирование микроконтроллера не совсем похоже на программирование общего назначения, чтобы сделать это правильно, вы должны знать о его ограничениях и программировать соответственно. Есть инструменты, помогающие это обеспечить. Ищите их и изучайте — по крайней мере, как читать скрипты компоновщика и предупреждения.
Однако, как говорят Маженко и другие, плохо запрограммированный микроконтроллер может исчерпать память, а затем сделать что угодно, включая бесконечный цикл (что, по крайней мере, дает сторожевому таймеру возможность сбросить его. Вы включили сторожевой таймер, не так ли? )
Общие правила программирования для микроконтроллеров избегают этого: например, вся память либо выделяется в стеке, либо распределяется статически (глобально); "new" или "malloc" запрещены. Так же как и рекурсия, так что можно проанализировать максимальную глубину вложенности подпрограмм и показать, что она помещается в доступный стек.
Таким образом, максимальный требуемый объем памяти можно рассчитать при компиляции или компоновке программы и сравнить с объемом памяти (часто закодированным в скрипте компоновщика) для конкретного процессора, на который вы ориентируетесь.
Тогда у микроконтроллера может не хватить памяти, а у вашей программы может. И в этом случае вы получаете
Одним из распространенных наборов правил для программирования микроконтроллеров является MISRA-C , принятый в автомобильной промышленности.
На мой взгляд, лучшей практикой является использование подмножества Ada SPARK-2014 . Ada на самом деле достаточно хорошо ориентируется на небольшие контроллеры, такие как AVR, MSP430 и ARM Cortex, и по своей сути обеспечивает лучшую модель для программирования микроконтроллеров, чем C. Но SPARK добавляет в программу аннотации в виде комментариев, которые описывают, что делает программа.
Теперь инструменты SPARK будут анализировать программу, включая эти аннотации, и подтверждать ее свойства (или сообщать о потенциальных ошибках). Вам не нужно тратить время или пространство кода на ошибочные обращения к памяти или целочисленные переполнения, потому что доказано, что они никогда не случаются.
Несмотря на то, что со SPARK связано больше предварительной работы, опыт показывает, что с его помощью можно получить продукт быстрее и дешевле, потому что вы не тратите время на погоню за таинственными перезагрузками и другими странными действиями.
malloc()
(и его компаньон C++ new
) на AVR — одна из худших вещей, которые могли бы сделать люди, работающие на arduino, и это привело к тому, что многие очень запутанные программисты с неработающим кодом как на их форуме, так и в обмене стеками arduino. Есть очень, очень мало ситуаций, когда malloc
использование ATmega выгодно.Мне очень нравится ответ Маженко, и я сам +1. Но я хочу уточнить это до острого момента:
Все может случиться, когда микроконтроллеру не хватает памяти.
Вы действительно не можете положиться ни на что, когда это происходит. Когда на машине заканчивается память стека, стек, скорее всего, повреждается. И когда это произойдет, все может случиться. Значения переменных, разливы, временные регистры — все повреждается, нарушая выполнение программы. If/then/elses может вычислить неправильно. Адреса возврата искажаются, заставляя программу переходить на случайные адреса. Любой код, который вы написали в программе, может выполняться. (Рассмотрите такой код: «если [условие], то {fire_all_missiles();}»). Кроме того, целая куча инструкций, которые вы не написали, может выполняться, когда ядро переходит в неподключенную область памяти. Все ставки сделаны.
AVR сбросил вектор по нулевому адресу. Когда вы перезаписываете стек случайным мусором, вы в конечном итоге зацикливаетесь и перезаписываете некоторый адрес возврата, и он будет указывать «никуда»; затем, когда вы вернетесь из подпрограммы в это никуда, выполнение зациклится на адресе 0, где обычно находится обработчик перехода к сбросу.
Спехро Пефхани
пользователь 253751