Princípios básicos do kernel do FreeRTOS - FreeRTOS

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

Princípios básicos do kernel do FreeRTOS

O kernel do FreeRTOS é um sistema operacional em tempo real compatível com inúmeras arquiteturas. É ideal para criar aplicativos de microcontroladores incorporados. Ele fornece:

  • Um programador multitarefa.

  • Várias opções de alocação de memória (inclusive a possibilidade de criar sistemas totalmente alocados estaticamente).

  • Primitivos de coordenação entre tarefas, inclusive notificações de tarefas, filas de mensagens, vários tipos de semáforos e buffers de fluxo e de mensagens.

  • Suporte para multiprocessamento simétrico (SMP) em microcontroladores de vários núcleos.

O kernel do FreeRTOS nunca realiza operações não determinísticas, como percorrer uma lista vinculada, dentro de uma seção crítica ou interrupção. O kernel do FreeRTOS inclui uma implementação de temporizador de software eficiente que não usa nenhum tempo de CPU, a menos que um temporizador precise de manutenção. Tarefas bloqueadas não exigem manutenção periódica e demorada. Notificações diretas para tarefas permitem a sinalização rápida de tarefas, praticamente sem sobrecarga de RAM. Elas podem ser usadas na maioria dos cenários de sinalização entre tarefas e de interrupção para tarefa.

O kernel do FreeRTOS foi projetado para ser pequeno, simples e fácil de usar. Uma imagem binária típica do kernel do RTOS está no intervalo de 4.000 a 9.000 bytes.

Para obter a documentação mais atualizada sobre o kernel FreeRTOS, consulte FreeRTOS.org. O Freertos.org oferece uma série de tutoriais detalhados e guias sobre o uso do kernel FreeRtos, incluindo um Quick Start Guide (Guia de início rápido) e o documento mais detalhado Mastering the FreeRTOS Real Time Kernel.

Programador de kernel do FreeRTOS

Um aplicativo incorporado que usa um RTOS pode ser estruturado como um conjunto de tarefas independentes. Cada tarefa é executada em seu próprio contexto, sem dependência de outras tarefas. Somente uma tarefa no aplicativo será executada a qualquer momento. O programador do RTOS em tempo real determina quando cada tarefa deve ser executada. Cada tarefa é fornecida com a própria pilha. Quando uma tarefa é trocada para que outra possa ser executada, o contexto de execução da tarefa é salvo na pilha de tarefas para que ele possa ser restaurado quando a mesma tarefa for trocada de novo posteriormente para retomar sua execução.

Para fornecer um comportamento determinístico em tempo real, o programador de tarefas do FreeRTOS permite que as tarefas recebam prioridades estritas. O RTOS garante que a tarefa de maior prioridade que pode ser executada receba o tempo de processamento. Isso requer o compartilhamento do tempo de processamento entre tarefas de igual prioridade, se elas estiverem prontas para serem executadas simultaneamente. O FreeRTOS também cria uma tarefa inativa que é executada somente quando nenhuma outra tarefa está pronta para ser executada.

Gerenciamento de memória

Essa seção fornece informações sobre a alocação de memória do kernel e o gerenciamento de memória do aplicativo.

Alocação de memória do kernel

O kernel do RTOS precisa de memória RAM toda vez que uma tarefa, fila ou outro objeto do RTOS é criado. A memória RAM pode ser alocada:

  • Estaticamente no momento da compilação.

  • Dinamicamente a partir do heap do RTOS pelas funções de criação de objeto da API do RTOS.

Quando os objetos do RTOS são criados dinamicamente, o uso da biblioteca malloc() e das funções C free() padrão nem sempre é apropriado por diversos motivos:

  • Elas podem não estar disponíveis em sistemas incorporados.

  • Elas ocupam um espaço de código valioso.

  • Normalmente, elas não são seguras para o thread.

  • Elas não são deterministas.

Por esses motivos, o FreeRTOS mantém a API de alocação de memória em seu nível portátil. A camada portátil está fora dos arquivos de origem que implementam a funcionalidade principal do RTOS, para que você possa fornecer uma implementação específica do aplicativo apropriada para o sistema em tempo real que você está desenvolvendo. Quando o kernel do RTOS exige RAM, ele chama pvPortMalloc() em vez de malloc(). Quando a memória RAM está sendo liberada, o kernel do RTOS chama vPortFree() em vez de free().

Gerenciamento de memória de aplicativos

