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
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
- Page 2 and 3: 4 Criação de Processos Um sistema
- Page 4 and 5: 10 Estados de Processos (um pouco m
- Page 6 and 7: Troca de Contexto • Consiste de s
- Page 8 and 9: Interrupções vs. Exceções O con
- Page 10 and 11: Escalonamento • A cada instante u
- Page 12 and 13: Escalonamento 1. Escalonamento de l
- Page 14 and 15: Algoritmo geral para um escalonador
- Page 16 and 17: • Possíveis parâmetros: Funçã
- Page 18 and 19: Escalonamento com múltiplas filas
- Page 20 and 21: Algorítmos de escalonamento Rate M
- Page 22 and 23: Limitações do Modelo de Processos
- Page 24 and 25: 76 77 Exemplo de uso de threads Fig
- Page 26 and 27: Exemplo de Uso de Threads #include
- Page 28 and 29: O Descritor de Thread • Para cada
- Page 30 and 31: Threading híbrido (N para M): •
- Page 32 and 33: Outras questões de projeto Impleme
- Page 34 and 35: IPC básico no Unix • Sinais - no
- Page 36 and 37: Condição de Corrida Análogo vale
- Page 38 and 39: Região Crítica Para implementar u
- Page 40 and 41: Exclusão Mútua com Espera Ocupada
- Page 42 and 43: Problema do Produtor e Consumidor C
- Page 44 and 45: Semáforos - Implementação Mutex:
- Page 46 and 47: wait(c) signal(c) Monitor • Monit
- Page 48 and 49: O Problema dos Leitores e Escritore
- Page 50 and 51: Sincronizacão de Barreira • Quan
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