O que acontece quando um programa incorporado termina?

29

O que acontece em um processador incorporado quando a execução atinge essa returnafirmação final Tudo fica congelado como está; consumo de energia etc, com um NOP eterno longo no céu? ou os NOPs são executados continuamente ou o processador será desligado completamente?

Parte do motivo pelo qual pergunto é: estou me perguntando se um processador precisa desligar antes de concluir a execução e se o faz, como termina a execução se já desligou antes?

Toby
fonte
22
Depende de suas crenças. Alguns dizem que isso reencarnará.
Telaclavo 28/04/12
9
É para um míssil?
Lee Kowalkowski
6
alguns sistemas suportam a instrução HCF (Halt and Catch Fire) . :)
Stefan Paul Noack
1
ele irá desviar para rotina de destruição auto

Respostas:

41

Essa é uma pergunta que meu pai sempre me perguntava. " Por que simplesmente não percorre todas as instruções e pára no final? "

Vamos dar uma olhada em um exemplo patológico. O código a seguir foi compilado no compilador C18 da Microchip para o PIC18:

void main(void)
{

}

Produz a seguinte saída do assembler:

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

Como você pode ver, main () é chamado e, no final, contém uma declaração de retorno, embora não tenhamos explicitamente colocado lá. Quando main retorna, a CPU executa a próxima instrução, que é simplesmente um GOTO para voltar ao início do código. main () é simplesmente chamado repetidamente.

Agora, tendo dito isso, não é assim que as pessoas costumam fazer as coisas. Eu nunca escrevi nenhum código incorporado que permita que o main () saia assim. Principalmente, meu código seria algo como isto:

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

Então, eu normalmente nunca deixaria main () sair.

"OK ok" você está dizendo. Tudo isso é muito interessante que o compilador garante que nunca haja uma última declaração de retorno. Mas o que acontece se forçarmos o problema? E se eu codificasse manualmente meu montador e não voltasse ao começo?

Bem, obviamente, a CPU continuaria executando as próximas instruções. Aqueles seriam algo como isto:

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

O próximo endereço de memória após a última instrução em main () está vazio. Em um microcontrolador com memória FLASH, uma instrução vazia contém o valor 0xFFFF. Em um PIC, pelo menos, esse código operacional é interpretado como um 'nop' ou 'nenhuma operação'. Simplesmente não faz nada. A CPU continuaria executando esses nops por toda a memória até o fim.

O que é depois disso?

Na última instrução, o ponteiro de instrução da CPU é 0x7FFe. Quando a CPU adiciona 2 ao ponteiro de instruções, obtém 0x8000, que é considerado um estouro em uma PIC com apenas 32k FLASH, e volta para 0x0000, e a CPU continua felizmente executando as instruções no início do código , como se tivesse sido redefinido.


Você também perguntou sobre a necessidade de desligar. Basicamente, você pode fazer o que quiser, e isso depende da sua aplicação.

Se você tinha um aplicativo que só precisava fazer uma coisa depois de ligar, e não fazer mais nada, você poderia esperar um pouco (1); no final de main () para que a CPU pare de fazer qualquer coisa perceptível.

Se o aplicativo exigir que a CPU seja desligada, dependendo da CPU, provavelmente haverá vários modos de suspensão disponíveis. No entanto, as CPUs têm o hábito de acordar novamente, portanto, você deve garantir que não haja limite de tempo para dormir e que o Watch Dog Timer esteja ativo etc.

Você pode até organizar alguns circuitos externos que permitiriam à CPU cortar completamente sua própria energia quando ela terminasse. Veja esta pergunta: Usando um botão momentâneo como uma chave seletora liga / desliga .

Rocketmagnet
fonte
20

Para código compilado, isso depende do compilador. O compilador Rowley CrossWorks gcc ARM que eu uso salta para codificar no arquivo crt0.s que possui um loop infinito. O compilador Microchip C30 para os dispositivos dsPIC e PIC24 de 16 bits (também baseados em gcc) redefine o processador.

Obviamente, a maioria dos softwares incorporados nunca termina assim e executa o código continuamente em um loop.

Leon Heller
fonte
13

