O que acontece quando os microcontroladores ficam sem memória RAM?

12

Pode ser apenas uma coincidência, mas notei que os microcontroladores que usei foram reiniciados quando a RAM ficou sem memória (o Atmega 328, se específico do hardware). É isso que os microcontroladores fazem quando ficam sem memória? Se não, o que acontece então?

Porque como? O ponteiro da pilha certamente é aumentado às cegas para um intervalo de memória não alocado (ou rolado), mas o que acontece então: existe algum tipo de proteção que o reinicia ou é (entre outros efeitos) o resultado da substituição de informações críticas dados (que eu assumo diferente do código que eu acho que é executado diretamente do flash)?

Não tenho certeza se isso deve estar aqui ou no Stack Overflow, por favor, deixe-me saber se isso deve ser movido, embora eu tenha certeza de que o hardware tem um papel nisso.

Atualizar

Devo salientar que estou particularmente interessado no mecanismo real por trás da corrupção de memória (é o resultado da rolagem do SP -> isso depende do mapeamento de memória do uC etc.)?

Mister Mystère
fonte
8
Alguns micros serão redefinidos se você tentar acessar endereços inválidos. É um recurso valioso implementado em hardware. Outras vezes, pode acabar saltando para algum lugar arbitrário (digamos que você tenha roubado o endereço de retorno de um ISR), talvez executando dados em vez de código, se a arquitetura permitir, e talvez seja pego em um loop que o cão de guarda o divulga do.
Spehro Pefhany
2
Um processador não pode ficar sem RAM, não há instruções que o façam ficar sem RAM. Ficar sem RAM é inteiramente um conceito de software.
usar o seguinte comando

Respostas:

14

Em geral, a pilha e o heap se chocam. Nesse ponto, tudo fica confuso.

Dependendo do MCU, uma de várias coisas pode (ou vai) acontecer.

  1. Variáveis ​​corrompidas
  2. A pilha é corrompida
  3. O programa é corrompido

Quando isso acontece, você começa a ter um comportamento estranho - as coisas não estão fazendo o que deveriam. Quando 2 acontece, todo tipo de inferno se abre. Se o endereço de retorno na pilha (se houver um) estiver corrompido, o local em que a chamada atual retornará é de todo mundo. Naquele momento, basicamente, o MCU começará a fazer coisas aleatórias. Quando o 3 acontecer novamente, quem sabe o que aconteceria. Isso só acontece quando você está executando um código fora da RAM.

Em geral, quando a pilha é corrompida, tudo acaba. Apenas o que acontece depende do MCU.

