Processos e Threads Capítulo 2 Processos - PUC-Rio

Processos e Threads Capítulo 2 Processos - PUC-Rio Processos e Threads Capítulo 2 Processos - PUC-Rio

di.inf.puc.rio.br
from di.inf.puc.rio.br More from this publisher
11.04.2013 Views

1 2 Capítulo 2 Processos e Threads 2.1 Processos 2.2 Threads 2.3 Comunicação interprocesso 2.4 Problemas clássicos de IPC 2.5 Escalonamento Processos O Modelo de Processo • Considere a multiprogramação de 4 programas: a) O contador de programa (PC) alternadamente assume endereços de cada programa b) Conceitualmente são 4 processos sequenciais independentes c) Somente um programa está ativo a cada momento 1

1<br />

2<br />

<strong>Capítulo</strong> 2<br />

<strong>Processos</strong> e <strong>Threads</strong><br />

2.1 <strong>Processos</strong><br />

2.2 <strong>Threads</strong><br />

2.3 Comunicação interprocesso<br />

2.4 Problemas clássicos de IPC<br />

2.5 Escalonamento<br />

<strong>Processos</strong><br />

O Modelo de Processo<br />

• Considere a multiprogramação de 4 programas:<br />

a) O contador de programa (PC) alternadamente assume<br />

endereços de cada programa<br />

b) Conceitualmente são 4 processos sequenciais<br />

independentes<br />

c) Somente um programa está ativo a cada momento<br />

1


4<br />

Criação de <strong>Processos</strong><br />

Um sistema pode executar um conjunto grande de<br />

processos simultâneos/concorrentes.<br />

Principais eventos que levam à criação de<br />

processos:<br />

1. Ao iniciar o sistema operacional (o init)<br />

2. Um processo pai cria um novo processo<br />

(chamda fork())<br />

• Usuário executa comando/ inicia programa através da<br />

shell<br />

• Processo cria um filho para tratar de uma requisição<br />

específica (p.ex. Inet cria processo para tratar<br />

requisição ftp, rsh, etc.)<br />

5<br />

• Início de um programa e em momento prédeterminado<br />

(através do cron daemon)<br />

Término de <strong>Processos</strong><br />

Condições que levam ao término de<br />

processos:<br />

1. Saída normal (voluntária)<br />

2. Saída por erro (voluntária)<br />

3. Erro fatal (involuntário)<br />

4. Cancelamento por um outro processo<br />

(involuntário), através de um sinal.<br />

2


6<br />

Hierarquias de <strong>Processos</strong><br />

• Processo pai cria um processo filho,<br />

processo filho pode criar seu próprio<br />

processo, etc.<br />

• Forma-se uma hierarquia de processos<br />

– UNIX chama isso de “grupo de processos”<br />

• Windows não possui o conceito de<br />

hierarquia de processos<br />

– Todos os processos são criados no mesmo<br />

nível<br />

Estados de <strong>Processos</strong><br />

• Ao longo de sua execução, um processo pode assumir , os<br />

seguintes estados:<br />

– new: processo foi criado.<br />

– running: instruções sendo executadas.<br />

– waiting: Processo aguarda a ocorrência de algum sinal/interrupção.<br />

– ready: Processo aguarda alocação do processador.<br />

– terminated: Processo terminou a sua execução.<br />

7<br />

3


10<br />

Estados de <strong>Processos</strong> (um pouco<br />

mais proximo da realidade)<br />

Implementação de <strong>Processos</strong><br />

A cada processo estão associadas informações sobre o seu<br />

estado de execução (o seu contexto de execução),<br />

Estas ficam armazenadas em uma entrada da Tabela de<br />

<strong>Processos</strong> (e em Process Control Blocks)<br />

Fig.: Campos da entrada de uma tabela de processos<br />

8<br />

4


Process Control Block (PCB)<br />

P1 P2 P3<br />

PCB 1<br />

PCB 2<br />

PCB 3<br />

Contém informações que são necessárias<br />

quando o processo está em execução.<br />

Em sistemas Unix, o PCB é uma estrutura<br />

no espaço do usuário que é acessada<br />

pelo núclro ( área u)<br />

Para ser capaz de reiniciar um processo<br />

interrompido (ou esperando) o estado<br />

anterior em que deixou a CPU precisa ser<br />

restaurado;<br />

Carrega-se a CPU (e MMU) com as<br />

variáveis do PCB<br />

Troca de contexto<br />

Troca de Contexto<br />

12<br />

13<br />

5


Troca de Contexto<br />

• Consiste de salvar o estado dos recursos em uso<br />

(especialmente CPU) do processo interrompido n<br />

PCB, e carregar a CPU com um novo estado (PC,<br />

registradores, stack pointer, PSW, etc.)<br />

• Esta troca precisa ser:<br />

• Completa e consistente<br />

• Eficiente<br />

• O núcleo não pode ser interrompido durante o<br />

processo<br />

– Precisa-se garantir a atomicidade da operação<br />

• Realizado por um tratador de interrupção genérico,<br />

tratador de interrução de primeiro nível<br />

Tabela de <strong>Processos</strong> (proc)<br />

Além do PCB, o núcleo gerencia uma tabela de processos, com informações<br />

adicionais por processo;<br />

É uma área no núcleo (vetor de entradas ou lista ligada) com informações<br />

sobre todos os processos, tais como:<br />

– PID<br />

– Endereço do PCB do processo<br />

– Estado do processo<br />

– Ponteiros entre processos nas filas de prontos/bloqueados (usados pelo<br />

escalonador)<br />

– Ponteiros para os processos pai, filho e irmão<br />

– Informação para o tratamento de sinais (máscaras, etc.)<br />

– Informação para gerenciamento de memória,<br />

– Informações estatísticas<br />

– etc.<br />

Obs1: Conjuntamente, o PCB e a entrada na Tabela de <strong>Processos</strong> contém todas as<br />

informações necessárias para a gerência dos processos<br />

Obs2: No Minix, a entrada TP é uma estrutura em<br />

kernel/proc.c e o array tem tamanho NR_TASKS+NR_PROCS.<br />

14<br />

15<br />

6


Filas dos prontos e de espera por E/S<br />

Tratamento de Interrupções<br />

• Para desviar o controle de execução (para o tratador da<br />

interrpção), o contexto precisa ser trocado.<br />

• Ao receber uma interrupção, o HW empilha novo PC contido<br />

na entrada do vetor de interrupção.<br />

16<br />

17<br />

7


Interrupções vs. Exceções<br />

O conjunto de interrupções depende da arquitetura do<br />

sistema.<br />

A especificação da Intel Architecture IA-32 distingue<br />

dois tipos de sinais que um processador pode<br />

receber:<br />

• Interrupções<br />

– Notificam o processador que um evento ocorreu, e/ou que<br />

o estado de um recurso (p.ex. dispositivo de E/S )mudou<br />

– Gerado por um dispositivo externo ao processador<br />

• Exceção<br />

– Indica a ocorrência de um erro, de hardware ou causado<br />

por uma instrução sendo executada<br />

– Classificados como faults, traps or aborts<br />

Tipos de interrupção reconhecidos pela Intel Architecture IA-32:.<br />

Tipo<br />

Tipos de Interrupções<br />

Descrição para cada tipo<br />

I/O Iniciados pelo HW, notificam o processador de que o estado do<br />

dispositivo de E/S mudou (p.ex. E/S finalizada)<br />

Timer evento periódico para agendamento de ações e/ou monitoramento de<br />

desempenho<br />

