Toda a verdade sobre o RTOS de Colin Walls. Artigo 5. Interação e sincronização de tarefas



Em artigos anteriores, examinamos o modelo de multitarefa e descobrimos que cada tarefa é um programa quase independente. Embora as tarefas nos sistemas embarcados tenham um certo grau de independência, isso não significa que elas não "se conhecem". Algumas tarefas serão realmente isoladas de outras, mas a interação e a sincronização entre elas são um requisito comum. Este mecanismo é uma das principais funções do RTOS. A gama de funções pode variar dependendo do RTOS, portanto, neste artigo, consideraremos as opções disponíveis ao público.

Artigos anteriores da série:
Artigo 4. Tarefas, alternância de contexto e interrupções
Artigo # 3 Tarefas e planejamento
Artigo 2. RTOS: estrutura e modo em tempo real
Artigo 1. RTOS: introdução.

Faixa de funções


Existem três modelos de interação e sincronização entre tarefas:

  • Os serviços estão vinculados a tarefas: o RTOS fornece tarefas com atributos que fornecem interação entre eles. Considere os sinais como um exemplo.
  • Os objetos do kernel são meios autônomos de comunicação ou sincronização. Exemplos: sinalizadores de eventos, caixas de correio, filas / canais, semáforos e mutexes.
  • O sistema de mensagens é um esquema simplificado no qual o RTOS permite criar objetos de mensagens e transferi-los de uma tarefa para outra ou várias. Isso é fundamental para a arquitetura do kernel e, portanto, esse sistema é chamado de "mensagens RTOS".

Os mecanismos ideais para diferentes processos variam. Seus recursos podem se sobrepor, por isso vale a pena considerar a escalabilidade desses modelos. Por exemplo, se um aplicativo exigir várias filas, mas apenas uma caixa de correio, você poderá implementar uma caixa de correio com uma fila para um item. Esse objeto não será completamente ideal, mas o código inteiro da caixa de correio não será incluído no aplicativo e, portanto, a escalabilidade reduzirá a quantidade de memória usada pelo RTOS.

Variáveis ​​comuns ou áreas de memória


Uma abordagem simplificada para a interação entre tarefas é a presença no sistema de variáveis ​​ou áreas de memória que estão disponíveis para todas as tarefas. Essa abordagem pode ser aplicada a vários processos, apesar de sua simplicidade. O acesso deve ser controlado. Se a variável for apenas um byte, é provável que gravar ou ler nela seja uma operação atômica (ou seja, contínua), mas é necessário ter cuidado se o processador permitir outras operações nos bytes de memória, pois elas podem ser interrompidas e podem ocorrer problema de sincronização. Uma maneira de implementar o bloqueio / desbloqueio é desativar as interrupções por um curto período de tempo.

Se você usa uma área de memória, ainda precisa de uma trava. Você pode usar o primeiro byte como um sinalizador de bloqueio, já que a arquitetura da memória fornece acesso atômico a esse byte. Uma tarefa carrega dados em uma área de memória, define um sinalizador e aguarda a redefinição. Outra tarefa está aguardando o sinalizador ser definido, lendo os dados e redefinindo o sinalizador. Usar a interrupção desativada como um bloqueio é menos sensível, pois a movimentação de todo o buffer de dados pode levar algum tempo.

Esse uso da memória compartilhada é semelhante à implementação de muitas comunicações entre processadores em sistemas com vários núcleos. Em alguns casos, o bloqueio e / ou interrupção do hardware são incorporados na interface interprocessador da memória compartilhada.

Signals


