• Nenhum resultado encontrado

Os sistemas operativos de tempo-real são utilizados para vários propósitos tais como em sistemas de controlo ou no processamento de dados onde estes requerem

tempo-real. A principal característica deste sistema está no cumprimento de certas

deadlines. Existem dois tipos de RTOS: o soft e o hard real-time (figura 2.3).

Sistemas áudio utilizam o soft real-time pois no sistema em que se insere é tolerável uma falha de cumprimento de uma deadline, mas em sistemas como aeronáuticos o não cumprimento de uma deadline pode tornar-se fatal, por isso a utilização hard

real-time torna-se importante porque promove o determinismo e a satisfação de

todos os deadlines [18]. Deadline t v(t) Deadline t v(t) (a) (b)

Figura 2.3: Diferença de resposta: soft real-time (a) hard real-time (b)

Sistemas como estes são valorizados pela rapidez e previsibilidade de resposta e não na quantidade de tarefas realizadas num determinado tempo. A maior parte destes sistemas optam por três comportamentos: escalonamento e mecanismos que permitem a execução prioritária de tarefas de alta prioridade; previsibilidade na sincronização entre as tarefas, permitindo saber o tempo e duração necessária de cada execução; e ter um comportamento determinístico [6]. A junção destes comportamentos e a minimização das latências de interrupções e das comutações entre tarefas torna a implementação destes sistemas muito complexo.

Gestor de Tarefas

Uma grande vantagem de ter um sistema operativo é a possibilidade de correr várias aplicações em simultâneo. O multitasking, é um processo utilizado que permite o escalonamento e comutação pelos CPU das várias tarefas presentes no sistema operativo dando essa sensação que estão a correr em simultâneo. O TCB (task control block), uma estrutura de dados que contém informações referentes a cada tarefa. Como informações básicas está o seu contexto ( program counter e registos com contextos), o seu nome ou número de identificação, o seu estado e a

sua prioridade. A importância de guardar o contexto de cada tarefa, deve-se ao escalonamento das tarefas. Estas podem ser interrompidas durante a sua execução, levando assim à necessidade de guardar as informações necessárias para permitir executar a tarefa no ponto que foi interrompida (figura 2.4). As informações estão relacionados com o conteúdo presente em cada registo de CPU utilizados, na posição da stack que pertence a dita tarefa e na posição anterior do program

counter no momento da interrupção. Este processo é designado por context switch.

Inativa Disponível para execução Em execução Pendente Tarefa criada Em espera por um evento Context switch Evento disponível Tarefa eliminada Tarefa Preempted Tarefa eliminada

Figura 2.4: Diferentes estados das tarefas

O gestor de tarefas é um serviço que disponibiliza ferramentas para a criação, manutenção e eliminação de tarefas. Este é responsável pela inicialização e gestão da TCB de cada tarefa. Contudo, o gestor de tarefas varia consoante o sistema operativo. O número máximo de tarefas disponíveis pode estar limitado pela memória (de código e de dados) ou pelo número máximo definido pelo sistema, o número de estados existentes para cada tarefa e o seu tipos de escalonamento também pode variar.

O gestor de tarefas pode restringir as tarefas em prioridades fixas ou em prioridades dinâmicas. Prioridades fixas, o sistema não permite que a tarefa possa mudar de prioridade, sendo que esta durará até ao seu fim de vida. Prioridades dinâmicas, o gestor de tarefas permite alterar a prioridade da tarefa permitindo assim conjugar

os momentos onde é mais importante que essa tarefa seja execute e vice-versa [21, 20].

Escalonador

Existem vários algoritmos de escalonamentos que podem variar consoante o pro- pósito do sistema operativo. Sistemas operativos que se baseiam no algoritmo HPF (Highest Priority First) baseiam-se num escalonamento por prioridade. A prioridade das tarefas varia consoante a sua importância: tarefas de maior prio- ridade prevalecem sobre as tarefas de menor prioridade, e estas só executam no término de tarefas de maior prioridade. A atribuição da prioridade neste tipos de escalonamentos deve ser cuidada para não acontecer o chamado starvation. Este fenómeno pode ocorrer no caso de tarefas em loop terem maior prioridade que outras, impossibilitando assim que a tarefa de menor prioridade seja executada.

O Round Robin é outro algoritmo de escalonamento. Este é usado na maior parte dos sistemas, sendo usado como algoritmo de desempate para o escalonamento de tarefas com prioridade iguais. Baseia-se no estado de cada tarefa, dando-lhe uma execução sequencial dos vários estados existentes no sistema até ao seu fim de execução. Contudo, este escalonamento é muito usado com um time slicing, figura 2.5. Em cada tarefa é dado um tempo limite de execução. A expiração desse tempo obriga a tarefa a suspender a sua execução dando oportunidade da execução de outras tarefas pendentes ou sem estarem inicializadas.