Quando os aplicativos precisam de memória, eles podem alocá-la do heap do FreeRTOS. O FreeRTOS oferece vários esquemas de gerenciamento de heap que variam em termos de complexidade e recursos. Você também pode fornecer sua própria implementação de heap.

O kernel do FreeRTOS inclui cinco implementações de heap:

heap_1

É a implementação mais simples. Não permite que a memória seja liberada.

heap_2

Permite que a memória seja liberada, mas não une blocos livres adjacentes.

heap_3

Encapsula o malloc() e o free() padrão para segurança de threads.

heap_4

Une blocos livres adjacentes para evitar a fragmentação. Inclui uma opção de posicionamento de endereço absoluto.

heap_5

É semelhante a heap_4. Pode abranger o heap em várias áreas de memória não adjacentes.

Coordenação entre tarefas

Esta seção contém informações sobre primitivos do FreeRTOS.

Filas

As filas são a principal forma de comunicação entre tarefas. Elas podem ser usadas para enviar mensagens entre tarefas e entre interrupções e tarefas. Na maioria dos casos, elas são usadas como buffers FIFO (primeiro a entrar, primeiro a sair) seguros para threads com novos dados sendo enviados para o final da fila. (Os dados também podem ser enviados para a frente da fila.) As mensagens são enviadas por meio de filas por cópia, o que significa que os dados (que podem ser um ponteiro para buffers maiores) são copiados na fila, em vez de simplesmente armazenar uma referência aos dados.

As APIs de fila permitem que um tempo de bloqueio seja especificado. Quando uma tarefa tenta ler a partir de uma fila vazia, a tarefa é colocada no estado Bloqueada até que os dados se tornem disponíveis na fila ou que o tempo de bloqueio termine. As tarefas no estado Bloqueadas não consomem nenhum tempo de CPU, permitindo que outras tarefas sejam executadas. Da mesma forma, quando uma tarefa tenta gravar em uma fila cheia, a tarefa é colocada no estado Bloqueada até que o espaço seja disponibilizado na fila ou o tempo de bloqueio termine. Se mais de uma tarefa bloquear na mesma fila, a tarefa com a prioridade mais alta será desbloqueada primeiro.

Outros primitivos do FreeRTOS, como notificações diretas para tarefas e buffers de fluxo e de mensagens, oferecem alternativas leves para filas em muitos cenários de projeto comuns.

Semáforos e mutexes

O kernel do FreeRTOS fornece semáforos binários, semáforos de contagem e mutexes para fins de sincronização e exclusão mútua.

Semáforos binários só podem ter dois valores. Eles são uma boa opção para implementar a sincronização (entre tarefas ou entre tarefas e uma interrupção). Os semáforos de contagem podem usar mais de dois valores. Eles permitem que muitas tarefas compartilhem recursos ou executem operações de sincronização mais complexas.

Mutexes são semáforos binários que incluem um mecanismo de herança de prioridade. Isso significa que, se uma tarefa de alta prioridade for bloqueada ao tentar obter um mutex que esteja retido no momento por uma tarefa de prioridade mais baixa, a prioridade da tarefa que contém o token é temporariamente aumentada para o nível da tarefa de bloqueio. Esse mecanismo é projetado para garantir que a tarefa de prioridade mais alta seja mantida no estado Bloqueada pelo menor tempo possível, para minimizar a inversão de prioridade que ocorreu.

Notificações diretas para tarefas

As notificações de tarefas permitem que as tarefas interajam com outras tarefas e sincronizem com rotinas de serviço de interrupção (ISRs), sem a necessidade de um objeto de comunicação separado, como um semáforo. Cada tarefa do RTOS possui um valor de notificação de 32 bits que é usado para armazenar o conteúdo da notificação, se houver. Uma notificação de tarefa do RTOS é um evento enviado diretamente a uma tarefa que pode desbloquear a tarefa de recebimento e, opcionalmente, atualizar o valor de notificação da tarefa de recebimento.

As notificações de tarefas do RTOS podem ser usadas como uma alternativa mais rápida e leve aos semáforos binários e de contagem e, em alguns casos, às filas. Notificações de tarefas têm vantagens de velocidade e espaço de RAM em relação a outros recursos do FreeRTOS que podem ser usados para executar funcionalidades equivalentes. No entanto, as notificações de tarefas só podem ser usadas quando houver apenas uma tarefa que possa ser o destinatário do evento.

Buffers de fluxo

