37
Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de uma tarefa para outra no meio da execução da função. As regras quem decidem se uma função é reentrante são: 1. Uma função reentrante não pode usar variáveis de uma forma não atômica, a menos que elas estejam armazenadas na pilha da tarefa que chamou a função ou são de outra forma, variáveis privadas desta pilha. 2. Uma função reentrante não pode chamar uma outra função que não seja ela própria uma função reentrante. 3. Uma função reentrante não pode usar o hardware REENTRÂNCIA

Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Embed Size (px)

Citation preview

Page 1: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de uma tarefa para outra no meio da execução da função.

As regras quem decidem se uma função é reentrante são:

1. Uma função reentrante não pode usar variáveis de uma forma não atômica, a menos que elas estejam armazenadas na pilha da tarefa que chamou a função ou são de outra forma, variáveis privadas desta pilha.

2. Uma função reentrante não pode chamar uma outra função que não seja ela própria uma função reentrante.

3. Uma função reentrante não pode usar o hardware de uma forma não atômica.

REENTRÂNCIA

Page 2: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Uma revisão de Armazenamento de Variáveis em Linguagem C

A melhor maneira de compreender a reentrância e em particular a regra 1 acima, é entender onde os compiladores C armazenam as variáveis. Veja o código abaixo e indique quais variáveis são armazenadas na pilha e quais são armazenadas em uma localização fixa da memória?

static int static_int;

int public_int;

int inicializada=4;

char *string=“Para onde esta string vai?”;

void *vponteiro;

void function (int parm, int *ptr_parm)

{

static int static_local;

int local;

:

}

Page 3: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Resposta:

•Static_int - É armazenada em uma localização física da memória e portanto é compartilhada por qualquer tarefa que venha, porventura, a chamar a função.

•Public_int – A única diferença entre static int e public int, é que as funções de outros múdulos C, podem acessar public int.

•inicializada – O mesmo que para as outras variáveis, uma vez que a inicialização de uma variável não muda a forma de acesso da mesma.

•String – O mesmo.

•Vponteiro - O ponteiro em si, está em uma localização fixada na memória e portanto é uma variável compartilhada. Se function() usar ou alterar o valor do dado apontado por vponteiro, então estes valores também serão compartilhados entre as tarefas que venham a chamar function().

•Parm – Está na pilha. Se mais que uma tarefa chamar function(), parm estará em uma localização diferente para cada uma, uma vez que cada tarefa tem sua própria pilha. Não importa quantas tarefas venha a chamar function(), parm não será um problema.

•ptr_parm – está na pilha, portanto function() pode fazer qualquer coisa com o valor de ptr_parm sem problemas. Contudo, se function() utilizar ou alterar o valor apontado por ptr_parm, então devemos nos preocupar com onde esse dado será armazenado antes de sabermos se termos ou não problemas. Se for garantido que cada tarefa que chamar function() passar um valor diferente para ptr_parm, tudo bem, caso contrário, teremos problemas.

Page 4: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

•static local – Está em uma posição fixa na memória. A única diferença entre esta e static_int é que static_int pode ser utilizada por outra função do mesmo arquivo C, enquanto static_local somente poderá ser utilizada por function().

•local – Está na pilha.

APLICANDO AS REGRA DE REENTRÂNCIA

Examine o código da função display abaixo e determine se ela é ou não reentrante e porque sim ou não.

Desafio:

Page 5: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

BOOL fError;

Void diplay(int j)

{

if(!(fError))

{

printf(“Valor: %d”, j);

j=0;

fError=TRUE;

}

else

{

printf(“\nNão pude exibir o valor”);

}

}

Page 6: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

A função não é reentrante por duas razões. Primeira, a variável fError está em uma posição fixa da memória e, portanto, é compartilhada com todas as funções que chamarem display(). O uso de fError não é atômico, porque o SOTR pode chavear entre o tempo que ela é testada e o tempo em que ela é fixada. Portanto, esta função viola a regra 1. Note que a variável j não é problema, pois ela está na pilha.

O segundo problema é que esta função pode, também violar a regra 2. Para esta função ser reentrante, printf() também deve ser reentrante. Pergunta, printf() é reentrante? Pode ser, mas não conte com isso antes de ler o manual que vem com o compilador que você está utilizando e encontrar uma declaração explícita disso.

Page 7: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

As Áreas Cinzas da Reentrância