Prioridade Tarefa A Tarefa B Tarefa A Tarefa C Tarefa A Tarefa B Time slice t - Fim da tarefa - Ação preemptive

Figura 2.5: Exemplo de preempção no escalonamento Round Robin.

bém é usado na existência de tarefas com a mesma prioridade. Esta filosofia é mais simples que a anterior, baseia-se também nos estados mas o seu escalona- mento é baseado na primeira tarefa que entra no estado de RUN ou de READY obtém maior precedência. Contudo chamadas ao sistema podem facilitar a mudar a relação de precedências entre tarefas com a mesma prioridade [21, 20].

Gestor de Temporização

A maior parte dos sistemas operativos necessitam de temporizadores, tanto para um funcionamento a nível do kernel com a nível das aplicações. Este serviço ofe- rece ao sistema e às aplicações, um maior controlo com a utilização de interrupções periódicas na forma de delays ou de timeouts. Uma dessas necessidades está pre- sente no escalonamento por Round Robin com time-slice. Este serviço auxilia o escalonador para utilização de timeouts com finalidade de interromper uma tarefa no caso destas se tratarem de tarefas em loop.

A utilização deste serviço está baseada num timer geral chamado de Clock Tick. Este é usado para a frequência geral do sistema sendo usado para aumentar ou diminuir a resolução das aplicações. A frequência pode ser ajustada consoante o propósito da aplicação, pois a utilização de frequências altas do tick permite uma maior resolução mas como consequência pode aumentar o overhead do sistema.

Neste sentido, o gestor de temporização disponibiliza um número de serviços de gestão de tempos, como delays ou suspensão depois de “N” ticks, delays de se- gundos ou milissegundos para um propósito específico, obter o valor do tick, ou atribuir o valores ao tick ou entre outros tipos funcionalidades que podem ser exigidas a um temporizador [21, 20].

Gestor de Memória

A necessidade de algumas aplicações requisitarem blocos de memória exigiu a introdução de um gestor de memória. Fazer a gestão da memória em si não é algo trivial. Geralmente o uso de funções como o malloc() e free() é usado de forma não determinística, criando assim fragmentações de memória. Chama-se fragmentação de memória ao desenvolvimento de descontinuidades de pequenos blocos de memória livre na memória do sistema. Isto pode-se tornar perigoso em sistemas embebidos de tempo-real, pois leituras erradas ou perdas de dados podem

corromper o seu funcionamento.

Uma das maneiras de contornar as descontinuidades de memória resume-se à di- visão da memória em pequenos blocos iguais e agrupá-los com intuito de formar partições de memória contínua (figura 2.6). Este mecanismo permite ao kernel alocar as aplicações para uma partição, possibilitando organizar e guardar o con- texto dessa mesma, num bloco contínuo de memória. Assim, à eliminação de uma aplicação, resume-se a eliminação da partição, e não na eliminação de pequenos blocos memória descontínuos. Estes dois tipos de eventos, alocação e eliminação de partições, já se podem considerar determinísticos, pois geralmente é possível prever quando vão ser eliminados ou criadas partições de aplicações.

Tamanho da partição

Tamanho do bloco

Partição #1 Partição #2 Partição #3

Figura 2.6: Diferentes divisões de memória cada uma com diferentes tamanhos

Contudo, partições podem conter diferentes tamanhos. Isto acontece porque não existe aplicações iguais e cada aplicação tem os seus requisitos mínimos de memó- ria. Por isso, cada aplicação deve saber o espaço necessário de memória que irá precisar para ser feita a alocação da partição e esta deverá ser apresentada num bloco contínuo[21, 20].

Sincronização

Os serviços de sincronização permitem a gestão de recursos partilhados. Estes re- cursos partilhados são considerados secções críticas e estão tipicamente na forma de variáveis (estáticas ou globais), estruturas de dados, ou registos de dispositi-

vos de input/output. Esta necessidade deveu-se ao ambiente multitasking com a conveniência de tarefas ou interrupções poderem comunicar ou partilhar recursos de forma segura entre elas. Os mecanismos mais conhecidos que permitem fazer a gestão do acesso exclusivo a determinados resursos são: (i) desativação das in- terrupções, (ii) semáforos e (iii) mutexes. Contudo, estes mecanismos têm que ser cuidadosamente usados, garantindo que cada tarefa tenha acesso exclusivo aos dados, evitando assim a contenção e corrupção dos dados. O primeiro mecanismo, desabilitar e habilitar interrupções, não é muito aconselhável para sistemas de tempo-real, pois mesmo usando para aceder a dados de forma muito rápida pode ter impacto na latência das interrupções.

Esperar pelo semáforo() Pode aceder ao buffer

Fim do semáforo()

Esperar pelo semáforo() Pode aceder ao buffer

Fim do semáforo() Buffer Tarefa B Tarefa A

Figura 2.7: Tarefas a pedir acesso ao recurso.