Inter-CPU Em sistemas multi-processadores, para comunicação e<br />

sincronização entre processadores<br />

18<br />

19<br />

8


Dispositivo -> I/O Interrupt<br />

Segmentation Fault -> Error<br />

System Call -> Trap<br />

send message -> Trap<br />

Clock Interrupt<br />

Tratamento de Interrupções<br />

Tratador de interrupção específico():<br />

- trata a interrupção (p.ex. Escreve/le dados<br />

de buffer do driver)<br />

- se algum processo foi desbloqueado então<br />

- retorna<br />

Dispatcher, em Assembly:<br />

- desabilita interrupções<br />

- carrega o contexto na CPU &<br />

mapeamento de memória do<br />

processo a ser executado<br />

- habilita interrupções<br />

First Level Int. Handler (FLIH), em Assembly<br />

- desabilita interrupções<br />

- salva contexto em tabela de processos/PCB<br />

- Cria nova pilha temporária no kernel<br />

- carrega no PC o end. do Vetor de Interrupções<br />

- habilita interrupções<br />

Scheduler():<br />

- insere o processo<br />

desbloqueado na fila de<br />

prontos q<br />

- Escolhe próximo processo<br />

- retorna<br />

Tratamento de Interrupções<br />

Esqueleto do que o nível mais baixo do SO faz quando<br />

ocorre uma interrupção<br />

Vetor de Interrupção:<br />

• Localizado em endereço baixo de memória (núcleo)<br />

• Uma entrada para cada tipo de interrupção (trap, clock, E/S)<br />

• Cada entrada contém endereço para um procedimento tratador da interrupção<br />

(tratamento do serviço da interrupção) que identificará de qual dispositivo veio<br />

a interrupção.<br />

20<br />

21<br />

9


Escalonamento<br />

• A cada instante um ou mais processos podem estar<br />

no estado pronto, e.g.:<br />

– <strong>Processos</strong> do sistema e de usuários<br />

– <strong>Processos</strong> de vários usuários (sistema time-sharing)<br />

– Mix de processos interativos e batch (simulação, folha de<br />

pagamento)<br />

• Escalonador é responsável por gerenciar a fila de<br />

prontos, e escolher qual dos processos prontos vai<br />

ser o próximo a usar CPU(de acordo com as<br />

prioridades dos processos)<br />

• Também é responsável por aumentar/diminuir a<br />

prioridade dos processos<br />

Escalonamento<br />

• O algoritmo poderá lidar com objetivos conflitantes.<br />

• Por exemplo:<br />

– Garantir justiça (fairness): cada processo ganha fatia igual da CPU<br />

– Aumentar eficiência: manter utilização de CPU alta (próxima a<br />

100%)<br />

– Minimizar tempo de resposta (para processos interativos)<br />

– Minimizar de tempo de retorno (Δt entre início-fim de processos<br />

batch)<br />

– Maximizar taxa de saída: número de processos processados por<br />

unidade de tempo<br />

• Sempre que se beneficia uma classe de processos,<br />

prejudica-se outras classes.<br />

25<br />

26<br />

10


Escalonamento<br />

Tipos de sistemas e objetivos do escalonamento<br />

Escalonamento<br />

• A política de escalonamento deve ser independente do<br />

mecanismo (carregamento da CPU com um contexto)<br />

• Têm parâmetros que precisam ser ajustados para:<br />

• maximizar a “satisfação geral” dos usuários e<br />

• garantir execução mais eficiente das tarefas essenciais ao<br />

sistema<br />

• Principal problema : o comportamento futuro de um processo<br />

não é previsível (fases de uso intensivo da CPU, fases de E/S<br />

frequente)<br />

27<br />

28<br />

11


Escalonamento<br />

1. Escalonamento de longo prazo<br />

– ao ser criado, processo vai para fila dos prontos<br />

– Questão: quando isso é feito e em qual posição ele<br />

entra?<br />

2. “dispatching”<br />

– escalonamento de curto prazo<br />

– Escolhe um dos processos da/s lista/s de prontos para<br />

executar<br />

• Usa-se o termo escolanamento para ambos<br />

Formas de implementar o escalonador<br />

• “embutido” na execução do processo<br />

– Ao final do tratamento da interrupção, o procedimento<br />

para escalonamento é chamado<br />

– Executa como parte do fluxo de controle do processo que<br />

estava em execução<br />

• “autónomo”<br />

– Executa como um processo independente<br />

– Pode estar dedicado a uma CPU em uma arquitetura multicore<br />

– Em máquinas com 1 processador, é executado uma vez a<br />

cada quantum de tempo<br />

– Há uma alternância entre o processo escalonador e os<br />

demais processos<br />

30<br />

31<br />

12


Tipos de Escalonamento<br />

Com relação:<br />

• ao momento da invocação do escalonador:<br />

• preemptivo: a cada clock tick escalonador verifica se<br />

processo corrente já expriou seu quantum de tempo, e se<br />

sim, interrompe-o, e escolhendo outro processo para<br />

executar<br />

• não-preemptivo: escalonador só é chamado quando<br />

processo é bloqueado (chamda de sistema), ou termina<br />

• ao método de seleção do processo mais prioritário:<br />

– Uso da função P = Priority(p)<br />

– Regra de desempate (para processos de mesma prioridade)<br />

• Escolha randômica<br />

• Cronológica (FIFO)<br />

• Cíclica (Round Robin)<br />

Escalonamento por prioridade<br />

• Função de prioridade retorna valor P para processo p:<br />

P = Priority(p)<br />

– Prioridade estática: não muda ao longo da execução de p;<br />

– Prioridade dinâmica: muda em tempo de execução<br />

• Prioridades separam todos processos em níveis:<br />

– Implementado através de filas de pronto multi-nível (e.g.<br />

várias Ready Lists – RLs)<br />

– p @ RL[i] executa antes de q @ RL[j] se i>j<br />

– p, q na mesmo nível são ordenados usando outro critério<br />

32<br />

33<br />

13


Algoritmo geral para um escalonador<br />

preemptivo para multi-processador<br />

Scheduler() {<br />

do { // existe alguma CPU livre<br />

Pegue o processo P mais prioritário de ready_a;<br />

Ache uma CPU livre;<br />

if (cpu != NIL) Aloca_CPU(P,cpu);<br />

} while (cpu != NIL);<br />

do { // todas CPUs estão em uso<br />

Pegue o processo P mais prioritário de ready_a;<br />

Pegue o processo Q em execução de menor prioridade;<br />

if (Priority(P) > Priority(Q)) Preempta(P,Q);<br />

} while (Priority(P) > Priority(Q));<br />

if (self->Status.Type!=’running’) Preempt(P,self);<br />

}<br />

Escalonamento Preemptivo:<br />

como clock ticks são tratados?<br />

