
A manipulação de erros não é a coisa mais comum para sistemas operacionais projetados para aplicativos de sistemas incorporados. Este é um resultado inevitável de recursos limitados, pois todos os sistemas embarcados possuem certas restrições. E apenas um pequeno número desses sistemas tem a capacidade de se comportar como sistemas de desktop, ou seja, oferece ao usuário a capacidade de escolher ações no caso de eventos excepcionais.
No Nucleus SE, em geral, existem três tipos de verificação de erros:
- meios para verificar a integridade da configuração selecionada para garantir que os parâmetros selecionados não levem a erros;
- código opcionalmente incluído para verificar o comportamento do tempo de execução;
- Certas funções da API que contribuem para o desenvolvimento de código mais robusto.
Tudo isso será discutido neste artigo, juntamente com algumas idéias sobre diagnóstico pelo usuário.
Artigos anteriores da série: Verificar configurações
O Nucleus SE foi projetado com foco na alta configurabilidade do usuário, o que deve garantir o melhor uso dos recursos disponíveis. Essa configurabilidade é uma tarefa complexa, pois o número de parâmetros possíveis e as interdependências entre eles são enormes. Conforme declarado em muitos artigos anteriores, a maioria das etapas do usuário para configurar o Nucleus SE são executadas usando as diretivas
#define no arquivo
nuse_config.h .
Para ajudar a identificar erros de configuração, o arquivo
nuse_config.c a
#include inclui o arquivo
nuse_config_check.h , que executa verificações de integridade nas diretivas
#define . A seguir está um trecho deste arquivo:
/*** Tasks and task control ***/ #if NUSE_TASK_NUMBER < 1 || NUSE_TASK_NUMBER > 16 #error NUSE: invalid number of tasks - must be 1-16 #endif #if NUSE_TASK_RELINQUISH && (NUSE_SCHEDULER_TYPE == NUSE_PRIORITY_SCHEDULER) #error NUSE: NUSE_Task_Relinquish() selected - not valid with priority scheduler #endif #if NUSE_TASK_RESUME && !NUSE_SUSPEND_ENABLE #error NUSE: NUSE_Task_Resume() selected - task suspend not enabled #endif #if NUSE_TASK_SUSPEND && !NUSE_SUSPEND_ENABLE #error NUSE: NUSE_Task_Suspend() selected - task suspend not enabled #endif #if NUSE_INITIAL_TASK_STATE_SUPPORT && !NUSE_SUSPEND_ENABLE #error NUSE: Initial task state enabled - task suspend not enabled #endif /*** Partition pools ***/ #if NUSE_PARTITION_POOL_NUMBER > 16 #error NUSE: invalid number of partition pools - must be 0-16 #endif #if NUSE_PARTITION_POOL_NUMBER == 0 #if NUSE_PARTITION_ALLOCATE #error NUSE: NUSE_Partition_Allocate() enabled – no partition pools configured #endif #if NUSE_PARTITION_DEALLOCATE #error NUSE: NUSE_Partition_Deallocate() enabled – no partition pools configured #endif #if NUSE_PARTITION_POOL_INFORMATION #error NUSE: NUSE_Partition_Pool_Information() enabled – no partition pools configured #endif #endif
O código acima executa as seguintes verificações:
- verifique se pelo menos uma, mas não mais que dezesseis tarefas estão configuradas;
- Confirmação de que as funções da API selecionadas são compatíveis com o planejador selecionado e outros parâmetros especificados;
- verificação de que não foram criadas mais de dezesseis instâncias de outros objetos do kernel;
- Confirmação de que as funções da API relacionadas a objetos não declarados não foram selecionadas.
- garantir que as funções da API para sinais e hora do sistema não sejam usadas quando o suporte a esses serviços estiver desativado;
- verificação do tipo selecionado de planejador e parâmetros relacionados.
Em todos os casos, a detecção de erros leva à execução da diretiva
#error na compilação. Isso geralmente faz com que a compilação pare e exiba a mensagem correspondente.
Este arquivo não garante a impossibilidade de criar uma configuração / configuração incorreta, mas torna muito improvável.
Verificando as configurações da API
Como o Nucleus RTOS, o Nucleus SE tem a capacidade de incluir código para verificar os parâmetros de chamada de funções da API em tempo de execução. Geralmente, isso é usado apenas durante a depuração e o teste inicial, pois no código final do programa o consumo excessivo de memória é indesejável.
A verificação de parâmetro é ativada configurando o parâmetro
NUSE_API_PARAMETER_CHECKING no arquivo
nuse_config.h como
TRUE . Isso leva à compilação do código adicional necessário. A seguir, é apresentado um exemplo de verificação dos parâmetros de função da API:
STATUS NUSE_Mailbox_Send(NUSE_MAILBOX mailbox, ADDR *message, U8 suspend) { STATUS return_value; #if NUSE_API_PARAMETER_CHECKING if (mailbox >= NUSE_MAILBOX_NUMBER) { return NUSE_INVALID_MAILBOX; } if (message == NULL) { return NUSE_INVALID_POINTER; } #if NUSE_BLOCKING_ENABLE if ((suspend != NUSE_NO_SUSPEND) && (suspend != NUSE_SUSPEND)) { return NUSE_INVALID_SUSPEND; } #else if (suspend != NUSE_NO_SUSPEND) { return NUSE_INVALID_SUSPEND; } #endif #endif
Essa verificação dos parâmetros pode levar ao fato de que a chamada da API produzirá um código de erro. É um valor negativo no formato
NUSE_INVALID_xxx (por exemplo,
NUSE_INVALID_POINTER ) - um conjunto completo de definições está contido no arquivo
nuse_codes.h .
Para processar valores de erro, um código de aplicativo adicional (possivelmente criado usando compilação condicional) pode ser adicionado; no entanto, para detectá-los, é melhor usar as ferramentas de monitoramento dos dados dos depuradores de firmware modernos.
A verificação dos parâmetros leva ao consumo adicional de memória (código adicional) e afeta o desempenho do código; portanto, seu uso afetará todo o sistema. Como todo o código fonte do Nucleus SE está disponível para o desenvolvedor, a verificação e a depuração podem ser realizadas manualmente no código final do aplicativo, se for necessária precisão absoluta.
Verificando a pilha de tarefas
Até que o Agendador de execução até a conclusão seja usado, o Nucleus SE oferece a capacidade de verificar a pilha de tarefas, que é semelhante a uma função semelhante no Nucleus RTOS e mostra o espaço restante na pilha. Essa chamada de utilitário da API (
NUSE_Task_Check_Stack () ) foi descrita em detalhes em um
artigo anterior (nº 12). Algumas idéias para verificar erros de pilha são fornecidas posteriormente neste artigo na seção Diagnóstico personalizado.
Informações da versão
O Nucleus RTOS e o Nucleus SE têm uma função de API que simplesmente retorna informações sobre a versão / construção do kernel.
Chamada da API do RTOS do núcleo
Protótipo de chamada de serviço:
CHAR * NU_Release_Information (VOID);
Parâmetros: nenhum.
Valor de retorno:
Ponteiro para uma sequência contendo informações de versão que terminam com um byte nulo.
Chamada da API do Nucleus SE
Essa chamada de API suporta a funcionalidade principal da API do Nucleus RTOS.
Protótipo de chamada de serviço:
char * NUSE_Release_Information (nulo);
Parâmetros: nenhum.
Valor de retorno:
Ponteiro para uma sequência contendo informações de versão que terminam com um byte nulo.
Fazendo uma chamada para obter informações de montagem do Nucleus SE
A implementação desta chamada à API é bastante simples. Um ponteiro é retornado para a linha constante
NUSE_Release_Info , declarada e inicializada no arquivo
nuse_globals.c .
Essa linha tem o formato Nucleus SE -
Xyymmmdd , em que:
X - status da compilação:
A = alfa;
B = beta;
R = liberação
aa - ano de lançamento
mm - mês de lançamento
dd - dia do lançamento
Compatível com núcleo RTOS
O Nucleus RTOS contém suporte opcional a revistas de história. O kernel registra os detalhes de várias ações do sistema. Existem funções de API que permitem que os programas:
- ativar / desativar o registro;
- crie uma entrada no diário;
- Obter uma entrada no diário.
Este recurso não é suportado no Nucleus SE.
O Nucleus RTOS também possui várias macros de gerenciamento de erros que permitem executar confirmações sem erro (ASSERT) e fornecem a capacidade de chamar funções de erro críticas definidas pelo usuário. Eles são opcionalmente incluídos no conjunto do SO. O Nucleus SE não suporta essa funcionalidade.
Diagnóstico do usuário
Até agora neste artigo, examinamos as ferramentas de diagnóstico e verificação de erros fornecidas pelo próprio Nucleus SE. Agora vale a pena dizer como as ferramentas de diagnóstico definidas pelo usuário ou orientadas para um aplicativo específico podem ser implementadas usando as ferramentas fornecidas pelo kernel e / ou nosso conhecimento sobre sua estrutura interna e implementação.
Diagnóstico específico do aplicativo
Em quase todos os aplicativos, você pode adicionar código adicional para verificar sua integridade no tempo de execução. O núcleo de multitarefa facilita e simplifica a criação de uma tarefa especial para este trabalho. Obviamente, neste artigo, não consideraremos casos de diagnóstico muito incomuns, mas consideraremos algumas idéias gerais.
Verificações de memória
Obviamente, a operação adequada da memória é fundamental para a integridade de qualquer sistema de processador. Não é menos óbvio que um erro crítico não permitirá que você execute, não apenas diagnósticos, mas todo o software como um todo (
nota do tradutor: a propósito, esse é exatamente o caso que examinamos no artigo “Fake Blue Pill” ). No entanto, há situações em que um determinado erro aparece, o que é um sério motivo de preocupação, mas não interfere na execução do código. O teste de memória é um tópico bastante complicado que está além do escopo deste artigo, portanto, darei apenas algumas idéias gerais.
Os dois erros mais comuns que ocorrem na RAM são:
- "Bits aderentes" quando o bit tem um valor de 0 ou 1, que não pode ser alterado;
- "Diafonia" quando bits adjacentes causam interferência um no outro.
Ambos os erros podem ser verificados escrevendo e lendo determinados padrões de teste em cada área da RAM, por sua vez. Algumas verificações podem ser realizadas apenas na inicialização, mesmo antes da formação da pilha (
nota do tradutor: no artigo mencionado acima, constatou-se que foi o primeiro uso da pilha que destruiu tudo de uma vez ). Por exemplo, uma verificação de "unidade em execução", na qual cada bit de memória recebe um valor de um e todos os outros bits são verificados para garantir que sejam iguais a zero. Outros padrões de teste bit a bit podem ser executados durante a operação, desde que enquanto a área de RAM esteja danificada, a alternância de contexto não ocorrerá. O uso das macros de restrição de seção crítica do
Nucleus SE NUSE_CS_Enter () e
NUSE_CS_Exit () é bastante simples e escalável.
Diferentes tipos de ROMs também são propensos a erros periódicos, mas não existem muitas ferramentas para verificá-las. Uma soma de verificação calculada após a montagem do código pode ser útil aqui. Essa verificação pode ser realizada no momento da inicialização e, possivelmente, no tempo de execução.
Um erro na lógica de endereçamento de memória pode afetar a ROM e a RAM. Você pode desenvolver uma verificação especial para esse erro, mas provavelmente será detectado como parte das verificações descritas acima.
Periféricos de teste
Além da CPU, os circuitos periféricos também podem estar sujeitos a erros. Obviamente, isso varia muito de sistema para sistema; no entanto, na maioria dos dispositivos, existem várias maneiras de verificar sua integridade usando o software de diagnóstico. Por exemplo, um canal de comunicação pode ter um modo de verificação de loopback no qual todos os dados que entram no canal são retornados imediatamente.
Serviço de vigilância
Os desenvolvedores incorporados costumam usar um cão de guarda. Este é um dispositivo periférico que interrompe a CPU e aguarda uma resposta ou (mais preferencialmente) requer acesso periódico do software. Nos dois casos, um resultado comum de um cronômetro de vigilância é uma redefinição do sistema.
O uso efetivo de um cão de guarda em um ambiente multitarefa é um problema complexo. Se você criar uma tarefa que a acesse periodicamente (cronômetro de vigilância), confirmará que essa tarefa específica está funcionando. Uma solução possível poderia ser a implementação da "tarefa do despachante". Um exemplo dessa tarefa será fornecido abaixo.
Verificação de estouro de pilha
Se você não usar o Agendador de Execução até a Conclusão, será criada uma pilha para cada tarefa no aplicativo Nucleus SE. A integridade dessas pilhas é muito importante, mas é provável que a quantidade de RAM seja limitada; portanto, é importante otimizar o tamanho do aplicativo. Prever estaticamente os requisitos para a pilha de cada tarefa é possível, mas muito difícil. A pilha deve ser grande o suficiente para até as funções mais aninhadas, junto com o manipulador de interrupções mais exigente. Uma abordagem mais simples para resolver esse problema seria usar testes exaustivos de tempo de execução.
De um modo geral, existem duas abordagens para empilhar a verificação. Se você usar um depurador sofisticado de software incorporado, os limites da pilha poderão ser monitorados e todas as violações serão detectadas. A
localização e o tamanho das pilhas do Nucleus SE estão disponíveis nas estruturas de dados globais da ROM:
NUSE_Task_Stack_Base [] e
NUSE_Task_Stack_Size [] .
Uma alternativa é o teste de tempo de execução. Uma abordagem comum é usar “palavras de guarda” no final de cada pilha, geralmente o primeiro elemento de cada área dos dados da pilha. Essas palavras são inicializadas com um valor diferente de zero reconhecido. A tarefa de serviço / diagnóstico verifica se essas palavras foram alteradas e executa as ações apropriadas. Pressionar a palavra segurança não significa que a pilha esteja cheia, mas indica que isso está prestes a acontecer. Portanto, o software pode continuar funcionando, pode ser necessário tomar ações corretivas ou relatar um erro ao usuário.
Tarefa do supervisor
Apesar do Nucleus SE não reservar nenhuma das dezesseis tarefas possíveis para suas próprias necessidades, o usuário pode selecionar uma tarefa para diagnóstico. Essa pode ser uma tarefa de baixa prioridade que simplesmente usa qualquer tempo "livre" do processador ou pode ser uma tarefa de alta prioridade que é executada periodicamente, levando um curto período de tempo, o que garante que os diagnósticos sejam realizados regularmente.
A seguir, é apresentado um exemplo de como uma tarefa semelhante pode funcionar.
Os sinalizadores de sinal da tarefa do despachante são usados para rastrear a operação de seis tarefas críticas do sistema. Cada uma dessas tarefas usa um sinalizador específico (do bit 0 ao bit 5) e deve defini-lo regularmente. A tarefa do expedidor redefine todos os sinalizadores e pausa seu trabalho por um determinado período de tempo. Quando ela retoma o trabalho, espera que todas as seis tarefas sejam "verificadas" configurando o sinalizador apropriado, e procura uma correspondência exata com o valor de
b00111111 (do arquivo
nuse_binary.h ). Se tudo atender aos requisitos, ele redefine os sinalizadores e pausa novamente. Caso contrário, ele chama a rotina de tratamento de erros críticos, que por sua vez pode, por exemplo, reiniciar o sistema.
Em uma implementação alternativa, grupos de sinalizadores de eventos podem ser usados. Isso faz sentido se os sinais não forem usados em outras partes do aplicativo (caso contrário, isso levará ao uso excessivo de RAM por todas as tarefas) e, especialmente, se os sinalizadores de eventos forem usados para outros fins.
Rastreamento e criação de perfil
Apesar do fato de que muitos depuradores de software embarcados modernos têm um alto grau de personalização e podem ser usados para trabalhar com o RTOS, a depuração de um aplicativo multiencadeado ainda pode ser difícil. Uma abordagem amplamente usada é a criação de perfil de pós-execução, na qual o código (RTOS) é implementado para que uma auditoria detalhada de seu trabalho possa ser analisada em retrospecto. Normalmente, a implementação de um serviço desse tipo inclui dois componentes:
- Um código adicional é adicionado ao RTOS para registrar ações. Normalmente, ele será envolvido em diretivas de pré-processador para usar a compilação condicional. Esse código registra vários bytes de informações quando ocorre um evento importante (por exemplo, chamar uma função de API ou alternar o contexto). Essas informações podem incluir:
- endereço atual (PC);
- ID da tarefa atual (índice);
- índices de outros objetos usados;
- código correspondente à operação executada.
- A tarefa alocada para descarregar o buffer de informações do perfil no armazenamento externo, geralmente no computador host.
A análise dos dados assim obtidos também exigirá algum trabalho, mas não é mais complicado do que usar uma planilha regular do Excel.
No próximo artigo, examinaremos em detalhes a compatibilidade do Nucleus SE e Nucleus RTOS.
Sobre o autor: Colin Walls trabalha na indústria eletrônica há mais de trinta anos, dedicando a maior parte de seu tempo ao firmware. Ele agora é engenheiro de firmware na Mentor Embedded (uma divisão da Mentor Graphics). Colin Walls frequentemente fala em conferências e seminários, autor de vários artigos técnicos e dois livros sobre firmware. Vive no Reino Unido.
Blog profissional
de Colin , e-mail: colin_walls@mentor.com.