Existem algumas áreas cinzas entre funções reentrantes e não reentrantes. O código abaixo mostra uma função simples na área cinza.

static int cErros;

void vContaErros(void)

{

++cErros;

}

Esta função obviamente modifica uma variável fora da pilha, mas a regra 1 diz que uma função reentrante pode não usar variáveis fora da pilha de uma forma não atômica. A questão aqui é: será o incremento de cErros atômico?

Pode ser que sim ou não, a resposta depende do ,processador e do compilador que se está utilizando.

Page 8: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Se você estiver utilizando um microcontrolador de 8 bits do tipo 8051, então o compilador pode gerar algo do tipo para cErros():

MOV DPTR,#cErros+01H

MOVX A,@DPTR

INC A

MOV @DPTR,A

JNZ semCarry

MOV DPTR,#cErros

MOVX A,@DPTR

INC a

MOVX @DPTR,A

semCarry:

RET

Page 9: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Que não é atômico e realmente está longe disso, visto que utiliza 10 instruções para fazer o trabalho real e uma interrupção e conseqüentemente uma mudança de tarefa pode ocorrer em qualquer lugar entre essas 10 instruções.

Mas se utilizarmos o 80x86 da Intel. Podemos obter:

INC (cErros)

RET

Que é atômica.

Page 10: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

SEMÁFOROS E DADOS COMPARTILHADOS

Já vimos que os SOTR podem causar uma nova classe de problemas, as variáveis compartilhadas, devido ao chaveamento do microprocessador entre as várias tarefas que concorrem pelo mesmo. Porém os SOTR também fornecem algumas novas ferramentas para lidar com este tipo de problema. O SEMÁFORO é uma dessas ferramentas.

SEMÁFOROS PARA OS SOTR

Os semáforos foram, originalmente, inseridos para controlar o fluxo de trens nas ferrovias e os trens fazem duas coisas com os semáforos: Primeiro, quando um trem sai de uma área protegida dos trilhos, ele levanta o semáforo. Segundo, quando um trem encontra um semáforo, ele espera o semáforo levantar, após o semáforo levantar ele pode passar, porém a sua segurança na área protegida dos trilhos exige que ele abaixe o semáforo ao passar pelo mesmo.

Trens e Linhas Férreas

Page 11: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Embora cunhada para um uso específico, a palavra semáfora encontra, na área de computação, e principalmente nos SOTR, muitos significados diferentes. Nenhum SOTR utiliza os termos rise (levantar) ou lower (abaixar) para a sua terminologia agregada a semáforos, mas utilizam get e give, take e release, pend e post, p e v, wait e signal e inumeras outras combinações. Vamos convencionar o uso dos nomes take para abaixar e release para levantar.Vamso discutir o semáforo mais simples e mais comumente utilizado que é semáforo binário e que lembra muito a operação dos semáforos das linhas férreas.

Esistem duas funções básicas do SOTR para operar com semáforos: TakeSemaforo() para abaixar o semáforo e ReleaseSemaforo() para levantar o semáforo. Se uma tarefa chamar TakeSemaforo() e não chamar ReleaseSemaforo() para libera-lo, todas as outras tarefas que chamarem TakeSemaforo() poderá obtê-lo ficando assim bloqueadas até que a primeira tafera chame ReleaseSemaforo(), liberando o semáforo para o sistema. Somente uma tarefa poderá utilizar o semáforo em um determinado tempo.

Page 12: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Vamos aplicar um semáforo binário ao problema dos tanques.Struct

{

long lNivel_Tanque;

long lTempo_Atualizacao;

} Dados_Tanques[N_MAX_TANQUES];

/* Tarefa Botão */

void vRespostaAoBotao(void) /* Alta prioridade */

