Baixe o app para aproveitar ainda mais
Prévia do material em texto
3.3 - Laço Principal com Tratadores de Interrupções Laço Principal com Tratadores de Interrupções, o executivo cíclico é uma forma simples e efetiva de resolver o problema de escalonamento quando todas as tarefas são periódicas e possuem um deadline relativo igual ao período. Para sistemas onde existem tarefas esporádicas, especialmente com deadlines relativos menores que o intervalo mínimo entre chegadas, um design mais eficiente para o sistema é não usar um executivo cíclico puro, mas sim um Laço Principal com Tratadores de Interrupções. Desta forma, todas as atividades que não são de tempo real, ou são periódicas com um deadline relativo igual ao período, são colocadas no laço principal que executa periodicamente. Se assume implicitamente que as tarefas no laço ou não possuem deadline ou possuem um deadline relativo maior ou igual ao período do laço. Também se assume implicitamente que o período de tais tarefas é igual ao período do laço, pois elas são executadas uma vez dentro do laço. É possível acomodar facilmente tarefas com períodos múltiplos inteiros do período do laço, basta colocar um contador que é incrementado a cada execução do laço e executar a tarefa em questão a cada X execuções do laço principal. Soluções mais complexas para o código do laço principal podem ser pensadas. Na solução baseada em laço principal com tratadores de interrupções, as tarefas de tempo real mais exigentes são implementadas como tratadores de interrupção. Todos os tratadores de interrupções têm prioridade sobre as tarefas do laço principal, salvo por aqueles momentos onde interrupções são desabilitadas. Isto pode ocorrer quando tarefas do laço principal precisam acessar variáveis globais compartilhadas com os tratadores de interrupções. Em qualquer caso, estas atividades mais urgentes executarão mais rapidamente, não precisando esperar pelas tarefas que agora formam o laço principal. Caso o laço principal seja periódico, é importante observar que o período do laço principal deve comportar não somente o tempo de execução no pior caso de todas as tarefas implementadas no laço, mas também o tempo de execução no pior caso dos tratadores de interrupções, considerando-se todas as interrupções que podem ocorrer durante um ciclo do laço. 3.4 Microkernel Simples como Sistema Operacional Multitarefa Quando o kernel oferece apenas serviços básicos de gerência do processador e não inclui outros serviços, tais como sistema de arquivos ou gerência de memória sofisticada, ele é chamado de microkernel. A técnica conhecida como multiprogramação é empregada para criar a abstração thread, a partir da divisão do tempo do processador. Desta forma, acima do microkernel existem threads as quais executam o código de aplicação. O microkernel cria a ilusão de que as threads executam simultaneamente, enquanto na verdade elas se revezam no uso do processador, sob o controle do microkernel. Tarefas de tempo real são implementadas como threads, cada thread representando um fluxo de execução distinto, o qual é composto basicamente pelo conteúdo dos registradores do processador. 3.4.1 Chamadas de Sistema Threads solicitam serviços ao microkernel através de chamadas de sistema. Tipicamente, chamadas de sistema são implementadas como interrupções de software cujo tratador faz parte do microkernel. Chamadas de sistema são sinalizadas através da execução de uma instrução do tipo interrupção de software. Cada thread representa um fluxo de execução independente. Quando uma thread da aplicação requer um serviço do microkernel e faz uma chamada de sistema na forma de uma interrupção de software, neste momento, o microkernel entra em execução, atendendo esta chamada de sistema. Se ela pode ser atendida imediatamente, isto é feito e a thread retoma sua execução. Na multiprogramação, quando uma thread não pode continuar a executar pois está esperando por algum evento, o que o microkernel faz é colocar outra thread para executar. A maioria dos sistemas inclui uma thread ociosa, a qual executa quando não existe nenhuma outra thread para executar, apenas para evitar que o microkernel fique sem threads para executar. O microkernel não faz nada até que ocorra um evento. 3.4.2 Estados de uma Thread Threads podem ser criadas e destruídas dinamicamente, através de chamadas de sistema realizadas por threads já existentes. É claro que a primeira thread precisa ser criada na inicialização do microkernel, pois neste momento ainda não existe ninguém para fazer chamadas de sistema. No caso de um microkernel também é possível que todas as threads sejam criadas automaticamente, apenas na inicialização do sistema. A thread executa instruções de máquina até o ponto onde algum serviço é requerido do microkernel através de uma chamada de sistema. Normalmente tais atividades são demoradas se comparadas com a velocidade do processador e a thread em questão ficará bloqueada, liberando o processador para ser usado por outra thread. Mesmo no caso de bloqueio, após algum tempo, a ação solicitada pela thread é concluída. Neste momento a thread que estava bloqueada é liberada e poderá retornar para a fila de aptos onde novamente irá disputar o tempo do processador com as demais threads na fila. A forma tradicional de uma thread terminar é a mesma solicitar isto ao microkernel através de uma chamada de sistema. Entretanto, existe a possibilidade de uma thread ser destruída por outra thread através de chamada de sistema. Também é possível a destruição de uma thread caso a mesma cometa um erro crítico que gere uma interrupção de proteção. Quando uma thread torna-se apta, cabe ao escalonador decidir se a thread em execução prossegue com o processador, ou se a mesma deve ceder lugar para a thread recém tornada apta, por uma questão de prioridades entre as threads, por exemplo. 3.4.3 Chaveamento de Contexto entre Threads Na multiprogramação, threads que estão executando são por vezes interrompidas e mais tarde continuadas. Ela é suspensa, o tratador de interrupção é executado, talvez uma outra thread de alta prioridade seja liberada pela chegada de um pacote de dados pela rede e passe a executar. Somente no futuro a thread interrompida será novamente escolhida pelo escalonador e colocada para retomar sua execução. A operação de suspender a thread em execução e colocar uma outra thread para executar é chamada de troca de contexto. Para que uma thread possa passar a executar, ocupando o processador, é necessário recarregar o seu contexto. Quando a thread é interrompida, o conteúdo dos registradores precisa ser salvo. No futuro, quando a thread retoma sua execução, este mesmo conteúdo é recolocado nos registradores, de tal forma que a thread não perceba que sua execução foi temporariamente interrompida, a não ser por um lapso de tempo no relógio de tempo real. O microkernel precisa manter as informações relativas ao contexto de execução de cada thread, de forma a efetuar as cargas e os salvamentos de contexto quando necessário. Para isto o microkernel mantém uma estrutura de dados chamada de Bloco Descritor de Thread (TCB - Thread Control Block). As informações específicas de cada thread são mantidas no seu respectivo TCB, e são constituídas basicamente pelos conteúdos dos registradores quando a thread não estiver executando, além de informações complementares tais como um número Identificador Da Thread (TID – Thread Identification) e a prioridade da thread usada pelo escalonador para definir a ordem de execução. Tipicamente as filas de threads esperando por recursos são representadas dentro do microkernel como listas encadeadas dos respectivos TCBs. Somente quando uma thread nova é criada, um contexto inicial de execução é definido para ela pelo microkernel. Depois disto, a thread executa, altera os registradores, seu contexto de execução é sempre salvo e posteriormentereposto. 3.4.4 Design do Microkernel O microkernel é um programa de computador, normalmente escrito na linguagem C, com algumas pequenas partes em linguagem de montagem. Quando o computador é energizado ou reinicializado o microkernel começa a executar e pode inicializar as suas estruturas de dados. A partir deste momento, não é o microkernel que executa, mas as threads da aplicação. O microkernel não faz nada até que ocorra um evento, ele reage ao evento, e depois volta a não fazer nada. Os eventos que acionam o microkernel são as interrupções. No caso de uma chamada de sistema, a primeira coisa a fazer é «salvar o contexto» da thread executando, pois quando ela voltar a executar no futuro, ela deverá encontrar os mesmos valores nos registradores do processador, com exceção de algum resultado da própria chamada de sistema retornado em registrador. Isto é feito copiando tais valores para os campos apropriados no TCB da thread em questão. Em seguida, o microkernel deve «identificar a chamada» feita. Algumas chamadas de sistema tem um «retorno imediato», tais como «obtem a hora atual» ou «obtem a versão do microkernel». Uma vez concluída a chamada, o «escalonador» é chamado para decidir que thread executará em seguida. O microkernel «carrega o contexto» da thread escolhida a partir do seu TCB para os registradores do processador. Várias chamadas de sistema não podem ser concluídas imediatamente, levando a thread chamadora a um estado de bloqueado. No caso de «não haver retorno imediato» é necessário «retirar a thread chamadora da fila de aptos» e «inserir esta thread chamadora na fila do periférico em questão». A manutenção da fila do periférico é tipicamente feita pelo devicedriver daquele periférico. O mesmo também é responsável por “enviar comandos” ao controlador do periférico, quando isto for necessário. Uma vez que o device-driver concluiu sua execução, não há nada mais a fazer, senão esperar pelo periférico trabalhar. Neste momento, o “escalonador” é chamado, ele escolhe uma nova thread para executar. No caso de uma exceção, a resposta do microkernel vai depender da seriedade da violação de proteção que ocorreu. No caso da «chamada de sistema ter sido concluída», é necessário «retirar a respectiva thread da fila do periférico». Esta thread passa do estado de bloqueada para o estado de apta e, portanto, é necessário «inserir a thread na fila de aptos». Como sempre, uma vez concluídas as ações do device-driver, o «escalonador» é chamado para escolher a próxima thread e o microkernel «carrega o contexto» desta thread.
Compartilhar