Os buffers de fluxo permitem que um fluxo de bytes seja transmitido de uma rotina de serviço de interrupção para uma tarefa ou de uma tarefa para outra. Um fluxo de bytes pode ser de tamanho arbitrário e não tem necessariamente um começo ou um fim. Qualquer número de bytes pode ser gravado de uma só vez e lido ao mesmo tempo. Você ativa a funcionalidade de buffer de fluxo incluindo o arquivo de origem stream_buffer.c em seu projeto.

Os buffers de fluxo presumem que há apenas uma tarefa ou interrupção que grava no buffer (o gravador) e apenas uma tarefa ou interrupção que lê a partir do buffer (o leitor). É seguro que o gravador e o leitor sejam tarefas ou rotinas de serviço de interrupção diferentes, mas não é seguro ter vários gravadores ou leitores.

A implementação do buffer de fluxo usa as notificações diretas para tarefas. Portanto, chamar uma API de buffer de fluxo que coloca a tarefa de chamada no estado Bloqueada pode alterar o estado e o valor de notificação da tarefa de chamada.

Envio de dados

xStreamBufferSend() é usado para enviar dados a um buffer de fluxo em uma tarefa. xStreamBufferSendFromISR() é usado para enviar dados a um buffer de fluxo em uma rotina de serviço de interrupção (ISR).

xStreamBufferSend() permite que um tempo de bloqueio seja especificado. Se xStreamBufferSend() for chamado com um tempo de bloqueio diferente de zero para gravar em um buffer de fluxo e o buffer estiver cheio, a tarefa será colocada no estado Bloqueada até que o espaço seja disponibilizado ou o tempo de bloqueio expire.

sbSEND_COMPLETED() e sbSEND_COMPLETED_FROM_ISR() são macros que são chamadas (internamente pela API do FreeRTOS) quando os dados são gravados em um buffer de fluxo. É utilizado o identificador do buffer de fluxo que foi atualizado. As duas macros verificam se há uma tarefa bloqueada no buffer de fluxo aguardando dados e, se esse for o caso, removem a tarefa do estado Bloqueada.

Você pode alterar esse comportamento padrão fornecendo sua própria implementação de sbSEND_COMPLETED() em FreeRTOSConfig.h Isso é útil quando um buffer de fluxo é usado para transmitir dados entre núcleos em um processador de vários núcleos. Nesse cenário, sbSEND_COMPLETED() pode ser implementado para gerar uma interrupção no outro núcleo da CPU, em seguida, a rotina de serviço da interrupção poderá usar a API xStreamBufferSendCompletedFromISR() para verificar e, se necessário, desbloquear uma tarefa que esteja aguardando os dados.

Recebimento de dados

xStreamBufferReceive() é usado para ler dados de um buffer de fluxo em uma tarefa. xStreamBufferReceiveFromISR() é usado para ler dados de um buffer de fluxo em uma rotina de serviço de interrupção (ISR).

xStreamBufferReceive() permite que um tempo de bloqueio seja especificado. Se xStreamBufferReceive() for chamado com um tempo de bloqueio diferente de zero para ler de um buffer de fluxo e o buffer estiver vazio, a tarefa será colocada no estado Bloqueada até que uma quantidade especificada de dados seja disponibilizada no buffer de fluxo ou o tempo de bloqueio expire.

A quantidade de dados que deve estar no buffer de fluxo antes de uma tarefa ser desbloqueada é chamada de nível de ativação do buffer de fluxo. Uma tarefa bloqueada com um nível de ativação de 10 é desbloqueada quando pelo menos 10 bytes são gravados no buffer ou o tempo de bloqueio da tarefa expira. Se o tempo de bloqueio de uma tarefa de leitura expirar antes do nível de ativação ser atingido, a tarefa receberá todos os dados gravados no buffer. O nível de ativação de uma tarefa deve ser definido para um valor entre 1 e o tamanho do buffer de fluxo. O nível de ativação de um buffer de fluxo é definido quando xStreamBufferCreate() é chamado. É possível alterá-lo chamando xStreamBufferSetTriggerLevel().

sbRECEIVE_COMPLETED() e sbRECEIVE_COMPLETED_FROM_ISR() são macros que são chamadas (internamente pela API do FreeRTOS) quando os dados são lidos de um buffer de fluxo. As macros verificam se há uma tarefa bloqueada no buffer de fluxo aguardando que o espaço fique disponível dentro do buffer e, se esse for o caso, removem a tarefa do estado Bloqueada. Você pode alterar o comportamento padrão de sbRECEIVE_COMPLETED() fornecendo uma implementação alternativa no FreeRTOSConfig.h.

Buffers de mensagens