O segundo mecanismo, semáforos, são divididos em dois tipos: semáforos binários e semáforos contadores. Como o seu nome indica, os semáforos binários contém apenas dois valores: 0 e 1. Por outro lado, os semáforos contadores dependem da quantidade de bits que têm disponíveis. A escolha sobre o tipo de semáforos depende do número tarefas que necessitam do acesso ao mesmo tempo a um certo tipo de recurso. A funcionalidade do semáforo serve para sinalizar um pedido de acesso a um recurso. Imagine-se, por exemplo, que um determinado dado está “fechado à chave” e nesse preciso momento, uma tarefa em execução necessita dessas informações para poder avançar no seu processo. Esta, com a utilização do semáforo, faz um pedido e espera até poder obter a “chave” para abrir o acesso aos dados como está apresentado na figura 2.7.

Entretanto, este tipo de sincronização em sistemas de tempo-real baseado na pre- emptividade pode originar um fenómeno designado de inversões de prioridade. Isto acontece quando tarefas de diferentes prioridades requisitam o mesmo re-

curso. Uma tarefa com prioridade alta pode ser posta em espera devido a um recurso não estar disponível por este já ter sido requisitado por uma tarefa de prioridade menor. É possível dizer que a tarefa de prioridade alta teve que reduzir a sua prioridade, para a mesma prioridade da tarefa de prioridade menor. Por esse mesmo motivo, este método não é aconselhado para sistemas com requisito de

deadlines. O exemplo da figura 2.8 retrata esse fenómeno: (1) a tarefa C de menor

prioridade está a ser executada e pede ao semáforo;(2) a tarefa A de maior priori- dade interrompe a tarefa C e começa a ser executada; (3) a tarefa A tenta obter o semáforo;(4) a tarefa C interrompe a tarefa A para libertar o semáforo, mas no preciso momento a tarefa B entra em execução deixando a tarefa C pendente;(5) a tarefa C volta a ser executada e liberta o semáforo;(6) a tarefa de maior prioridade volta a ser executada obtendo o semáforo [21, 20].

Prioridade Tarefa C Tarefa A Tarefa B t Prioridade Invertida (1) (2) (3) (4) (5) (6)

Figura 2.8: Inversão de prioridade.

A situação da inversão de prioridade pode ser resolvida com a utilização de mutex. Se fosse utilizado um mutex no exemplo anterior, tarefa de menor prioridade pas- saria a ter o valor de prioridade igual à tarefa que necessitava do mesmo recurso até libertar o recurso. Terminado, voltaria a prioridade inicialmente dada. Con- tudo, este mecanismo apresenta também as suas desvantagens: deadlock. Isto pode ocorrer no seguimento de mais do que uma tarefa estar à espera de um recurso que está a ser usado por outra tarefa.

Mensagens

Como anteriormente descrito, tarefas e ISR partilham recursos, e consequente- mente a partir desses recursos podem trocar informações entre elas. Chama-se

inter-task communication a troca de informações entre tarefas ou ISR. Esta troca

de informações pode ser feita de duas formas: por variáveis globais ou por mensa- gens. A partir de variáveis globais a troca de informações é executada passando a informação nas variáveis globais, contudo, não existe mecanismos que informem da existência de troca de informação, a menos que sejam utilizados sinais ou então que seja verificado periodicamente uma determinada flag. O conceito de envio de informação por mensagem está baseado na troca de informação a partir do intermediário chamado de “message queue”, figura 2.9.

ISR Receber() Enviar () Enviar () Message queue Tarefa B Tarefa A

Figura 2.9: Enviar e receber mensagens a partir da queue

Message queue é um objeto alocado no kernel que disponibiliza mecanismo para

as tarefas poderem criar uma queue, mandar uma mensagem a partir da queue ou esperar por uma mensagem. Neste sentido, as ISR também podem utilizar este objeto estando limitadas só ao mecanismo de envio de mensagem a partir da queue, pois a rapidez que as ISR necessitam de ser processada não permite estarem em modo de espera para uma possível mensagem. Dependendo do kernel as message

queue podem ter dois tipos de comportamentos: FIFO (first-in,first-out) ou LIFO

(last-in,first-out). Nem todos os kernel disponibilizam o comportamento LIFO, contudo, este mecanismo pode ser bastante benéfico, no caso de dar prioridade a certas mensagens consideradas como urgentes. Neste caso e na existência de uma fila com várias mensagens, a mensagem “urgente” seria processada em primeiro lugar.

Receber() Enviar ()

Message queue

Enviar sinal() Esperar por sinal()

Tarefa A

Tarefa B

Figura 2.10: Gestão de mensagens com semáforos.

devido a estas não serem processadas ao mesmo nível que são enviadas. Neste sentido, o modelo mais adequado é a utilização de semáforos contadores permitindo limitar o envio de mensagens consoante o nível disponível de processamento [21, 20].

Documentos relacionados