Há dois pontos a serem feitos aqui:

  • Um programa incorporado, estritamente falando, não pode "terminar".
  • Raramente é necessário executar um programa incorporado por algum tempo e depois "concluir".

O conceito de desligamento de um programa normalmente não existe em um ambiente incorporado. Em um nível baixo, uma CPU executa instruções enquanto pode; não existe uma "declaração final de retorno". Uma CPU pode interromper a execução se encontrar uma falha irrecuperável ou se for interrompida explicitamente (coloque no modo de suspensão, modo de baixa energia etc.), mas observe que mesmo os modos de suspensão ou falhas irrecuperáveis ​​geralmente não garantem que não haja mais código. ser executado. Você pode ativar a partir dos modos de suspensão (é assim que normalmente são usados) e até mesmo uma CPU bloqueada ainda pode executar um manipulador de NMI (este é o caso do Cortex-M). Um watchdog ainda será executado também, e talvez você não consiga desabilitá-lo em alguns microcontroladores depois de habilitado. Os detalhes variam muito entre as arquiteturas.

No caso de firmware escrito em um idioma como C ou C ++, o que acontece se a saída main () for determinada pelo código de inicialização. Por exemplo, aqui está a parte relevante do código de inicialização da STM32 Standard Peripheral Library (para uma cadeia de ferramentas GNU, os comentários são meus):

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

Este código entrará em um loop infinito quando main () retornar, embora de uma maneira não óbvia ( bl maincarregue lrcom o endereço da próxima instrução que é efetivamente um salto para si mesmo). Não são feitas tentativas para interromper a CPU ou fazê-la entrar no modo de baixo consumo de energia, etc. Se você tiver uma necessidade legítima de algo disso em seu aplicativo, precisará fazer isso sozinho.

Observe que, conforme especificado no ARMv7-M ARM A2.3.1, o registro do link é definido como 0xFFFFFFFF na redefinição e uma ramificação para esse endereço acionará uma falha. Portanto, os designers do Cortex-M decidiram tratar um retorno do manipulador de redefinição como anormal, e é difícil argumentar com eles.

Falando de uma necessidade legítima de interromper a CPU após a conclusão do firmware, é difícil imaginar um que não seria melhor atendido pelo desligamento do seu dispositivo. (Se você desativar a CPU "para sempre", a única coisa que pode ser feita ao seu dispositivo é um ciclo de energia ou redefinição de hardware externo.) Você pode desativar o sinal ENABLE para o seu conversor DC / DC ou desligar a fonte de alimentação. de alguma outra maneira, como um PC ATX faz.

Espinho
fonte
1
"Você pode ativar a partir dos modos de suspensão (é como eles costumam ser usados), e até mesmo uma CPU bloqueada ainda pode executar um manipulador de NMI (esse é o caso do Cortex-M)." <- soa como a parte impressionante de um enredo de livro ou filme. :)
Mark Allen
O "bl main" carregará "lr" com o endereço da instrução a seguir (o "bx lr"), não será? Existe algum motivo para esperar que "lr" contenha mais alguma coisa quando o "bx lr" for executado?
Supercat
@ supercat: você está certo, é claro. Editei minha resposta para remover o erro e expandi-lo um pouco. Pensando nisso, a maneira como eles implementam esse loop é bem estranha; eles poderiam ter feito facilmente loop: b loop. Gostaria de saber se eles realmente pretendiam retornar, mas se esqueceram de economizar lr.
Thorn
Isso é curioso. Eu esperaria que muito código ARM fosse encerrado com LR mantendo o mesmo valor que mantinha na entrada, mas não sei se é garantido. Essa garantia geralmente não seria útil, mas sua manutenção exigiria a adição de uma instrução às rotinas que copiam r14 para algum outro registro e depois chamam outra rotina. Se lr for considerado "desconhecido" no retorno, pode-se "bx" o registro que contém a cópia salva. Isso causaria um comportamento muito estranho com o código indicado.
Supercat 29/12
Actually I'm pretty sure that non-leaf functions are expected to save lr. These usually push lr onto the stack in the prolog and return by popping the saved value into pc. This is what e.g. a C or C++ main() would do, but the developers of the library in question obviously didn't do anything like this in Reset_Handler.
Thorn
9