Os buffers de mensagens permitem que mensagens discretas de tamanho variável sejam transmitidas de uma rotina de serviço de interrupção para uma tarefa ou de uma tarefa para outra. Por exemplo, mensagens com 10, 20 e 123 bytes de tamanho podem ser gravadas e lidas a partir do mesmo buffer de mensagens. Uma mensagem de 10 bytes só pode ser lida como uma mensagem de 10 bytes, não como bytes individuais. Os buffers de mensagens são criados na implementação do buffer de fluxo. É possível habilitar a funcionalidade do buffer de mensagens incluindo o arquivo de origem stream_buffer.c em seu projeto.

Os buffers de mensagens presumem que há apenas uma tarefa ou interrupção que grava no buffer (o gravador) e apenas uma tarefa ou interrupção que lê a partir do buffer (o leitor). É seguro que o gravador e o leitor sejam tarefas ou rotinas de serviço de interrupção diferentes, mas não é seguro ter vários gravadores ou leitores.

A implementação do buffer de mensagens usa as notificações diretas para tarefas. Portanto, chamar uma API de buffer de fluxo que coloca a tarefa de chamada no estado Bloqueada pode alterar o estado e o valor de notificação da tarefa de chamada.

Para permitir que os buffers de mensagens manipulem mensagens de tamanho variável, o tamanho de cada mensagem é gravado no buffer de mensagens antes da própria mensagem. O tamanho é armazenado em uma variável do tipo size_t, que normalmente é de 4 bytes em uma arquitetura de 32 bytes. Portanto, gravar uma mensagem de 10 bytes em um buffer de mensagens consome na verdade 14 bytes de espaço em buffer. Da mesma forma, gravar uma mensagem de 100 bytes em um buffer de mensagens usa na verdade 104 bytes de espaço em buffer.

Envio de dados

xMessageBufferSend() é usado para enviar dados a um buffer de mensagens a partir de uma tarefa. xMessageBufferSendFromISR() é usado para enviar dados a um buffer de mensagens a partir de uma rotina de serviço de interrupção (ISR).

xMessageBufferSend() permite que um tempo de bloqueio seja especificado. Se xMessageBufferSend() for chamado com um tempo de bloqueio diferente de zero para gravar em um buffer de mensagens e o buffer estiver cheio, a tarefa será colocada no estado Bloqueada até que o espaço seja disponibilizado no buffer de mensagens ou o tempo de bloqueio expire.

sbSEND_COMPLETED() e sbSEND_COMPLETED_FROM_ISR() são macros que são chamadas (internamente pela API do FreeRTOS) quando os dados são gravados em um buffer de fluxo. É utilizado um único parâmetro, que é o identificador do buffer de fluxo que foi atualizado. As duas macros verificam se há uma tarefa bloqueada no buffer de fluxo aguardando dados e, se esse for o caso, elas removem a tarefa do estado Bloqueada.

Você pode alterar esse comportamento padrão fornecendo sua própria implementação de sbSEND_COMPLETED() em FreeRTOSConfig.h Isso é útil quando um buffer de fluxo é usado para transmitir dados entre núcleos em um processador de vários núcleos. Nesse cenário, sbSEND_COMPLETED() pode ser implementado para gerar uma interrupção no outro núcleo da CPU, em seguida, a rotina de serviço da interrupção poderá usar a API xStreamBufferSendCompletedFromISR() para verificar e, se necessário, desbloquear uma tarefa que estava aguardando os dados.

Recebimento de dados

xMessageBufferReceive() é usado para ler dados de um buffer de mensagens em uma tarefa. xMessageBufferReceiveFromISR() é usado para ler dados de um buffer de mensagens em uma rotina de serviço de interrupção (ISR). xMessageBufferReceive() permite que um tempo de bloqueio seja especificado. Se xMessageBufferReceive() for chamado com um tempo de bloqueio diferente de zero para ler a partir de um buffer de mensagens e o buffer estiver vazio, a tarefa será colocada no estado Bloqueada até que os dados sejam disponibilizados ou o tempo de bloqueio expire.

sbRECEIVE_COMPLETED() e sbRECEIVE_COMPLETED_FROM_ISR() são macros que são chamadas (internamente pela API do FreeRTOS) quando os dados são lidos de um buffer de fluxo. As macros verificam se há uma tarefa bloqueada no buffer de fluxo aguardando que o espaço fique disponível dentro do buffer e, se esse for o caso, removem a tarefa do estado Bloqueada. Você pode alterar o comportamento padrão de sbRECEIVE_COMPLETED() fornecendo uma implementação alternativa no FreeRTOSConfig.h.