Os sinais são um dos mecanismos mais simples de interação entre tarefas oferecidas pelo RTOS tradicional. Eles contêm um conjunto de sinalizadores de bit (8, 16 ou 32, dependendo do aplicativo específico), que está associado a uma tarefa específica.
O sinalizador de sinal (ou vários sinalizadores) pode ser definido por qualquer tarefa usando a operação lógica "OR". Os sinalizadores podem ser lidos apenas por uma tarefa que contém um sinal. O processo de leitura geralmente é destrutivo, ou seja, os sinalizadores também são redefinidos.
Em alguns sistemas, os sinais são implementados de uma maneira mais complexa, de modo que uma função especial atribuída pelo proprietário da tarefa do sinal seja executada automaticamente quando qualquer sinalizador for definido. Isso elimina a necessidade da tarefa de controlar os sinalizadores em si. Isso é um pouco semelhante a um manipulador de interrupção.

Grupos de Sinalizadores de Eventos


Grupos de sinalizadores de eventos são semelhantes aos sinais, pois são uma ferramenta orientada a bits para interação entre tarefas. Da mesma forma, eles podem conter 8, 16 ou 32 bits. Ao contrário dos sinais, eles são objetos principais independentes e não "pertencem" a nenhuma tarefa específica. Qualquer tarefa pode definir e redefinir sinalizadores de eventos usando as operações lógicas “OR” e “AND”. Da mesma forma, qualquer tarefa pode verificar sinalizadores de eventos usando as mesmas operações. Em muitos RTOS, você pode fazer uma chamada de API de bloqueio para uma combinação de sinalizadores de eventos. Ou seja, a tarefa pode ser suspensa até que uma combinação específica de sinalizadores de eventos seja definida. A opção "consumir" também pode estar disponível ao verificar os sinalizadores de eventos, que redefinem todos os sinalizadores.

Semáforos


Semáforos são objetos independentes do kernel usados ​​para a contabilidade de recursos. Existem dois tipos de semáforos: binário (pode ter apenas dois valores) e geral (número ilimitado de valores). Alguns processadores suportam instruções (atômicas) que facilitam a rápida implementação de semáforos binários. Os semáforos binários podem ser implementados como semáforos gerais com o valor 1.

Qualquer tarefa pode tentar atribuir um semáforo para obter acesso ao recurso. Se o valor atual do semáforo for maior que 0 (o semáforo está livre), o valor do contador será reduzido em 1; portanto, a atribuição será bem-sucedida. Em muitos sistemas operacionais, um mecanismo de bloqueio pode ser usado para atribuir um semáforo. Isso significa que a tarefa pode estar em um estado de espera até que o semáforo seja liberado por outra tarefa. Qualquer tarefa pode liberar o semáforo e, em seguida, o valor do semáforo aumentará.

Caixas de correio


Caixas de correio são objetos independentes do kernel que fornecem os meios para enviar mensagens. O tamanho da mensagem depende da implementação, mas geralmente é corrigido. Os tamanhos típicos das mensagens são de um a quatro elementos do tamanho de um ponteiro. Normalmente, um ponteiro para dados mais complexos é enviado pela caixa de correio. Alguns kernels implementam caixas de correio de forma que os dados sejam simplesmente armazenados em uma variável regular e o kernel controla o acesso a eles. As caixas de correio também podem ser chamadas de "troca", embora esse nome agora seja raramente visto.

Qualquer tarefa pode enviar mensagens para uma caixa de correio, que é preenchida. Se uma tarefa tentar enviar uma mensagem para uma caixa de correio cheia, ela receberá uma resposta de erro. Em muitos RTOS, você pode usar um mecanismo de bloqueio para enviar para a caixa de correio. Isso significa que a tarefa será suspensa até que a mensagem na caixa de correio seja lida. Qualquer tarefa pode ler mensagens da caixa de correio, após o que está vazia. Se uma tarefa tentar ler de uma caixa de correio vazia, ela receberá uma resposta de erro. Em muitos RTOS, você pode usar uma chamada de bloqueio para ler de uma caixa de correio. Isso significa que a tarefa será suspensa até que uma nova mensagem apareça na caixa de correio.

Alguns RTOS suportam a função "broadcast". Isso permite que você envie mensagens para todas as tarefas atualmente pausadas durante a leitura de uma caixa de correio específica.