When asking about return, you're thinking too high level. The C code is translated into machine code. So, if you instead think about the processor blindly pulling instructions out of memory and executing them, it has no idea which one is the "final" return. So, processors have no inherent end, but instead it's up to the programmer to handle the end case. As Leon points out in his answer, compilers have programmed a default behavior, but often times the programmer may want their own shutdown sequence (I have done various things like entering a low power mode and halting, or waiting for a USB cable to get plugged in and then rebooting).

Many microprocessors have halt instructions, which stops the processor without affecting perhiperals. Other processors may rely on "halting" by simply just jumping to the same address repeatedly. There are may options, but it's up to the programmer because the processor will simply keep reading instructions from meory, even if that memory wasn't intented to be instructions.

Klox
fonte
7

The issue is not embedded (an embedded system can run Linux or even Windows) but stand-alone or bare-metal: the (compiled) application program is the only thing that is running on the computer (It does not matter if it is a microcontroller or microprocessor).

For most languages the language does not define what happens when the 'main' terminates and there is no OS to return to. For C it depends on what is in the startupfile (often crt0.s). In most cases the user can (or even must) supply the startup code, so the final answer is: whatever you write is the startup code, or what happens to be in the startup code you specify.

In practice there are 3 approaches:

  • take no special measures. what happens when the main returns is undefined.

  • jump to 0, or use any other means to restart the application.

  • enter a tight loop (or disabling interrupts and executing a halt instruction), locking up the processor forever.

What is appropriate depends on the application. A fur-elise greeting card and a brake-control-system (just to mention two embedded systems) should probably restart. The downside of restarting is that the problem might go unnoticed.

Wouter van Ooijen
fonte
5

I was looking at some ATtiny45 disassembled (C++ compiled by avr-gcc) code the other day and what it does at the end of the code is jump to 0x0000. Basically doing a reset/restart.

If that last jump to 0x0000 is being left out by the compiler/assembler, all bytes in program memory are interpreted as 'valid' machine code and it runs all the way until the program counter rolls over to 0x0000.

On AVR a 00 byte (de default value when a cell is empty) is a NOP = No Operation. So it runs really quickly, doing nothing but just taking some time.

jippie
fonte
1

Generally compiled main code is afterwards linked with startup code (it might be integrated into toolchain, provided by chip vendor, written by you etc.).

Linker then places all application and startup code in memory segments, so answer to you questions depends on: 1. code from startup, because it can for example:

  • end with empty loop (bl lr or b .), which will be similar to "program end", but interrupts and peripherals enabled previously will still operate,
  • end with jump to beginning of program (either completely re-run startup or jsut to main).
  • simply ignore "what will be next" after call to main returns.

    1. In the third bullet, when program counter simply increments after returning from main behavior will depend on you linker (and/or linker script used during linking).
  • If other function/code is placed after your main it will be executed with invalid/undefined argument values,

  • If following memory starts with bad instruction exception migh be generated and MCU will eventually reset (if exception generates reset).

If watchdog is enabled, it will eventually reset MCU despite all endless loops you are in (of course if it will be not reloaded).

kwesolowski
fonte
-1

The best way to stop an embedded device is to wait forever with NOP instructions.

The second way is to closing the device by using device itself. If you can control a relay with your instructions, you can just open the switch which is powering your embedded device and huh your embedded device is gone with no power consumption.

Enes Unal
fonte
That really doesn't answer the question.
Matt Young
-4

It was clearly explained in the manual. Typically an general exception will be thrown by the CPU because it will access a memory location which is outside the stack segment. [ memory protection exception ].

What did you meant by the embedded system? Microprocessor or microcontroller ?Either ways , it's defined on the manual.

In x86 CPU we turn off the computer by sending the command to the ACIP controller. Entering the System Management Mode. So that controller is a I/O chip and you don't need to manually turn it off.

Read the ACPI specification for more information.

Standard Sandun
fonte
3
-1 : the TS did not mention any specific CPU, so don't assume too much. Different systems handle this case in very different ways.
Wouter van Ooijen