Pode ser que a tentativa de alocar a memória falhe para que a corrupção não aconteça. Nesse caso, o MCU pode gerar uma exceção. Se não houver um manipulador de exceção instalado, na maioria das vezes o MCU será interrompido (o equivalente a while (1);. Se houver um manipulador instalado, ele poderá ser reiniciado corretamente.

Se a alocação de memória continuar, ou se tentar, falhar e continuar sem memória alocada, você estará no reino de "quem sabe?". O MCU pode acabar se reinicializando através da combinação correta de eventos (interrupções causadas que acabam redefinindo o chip etc.), mas não há garantia de que isso aconteça.

O que geralmente pode haver uma alta probabilidade de acontecer, no entanto, se estiver ativado, é o temporizador interno do watchdog (se houver) exceder o tempo limite e reiniciar o chip. Quando o programa passar completamente por AWOL nesse tipo de falha, as instruções para redefinir o timer geralmente não serão executadas, portanto o tempo limite será redefinido.

Majenko
fonte
Obrigado pela sua resposta, é um excelente resumo dos efeitos. Talvez eu devesse ter especificado que gostaria de obter mais detalhes sobre o mecanismo real dessas corrupções: toda a RAM é alocada para empilhar e empilhar, de modo que o ponteiro da pilha role e substitua variáveis ​​/ endereços anteriores? Ou isso é menos dependente do mapeamento de memória de cada micro? Opcionalmente (provavelmente é um tópico em si), eu estaria interessado em aprender como esses manipuladores de hardware são implementados.
Mister Mystère
1
Depende principalmente do compilador e da biblioteca C padrão em uso. Às vezes, também depende de como o compilador está configurado (scripts do vinculador, etc.).
Majenko
Você poderia expandir isso, talvez com alguns exemplos?
Mister Mystère
Não, realmente não. Alguns sistemas alocam espaço finito para diferentes segmentos, outros não. Alguns usam scripts de vinculação para definir segmentos, outros não. Escolha um microcontrolador que seja do seu interesse e faça algumas pesquisas sobre como funcionam as alocações de memória.,
Majenko
12

Uma visão alternativa: os microcontroladores não ficam sem memória.

Pelo menos, não quando programado corretamente. Programar um microcontrolador não é exatamente como a programação de uso geral; para fazê-lo corretamente, você deve estar ciente de suas restrições e programar adequadamente. Existem ferramentas para ajudar a garantir isso. Pesquise-os e aprenda-os - pelo menos como ler scripts e avisos do vinculador.

No entanto, como Majenko e outros dizem, um microcontrolador mal programado pode ficar sem memória e, em seguida, fazer qualquer coisa, incluindo loop infinito (que pelo menos dá ao cronômetro do watchdog uma chance de redefini-lo. Você ativou o timer do watchdog, não foi? )

Regras de programação comuns para microcontroladores evitam isso: por exemplo, toda a memória é alocada na pilha ou estaticamente (globalmente) alocada; "new" ou "malloc" são proibidos. O mesmo ocorre com a recursão, para que a profundidade máxima do aninhamento de sub-rotinas possa ser analisada e mostrada para caber na pilha disponível.

Portanto, o armazenamento máximo necessário pode ser calculado quando o programa é compilado ou vinculado e comparado com o tamanho da memória (geralmente codificada no script do vinculador) para o processador específico que você está direcionando.

Em seguida, o microcontrolador pode não ficar sem memória, mas o seu programa pode. E nesse caso, você começa a

  • reescreva-o, menor ou
  • escolha um processador maior (eles geralmente estão disponíveis com tamanhos de memória diferentes).

Um conjunto comum de regras para a programação de microcontroladores é o MISRA-C , adotado pela indústria automobilística.

A melhor prática, na minha opinião, é usar o subconjunto SPARK-2014 do Ada. Na verdade, o Ada tem como alvo pequenos controladores como AVR, MSP430 e ARM Cortex razoavelmente bem e, inerentemente, fornece um modelo melhor para a programação de microcontroladores do que o C. Mas o SPARK adiciona anotações ao programa, na forma de comentários, que descrevem o que o programa está fazendo.

Agora, as ferramentas SPARK analisarão o programa, incluindo essas anotações, e comprovarão suas propriedades (ou reportarão possíveis erros). Você não precisa perder tempo ou espaço de código lidando com acessos de memória incorretos ou estouros de números inteiros, porque comprovadamente nunca aconteceram.

Embora exista mais trabalho inicial envolvido com o SPARK, a experiência mostra que ele pode chegar a um produto mais rápido e mais barato, porque você não gasta tempo perseguindo reinicializações misteriosas e outros comportamentos estranhos.

Uma comparação de MISRA-C e SPARK

Brian Drummond
fonte
3
+1 isso. Portar malloc()(e é companheiro de C ++ new) para o AVR é ​​uma das piores coisas que o pessoal do arduino poderia ter feito, e levou a muitos, muitos programadores muito confusos com código quebrado tanto no fórum quanto na troca de pilhas do arduino. Existem muito, muito poucas situações em que ter mallocum ATmega é benéfico.
Connor Wolf
3
+1 para filosofia, -1 para realismo. Se as coisas fossem programadas corretamente, não haveria necessidade dessa pergunta. A questão era o que acontece quando os microcontroladores ficam sem memória. Como impedi-los de ficar sem memória é outra questão. Em outra nota, a recursão é uma ferramenta poderosa, tanto para solucionar problemas quanto para ficar sem pilha.
PkP 03/01
2
@ Brian, desde que eu não sou um idiota, eu obviamente concordo com você. Eu só gosto de pensar sobre isso do ponto de vista inverso - espero que, quando você perceber as horríveis consequências da falta de memória (pilha), procure formas de impedir que isso aconteça. Dessa forma, você tem um verdadeiro impulso para encontrar boas práticas de programação, em vez de apenas seguir os bons conselhos de programação ... e quando você atinge a barreira da memória, é mais provável que imponha as boas práticas, mesmo à custa da conveniência. É apenas um ponto de vista ...
PkP 3/15
2
@PkP: ouvi-lo alto e claro. Chamei isso de uma visão alternativa - porque na verdade não responde à pergunta!
Brian Drummond
2
@ MisterMystère: Os microcontroladores geralmente não ficam sem memória. Um microcontrolador que possui 4096 bytes de RAM quando é ligado pela primeira vez terá 4096 bytes para sempre. É possível que o código tente erroneamente acessar endereços que não existem ou espere que dois métodos diferentes de computação acessem memória diferente quando não existirem, mas o próprio controlador simplesmente executará as instruções fornecidas.
Supercat
6

Eu realmente gosto da resposta de Majenko e marquei isso com +1. Mas quero esclarecer isso em um ponto agudo:

Tudo pode acontecer quando um microcontrolador fica sem memória.

Você realmente não pode confiar em nada quando isso acontece. Quando a máquina fica sem memória da pilha, a pilha provavelmente fica corrompida. E quando isso acontece, tudo pode acontecer. Valores variáveis, derramamentos, registros temporários, todos ficam corrompidos, interrompendo os fluxos do programa. Se / then / elses pode avaliar incorretamente. Os endereços de retorno são ilegíveis, fazendo o programa pular para endereços aleatórios. Qualquer código que você escreveu no programa pode ser executado. (Considere um código como: "if [condition] then {fire_all_missiles ();}"). Além disso, várias instruções que você não escreveu podem executar quando o núcleo salta para um local de memória não conectado. Todas as apostas estão encerradas.

PkP
fonte
2
Obrigado pelo adendo, gostei particularmente da linha fire_all_missiles ().
Mister Mystère
1

O AVR redefiniu o vetor no endereço zero. Quando você sobrescreve a pilha com lixo aleatório, você eventualmente volta e sobrescreve algum endereço de retorno e ele aponta para "lugar nenhum"; quando você retornar de uma sub-rotina para esse lugar nenhum, a execução retornará para o endereço 0, onde normalmente ocorre um salto para redefinir o manipulador.

Ilia
fonte