Alguns RTOS não oferecem suporte a caixas de correio. Em vez disso, é recomendável usar uma fila de elemento único. Isso é funcionalmente equivalente, mas carrega uma sobrecarga adicional para memória e tempo de execução.

Filas


Filas são objetos independentes do kernel que fornecem um mecanismo para o envio de mensagens. Eles são um pouco mais flexíveis e complexos que as caixas de correio. O tamanho da mensagem depende da implementação, mas geralmente é fixo e orientado para a palavra / ponteiro.

Qualquer tarefa pode enviar mensagens para a fila, e isso pode ser repetido até que a fila esteja cheia, após o que qualquer tentativa de envio resultará em erro. O comprimento da fila geralmente é determinado pelo usuário ao criar ou configurar o sistema. Em muitos RTOS, você pode aplicar um mecanismo de bloqueio à fila. Ou seja, se a fila estiver cheia, a tarefa poderá ser suspensa até que a mensagem na fila seja lida por outra tarefa. Qualquer tarefa pode ler mensagens da fila. As mensagens são lidas na mesma ordem em que foram enviadas (primeiro a entrar - primeiro a sair, FIFO). Se uma tarefa tentar ler de uma fila vazia, receberá uma resposta de erro. Em muitos RTOS, um mecanismo de bloqueio pode ser usado para ler de uma fila vazia. Ou seja, se a fila estiver vazia, a tarefa poderá ser suspensa até que a mensagem seja enviada para a fila por outra tarefa.

Provavelmente, haverá um mecanismo no RTOS para enviar uma mensagem para a frente da fila, isso é chamado de interferência. Alguns RTOS também suportam a função de transmissão. Isso permite que você envie mensagens para todas as tarefas pausadas durante a leitura da fila.

Além disso, o RTOS pode suportar o envio e a leitura de mensagens de tamanho variável. Isso proporciona mais flexibilidade, mas implica sobrecarga adicional.

Muitos RTOSs suportam outro tipo de objeto de kernel, os "pipes". Em essência, um canal é semelhante a uma fila, mas processa dados orientados a bytes.

A funcionalidade das filas não é interessante, mas deve-se entender que elas têm mais sobrecarga de memória e tempo de execução que as caixas de correio, principalmente porque é necessário salvar dois ponteiros: o início e o fim da fila.

Mutexes


Os mutexes (semáforos mutuamente exclusivos) são objetos independentes do kernel que se comportam muito como semáforos binários regulares. Eles são um pouco mais complicados que os semáforos e incluem o conceito de propriedade temporária (um recurso para o qual o acesso é controlado). Se uma tarefa atribuir um mutex, apenas a mesma tarefa poderá liberá-lo novamente: o mutex (e, portanto, o recurso) pertence temporariamente à tarefa.

Os mutexes não são fornecidos por todos os RTOS, mas o semáforo binário comum é bastante fácil de adaptar. Você deve escrever uma função de obtenção mutex que atribua um semáforo e um identificador de tarefa. Em seguida, a função adicional “release mutex” verifica o identificador da tarefa que está chamando e libera o semáforo apenas se corresponder ao valor armazenado, caso contrário, retornará um erro.

Quando trabalhamos em nosso próprio sistema operacional OSRV MAX em tempo real (artigos publicados anteriormente sobre ele), nossa equipe encontrou o blog de Colin Walls, especialista em microeletrônica e firmware da Mentor Graphics. Os artigos pareciam interessantes, os traduziam por si mesmos, mas, para não "escrever para a mesa", eles decidiram publicar. Espero que eles também sejam úteis para você. Nesse caso, planejamos publicar todos os artigos traduzidos da série.

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: blogs.mentor.com/colinwalls , e-mail: colin_walls@mentor.com

Leia o primeiro, segundo, terceiro e quarto artigos publicados anteriormente.

Source: https://habr.com/ru/post/pt415429/


All Articles