Estratégias de uso de watchdog com um RTOS

- por Sergio Prado

Categorias: RTOS Tags: , ,

Implementar um mecanismo eficiente de watchdog em um sistema multitarefa com um RTOS não é algo simples, e envolve algumas questões importantes, incluindo o que monitorar, como monitorar e que ação tomar se o temporizador do watchdog expirar.

Mas antes de mais nada, o que é um watchdog?

De forma geral, um watchdog é um mecanismo de proteção contra falhas e travamentos. É uma espécie de temporizador que, quando ligado, deve ser periodicamente reiniciado para evitar um timeout. No caso de um travamento ou mal funcionamento da aplicação, o watchdog deixará de ser reiniciado, seu temporizador irá expirar, e ele tomará uma decisão, que normalmente é reiniciar o sistema.

Um watchdog pode ser implementado em software ou em hardware.

Um watchdog implementado em software não é tão eficiente porque qualquer travamento do hardware ou até mesmo da aplicação pode evitar com que a própria rotina do watchdog execute e tome uma ação.

Um watchdog implementado em hardware é uma solução muito mais robusta e seu uso é bastante comum em sistemas embarcados críticos. Uma vez ligado, o contador do watchdog precisa ser periodicamente reiniciado pela aplicação. Qualquer travamento do sistema, seja de hardware ou software, irá causar um timeout do contador do watchdog, que irá tomar uma ação, normalmente enviando um sinal de reset para a CPU, com o objetivo de se recuperar do travamento ou da falha.

Implementar uma estratégia de watchdog em um sistema bare-metal (sem um sistema operacional) é relativamente simples. Basta inicializar o watchdog, implementar uma função para reiniciar o contador do watchdog e chamar esta função em pontos estratégicos do código. Em qualquer travamento do hardware ou da aplicação, o contador não será reiniciado e o watchdog entrará em ação.

E no caso de sistemas multitarefa com um RTOS? Reiniciar o watchdog em todas as tarefas não resolve, porque se a execução travar em uma das tarefas, o watchdog continuará sendo reiniciado, mesmo com as outras tarefas impossibilitadas de execução.

Qual seria então um método eficiente de utilizar o watchdog em um sistema multitarefa?

Uma implementação de watchdog em um sistema multitarefa deve ser capaz de garantir a execução correta do sistema operacional e de cada uma das tarefas da aplicação. Para isso, devemos monitorar individualmente a execução de cada uma das tarefas da aplicação (ou pelo menos das mais importantes), e garantir que todas elas estão sendo executadas conforme o planejado.

Existem algumas formas de implementar este monitoramento, cada uma com seus prós e contras. Neste artigo vou descrever duas estratégias que costumo utilizar. A primeira estratégia é bastante simples, porém não é tão robusta e possui algumas deficiências. A segunda estratégia é um pouco mais trabalhosa de implementar, mas possui uma boa relação custo-benefício (complexidade vs robustez).

Para exemplificar, utilizei a API do FreeRTOS, mas o código pode ser facilmente portado para outros sistemas operacionais.

ESTRATÉGIA 1: TAREFA DE WATCHDOG

A idéia aqui é simples. Implementar uma tarefa periódica com a menor prioridade possível para reiniciar o watchdog periodicamente.

1
2
3
4
5
6
7
8
9
10
11
void watchdog_task(void *pvParameters)
{
    /* initialize and start watchdog */
    watchdog_init();
 
    /* kick watchdog every 1 second */
    for (;;) {
        vTaskDelay(1000/portTICK_PERIOD_MS);
        watchdog_kick();
    }
}

Esta estratégia garante proteção contra travamentos do hardware, já que se o hardware travar ela não executa e o watchdog entra em ação. Protege também contra travamentos do escalonador de tarefas do RTOS, já que se o escalonador travar, ela também não executa e o watchdog entra em ação. Por fim, como a tarefa de watchdog tem a menor prioridade possível, se qualquer outra tarefa travar o processamento, a tarefa de watchdog não executa e o watchdog entra em ação.

Parece uma boa estratégia de implementação, não é? Mas ela tem algumas deficiências.

A principal deficiência é que este método não garante que todas as tarefas estão executando conforme o esperado. Será que a tarefa do sensor de temperatura está conseguindo ler o conversor A/D periodicamente, ou ela está travada por um bug de hardware ou software? Não dá para saber, já que ela não está sendo monitorada.

Portanto, esta estratégia de watchdog é muito simples de implementar, mas não possui a robustez que boa parte dos projetos de sistemas embarcados exigem.

ESTRATÉGIA 2: MONITORAMENTO INDIVIDUAL DAS TAREFAS

Nesta estratégia, as tarefas são indivualmente monitoradas, e o contador do watchdog é reiniciado apenas se todas as tarefas monitoradas estiverem executando conforme planejado.

