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


29

Что происходит во встроенном процессоре, когда выполнение достигает этого последнего returnоператора. Все ли просто замирает, как есть; энергопотребление и т.д., с одним длинным вечным NOP в небе? или постоянно выполняются NOP, или процессор вообще отключится?

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


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

9
Это для ракеты?
Ли Ковальковски

6
некоторые системы поддерживают инструкцию HCF (Halt and Catch Fire) . :)
Стефан Пол Ноак

1
это приведет к самоуничтожению

Ответы:


41

Это вопрос, который мой папа всегда задавал мне. « Почему он не проходит через все инструкции и не останавливается в конце? »

Давайте посмотрим на патологический пример. Следующий код был скомпилирован в компиляторе C18 Microchip для PIC18:

void main(void)
{

}

Он производит следующий вывод ассемблера:

addr    opco     instruction
----    ----     -----------
0000    EF63     GOTO 0xc6
0002    F000     NOP
0004    0012     RETURN 0
.
. some instructions removed for brevity
.
00C6    EE15     LFSR 0x1, 0x500
00C8    F000     NOP
00CA    EE25     LFSR 0x2, 0x500
00CC    F000     NOP
.
. some instructions removed for brevity
.
00D6    EC72     CALL 0xe4, 0            // Call the initialisation code
00D8    F000     NOP                     //  
00DA    EC71     CALL 0xe2, 0            // Here we call main()
00DC    F000     NOP                     // 
00DE    D7FB     BRA 0xd6                // Jump back to address 00D6
.
. some instructions removed for brevity
.

00E2    0012     RETURN 0                // This is main()

00E4    0012     RETURN 0                // This is the initialisation code

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

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

void main(void)
{
    while(1)
    {
        wait_timer();
        do_some_task();
    }    
}

Поэтому я бы никогда не позволил main () выйти.

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

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

addr    opco     instruction
----    ----     -----------
00E6    FFFF     NOP
00E8    FFFF     NOP
00EA    FFFF     NOP
00EB    FFFF     NOP
.
. some instructions removed for brevity
.
7EE8    FFFF     NOP
7FFA    FFFF     NOP
7FFC    FFFF     NOP
7FFE    FFFF     NOP

Следующий адрес памяти после последней инструкции в main () пуст. На микроконтроллере с флэш-памятью пустая инструкция содержит значение 0xFFFF. По крайней мере, на PIC этот код операции интерпретируется как 'nop' или 'no operation'. Это просто ничего не делает. Процессор продолжит выполнение этих nops до конца памяти.

Что после этого?

В последней инструкции указатель команды процессора равен 0x7FFe. Когда ЦП добавляет 2 к своему указателю инструкций, он получает 0x8000, что считается переполнением PIC только с 32k FLASH, и поэтому он возвращается обратно к 0x0000, и ЦП с радостью продолжает выполнять инструкции в начале кода так же, как если бы он был сброшен.


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

Если у вас есть приложение, которое должно было выполнить только одно действие после включения, а затем ничего не делать, вы можете просто добавить время (1); в конце main (), так что процессор перестает делать что-либо заметное.

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

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


20

Для скомпилированного кода это зависит от компилятора. Компилятор Rowley CrossWorks gcc ARM, который я использую, переходит к коду в файле crt0.s с бесконечным циклом. Компилятор Microchip C30 для 16-разрядных устройств dsPIC и PIC24 (также основанный на gcc) сбрасывает процессор.

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


13

Здесь нужно сделать два замечания:

  • Строго говоря, встроенная программа не может «закончить».
  • Очень редко требуется запустить встроенную программу в течение некоторого времени, а затем «завершить».

Концепция выключения программы обычно не существует во встроенной среде. На низком уровне процессор будет выполнять инструкции, пока он может; не существует такого понятия, как «окончательное возвращение». Процессор может остановить выполнение, если он обнаружит неисправимую ошибку или если явно остановлен (переведен в режим ожидания, режим низкого энергопотребления и т. Д.), Но учтите, что даже режимы ожидания или неисправимые ошибки обычно не гарантируют, что больше кода не будет быть казненным. Вы можете выйти из спящих режимов (именно так они обычно используются), и даже заблокированный ЦП все еще может выполнять обработчик NMI (это имеет место в Cortex-M). Сторожевой таймер также будет работать, и вы не сможете отключить его на некоторых микроконтроллерах после его включения. Детали сильно различаются между архитектурами.

В случае прошивки, написанной на языке, таком как C или C ++, что произойдет, если main () завершится, определяется кодом запуска. Например, вот соответствующая часть кода запуска из стандартной периферийной библиотеки STM32 (для цепочки инструментов GNU комментарии мои):

Reset_Handler:  
  /*  ...  */
  bl  main    ; call main(), lr points to next instruction
  bx  lr      ; infinite loop

Этот код войдет в бесконечный цикл, когда main () вернется, хотя и неочевидным способом ( bl mainзагружается lrс адресом следующей инструкции, которая фактически является переходом к самой себе). Не делается никаких попыток остановить процессор или перевести его в режим пониженного энергопотребления и т. Д. Если у вас есть законные потребности в этом в вашем приложении, вам придется сделать это самостоятельно.

Обратите внимание, что, как указано в ARMv7-M ARM A2.3.1, регистр связи при сбросе устанавливается в 0xFFFFFFFF, и переход по этому адресу вызовет ошибку. Поэтому разработчики Cortex-M решили рассматривать возврат из обработчика сброса как ненормальный, и с ними трудно спорить.