• Interrupção clock tick é o 2o. mais prioritário (e ocorre a<br />

cada 10 mseg, (10 -2 segundos)<br />

• Tarefas do tratador:<br />

– Re-arma o clock (se necessário)<br />

– Atualiza estatísticas sobre uso de CPU do processo corrente<br />

– Re-calculo de priordades e tratamento de quantum expirado<br />

– Envia um sinal SIGXCPU para processo corrente, caso seu<br />

quantum tenha expirado<br />

– Atualiza contador time-of-the-day<br />

– Executa funções agendadas do kernel (callouts)<br />

– Trata alarmes<br />

• Algumas das tarefas apenas executadas apenas em major<br />

clock ticks (p.ex. cada 4 ou 10 ticks)<br />

34<br />

35<br />

14


Callouts e Alarmes<br />

callout = função que o kernel deve executar em um<br />

momento futuro, por exemplo:<br />

– retransmissão de pacotes de rede<br />

– Funções de gerenciamento do escalonador ou do<br />

gerente de memória<br />

– Polling de dispositivos que não emitem interrupções<br />

Obs: São mantidos em uma fila ordenada (a qualquer<br />

momento podem surir novos)<br />

alarmes = solicitações “me acorda” feitos por<br />

processos ao kernel, para:<br />

profiling, processos de tempo real, o tempo que<br />

o processo usou em uesr mode.<br />

Parâmetros típicos da Função<br />

Prioridade<br />

• Internos (do sistema)<br />

– Tipo do processo (sistema vs usuário)<br />

– Quantidade de memória necessária<br />

– Tempo total de CPU requisitado<br />

– Tempo de serviço obtido / alcançado<br />

– Tempo total de permanência no sistema<br />

• Externos<br />

– Prazo para término de ação (Deadline)<br />

– Prioridade do usuário: root vs normal (função na<br />

empresa, valor desembolsado)<br />

36<br />

37<br />

15


• Possíveis parâmetros:<br />

Função Prioridade<br />

– a = tempo de serviço alcançado<br />

– r = tempo de permanência no sistema<br />

– t = tempo total de serviço<br />

– d = Periodicidade (para tempo real)<br />

– deadline (explítio ou definido pelo período)<br />

– e = prioridade externa<br />

– Quantidade de memória requisitada (p/ processamento<br />

em lotes)<br />

a<br />

d<br />

r<br />

t<br />

tempo<br />

Algorítmos de escalonamento<br />

Nome, Modo decisão, Priorid., Desempate<br />

FIFO: não-preemptivo P = r randomico<br />

SJF: não-preemptivo P = –t cron./randomico<br />

SRT: preemptivo P = –(t–a) cron./randomico<br />

RR: preemptivo P = 0 cíclico<br />

ML: preemptivo P = e cíclico<br />

não-preemptivo P = e cronológico<br />

• n níveis de prioridade fixa<br />

• nível P é atendido quando as filas n a P+1 estão vazias<br />

SJF= Shortest Job First; SRT = Shortest Remaining Time; RR= RoundRobin;<br />

ML = Multi-level<br />

a = serviço alcançado; r = permanência no sistema, t = tempo total de serviço<br />

e = prioridade externa<br />

38<br />

39<br />

16


First In First Out (FIFO)<br />

Execução por ordem de chegada<br />

Job<br />

A<br />

B<br />

C<br />

Tempo de CPU<br />

8<br />

1<br />

1<br />

A B C<br />

0 8 9 10<br />

Tempo médio de espera (0 + 8 + 9) / 3 = 5.7<br />

Job<br />

A<br />

B<br />

C<br />

Shortest Job First<br />

B C<br />

Tempo de CPU<br />

8<br />

A<br />

0 1 2 10<br />

Tempo médio de espera ótimo:<br />

(0 + 1 + 2) / 3 = 1<br />

1<br />

1<br />

40<br />

41<br />

17


Escalonamento com múltiplas filas (ML)<br />

• Para sistemas com mix de processos interativos e em lote<br />

• <strong>Processos</strong> são classificados segundo prioridade, e cada<br />

classe tem sua própria fila de prontos.<br />

Priority 1<br />

Priority 2<br />

Priority 3<br />

... ...<br />

<strong>Processos</strong> sistema<br />

<strong>Processos</strong> interativos<br />

<strong>Processos</strong> lote<br />

• Executa todos de prioridade 1, depois 2 …<br />

• Para evitar o problema de inanição (= alguns processos nunca<br />

ganham a vez), pode-se definir períodos de tempo máximos<br />

para cada categoria: por exemplo, 70% para 1, 20% para 2 …<br />

ML – Princípio Geral<br />

• No ML adaptou-se SJF para processos interativos,<br />

considerando o tempo efetivo de CPU entre requisições de E/S<br />

P1<br />

P2<br />

Principal problema: como descobrir qual dos processos prontos<br />

requisitará a CPU por menor período de tempo.<br />

Princípio adotado: Estimar a próxima fatia de tempo necessária,<br />

olhando para o passado.<br />

Exemplo: Seja T0 a estimativa de tempo de uso de CPU e T1 o<br />

tempo de CPU efetivamente utilizado da última vez. Então,<br />

a estimativa para a próxima vez, T2, deveria ser ajustada.<br />

T2 = α*T1 + (1-α)*T0.<br />

Se α > 0.5 dá-se mais importância para o comportamento mais<br />

recente, e α < 0.5 maior importância para o comportamento<br />

mais no passado<br />

42<br />

43<br />

18


Algorítmos de escalonamento<br />

Multiplos níveis com feedback - MLF (Multilevel<br />

with feedback):<br />

– Similar ao ML, mas com uma prioridade que<br />

muda dinamicamente<br />

– Todo processo começa no nível mais alto n<br />

– Cada nível P prescreve um tempo máximo t P<br />

– t P aumenta à medida que P diminui<br />

– geralmente:<br />

t n = Δt (constante)<br />

t P = 2 × t P+1<br />

Filas em múltiplos níveis com feedback<br />

Idéia: Maior prioridade para processos que precisam de fatia<br />

(ou quantum) de tempo (Δt) menor. Se um processo<br />

repetidamente gasta todo seu quantum Δt, passa para<br />

prioridade mais baixa.<br />

Priority 1<br />

Priority 2<br />

Priority 3<br />

Queue<br />

Queue<br />

Queue<br />

1x Δt<br />

2x Δt<br />

4x Δt<br />

... ... ...<br />

• Problema: processos longos, p.ex. que precisam de 100x Δt<br />

– Percorrem 7 prioridades: 1, 2, 4, 8, 16, 32, 64<br />

• Grande vantagem para processos com alta frequência de E/S<br />

44<br />

45<br />

19


Algorítmos de escalonamento<br />

Rate Monotonic (RM):<br />

– Usado para processos periódicos (em sistemas de tempo<br />

real)<br />

– Preemtivo<br />

– Prioridade maior para menor período: P = –d<br />

Earliest Deadline First (EDF):<br />

– Usado para processos periódicos (tempo real)<br />

– Preemtivo<br />

– Prioridade maior para aquele com menor tempo até a<br />

próxima deadline:<br />

• r / d número de períodos completados<br />

• r % d tempo executado no período atual<br />

• d – r % d tempo residual no perídodo atual<br />

• P = –(d – r % d)<br />

• Sistemas em lote<br />

– FIFO, SJF, SRT:<br />

– FIFO é o mais simples<br />

Comparação<br />

– SJF/SRT possuem tempos médios de turnaround (#<br />

processos/tempo) menores<br />

• Sistemas time-sharing<br />

– Tempo de resposta é crítico<br />

– RR puro ou MLF (c/ RR por nível) são apropriados<br />

– A escolha do quantum de tempo q determina o overhead<br />

• Quando q → ∞, RR se aproxima de FIFO<br />

• Quando q → 0, overhead de troca de contexto (TC) → 100%<br />

• Quando q >> overhead de TC, n processos executam desempenho ≈<br />

1/n CPU velocidade<br />

47<br />

48<br />

20


Outras políticas de escalonamento<br />

Escalonamento garantido<br />

• cada um dos n usuários recebe aproximadamente 1/n dos<br />

ciclos de CPU<br />

• Muito simples, e só é feito para processos do usuário (e<br />

não de sistema)<br />

Escalonamento por sorteio (lottery scheduling)<br />

• Sorteio quase aleatório de procesos (todos ou em cada<br />

nível de prioridade)<br />

• Vantagem: simplicidade e distribuição unifore de valores<br />

sorteados geralmente garante igualdade de chances<br />

• Para garantir justiça, impõe-se um limite no número de<br />

vezes que um processo pode ser sorteado em determinado<br />

período<br />

Escalonamento no Minix 3<br />

Algoritmo de escalonamento mul1-­‐nível (16 níveis). Tarefas são escalonadas<br />

sem preempção. Demais processos com Round-­‐Robin Adaptado: se<br />

processo desbloqueado ainda 1ver parte de seu quantum, é posicionado<br />

no começo da fila.<br />

<strong>Processos</strong> “servidores” tem quantum de tempo maior.<br />

Níveis de prioridade:<br />

– Task_Q<br />

1. Tarefas Sistema e Relógio<br />

2. Tarefa Tty<br />

3. Tarefas Disco, log e mem<br />

4. Servidores RS e PM<br />

5. Servidor FS<br />

– User_Q<br />

6. <strong>Processos</strong> usuário<br />

7. …<br />

– Idle_Q<br />

16. IDLE<br />

vetores rdy_head[16] e rdy_tail[16], apontam para o começo e final de cada fila<br />

Leitura: seção 2.5.4 no livro do Tanenbaum e Woodhull<br />

59<br />

21


Limitações do Modelo de<br />

<strong>Processos</strong><br />

1. Várias aplicações precisam executar<br />

funções inerentemente concorrentes,<br />

compartilhando estruturas de dados<br />

internas. Ex: servidores, monitores de<br />

transacões, protocolos de rede, etc.<br />

2. Não facilita usar o paralelismo de<br />

arquiteturas multi-processadores: aplicação<br />

teria que ser formada por vários processos,<br />

que teriam quecompartilhar dados<br />

<strong>Threads</strong><br />

Thread = linha de execução independente dentro de um<br />

mesmo processo<br />

• Multiplas threads são necessárias quando >1 pedidos de E/<br />

S devem ser tratados concorrentemente, e que precisam<br />

compartilhar algumas estruturas de dados (e.g. uma cache<br />

em um servidor de arquivos ou conexões TCP em um<br />

servidor Web;<br />

•<br />

72<br />

73<br />

22


<strong>Processos</strong> com 1 ou mais threads<br />

Principais Características<br />

• Cada thread tem a sua pilha própria, mas compartinha o<br />

mesmo espaço de endereçamento do processo em que foi<br />

criada;<br />

• Se duas threads executam o mesmo procedimento/método,<br />

cada uma terá a sua própria cópia das variáveis locais;<br />

• As threads podem acessar todas os dados globais do<br />

programa, e o heap (memória alocada dinamicamente)<br />

• Nesse acesso a dados globais (i.e. quando acesso inclui<br />

mais do que uma instrução de máquina), threads precisam<br />

ter acesso em regime de exclusão mútua (p.ex. usando<br />

locks())<br />

74<br />

75<br />

23


76<br />

77<br />

Exemplo de uso de threads<br />

Fig.: Um processador de texto com três threads<br />

Exemplo de uso de <strong>Threads</strong><br />

Um servidor web com múltiplas threads<br />

24


Thread Pool<br />

Cada thread executa um procedimento que consome<br />

um request R, processa-o e gera uma resposta 78<br />

Sincronização entre <strong>Threads</strong><br />

int pthread_join( pthread_t tid, void* status )<br />

// a thread invocadora é bloqueada até que a thread tid termina<br />

• tid A threadID pela qual deseja-se esperar;<br />

• status O valor de retorno da thread execurando o exit(), será copiada para s<br />

void main() {<br />

pthread_t tid;<br />

int status;<br />

pthread_create(&tid,NULL,thread_main,NULL);<br />

….<br />

pthread_join(tid, (void*) &status);<br />

}<br />

printf(“Return value is: %d\n”, status);<br />

}<br />

void *thread_main( ){<br />

int result;<br />

....<br />

Pthread_exit((void*) result);<br />

80<br />

25


Exemplo de Uso de <strong>Threads</strong><br />

#include <br />

#include <br />

#define NUM_THREADS 5<br />

void *PrintHello(void *threadid)<br />

{<br />

printf("\n%d: Hello World!\n", threadid);<br />

/* do other things */<br />

pthread_exit(NULL); /*not necessary*/<br />

}<br />

int main()<br />

{<br />

pthread_t threads[NUM_THREADS];<br />

int t;<br />

for(t=0;t < NUM_THREADS;t++)<br />

{<br />

printf("Creating thread %d\n", t);<br />

pthread_create(&threads[t], NULL, PrintHello, (void *)t);<br />

}<br />

}<br />

for(t=0; t < NUM_THREADS; t++)<br />

pthread_join(threads[t],NULL); /* wait for all the threads to terminate*/<br />

Diagrama de estados de threads<br />

new ThreadExample();<br />

A1vo<br />

Executando<br />

while (…) { … }<br />

New Thread Runnable<br />

Dead Thread<br />

thread.start();<br />

Método run() retorna<br />

Blocked<br />

Object.wait()<br />

Thread.sleep()<br />

blocking IO call<br />

wai1ng on a monitor<br />

81<br />

82<br />

26


Gerenciamento de <strong>Threads</strong><br />

Ao contrário de processos, threads compartilham a<br />

mesma região de memória<br />

• Cada thread possui sua própria pilha e contexto de<br />

CPU (conteúdo de PC, SP, registradores, PSW,<br />

etc.)<br />

• Uma tread pode estar nos estados: running,<br />

blocked & ready<br />

Gerenciamento processos vs threads<br />

83<br />

84<br />

27


O Descritor de Thread<br />

• Para cada thread, o kernel (ou biblioteca de threads) mantém a<br />

seguinte informação, que é mantida independente dos descritores<br />

de processos (PCBs)<br />

Contexto:<br />

program counter (PC) /* próxima instrução */<br />

process status word (PSW) /* resultado da operação, ex: carry-bit */<br />

stack pointer (SP) /* pilha de execução do thread */<br />

registers /* conteúdo dos registradores da CPU */<br />

state /* blocked, running, ready */<br />

priority<br />

host_process /* processo hospedeiro ou kernel */<br />

thread_Id /* identificador do thread */<br />

processID /* processo ao qual a thread pertence */<br />

Formas de Implementar <strong>Threads</strong><br />

Kernel-level threads (1 para 1):<br />

• thread é a unidade de<br />

escalonamento do núcleo<br />

• A biblioteca de chamadas de<br />

sistema inclui operações para criar/<br />

controlar threads<br />

• Algoritmo de escalonamento é o<br />

implementado pelo núcleo<br />

• Exemplos:<br />

– Windows NT/XP/2000<br />

– Solaris (anterior à vers. 9)<br />

– Linux: Linux<strong>Threads</strong> ou<br />

"Native Posix Thread Library<br />

(NPTL)”<br />

– Apple: Multiprocessing<br />

Services<br />

– Unix: NetBSD, FreeBSD<br />

User-level threads (N para 1):<br />

• todas as threads do processo são<br />

mapeadas para única unidade<br />

escalonável do núcleo<br />

• Se qualquer thread do processo fizer<br />

system-call, todo processo é<br />

bloqueado (não aproveita paralelismo<br />

de arquiteturas multi-core)<br />

• políticas de escalonamento são<br />

implementadas na biblioteca de<br />

threads<br />

• Exemplos:<br />

– GNU Portable threads,<br />

– Thread manager (Apple),<br />

– Netscape Portable Runtime,<br />

– State <strong>Threads</strong>, LWP (SunOS)<br />

– POSIX P-threads, C-threads<br />

85<br />

28


87<br />

88<br />

<strong>Threads</strong> em Modo Usuário<br />

Biblioteca de threads em nível usuário:<br />

• Escalonamento das threads de acordo com as<br />

necessidades do programa de aplicação. Quando uma<br />

thread requisita E/S, bloqueia todo o processo.<br />

• Exemplos: POSIX P-threads, C-threads<br />

<strong>Threads</strong> em modo Kernel<br />

(Lightweight Process)<br />

Kernel chaveia entre threads,<br />

independente do processo ao<br />

qual pertencem:<br />

Vantagens:<br />

As proprias funções do kernel<br />

podem ser concorrentes;<br />

Principais problemas:<br />

• Troca de contexto entre threads<br />

precisa considerar proteção de<br />

memória (limites de processo)<br />

• Cada troca de contexto (entre<br />

os LWP) requer um TRAP para<br />

o núcleo (troca modo usuário<br />

para modo supervisor)<br />

Fig.: <strong>Threads</strong> gerenciadas pelo núcleo<br />

29


Threading híbrido (N para M):<br />

• N theads de um processo são<br />

mapeadas em M threads do núcleo<br />

• Mais difícil de implementar (pois os<br />

escalonadores do modo usuário e do<br />

modo kernel precisam se coordenar)<br />

• troca de contexto pode ser muito<br />

eficiente, pois não requer sempre<br />

uma chamda de sistema<br />

• Exemplos:<br />

– Microsoft Windows7,<br />

– IRIX<br />

– HP-UX<br />

– Tru64 UNIX<br />

– Solaris 8<br />

Tipos de <strong>Threads</strong><br />

• Exemplo de “3 para 2”<br />

Mais informações em:<br />

http://www.ibiblio.org/pub/Linux/docs/faqs/<strong>Threads</strong>-FAQ/html/ThreadLibs.html<br />

90<br />

Implementações Híbridas<br />

Multiplexação de threads de usuário sobre<br />

threads de núcleo<br />

30


Algumas Implementações Posix<br />

<strong>Threads</strong> (P-threads)<br />

POSIX <strong>Threads</strong> = modelo de programação, coleção de interfaces que permitem criar,<br />

controlar e efetuar o escalonamento, a comunicação e a sincronização entre threads.<br />

<strong>Threads</strong> em modo kernel:<br />

• Native POSIX Threading Library (NPTL)<br />

• Linux<strong>Threads</strong> (para Linux)<br />

• Win32 Phtreads<br />

<strong>Threads</strong> em modo usuário:<br />

• FSU Pthreads (SunOS 4.1.x, Solaris 2.x, SCO UNIX, FreeBSD and Linux)<br />

• LPW (SunOS 3/4, mips-ultrix, 386BSD, HP-UX and Linux)<br />

• PCthreads<br />

• P<strong>Threads</strong><br />

Mais informações em:<br />

http://www.ibiblio.org/pub/Linux/docs/faqs/<strong>Threads</strong>-FAQ/html/ThreadLibs.html<br />

Prós e contras<br />

<strong>Threads</strong> implementados pelo núcleo:<br />

VANTAGENS:<br />

• threads nível usuário não permitem E/S<br />

concorrente se uma thread pede E/S, todo o<br />

processo é bloqueado<br />

DESVANTAGENS:<br />

• Troca de contexto é menos eficiente (requer troca<br />

entre modos: usuário → kernel →usuário)<br />

• Kernel fica mais complexo (precisa implementar<br />

tabelas de threads e de processos)<br />

• Desenvolvedor de aplicação tem menos controle<br />

sobre o escalonamento das threads de seu processo<br />

91<br />

92<br />

31


Outras questões de projeto<br />

Implementação de threads precisa estar coerente com semântica de algumas<br />

system-calls. Por exemplo:<br />

• O que ocorre se processo com >1 threads executa um FORK?, O processo filho<br />

deverá ter o mesmo número de threads do pai?<br />

• E quando a thread do pai está esperando por E/S, e aparece interrupção<br />

sinalizando o término da E/S. As threads no processo pai e no filho recebem o<br />

dado?<br />

• O que acontece se uma thread executa um close(fd) e arquivo fd ainda está em<br />

uso por outra thread?<br />

• Em algumas bibliotecas malloc não é reentrante (possui estado). Portanto, pode<br />

haver problemas se >1 thread executam esta chamada concorrentemente.<br />

• O que acontece se uma thread faz chamda de sistema, e antes que seja capaz de<br />

ler variável global errno, outra thread faz outra chamada a sistema e sobre<br />

escreve errno incial?<br />

Conclusão: quando suporte a threads é incluido no núcleo, a semântica de algumas<br />

chamadas de sistema precisa ser re-definida (e bibliotecas re-implementadas).<br />

Comunicação Inter-processos (IPC)<br />

Existem inúmeras situações em que processos do<br />

sistema (ou do usuário) precisam interagir para<br />

se comunicar ou sincronizar as suas ações, por<br />

exemplo, no acesso compartilhado a dados ou<br />

recursos.<br />

Comunicação Inter-processos envolve:<br />

– Sincronização entre execuções<br />

– Notificações assíncronas<br />

– Troca/compartilhamento de dados<br />

93<br />

95<br />

32


Comunicação Inter-processos (IPC)<br />

<strong>Processos</strong> e threads são entidades independentes,<br />

que podem ser executados em qualquer ordem.<br />

A ordem de escalonamento é imprevisível.<br />

Precisa-se de mecanismos para evitar problemas<br />

de inconsitência de dados compartilhados<br />

decorrentes da execução concorrente.<br />

recurso<br />

compartilhado<br />

IPC entre processos<br />

Dado<br />

compart.<br />

IPC entre threads<br />

Comunicação e Sincronização entre<br />

<strong>Processos</strong>: Duas Abordagens<br />

1. Baseada em memória<br />

compartilhada<br />

– Assume que processos/threads<br />

conseguem escrever & ler em<br />

memória compartilhada<br />

– Comunicação é implícita<br />

(através do comparithamento)<br />

mas<br />

– Sincronização precisa ser<br />

explicita<br />

2. Baseada em troca de<br />

mensagens<br />

– Comunication é explicita;<br />

– Sincronização é impícita<br />

process<br />

Dados<br />

thread thread<br />

data<br />

Na comunicação entre processos (Inter-Process Communication - IPC), o<br />

principal problema são as condições de corrida.<br />

process<br />

send(msg) receive(msg)<br />

96<br />

97<br />

33


IPC básico no Unix<br />

• Sinais – notificacões assíncronas entre processos de um<br />

mesmo usuário (e do núcleo para processos)<br />

– Ação default: terminar o processo<br />

– Processo pode definir um tratador de sinal, que reage<br />

independentemente à execução corrente do processo sinalizado<br />

– Limitações: execução tem alto overhead, e sinais não permitem<br />

transferir dados<br />

• Pipes: fluxo de dados unidirecional, entre processos<br />

quaisquer<br />

– Fluxo FIFO, não tipado (fluxo de bytes),<br />

– Ao criar uma pipe, são retornados um descritor de arquivo para<br />

escrita e outro para leitura (usado com read() e write())<br />

– A shell usa pipes para composição de comandos 98<br />

Comunicação Inter-processos (IPC)<br />

Exemplos:<br />

• Processo A e B trocam dados através de um duto (pipe):<br />

processo leitor bloqueia até que o outro processo tenha escrito<br />

algum dado na pipe;<br />

• Dois ou mais processos precisam ler e escrever no mesmo<br />

arquivo;<br />

• Jobs de impressão de dois processos devem ser processados de<br />

forma atômica, para garantir que as saídas (listagem) não saiam<br />

misturadas<br />

• <strong>Threads</strong> compartilham uma lista (ou vetor) de elementos com<br />

escrita: atualização requer escritas combinadas em vários<br />

endereços de memória<br />

99<br />

34


IPC: Condição de Corrida<br />

Dois processos querem acessar memória compartilhada “ao mesmo<br />

tempo” (e de forma concorrente e imprevisível)<br />

Exemplo:<br />

– processo A lê memória compartilhada“in=7”, e logo depois é interrompido,<br />

– Processo B faz o mesmo e adiciona um novo arquivo no diretório de spool de<br />

impressão<br />

– Quando A re-inicia , sobre-escreve o slot 7 com seu arquivo.<br />

Condição de Corrida<br />

Ocorre sempre que:<br />

• existem dois ou mais processos concorrentes<br />

• Cada processo precisa executar um conjunto de ações<br />

(a1,..aN) que envolvem mais de um dado/recurso<br />

compartilhado, e<br />

• os dados/recursos precisam manter um estado consistente<br />

entre sí;<br />

• antes que complete a execução de todo o conjunto de ações,<br />

um dos processos é interrompido pelo outro<br />

Processo1:<br />

…<br />

A1<br />

A2<br />

A3<br />

w<br />

x<br />

Dado1<br />

Recurso1<br />

w<br />

… Dado2<br />

r<br />

x<br />

Processo2:<br />

…<br />

B1<br />

B2<br />

B3<br />

…<br />

100<br />

101<br />

35


Condição de Corrida<br />

Análogo vale para troca de mensagens (entre processos<br />

clientes e processos servidores)<br />

Exemplos:<br />

1. Para requisitar um serviço, cliente precisa enviar<br />

duas mensagens (1.consulta ao estado, e 2.confirmar<br />

requisição do serviço)<br />

2. Só faz sentido confirmar a requisição, se outro<br />

serviço (complementar, ou anterior) já tiver sido<br />

completado.<br />

client<br />

Ready?<br />

Confirm req.<br />

server<br />

Condição de Corrida<br />

exemplo do dia-a-dia: ligação telefônica<br />

102<br />

103<br />

36


Condição de Corrida<br />

Problemas associados<br />

1. Ausência de atomicidade das ações feitas em dados<br />

compartilhados (requer exclusão mútua ou bloqueio)<br />

2. <strong>Processos</strong> tentam acessar dados compartilhados<br />

que ainda não estão prontos para serem acessados<br />

3. Operações simultâneas (não previstas) se<br />

bloqueiam mutuamente<br />

Para permitir uma cooperação correta entre<br />

processos é preciso:<br />

• Estabelecer um controle na ordem de execução<br />

• Garantir que algumas execuções ocorram de<br />

forma atômica<br />

Região Crítica<br />

Memória/Recursos compartilhados deveriam ser acessados<br />

em regime de exclusão mútua (um processo de cada vez)<br />

Região crítica (ou Sessão crítica) = parte do programa em<br />

que estão as ações que manipulam os recursos (dados)<br />

compartilhados.<br />

Quatro condições para garantir exclusão mútua:<br />

1. Nunca, dois ou mais processos executam simultaneamente em suas<br />

sessões críticas<br />

2. Não deve haver qualquer suposição sobre velociades e/ou número<br />

de processos<br />

3. Quando executa código fora de uma sessão crítica, um processo<br />

nunca bloqueia outro processo<br />

4. Qualquer processo que entrou em sua sessão crítica, em algum<br />

momento deixa a mesma.<br />

104<br />

105<br />

37


Região Crítica<br />

Para implementar uma região crítica deve haver um<br />

mecanismo/protocolo para garantir a entrada e<br />

saida segura (sincronizada, coordenada) desta<br />

desta parte do código.<br />

Código em um processo:<br />

…<br />

Enter_region; // bloqueia se outro processoe estiver dentro<br />

A1;<br />

A2;<br />

A3;<br />

Exit_region; // sai da região, e libera outros processos esperando<br />

…<br />

Veremos agora algumas possíveis abordagens e<br />

mecanismos<br />

Região Crítica<br />

Exclusão mútua usando Regiões Críticas<br />

106<br />

107<br />

38


Exclusão Mútua com Espera ocupada<br />

(Busy Waiting)<br />

Possibilidades:<br />

• Desabilitar interrupções:<br />

Pode ser usado em modo supervisor, mas não em modo usuário<br />

• Usar uma flag “lock” compartilhada: se lock=0, trocar valor para 1 e processo<br />

entra RC, senão processo espera<br />

Se leitura & atribuição do lock não for atômica, então problema permanece<br />

• Alternância regular de acesso por dois processos (PID= 0; PID= 1)<br />

É um problema, se os processos alternantes requisitam o recurso com<br />

alta frequência<br />

Região Crítica com Espera Ocupada<br />

Solução de Peterson:<br />

• turn e vetor interested[] são variáveis compartilhadas<br />

• Se dois processos PID = {0,1} executam simultaneamente enter_region, o<br />

primeiro valor de turn será sobreescrito (e o processo correspondente vai entrar),<br />

mas interested[first] vai manter o registro do interêsse do segundo processo<br />

108<br />

109<br />

39


Exclusão Mútua com Espera Ocupada<br />

TSL (Test-and-Set-Lock) = instrução de máquina atômica para<br />

leitura de um lock e armazenamento de um valor ≠ 0<br />

<strong>Processos</strong> que desejam entrar RC executam TSL:<br />

• se lock=0. Entram na RC, senão esperam em loop<br />

Espera Ocupada vs. Bloqueamento<br />

P1<br />

Enter()!<br />

Exit()!<br />

P2<br />

Enter()!<br />

Exit()!<br />

Esp.Oc: para arq. multi-core<br />

P1<br />

Enter()!<br />

Critical region Critical region<br />

Exit()!<br />

kernel<br />

P2<br />

Enter()!<br />

Exit()!<br />

Bloqu:.o núcleo garante atomicidade<br />

110<br />

111<br />

40


Espera Ocupada vs. Bloqueamento<br />

• Solução de Peterson e TSL apresentam o problema<br />

que o loop de espera consome ciclos de<br />

processamento.<br />

• Outro possível Problema: Inversão de prioridades<br />

Se um processo com baixa prioridade estiver na RC,<br />

demorará mais a ser escalonado (e a sair da RC), pois<br />

os processos de alta prioridade que esperam pela RC<br />

estarão em espera ocupada.<br />

A alternativa: Primitivas que bloqueiam o processo e<br />

o fazem esperar por um sinal de outro processo:<br />

Por exemplo:<br />

• sleep :: suspende o processo até que seja acordado<br />

wakeup(PID) :: envia sinal para acordar o processo PID<br />

Problema do Produtor e Consumidor<br />

Sincronização de 2 processos que compartilham um buffer<br />

(um produz itens, o outro consome itens do buffer), e que<br />

usam uma variável compartilhada count para controlar o<br />

fluxo de controle.<br />

• se count=N, produtor deve esperar, e<br />

• se count=0 consumidor deve esperar,<br />

Qualquer processo deve acordar o outro quando estado do<br />

buffer permite prosseguimento do processamento<br />

Produtor<br />

buffer<br />

count<br />

Consumidor<br />

Esse tipo de sincronização está relacionada ao estado do<br />

recurso Sincronização de condição<br />

112<br />

113<br />

41


Problema do Produtor e Consumidor<br />

Condição de corrida: consumidor verifica que count=0, mas antes que execute<br />

sleep, o produtor é escalonado, acrescenta item e executa wakeup. Mas como<br />

consumidor ainda não executou sleep, consumidor ficará bloqueado e sistema<br />

entrará em um impasse.<br />

Semáforos<br />

Em 1965 E.W. Dijkstra (1965) propôs o conceito de<br />

semáforos como mecanismo básico para<br />

sincronização entre processos. A inspiração: sinais<br />

de trens.<br />

114<br />

115<br />

42


Semáforos - Operações<br />

Um semáforo é um objeto do núcleo.<br />

Para cada semáforo s existem duas operações (P/V , Down/Up ou wait/signal):<br />

• Down(&s) :: verifica se pode entrar na Região crítica, e se não puder,<br />

bloqueia;<br />

• Up(&s) :: avisa que deixou região crítica, e causa o desbloqueio de um<br />

úncio processo bloqueado no semáforo;<br />

Semáforo - Implementação<br />

Trata-se de um contador que representa o número de processsos que podem<br />

entrar em uma Região Crítica.<br />

A cada semáforo está associado uma lista de processos bloqueados.<br />

Semântica das operacões:<br />

• Down(s) :: se s=0 processo invocador bloqueia nesta chamada. Se s ≠ 0,<br />

decrementa s e continua execução<br />

• Up(s) :: incrementa s, desbloqueia um dos processos bloqueados (se<br />

houver) e continua execução<br />

p2<br />

up(s)<br />

p6 down(s)<br />

s.val<br />

s.list<br />

p1 p4 p3 p5<br />

Fila de <strong>Processos</strong> bloqueados<br />

Operações Down e Up geralmente são implementadas como chamadas núcleo,<br />

e durante a sua execução o núcleo desabilita temporariamente as<br />

interrupções (para garantir a atomicidade)<br />

116<br />

117<br />

43


Semáforos - Implementação<br />

Mutex: semáforos binários<br />

Um Mutex, é um semáforo s que pode somente ter dois estados:<br />

Livre e Ocupado (s=1 e s=0, respectivamente)<br />

E as operações recebem outro nome:<br />

• Mutex_lock<br />

• Mutex_unlock<br />

Os mutexes são usados para implementar exclusão mútua<br />

simples, isto é, onde apenas 1 processo pode estar na região<br />

crítica.<br />

118<br />

119<br />

44


Semáforos: Exemplo de Uso<br />

O Problema Produtor-Consumidor usando 1 mutex e 2 semáforos<br />

Monitor – Objeto com sincronizacão<br />

Idéia básica:<br />

Usar o princípio de<br />

encapsulamento de dados<br />

também para a<br />

sincronisação:<br />

• Várias threads podem estar<br />

executando o mesmo<br />

monitor;<br />

• A cada momento, apenas um<br />

procedimento do monitor<br />

pode estar sendo executado;<br />

120<br />

122<br />

45


wait(c)<br />

signal(c)<br />

Monitor<br />

• Monitor é um elemento da<br />

linguagem de programação que<br />

combina o encapsulamento de<br />

dados com o controle para acesso<br />

sincronizado<br />

• Usa-se variáveis de condição<br />

(com operações wait e signal),<br />

quando o procedimento em<br />

execução não consegue completar<br />

e precisa que outro procedimento<br />

seja completado;<br />

• Em Java, tem-se algo similar:<br />

classe com métodos synchronized<br />

Monitor<br />

123<br />

124<br />

46


Monitor: Exemplo de Uso<br />

Resolvendo o problema do produtor-consumidor com monitores<br />

– Exclusão mútua dentro do monitor e controle explícito de sincronizaçnao garante<br />

a coerencia do estado do buffer<br />

– buffer tem N entradas<br />

Principal Diferença entre<br />

Monitores e Semáforos<br />

Monitores só servem para threads:<br />

• <strong>Processos</strong> não têm acesso a dados<br />

compartilhados (as instâncias de monitor)<br />

Semáforos podem ser usados por processos e<br />

threads<br />

• Semáforos são elementos do núcleo<br />

125<br />

126<br />

47


O Problema dos Leitores e Escritores<br />

Vários leitores podem entrar a RC ao mesmo tempo, mas escritores<br />

precisam executar em exclusão mútua.<br />

r<br />

r<br />

w<br />

w db<br />

• Pode haver até 5<br />

clientes esperando<br />

serviço.<br />

• Se todas cadeiras<br />

ocupadas, cliente<br />

aguarda fora<br />

• Se não há<br />

clientes, barbeiro<br />

tira soneca, até<br />

que chega um<br />

novo cliente<br />

O Problema do Barbeiro<br />

Dorminhoco<br />

127<br />

128<br />

48


O Barbeiro Dorminhoco: Semáforos<br />

Barbeiro sem Dorminhoco:<br />

Monitor<br />

Monitor Barber-shop{<br />

enum{sleep,work} barber;<br />

int waiting =0;<br />

condition x;<br />

Enter(){<br />

if(barber == sleep) barber=work;<br />

else if(waiting


Sincronizacão de Barreira<br />

• Quando todos os processos precisam alcançar um mesmo estado, antes<br />

de prosseguir (exemplo: processamento paralelo “em rodadas” com<br />

troca de informações)<br />

– <strong>Processos</strong> progridem a taxas distintas<br />

– Todos que chegam a barreira, são bloqueados para esperar pelos<br />

demais<br />

– Quando o retardatário chega, todos são desbloqueados e podem<br />

prosseguir<br />

Sincronização de Barreira<br />

Process {!<br />

! bool last = false;!<br />

! Semaphore barrier;!<br />

! Mutex m;!<br />

! Int count = N!<br />

! Init (&barrier, 0)!<br />

! down(&m)!<br />

! ! count--;!<br />

! ! if (count == 0) last= true;!<br />

! up(&m)!<br />

! if (NOT last) down (&barrier);! // espera pelos demais<br />

processos!<br />

! else for (i=0; i< N; i++) up (&barrier);!<br />

! …!<br />

}<br />

131<br />

132<br />

50


Semáforos<br />

• Todas bibliotecas de threads (ou system<br />

calls) provêm operacões para semáforos:<br />

– sem_t semaphore<br />

– sem_init(&semaphore, 0, some_value);<br />

– sem_down(&semaphore);<br />

– sem_up(&semaphore);<br />

• Em Pthreads usa-se: wait = down; post =<br />

up.<br />

O Jantar dos Filósofos<br />

Sincronização para compartilhamento de recursos 2 a 2<br />

Definição do Problema:<br />

• Filósofos só tem 2 estados:<br />

comem ou pensam;<br />

• Para comer, precisam de dois<br />

garfos, cada qual<br />

compartilhado com os seus<br />

vizinho;<br />

• Só conseguem pegar um<br />

garfo por vez;<br />

Questões globais:<br />

– Como garantir que nenhum<br />

filósofo morre de fome?<br />

– Como evitar o impasse?<br />

133<br />

134<br />

51


Relação entre Jantar dos<br />

Filósofos e S.O.?<br />

Isso é um problema que pode ocorrer em<br />

Sistemas Operacionais?<br />

Considere a situação:<br />

Um processo precisa escrever dados em 2 arquivos ao mesmo tempo, e<br />

cada um desses esses arquivos é compartilhado com outros processos.<br />

Possível solução(?):<br />

Lock fileA !<br />

! Lock fileB !<br />

! Write information to fileA and fileB !<br />

! Release the locks !<br />

Tentativa 1:<br />

O Jantar dos Filósofos<br />

Problema: Cada filósofos tenta pegar o garfo esquerdo, e se<br />

conseguir, espera pela devolução do garfo direito.<br />

E se todos pegarem o esquerdo ao mesmo tempo?<br />

Tentativa 2: Aguarde até obter garfo esquerdo; Se garfo direito<br />

estiver disponível, ok, senão devolve também o garfo esquerdo e<br />

espera.<br />

Qual é o problema agora?<br />

135<br />

136<br />

52


Jantar dos Filósofos<br />

Solução (parte 1)<br />

Jantar dos Filósofos<br />

Solução (parte 2): usar um vetor state[] para verificar o estado dos vizinhos e<br />

dos vizinhos dos vizinhos.<br />

137<br />

138<br />

53


Envio de Mensagens<br />

• É uma forma natural de interação entre processos<br />

• Duas primitivas:<br />

– send(dest, &message) – tam da mensagem fixo ou variável<br />

– receive(fonte, &message) - fonte pode ser ANY<br />

• Requer que processos se conheçam mutuamente<br />

– definem um enlace lógico de comunicação<br />

– Mensagens são tipadas e possuem (header e dados)<br />

• Um enlace pode ser:<br />

– Com processos co-localizados ou remotos<br />

– confiável ou não-confiável<br />

– Ponto-a-ponto ou Ponto-a-multiponto<br />

– Envolve elementos físicos (e.g., memória compartilhada,<br />

barramento, rede)<br />

Envio de Mensagens<br />

• Envio de mensagens é um mecanismo de sincronização<br />

mais genérico, porque:<br />

– Permite também a troca de dados<br />

– É independente se processos compartilham memória ou não.<br />

– Qualquer solução baseada em semáforos pode ser resolvida<br />

por envio de mensagens (considere: Down ≅ receive, Up ≅<br />

send, valor do semáforo = número de mensagens)<br />

• Decisões de projeto do mecanismo:<br />

– Com ou sem bufferização (send assíncrono ou Rendezvous)<br />

– Quando a comunicação é remota (pela rede) mensagens<br />

podem ser perdidas: são necessáriass confirmações,<br />

timeouts e re-transmissões<br />

– Na versão Rendezvous podem ocorrer impasses<br />

139<br />

140<br />

54


Envio de Mensagens<br />

Tipos de Envios de Mensagem<br />

Rendezvous (bloqueante)<br />

producer<br />

kernel<br />

Não-bloquenate<br />

producer<br />

kernel<br />

Entre processos co-localizados<br />

consumer<br />

consumer<br />

Request-Reply síncrono<br />

producer<br />

Request-Reply, não-bloqueante<br />

producer<br />

kernel<br />

kernel<br />

consumer<br />

consumer<br />

141<br />

142<br />

55


Request-Reply Síncrono: possíveis<br />

estados dos processos/threads<br />

Problema do Produtor-Consumidor<br />

com envio de N Mensagens<br />

Idéia: consumidor envia mensagem vazia, e produtor<br />

responde com a mensagem preenchida.<br />

143<br />

144<br />

56


ComunicaçãoRemota entre<br />

processos<br />

• Sockets<br />

• Remote Procedure Calls/ Remote Method<br />

Invocation<br />

Um socket é um ponto<br />

de comunicação de<br />

um processo: um<br />

objeto lógico através<br />

do qual mensagens<br />

podem ser enviadas<br />

e recebidas.<br />

É similar a um<br />

descritor de arquivo<br />

Principal Vantagem:<br />

não ter que conhecer<br />

o PID do processo<br />

Sockets<br />

145<br />

147<br />

57


Sockets<br />

• Concatenação de endereço IP address e porta<br />

• Socket 161.25.19.8:1625 se refere a porta 1625 no host<br />

161.25.19.8<br />

• Localhost: 127.0.0.1<br />

• Uma conexão TCP consiste de um par de sockets<br />

Portas reservadas para serviços Internet<br />

148<br />

149<br />

58


Chamada Remota de Procedimento<br />

(Remote Procedure Call, Remote Method Invocation)<br />

• RPC cria uma abstração de uma chamada de<br />

procedimento entre processos executando em<br />

maquinas uma rede;<br />

• Stubs – proxy no lado do cliente para o<br />

procedimento no lado servidor<br />

• O stub cliente encontra o servidor, estabelece uma<br />

conexão e faz o marshalling dos parámetros<br />

• Stub do lado servidor (skeleton) recebe a<br />

mensagem, desempacota os parâmetros, faz a<br />

chamada do procedimento, empacota o resultado e<br />

envia de volta ao stub cliente.<br />

RPC: Exemplo<br />

150<br />

153<br />

59


RPC<br />

Client Server<br />

Comunicação em Minix<br />

O núcleo implementa os seguintes tipos de comunicação:<br />

– send(dest, &message)<br />

– receive(src, &message)<br />

– send_rec(src_dst, &message)<br />

– notify (dest, &message)<br />

Observações:<br />

• Núcleo copia dados da área do processo fonte (src) para o processo<br />

destinatário (dest), usando CopyMess.<br />

• Existem 6 formatos de mensagem (mess_1, mess_2,…) para diferentes<br />

conjuntos de parâmetros (= union com source, type e tipos de dados),<br />

definidos em kernel/proc.h<br />

• Parâmetros podem ser ponteiros para estruturas de dados do sistema<br />

• Chamadas do sistema, interrupções de HW, do relógio, sinais, etc. todos<br />

são convertidos para envios de mensagens (p.ex. _syscall traduz código<br />

de chamada em mensagem)<br />

154<br />

156<br />

60


IPC no Minix<br />

• Todos os processos e tarefas de Minix interagem através de<br />

envio de mensagens<br />

• Send, receive e send_recv implementam Rendezvous, sempre<br />

entre pares de processos<br />

• Quando remetente e destinatário executaram essas primitivas,<br />

a mensagem é copiada da área do remetente para a área de<br />

memória do destinatário (não é armazenada no núcleo).<br />

• Os campos p_caller_q e p_q_link (nas entradas do dest e src,<br />

na tabela de processos do núcleo) apontam para a entrada dos<br />

processos dos quais está se esperando uma mensagem, ou o<br />

receive, respectivamente.<br />

• Notify é um envio assíncrono e possui prioridade mais alta.<br />

Quando o destinatário não está bloqueado, núcleo guarda<br />

informação de pendência, e quando o mesmo ficar bloqueado,<br />

e recria a mensagem original (BuildMess)<br />

Filas de processos<br />

Todos os processos esperando por enviar uma mensagem<br />

são enfileriados. Exemplo: processos 3 e 4 esperando<br />

para enviar msg ao processo 0;<br />

P_q_link<br />

P_q_link P_q_link<br />

157<br />

158<br />

61


Conclusão<br />

• Processo (thread) são as abstrações de fluxo de<br />

execução independente em um sistema<br />

• <strong>Processos</strong> possuem um espaço de endereçamento<br />

isolado/protegido dos demais procesos<br />

• Cada processo pode estar executando, pronto, ou<br />

bloqueado;<br />

• O núcleo gerencia a comunicação, a sincronização<br />

e o escalonamento entre processos<br />

• A maioria dos S.O. faz escalonamento preemptivo<br />

com vários níveis de prioridades<br />

163<br />

62

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!