{

int i;

while(TRUE)

{

// bloqueie até o usuário apertar um botão.

i = // obtenha o número do botão apertado => identificação do tanque

TakeSemaforo();

printf(“\nTempo: %08ld NIVEL: %08ld”,Dados_Tanque[i].Tempo_Atualizacao,

Dados_Tanque[i.lNivel_Tanque);

ReleaseSemaforo();

}

}

Page 13: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

/* Cont. do código */

/* Tarefa Niveis */

void vCacularNiveisTanques(void) /* prioridade menor */

{

int i=0;

while(TRUE)

{

:

:

TakeSemaforo();

// Estabeleça_Nível_do_Tanque[i].Atualização_Tempo

// Estabeleça_Nível_do_Tanque[i].Nivel_Tanque

ReleaseSemaforo();

:

:

}

}

Page 14: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Antes de “ Tarefa Níveis” (cCalularNiveisTaques) atualizar os dados da estrutura , ela chama TakeSemaforo() para obter (abaixar) o semáforo. Se o usuário pressionar um botão enquanto “Tarefa Níveis” está atualizando os dados e ainda tem o semáforo, então a seguinte seqüência de eventos ocorre:

1. O SOTR chaveará para “Tarefa Botão” como antes, movendo a “Tarefa Níveis” para o estado pronto.

2. Quando “Tarefa Botão” tentar obter o semáforo chamando TakeSemaforo(), ele bloqueará porque “Tarefa Níveis” já tem o semáforo.

3. O SOTR buscará uma outra tarefa e perceberá que “Tarefa Níveis” está ainda no estado pronto, com “Tarefe Botão” bloqueada, “Tarefa Níveis” voltará a rodar até que ela libere o semáforo.

4. Quando “Tarefa Níveis” liberar o semáforo chamando ReleaseSemaforo(), “Tarefa Botão” deixará de estar bloqueada, e o SOTR voltará a chavear para a mesma.

Page 15: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Seqüência das Instruções C

a tarefa de maior prioridade dosbotões desbloqueia; O SOTR

O usuário aperta um botão;

O semáforo não está disponívelA tarefa Botão bloqueia; o SOTR

chaveia de volta

chaveia a tarefa.

!! Determine o nível do tanque[i].NivelTanqueReleaseSemaphore();

A liberação do semáforo desbloqueiaa tarefa Botão; o SOTR chaveia

novamente

(Agora TakeSemaphore() retorna)printf(...................................);ReleaseSemaphore();!! Bloqueia até o usuário pressionar um botão

A tarefa Botão bloqueia; o SOTRretorna para a tarefa Níveis

FLUXO DE EXECUÇÃO COM SEMÁFORO

CÓDIGO DA TAREFA vCalulaNivelTanqueTarefa Níveis está

níveis dos tanques

!! Determine o nível do tanque[i].TempoAtualização

calculando os

TakeSemaphore()

TakeSemaphore();i = !! Obtém a ID do botão

(Isso retorna void)

CÓDIGO DA TAREFA vRespostaAoBotao

A tarefa Botão estábloqueada esperando

por um evento nos Botões

Page 16: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Vamos agora para uma situação de um sistema de controle de um reator nuclear, sendo que desta vez utilizaremos uma tarefa, ao invés de um interrupção para obter as temperaturas. As funções e estruturas de dados cujos nome iniciam com “OS” são aquelas utilizadas no uC/OS. As funções OSSemPost e OSSemPen levantam e abaixam o semáforo respectivamente. A função OSSemCreate inicializa o semáforo, e deve ser chamada antes das outras duas. A estrutura OS_EVENT armazena os dados que representam o semáforo e é inteiramente gerenciada pelo SOTR. O parâmetro WAIT_FOREVER da função OSSemPend indica que a tarefa que está fazendo a chamada, está preocupada com esperar para sempre o semáforo; isso será discutido posteriormente. A função OSTimeDly faz vTarefaLerTemperutura bloquear por aproximadamente um quarto de segundo, o evento que a desbloqueia é simplesmente a expiração desse período de tempo. Portanto, esta tarefa acorda lê as duas temperaturas e as coloca em um arranjo uma vez a cada quarto de segundo. Enquanto isso vTarefaDeControle verifica continuamente se as duas temperaturas são iguais.

Page 17: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

#define TASK_PRIORITY_READ 11

#define TASK_PRIORITY_CONTROL 12

#define STK_SIZE 1024

static unsigned int LeiaPilha[STK_SIZE];

static unsigned int ControlePilha[STK_SIZE];

static int iTemperaturas[2];

OS_EVENT *p_semTemp;

void main(void)

{

/* Inicialize ( mas não parta) o SOTR */

OS_INIT();

/* Explique ao SOTR as tarefas */

OSTaskKCreate(vTarefaLerTemperatura, NULLP,

(void *)&LeiaPilha[STK_SIZE],TASK_PRIORITY_READ);

OSTaskKCreate(vTarefaControle, NULLP,

(void *)&ControlePilha[STK_SIZE],TASK_PRIORITY_CONTROL);

/* Parta o SOTR – Esta função nuca retorna */

OSStart();

}

Page 18: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

void vTarefaLerTemperatura(void)

{

while(TRUE)

{

OSTimeDly(5); /* Deley de ¼ de segundo */

OSSemPend(p_semTemp, WAIT_FOREVER);

// leia a entrada da iTemperatura[0];

// leia a entrada da iTemperatura[1];

OSSemPost (p_semTemp);

}

}

void vTarefaControle(void)

{

p_semTemp = OSSemInit (1);

while(TRUE)

{

OSSemPend(psemTemp, WAIT_FOREVER);

if(iTemperatur´[0] != iTemperatura[1])

// Ative o alarme de temperatura do sistema

OSSemPost(p_semTemp);

// Faça algum outro trabalho útil

}

}

Page 19: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

O código acima apresenta um bug – tente identifica-lo.

Discussão do problema:

O bug se origina com a chamada de OSSemCreate, o qual deve acontecer antes de vTarefaLerTemperatura chamar OSSemPend para usar o semáforo. Como saber se isso realmente ocorre? Impossível.

Pode-se imaginar que desde que vTarefaLerTemperatura chama OSTimeDly no início, antes de chamar OSSemPend, VTarefaControle terá tempo suficiente para chamar OSSemCreate. Sim, você pode fazer isso, mas se seu código para sistemas embutidos confia neste tipo de coisa, você pode colher bugs misteriosos pelo resto de sua carreira. Como saber se, algum dia, não aparecerá alguma tarefa de alta prioridade que consuma todo o tempo de retardo em vTarefaLerTemperatura?

Page 20: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Alternativamente, pode-se imaginar que colocando uma prioridade maior para vTarefaControle em relação a vTarefaLerTemperatura, o problema será resolvido. Sim, estará até que outra pessoa que não participou do desenvolvimento do sistema, mas seja do ramo, perceba que vTarefaLerTemperatura deve ter maior prioridade e altere as prioridades do sistema e efetue a mudança. Aí seu código transformou-se em uma bomba de tempo.

Não se desespere. Ponha a chamada de inicialização do semáforo em algum ponto de partida do código que garanta que ela rode primeiro. Isso pode ser feito na função main, onde deve-se colocar OSSemInit em algum ponto antes da chamada de OSStart.

Page 21: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

REENTRÂNCIA E SEMÁFOROS

Vamos retornar à função vContaErros() que não era reentrante por tinha problema de variável compartilhada. Porém nesta nova versão, a variável static cErros é envolvida por rotinas de chamada de semáforo, ou na liguagem dos dados compartilhados, cErros foi protegida por semáforos. Sempre que uma tarefa chamar vContaErrros(), a primeira será bloqueada ao tentar utilizar o semáforo. Na linguagem da reentrância, nós tornamos cErros atômica., não no sentido de que ela não possa ser interrompida por um evento qualquer, mas no sentido que ela não pode ser interrompida por alguma coisa semelhante a ela que procure utilizar a mesma variável compartilhada. Portant, tornamos assim a função vContaErrros() reentrante.

As funções e estruturas de dados que comecem com “NU” são aquelas utilizadas em um SOTR denominado Nucleous (Accelerate Techology Incorporated)

Page 22: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

void Tarefa1(void)

{

:

vContaErros(9);

:

}

void Tarefa2 (void)

{

:

vContaErros(11);

:

}

static int cErros;

Static NU_SEMAPHORE semErros;

void vContaErros(int cNovosErros)

{

NU_Obtain_Semaphore (SemErros, NU_SUSPENDED);

cErros += cNovosErros;

NU_Release_Semaphore(&SemErros);

}

Page 23: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

O parâmetro NU_SUSPENDED da função Obtain_Semaphore() é semelhante ao WAIT_FOREVER utilizado em um outro exemplo.

Pode-se imaginar se o código do exemplo funcionaria se as chamadas NU_Obtain_Semaphore() e NU_Release_Semaphore() estivessem envolvendo a chamada de vContaErros()? Sim, mas é preciso se lembrar que obter e liberar o semáforo em torno de cada chamada da função. Com a colocação da chamada dos semáforos dentro da função vContaErros() é impossível esquecer.

Page 24: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

MÚLTIPLOS SEMÁFOROS

Nos dois exemplos anteriores pudemos perceber que as todas as funções semáforo tem um parâmetro que identifica o semáforo que está sendo utilizado, abaixado ou levantado. Visto que a maioria dos SOTR têm quantos semáforos quanto se desejar, cada chamada do SOTR deve identificar sobre qual semáforo deve-se operar. Os semáforos são todos independentes um do outro: se uma tarefa toma o semáforo A e outra o semáforo B, não ocorre bloqueio. Da mesma forma, se uma tarefa está esperando pelo semáforo C, ela ainda permanecerá bloqueada se uma outra tarefa liberar o semáforo D.

Qual é a vantagem de se ter múltiplos semáforos?

Sempre que uma Tarefa toma um semáforo, ela está, potencialmente, diminuindo o tempo de resposta de qualquer outra tarefa que necessite do mesmo semáforo. Em um sistema com apenas um semáforo, se a tarefa de menor prioridade tomar o semáforo para atualizar os dados em um arranjo de temperaturas, uma tarefa de maior prioridade pode ficar bloqueada esperando por esse semáforo.

Page 25: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Com múltiplos semáforos é possível colocar u, para proteger a temperaturas e outro para proteger a contagem de erros, com a tarefa de maior prioridade podendo obter o processador sem ficar bloqueado por uso de semáforo, e por outro lado as variáveis compartilhadas continuarão a ser protegidas quando as funções que a utilizam estiverem em suas regiões críticas.

Diferentes semáforos podem corresponder a diferentes recursos compartilhados.

Como pode um SOTR saber qual semáforo protege qual dado?

Ele não sabe. Se você estiver utilizando múltiplos semáforos, você deverá se lembrar de qual semáforo corresponde a qual dado. Uma tarefa que está modificando a contagem de erros deve ter um semáforo correspondente.

Você deve decidir qual dado compartilhado cada um de seus semáforos protege.

Page 26: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

OS SEMÁFOROS COMO DISPOSITIVOS DE SINALIZAÇÃOUm outro uso comum dos semáforos é como um meio simples de comunicação de uma tarefa com outra, ou de uma rotina de interrupção com uma tarefa.

Suponha que a tarefa que formata relatórios impressos construa esses relatórios em um buffer na memória fixa. Suponha também que a impressora interrompa após cada linha e que a rotina de interrupção da impressora alimente a próxima linha para a impressora cada vez que ela interrompe. Em tal sistema, após a formatação de um relatório no buffer, a rotina deve esperar até que a rotina de interrupção tenha terminado de imprimir esse relatório antes de poder formatar o próximo relatório.

Uma maneira de resolver esse problema muito facilmente, é fazer a tarefa esperar por um semáforo após ter formatado cada relatório. A rotina de interrupção assinala para a tarefa quando um relatório tiver sido mandado para a impressora liberando o semáforo. Quando a tarefa obtém o semáforo e desbloqueia, ela sabe que pode formatar o próximo relatório.

Exemplo:

Page 27: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

/* Local para construir os relatório */

static char a_chPrint[10][21];

/* Contagem das linhas do relatório */

static int iTotalLinhas;

/* Contagem de linhas já impressas */

static int iLinhasImpressas;

/* Semáforo para esperar pelo fim do relatório */

Static OS_EVENT *SemImpressora;

Continua:

Page 28: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

void vTarefaImpressora(void)

{

BYTE byErro; /* Local para um retorno de erro */

int wMsg;

/* Inicializar o semáforo como já abaixado */

SemImpressora = OSSemInit(0);

while(TRUE)

{

/* Espere por uma mensagem dizendo qual relatório formatar *

wMsg=(int) OSQPend (QPrinterTask, WAIT_FOREVER, &byErro);

!! Formate o relatório em c_chPrint

iTotalLinhas = !! Conta o número de linha do relatório

/* Imprime a primeira linha do relatório */

iLinhasImpressas=0;

vSaidaLinhaImpressoraHard(a_chPrint[iLinhasImpressas++]);

/* Espere a tarefa de impressão terminar */

OSSemPend(SemImpressora, WAIT_FOREVER,&byErro);

}

} continua

Page 29: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

void vInterrupcaoImpressora(void)

{

if(iLinhasImpressas == iTotalLinhas)

/* O relatório está pronto */

OSSemPost (SemImpressora);

else

/* Imprima a próxima linha */

vSaidaLinhaImpressoraHard(a_chPrint,[LinhasImpressas++]);

}

A rotina de interrupção libera o semáforo e portanto desbloqueia a tarefa quando o relatório já foi impresso.

Page 30: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

PROBLEMAS COM SEMÁFOROS

Aparentemente. Os semáforos constituem a solução para todos os problemas de dados compartilhados, porém isso não é verdade. De fato nossos sistemas trabalharão tanto melhor quanto menos utilizarmos os semáforos. O problema é que os semáforos funcionam bem somente se forem utilizados perfeitamente, e ninguém garante, dentro de uma equipe de desenvolvimento, que todos dominam bem o assunto.

Modos de se estragar a utilização dos semáforos:

Esquecer do abaixar ou tomar o semáforo. O semáforo somente funciona se a tarefa que utiliza o dado compartilhado, para escrita ou leitura, usar o semáforo. Se houver esquecimento, então o SOTR pode chavear pata fora de um código que esqueceu de tomar o semáforo e causa um bug feio de dado compartilhado.

Page 31: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Esquecer de liberar o semáforo. Se uma tarefa falha na liberação de um semáforo, então todas as tarefas que utilizam esse semáforo, mais cedo ou mais tarde ficarão bloqueadas esperando obtê-lo numa condição de bloqueio eterno.

Tomando o semáforo errado. Em um sistema de múltiplos semáforos, isto pode causar problemas imprevisíveis.

Mantendo um semáforo por muito tempo. Sempre que uma tarefa toma um semáforo, todas as tarefas que utilizam este semáforo deve esperar até que esse semáforo seja liberado. Se uma tarefa matém um semáforo por muito tempo, outras tarefas podem falhar em suas operações de tempo real devido a ficarem fora do tempo (deadline).

Um exemplo particularmente perverso disso, ocorre se o SOTR chavear de uma tarefa de baixa prioridade (Tarefa C) para uma de prioridade média de longa duração (Tarefa B) , após a tarefa C tomar um semáforo. Uma tarefa de alta prioridade (Tarefa A) que deseja o semáforo tem então que esperar até que a tarefa B libere o processador: A tarefa C não pode liberar o semáforo até que ela obtenha o controle do processador. Não importa o quão cuidadosamente se tenha codificado a tarefa C, a tarefa B não permite que a tarefa C acesse o processador e isso coloca a tarefa A em espera indefinida.

Page 32: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Problema de inversão de prioridade

Esse problema é denominado inversão de prioridade, alguns SOTR resolvem esse problema com herança de prioridade – Eles reforçam temporariamente a prioridade da tarefa C para igual a da tarefa A, toda vez que a tarefa C obtém o semáforo e a tarefa A está esperando por ele.

A tarefa A obtém uma mensagem de suafila e desbloqueia; O SOTR chaveiapara a tarefa A

A tarefa A tenta obter o semáforoque a tarefa C já tomou.

TAREFA C

A tarefa B obtém uma mensagemde sua fila e desbloqueia; o SOTR

que divide com a tarefa A

TAREFA B

TAREFA A

A tarefa C toma o semáforo

chaveia para a tarefa B..

Linha do tempo

A tarefa B continua rodando e rodandoe rodando, não dando nunca uma chancepara a tarefa C liberar o semáforo. A tarefade alta prioridade A fica bloqueada.

Page 33: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Provocando um abraço mortalO código do próximo slide mostra o problema do abraço mortal . As funções ajsmrsv() e ajmrls() do código, são de um SOTR denominado AMX (Kadak Products). A função ajsmrsv() “reserva” um semáforo, e a função ajmrls() “libera” o semáforo. Os dois parâmetros adicionais a ajsmrsv() são as informações de time-out e a prioridade que não são relevantes aqui. No código, tanto Tarefa1 como Tarefa2 atuam sobre as variáveis a e b após obter a permissão de uso pela obtenção dos semáforos hSemaphoreA e hSemaphoreB.

Dá para ver qual é o problema?Considere o que ocorre se vTarefa1 chamar ajsmrsv() para obter hSemaphoreA, mas antes dela poder chamar ajsmrsv() para obter o hSemaphoreB, o SOTR para e roda vTarefa2. A tarefa vTarefa2 agora chama ajsmrsv() para obter hSemaphoreB, quando vTarefa2 chama ajsmrsv() para obter hSemaphoreA, ela bloqueia, porque uma outra tarefa (vTarefa1) já está com hSemaphoreA. O SOTR chaveia de volta para vTarefa1, a qual chama agora ajsmrsv() para a obtenção de hSemaphoreB, que já está com vTarefa2, assim vTarefa1 bloqueia. Como nenhuma das tarefas liberará seu semáforo e ao mesmo temo estão esperando a liberação do semáforo da outra, termos um abraço mortal ou sistema em deadlock.

Page 34: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Int a;

Int b;

AMXID hSemaphoreA;

AMXID hSemaphoreB;

void Tarefa1(void)

{

ajsmrsv(hSemaphoreA, 0, 0);

ajsmrsv(hSemaphoreB, 0, 0);

a=b;

ajsmrls(hSemaphoreB, 0, 0);

ajsmrls(hSemaphoreB, 0, 0);

}

void Tarefa2(void)

{

ajsmrsv(hSemaphoreB, 0, 0);

ajsmrsv(hSemaphoreA, 0, 0);

b=a;

ajsmrls(hSemaphoreA, 0, 0);

ajsmrls(hSemaphoreB, 0, 0);

}

Page 35: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Variações nos Semáforos

Existem tipos diferentes de semáforos e podem variar de SOTR para SOTR.

Alguns sistemas oferecem semáforos que podem ser tomados múltiplas vezes. Tais semáforos são essencialmente inteiros, a tomada dos mesmo decrementa o inteiro associado ao mesmo e a liberação incrementa esse inteiro. Se uma tarefa tentar tomar o semáforo quando o inteiro associado ao mesmo é zero, ela bloqueará. Esses semáforos são chamados de semáforos contadores e constituem o tipo original de semáforos.

Alguns sistemas oferecem semáforos que podem ser liberados apenas pela tarefa que os tomou. Esses semáforos são úteis nos problemas de dados compartilhados, mas não podem ser utilizados para comunicação entre duas tarefas. Tais semáforos são chamados de semáforos de recurso.

Alguns SOTR oferecem semáforos para lidar especificamente com o problema de inversão de prioridade e outro que não lida com isso. O semásforo do primeiro caso é denominado de semáforo mutex ou simplesmente mutex. ( Alguns SOTR usam semáforos que eles chamam de mutex, mas não lidam com o problema de inversão de prioridade).

Page 36: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

Maneiras de Proteger Dados Compartilhados

Em geral os dois modos de se proteger dados compartilhados é através da desabilitando as interrupções e utilizando semáforos. Existe um outro modo que merecer ser mencionado que é a desabilitação do chaveamento de tarefa. A maioria dos SOTR tem duas funções que podem ser chamadas, uma de desabilitação do chaveamento de tarefa e outra de reabilitação do chaveamento das mesmas.

COMPARAÇÃO ENTRE OS TRÊS MÉTODOS

Desabilitação das Interrupções: é o método mais dramático, visto que afeta os tempos de respostas de todas as rotinas de interrupção e de todas as tarefas do sistema, uma vez que isso coloca o escalonador do sistema fora de ação, por outro lado a desabilitação das interrupções têm duas vantagens. (1) É o único método que funciona se nosso dados forem compartilhados entre nossas tarefas e nossas rotinas de interrupção. Note que a desabilitação do chaveamento entre tarefas não desabilita as interrupções, por outro lado a desabilitação das interrupções desabilitará o chaveamento entre tarefas.

Page 37: Funções reentrantes são aquelas que podem ser chamadas por mais de uma tarefa e ainda assim, sempre trabalham corretamente, mesmo que o SOTR chaveie de

(2)É rápido. A maioria doa processadores pode habilitar e desabilitar interrupções com uma única instrução, enquanto que todas as funções dos SOTR têm muitas instruções. Se o acesso de uma tarefa a um dado compartilhado demora apenas um curto período de tempo – o incremento de uma variável, por exemplo – as vezes é preferível sofre um pequeno contratempo na respostas das rotinas de interrupção, devido a desabilitação da interrupções, que contratempos nas respostas das tarefas devido ao uso de semáforos ou desabilitação do chaveamento das tarefas.

Semáforos é o meio mais visado para se proteger dados, devido aos seus efeitos apenas sobre as tarefas que necessitam utilizar o mesmo semáforo. Os tempos de resposta das tarefas e das rotinas de interrupção que não necessitam do semáforo ficam invariáveis. Por outro lado os semáforos consomem algum tempo de processamento, embora não muito na maioria dos SOTR e eles não funcionam para rotinas de interrupçao.

Desabilitação do Chaveamento de Tarefas Está em algum lugar entre os dois mencionados acima. Nâo tem efeito sobre as rotinas de interrupçao, mas paralisa a resposta do processador para todas as outras tarefas.