Suporte ao multiprocessamento simétrico (SMP)

O suporte ao SMP no kernel do FreeRTOS permite que uma instância kernel do FreeRTOS agende tarefas em vários núcleos de processador idênticos. As arquiteturas principais devem ser idênticas e compartilhar a mesma memória.

Modificação de aplicações para usar o kernel FreeRTOS-SMP

A API do FreeRTOS permanece substancialmente a mesma entre as versões de núcleo único e de SMP, exceto essas APIs adicionais. Portanto, uma aplicação escrita para a versão de núcleo único do FreeRTOS deve ser compilado com a versão SMP com o mínimo ou nenhum esforço. No entanto, pode haver alguns problemas funcionais, porque algumas suposições que eram verdadeiras para aplicações de núcleo único podem não ser mais verdadeiras para aplicações de vários núcleos.

Uma suposição comum é que uma tarefa de menor prioridade não pode ser executada enquanto uma tarefa de maior prioridade está em execução. Embora isso fosse verdade em um sistema de núcleo único, não é mais verdade para sistemas de vários núcleos porque várias tarefas podem ser executadas simultaneamente. Se a aplicação se basear em prioridades de tarefas relativas para fornecer exclusão mútua, ela poderá observar resultados inesperados em um ambiente com vários núcleos.

Outra suposição comum é que os ISRs não podem ser executados simultaneamente entre si ou com outras tarefas. Isso não é mais verdade em um ambiente com vários núcleos. O criador da aplicação precisa garantir a exclusão mútua adequada ao acessar dados compartilhados entre tarefas e ISRs.

Temporizadores de software

Um temporizador de software permite que uma função seja executada em um horário definido no futuro. A função executada pelo temporizador é chamada de função de retorno de chamada do temporizador. O tempo entre um temporizador ser iniciado e sua função de retorno de chamada ser executada é chamado de período do temporizador. O kernel do FreeRTOS fornece uma implementação de temporizador de software eficiente porque:

  • Não executa funções de retorno de chamada do temporizador a partir de um contexto de interrupção.

  • Não consome nenhum tempo de processamento, a menos que um temporizador tenha expirado.

  • Não adiciona nenhuma sobrecarga de processamento à interrupção do tique.

  • Não percorre estruturas de lista de links enquanto as interrupções estão desativadas.

Suporte para baixo consumo

Como a maioria dos sistemas operacionais incorporados, o kernel do FreeRTOS usa um temporizador de hardware para gerar interrupções de tique periódicas, que são usadas para medir o tempo. A economia de energia de implementações de temporizador de hardware regulares é limitada pela necessidade de sair periodicamente do estado de baixo consumo e, em seguida, entrar novamente para processar as interrupções de tique. Se a frequência da interrupção de tique for muito alta, a energia e o tempo consumidos ao entrar e sair de um estado de baixo consumo para cada tique superam quaisquer ganhos potenciais de economia de energia, exceto dos modos de economia de energia mais leves.

Para resolver essa limitação, o FreeRTOS inclui um modo de temporizador sem tiques para aplicativos de baixo consumo. O modo de inatividade sem tique do FreeRTOS para a interrupção periódica de tique durante períodos inativos (períodos em que não há tarefas de aplicativo que podem ser executadas) e, em seguida, faz um ajuste de correção no valor de contagem de tiques do RTOS quando a interrupção de tique é reiniciada. Parar a interrupção de tique permite que o microcontrolador permaneça em um estado de economia de energia profunda até que ocorra uma interrupção ou seja o momento de o kernel do RTOS fazer a transição de uma tarefa para o estado pronto.

Configuração do kernel

Você pode configurar o kernel do FreeRTOS para uma placa e aplicativo específicos com o arquivo de cabeçalho FreeRTOSConfig.h. Todo aplicativo criado no kernel deve ter um arquivo de cabeçalho FreeRTOSConfig.h em seu caminho de inclusão do pré-processador. FreeRTOSConfig.h é específico do aplicativo e deve ser colocado sob um diretório de aplicativo e não em um dos diretórios de código-fonte do kernel do FreeRTOS.

Os arquivos FreeRTOSConfig.h das aplicações de demonstração e teste do FreeRTOS estão localizados em freertos/vendors/vendor/boards/board/aws_demos/config_files/FreeRTOSConfig.h e freertos/vendors/vendor/boards/board/aws_tests/config_files/FreeRTOSConfig.h.

Para obter uma lista dos parâmetros de configuração disponíveis para especificar em FreeRTOSConfig.h, consulte FreeRTOS.org.