Para monitorar uma tarefa, associamos a ela um contador que deverá ser incrementado periodicamente pela própria tarefa para indicar que está tudo OK com sua execução.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void lcd_task(void *pvParameters)
{
    struct watchdog_counter *wdt;
 
    lcd_init();
 
    wdt = watchdog_counter_allocate(pcTaskGetName(NULL));
 
    for (;;) {
        vTaskDelay(200/portTICK_PERIOD_MS);
        lcd_update();
 
	watchdog_counter_increment(wdt);
    }
}

Perceba que temos um tipo opaco para armazenar o contador da tarefa (struct watchdog_counter). Este contador é alocado e inicializado através da função watchdog_counter_allocate(), que recebe o nome da tarefa como parâmetro. Desta forma, a tarefa de watchdog consegue saber qual tarefa está associada ao contador alocado. Por fim, o contador da tarefa é incrementado através da função watchdog_counter_increment(). Desta forma, encapsulamos toda a lógica dos contadores dentro do módulo de gerenciamento do watchdog.

A tarefa de watchdog será responsável por verificar periodicamente se o contador de cada tarefa monitorada foi incrementado. Caso afirmativo, todas as tarefas estão executando normalmente, então ela zera os contadores e reinicia o watchdog. Caso negativo, uma das tarefas não executou conforme o planejado. Neste caso, a tarefa de watchdog deverá fazer o log deste problema (assim temos registrado a origem de um possível reset) e tomar uma ação. Esta ação pode ser um reset por software, reiniciar a tarefa com problema, ou não fazer nada e deixar o reset de hardware do watchdog entrar em ação.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void watchdog_task(void *pvParameters)
{
    uint32_t err;
 
    /* initialize and start watchdog */
    watchdog_init();
 
    for (;;) {
 
        /* check tasks and kick watchdog every 1 second */
        vTaskDelay(1000/portTICK_PERIOD_MS);
 
        /* all tasks running, kick watchdog and restart counters */
	if ((err = watchdog_counter_check()) == WATCHDOG_TASKS_OK) {
            watchdog_kick();
            watchdog_counter_restart();
        }
 
        /* error on one or more tasks, log error and wait for hardware reset */
        else {
            watchdog_error_log(err);
            watchdog_hw_reset_wait();
        }
    }
}

É importante lembrar que, como estes contadores podem ser acessados por mais de uma tarefa ao mesmo tempo, eles devem ser protegidos de acesso concorrente. No caso do FreeRTOS, podemos proteger os contadores de acesso concorrente desabilitando as interrupções ou utilizando um mutex.

Se você quiser evitar contadores globais (que precisam ser protegidos de concorrência), pode substituí-los por semáforos. Neste caso, cada tarefa utilizaria um semáforo para notificar sua execução, e a tarefa de watchdog esperaria a notificação nestes semáforos. No FreeRTOS, esta estratégia de notificação via semáforos poderia ser implementada com Queue Sets, mas ficaria bastante complicado. Uma forma bem elegante de implementar esta estratégia com o FreeRTOS seria utilizando Event Groups.

E que prioridade a tarefa de watchdog deveria ter? Existem prós e contras neste caso.

Se a tarefa de watchdog tiver uma prioridade maior, é garantido que ela sempre será executada, mesmo se outra tarefa travar o processamento. Por um lado, isso é bom, já que a tarefa de watchdog conseguiria identificar e logar o nome da tarefa que travou o processamento. Por outro lado, a tarefa de watchdog ocuparia um tempo de CPU que poderia ser utilizado por tarefas de maior prioridade.

Se a tarefa de watchdog tiver uma prioridade menor, é garantido que ela não ocupará CPU das tarefas de maior prioridade, porém precisamos garantir também que outra tarefa não trave o processamento por um tempo maior do que o tempo necessário para reiniciar o watchdog. Esta situação poderia impedir a execução da tarefa de watchdog e consequentemente causar resets esporádicos da CPU. Por este motivo, sempre que possível, prefiro colocar a tarefa de watchdog com uma prioridade maior que as prioridades das tarefas que ela irá monitorar.

Concluindo, esta estratégia de watchdog é simples de implementar e possui uma robustez aceitável. A partir dela poderíamos evoluir, dependendo das necessidades da aplicação. Por exemplo, esta estratégia não é capaz de identificar problemas com ISR’s (ex: será que a ISR do canal A/D está sendo gerada/tratada?), ou validar a semântica da aplicação (ex: será que estamos recebendo mensagens válidas no barramento CAN?). É tudo questão de balancear a robustez necessária para a aplicação com a complexidade da implementação e futura manutenção.

E você, conhece alguma outra estratégia interessante de implementação de watchdog no projeto de um firmware com RTOS?

Um abraço!

Sergio Prado

Navegue
Creative Commons Este trabalho de Sergio Prado é licenciado pelo
Creative Commons BY-NC-SA 3.0.