Говоря о законной необходимости останавливать процессор после завершения прошивки, трудно представить себе что-либо, что не будет лучше обслуживаться при отключении питания вашего устройства. (Если вы отключите свой процессор «насовсем», единственное, что можно сделать с вашим устройством, - это выключить и снова включить или выключить внешнее оборудование.) Вы можете деактивировать сигнал ENABLE для преобразователя постоянного тока или отключить питание в каким-то другим способом, как это делает ПК ATX.


1
«Вы можете выйти из спящих режимов (именно так они обычно используются), и даже заблокированный ЦП все еще может выполнять обработчик NMI (это относится к Cortex-M).» <- звучит как удивительная часть сюжет книги или фильма. :)
Марк Аллен,

"Bl main" загрузит "lr" с адресом следующей инструкции ("bx lr"), не так ли? Есть ли какая-либо причина ожидать, что «lr» будет содержать что-то еще при выполнении «bx lr»?
суперкат

@supercat: ты прав конечно. Я отредактировал свой ответ, чтобы удалить ошибку и немного ее расширить. Размышляя об этом, то, как они реализуют этот цикл, довольно странно; они могли бы легко сделать loop: b loop. Интересно, они действительно хотели вернуть, но забыли сохранить lr?
Торн

Это любопытно. Я ожидал бы, что большая часть кода ARM будет завершена с LR, имеющим то же значение, что и при входе, но я не знаю, гарантировано ли это. Такая гарантия часто бывает бесполезной, но для ее соблюдения потребуется добавить инструкцию к подпрограммам, которые копируют r14 в какой-либо другой регистр, а затем вызывают какую-то другую подпрограмму. Если по возвращении lr считается «неизвестным», можно «bx» регистр, содержащий сохраненную копию. Это вызвало бы очень странное поведение с указанным кодом, хотя.
суперкат

На самом деле я уверен, что неконечные функции, как ожидается, спасут lr. Обычно они помещают lr в стек в прологе и возвращают, сохраняя сохраненное значение в pc. Это то, что, например, сделал бы main () C или C ++, но разработчики рассматриваемой библиотеки явно не делали ничего подобного в Reset_Handler.
Торн

9

Когда спрашиваешь return, ты думаешь о слишком высоком уровне. Код C переводится в машинный код. Таким образом, если вы вместо этого думаете о том, как процессор вслепую извлекает инструкции из памяти и выполняет их, он не знает, какая из них является «последней» return. Таким образом, процессоры не имеют собственного конца, но вместо этого дело программиста обрабатывать конечный случай. Как указывает Леон в своем ответе, компиляторы запрограммировали поведение по умолчанию, но часто программисту может потребоваться собственная последовательность выключения (я делал разные вещи, например, переход в режим низкого энергопотребления и останов или ожидание подключения кабеля USB. в и затем перезагрузка).

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


7

Проблема не встроенная (встроенная система может работать под управлением Linux или даже Windows), а автономная или «голая железо»: (скомпилированная) прикладная программа - единственное, что запускается на компьютере (не имеет значения, является ли она микроконтроллер или микропроцессор).

Для большинства языков язык не определяет, что происходит, когда завершается «main» и нет ОС, к которой можно вернуться. Для C это зависит от того, что находится в файле запуска (часто crt0.s). В большинстве случаев пользователь может (или даже должен) предоставить код запуска, поэтому окончательный ответ таков: что вы пишете, это код запуска или то, что происходит в указанном вами коде запуска.

На практике существует 3 подхода:

  • не принимать никаких специальных мер. что происходит, когда основной возврат не определен.

  • перейти к 0 или использовать любые другие средства для перезапуска приложения.

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

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


5

На днях я смотрел на какой-то дизассемблированный код ATtiny45 (C ++, скомпилированный avr-gcc), и в конце кода он переходит к 0x0000. В основном делаю сброс / перезапуск.

Если этот последний переход к 0x0000 пропущен компилятором / ассемблером, все байты в памяти программы интерпретируются как «допустимый» машинный код, и он выполняется до тех пор, пока счетчик программ не переходит на 0x0000.

На AVR байт 00 (значение по умолчанию, когда ячейка пуста) - это NOP = Нет операции. Так что он работает очень быстро, ничего не делая, а просто занимает некоторое время.


1

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

Затем компоновщик помещает весь код приложения и запуска в сегменты памяти, поэтому ответы на ваши вопросы зависят от: 1. кода при запуске, поскольку он может, например:

  • конец с пустым циклом ( bl lrили b .), который будет аналогичен «концу программы», но ранее включенные прерывания и периферийные устройства будут работать,
  • Завершите переходом к началу программы (либо полностью перезапустите запуск, либо просто нажмите main).
  • просто игнорируйте «что будет дальше» после вызова mainreturn.

    1. В третьем пункте, когда счетчик программы просто увеличивается после возврата из mainповедения, будет зависеть от вашего компоновщика (и / или сценария компоновщика, используемого при компоновке).
  • Если другая функция / код ставится после вашей, mainона будет выполняться с недопустимыми / неопределенными значениями аргументов,

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

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


-1

Лучший способ остановить встроенное устройство - это ждать вечно с инструкциями NOP.

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


Это действительно не отвечает на вопрос.
Мэтт Янг

-4

Это было четко объяснено в руководстве. Обычно общее исключение выдается процессором, потому что оно будет обращаться к области памяти, которая находится за пределами сегмента стека. исключение защиты памяти.

Что вы имели в виду под встроенной системой? Микропроцессор или микроконтроллер? В любом случае, это определено в руководстве.

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

Прочитайте спецификацию ACPI для получения дополнительной информации.


3
-1: TS не упомянул какой-либо конкретный процессор, поэтому не принимайте слишком много. Различные системы обрабатывают этот случай по-разному.
Wouter van Ooijen
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.