Sistemas Operacionais Processos Prof. Raimundo Macêdo, DCC/UFBA
Processo processo: programa em execução consiste de: código do programa executável dados pilha de execução contador de programa valores de registros informações sobre estado de acesso a arquivos etc.
Árvore de Processos criação dinâmica de processos A processo pai processo pai processos filhos de B B D E F C processos filhos de A X Processo
Criação de Processos no Unix #include <stdlib.h> #define SHELL /bin/sh int my_system (const char *command) { int status; pid_t pid; } pid = fork(); if (pid == 0) { execl (SHELL, SHELL, -c, command, NULL); exit (EXIT_FAILURE); } else if (pid < 0) status = -1; else if (waitpid (pid, &status, 0)!= pid) status = -1; return status; main (int argn, char **argc) { my_system ( ls -la ); }
Processos no Linux LaSiD - Laboratório de Sistemas Distribuídos
struct task_struct armazena informações sobre processos ver arquivo include/linux/sched.h anexo
Alguns Atributos de Processos uid (user identification) gid (group identification) diretório corrente de trabalho descritores de arquivo padrão: standard input standard output standard error
Estados de um Processo Executando Bloqueado Pronto
Escalonamento de Processos Processos 0 1 n-1... Scheduler
Implementação de Processos o sistema operacional mantém uma tabela de processos na tabela de processos há uma entrada por processo
Exemplo de Tabela de Processos Gerenciamento de Processos Gerenciamento de Memória Gerenciamento de Arquivos registradores program counter apontador da pilha estado do processo tempo de início de execução tempo de CPU usado... apontador para segmento de código apontador para segmento de dados... diretório raiz diretório corrente descritores de arquivos...
Processo Abstrato pilha Concreto heap Processos Comunicação para realização das tarefas Processos Independentes, Competidores ou Cooperantes dados código
Processos em Sistemas Centralizados Comunicação Compartilhamento de Memória Primitivas: Semáforos, filas de mensagens, monitores, sinais etc Relógio Único RAM Pi Pn Pj Pk
Processos em Sistemas Distribuídos Comunicação Troca de Mensagens Primitivas: send(destino, mensagem) e receive(origem, mensagem) Pi Pj Pk Pn RAM RAM RAM RAM
Sincronização entre Processos Race condition: dois processos compartilham dados e o resultado depende de quem executa exatamente quando Solução : Exclusão Mútua
Propriedades de um programa distribuído (Lamport, 1977) LaSiD - Laboratório de Sistemas Distribuídos Exemplo:exclusão mútua num sistema controlando semáforos safety: dois semáforos numa estarão verde ao mesmo tempo liveness: um carro esperando na luz vermelha, receberá luz verde no futuro
Exemplo de Race Condition Extraído do livro Tanenbaum pg. 54 spooler de fila de impressão 1- A atualiza nexta = in + 1 2- A é interrompido pelo escalonador 3 - B atualiza netxb := in + 1 Processo A Processo B 4 5 6 7 abc prog.c prog.n 4 - B faz spooler [nextb] := file; in := nextb; /* in = 7 * / 5 - Escalonador coloca A na CPU 6 - A faz spooler [nexta] := file; in := nexta; /* in = 7 * / out = 4 in = 6 in e out são variáveis compartilhadas Arquivo de B nunca será impresso
Formulação Abstrata do Requisito de Sincronização região crítica: parte do programa que acessa o recurso compartilhado exclusão mútua: garantia de que, se um processo está acessando a sua região crítica, nenhum outro processo estará fazendo o mesmo
Requisitos de uma boa solução dois processos não podem estar simultaneamente em suas regiões críticas (safety) nenhuma consideração deve ser feita sobre velocidades relativas de execução de processos nenhum processo executando fora de sua região crítica pode bloquear outros processos (safety) processos devem em algum instante poder entrar em suas regiões críticas (liveness)
As soluções em geral implicam : Ação anterior (testar condição)... RC Saída da região em crítica (liberar a RC para outros processos)
Métodos para Exclusão Mútua Desligar (desabilitar) interrupções Alternação estrita (strict alternation) Solução de Peterson Instrução TSL (test and set lock) Semáforos Monitores Troca de Mensagens
Desligar Interrupções solução simples não é recomendada no nível das aplicações pode ser útil para o kernel somente funciona para sistemas monoprocessados Desabilita as interrupções... RC Habilita interrupções
Tentando resolver o problema via software com uso de variáveis tipo tranca while (lock ==1) // wait lock = 1... R.C.... lock =0 Para sair Para entrar Problema : 2 processos podem entrar região crítica caso leia o valor de lock simultaneamente.
Resolvendo a Exclusão mútua com Alternação Estrita while (TRUE) { while (turn!= 0) /* wait */ critical_section(); turn = 1; noncritical_section(); } while (TRUE) { while (turn!= 1) /* wait */ critical_section(); turn = 0; noncritical_section(); } Problema : a falha ou não execução de um processo impedirá o acesso do outro processo
int wait_turn; Solução de Peterson int interested[2]; /* valores iniciais = 0 */ void enter_region (int process) { } int other; other = 1 - process; interested[process] = TRUE; wait_turn = process; while (wait_turn == process && interested[other] == TRUE); void leave_region (int process) { } interested[process] = False); Exercício para casa : 1) mostrar que a solução de peterson atende aos requisitos de exclusão mútua 2) fazer a versão generalizada para n processos Dica : usar esquema de senhas
Exemplo: P0 Solução de Peterson P1 Other = 1 0 = 1 Interested[0] = true wait_turn = 0 other = 1 1 = 0 interested[1] = true wiat_turn = 1 While(cond?) Entra RC LaSiD - Laboratório de Sistemas Distribuídos White(cond?) Espera
Solução de exercício Usar liga e desliga interrupções somente para implementar o teste do lock. Obs: lock assume inicialmente valor 1 Testa_RC: DI (desliga) LA lock (acumulador := lock) CMP A,#0 JNZ Entra_Região_Crítica LI (liga interrupções) JMP Testa_RC(testa novamente se valor de lock = 1) Entra_Região_Crítica Set lock,0 (lock:=0) LI (liga interrupções) Sai_RC set lock,#1 LaSiD - Laboratório de Sistemas Distribuídos
... until false; Exercício (trazer na próxima aula) LaSiD - Laboratório de Sistemas Distribuídos A primeira solução de software correta para o problema de exclusão mútua foi feita por Dekker. Mostre que está correta segundo critérios dados em sala de aula. Repeat flag[i] := true; /* para tentar entrar na RC */ while flag[j] do if turn = j then begin flag[i] := false; while turn = j do no_op; flag[i] := true; end; RC... Turn := j; /* saida da RC */ Flag[i]:= false;
Resolvendo o Problema no Nível do LaSiD - Laboratório de Sistemas Distribuídos Hardware : A Instrução TSL atomicamente transfere uma posição da memória para um registrador e armazena um valor diferente de zero na na posição de memória enter_region: tsl reg, lock /* reg:= lock; lock:= 1; */ cmp reg, #0 /* reg 0 o valor de lock era zero? */ jnz enter_region /* loop se o valor de lock era 1 */ ret leave_region: mov lock, #0 /* lock := 0*/ ret
Tentando resolver o problema: while Enter_region (lock ==1) // wait lock com TST = o... R.C.... Leave_region lock =1 com TST Para entrar Para sair Problema : stavation.
Uso de Semáforos para Exclusão Mútua Wait = Down = P Signal = UP = V Wait(mutex) - bloqueia processo se a RC está ocupada Rergião Crítica Signal(mutex) libera a RC para outros processos
Semáforos de Dijkstra Abstração conveniente para lidar com o problema de acesso a região crítica e sincronização entre processos Um semáforo é uma variável inteira operações WAIT ou P (Proberen em holandês) SIGNAL ou V (Verhogen em holandês)
Semáforos de Dijkstra Definição Clássica de Semáforos Inicialmente S := 1; WAIT(S) : while S 0 no_op S := S -1; SIGNAL(S) : S := S + 1; OBS: Wait e Signal são atômicos pois são regiões críticas Exercício para Casa : Implementar WAIT e SIGNAL
Implementando Semáforos de Dijkstra WAIT(S) Enter_region: tsl reg, S /* reg:= S; S:= 1; */ cmp reg, #0 /* reg 0 o valor de lock era zero? */ jnz enter_region /* loop se o valor de lock era 1*/ ret SIGNAL(S): mov lock, #0 /* lock := 0*/ ret
Usando Semáforos para resolver o problema de acesso à região crítica para n processos LaSiD - Laboratório de Sistemas Distribuídos Solução : os processos compartilham um semáforo mutex (mutual exclusion) iniciado com valor 1 Repeat wait (mutex) seção crítica signal (mutex) resto da seção until false OBS: BUSY WAIT irá ocorrer, mas pode ser interessante para multiprocessadores ou quando o lock for rápido
Usando semáforos para sincronização entre processos cooperantes Exemplo: suponhas processos P1 e P2 cuja operação S1 de P1 necessariamente ter que ser executada antes da operação S2 de P2 Processo 1 S1; Signal (synch); Processo 2 wait (synch); S2;
Espera Ocupada (Busy Waiting) Os métodos: Alternação estrita (strict alternation) Solução de Peterson Instrução TSL (test and set lock) Semáforos convencionais são baseados em espera ocupada! implicam em gasto de tempo de CPU
Como resolver o problema de BUSY WAIT da especificação clássica dos semáforos? LaSiD - Laboratório de Sistemas Distribuídos Solução : uso de bloqueio de processos e filas de espera associadas aos semáforos Processo P WAIT (S) ---> processo P é bloqueado e colocado na fila de espera associado a S. O controle passa para o escalonador que então passo o controle para outro processo. Processo P SIGNAL (S) ---> o escalonador tira um processo associado a S do estado waiting para ready
Estrutura do semáforo com filas LaSiD - Laboratório de Sistemas Distribuídos Type semáforo = Record value : integer; L : lista de processos End; WAIT (S) : S.value := S.value - 1; if S.value < 0 then begin add P to S.L; end; SIGNAL (S) : S.value := S.value + 1; if S.value 0 then begin remove a P from S.L; wakeup(p); end;
Problemas Clássicos de sincronização que modelam vários Mecanismos de Sistemas Operacionais Produtor/Consumidor o problema dos filósofos (the dining philosophers problem) o problema dos leitores/escrevedores (the readers and writers problem) o problema do barbeiro que dorme (the sleeping barber problem)
O Problema Produtor/Consumidor dois processos compartilham um buffer comum, de tamanho fixo um deles, o produtor, coloca informação no buffer o outro, o consumidor, tira informações do buffer quando o buffer estiver cheio, o produtor dorme quando o buffer ficar vazio, o consumidor dorme este problema é sujeito a race condition
Produtor/Consumidor Usando buffer infinito consumidor 0 1 2 3 4 5 6... produtor
Produtor entra_região_crítica buffer[fim] := item; fim := fim + 1; num_itens := num_itens +1 ; if num_itens If fim = in = then 1... then tira consumidor da fila FIFO sai_região_crítica Set in = 0; fim = 0;num_itens = 0; Consumidor entra_região_crítica if num_itens > 0 If fim > in then... item := buffer[in] in := in + 1; num_itens:= num_itens - 1; else coloca consumidor na fila FIF sai_região_crítica
Solução do problema do Produtor/Consumidor usando semáforo consumidor 0 1 2 3 4 5 6... produtor Produtor repeat produz um novo item; wait (mutex); buffer[fim] := item; fim := fim + 1; Consumidor reapeat wait (full) wait (mutex) item := buffer[in] in := in + 1 signal (mutex); until false; Exercício : fazer o signal algoritmo (mutex); para buffer finito de tamanho N signal (full) until false;
Monitores encapsulamento de procedimentos, variáveis e estruturas processos chamam procedimentos do monitor apenas um processo pode estar ativo dentro do monitor num dado instante o compilador constrói a exclusão mútua
Estrutura de Monitores monitor example integer i; condition c; procedure producer(x);... end procedure consumer(x);... end end monitor
Monitores: Primitivas wait/signal Primitivas do Monitor wait(cond): bloqueia o processo que a chamou e espera por um signal. signal(cond): acorda um processo bloqueado O que fazer depois do signal Hoare: processo acordado ganha o direito de executar e bloqueia o processo que acordou B. Hansen: processo que executou signal() fica obrigado a deixar o monitor
Produtor/Consumidor usando Monitor Monitor ProdCons condition full, empty; int cont = 0; Procedure Enter; if (cont == N) wait(full); enter_item; cont = cont + 1; if (cont == 1) signal(empty); end; Procedure Remove; if (cont == 0) wait(empty); remove_item; cont = cont + 1; if (cont == N -1) signal(full); END; Procedure Prod; while (TRUE) Produce_item; ProdCons.Enter; endwhile; end; Procedure Cons; while (TRUE) ProdCons.Remove; Consome_item; endwhile; end; Características Exige suporte da linguagem de programação
Troca de Mensagens primitivas send(destino, mensagem) receive(fonte, mensagem) se nenhuma mensagem está disponível, o recebedor pode bloquear até que uma mensagem chegue
Produtor/Consumidor com Troca de Mensagens #define N 100 #define MSIZE 4 typedef int message[msize]; void producer(void) { int item; message m; while (TRUE) { produce_item(&item); receive(consumer,&m); build_message(&m,item); send(consumer,&m); } } void consumer(void) { int item; message m; for (int i=0; i<n; i++) send(producer,&m); while (TRUE) { receive(producer,&m); extract_item(&m,item); send(producer,&m); consume_item(item) } }
O problema dos filósofos garfo prato com spaghetti mesa
O problema dos filósofos (cont.) um conjunto de filósofos está sentado em volta de uma mesa cada filófoso tem um prato de spaghetti para comer o spaghetti, um filósofo precisa de dois garfos entre cada prato tem um garfo a vida de um filósofo consiste de períodos alternados de comer e pensar quando um filósofo tem fome, ele tenta obter os dois garfos ao lado de seu prato se o filósofo consegue obter os garfos, ele come por um tempo e depois repõe os garfos sobre a mesa como programar a ação dos filósofos?
O problema dos filósofos Pega o 1o e espera pelo 2o (se ocupado)
O problema dos filósofos Se o 2o estiver ocupado, larga os dois garfos Larga e tenta de novo
O problema dos filósofos Se o 2o estiver ocupado, larga os dois garfos Larga e tenta de novo APÓS um Tempo Aleatório
O problema dos filósofos Usando mutex para exclusão mútua Down(mutex) pega 1o pega 2o come larga os garfos up(mutex)
Uma solução para o problema dos filósofos #define N 5 #define LEFT (i-1)%n #define RIGHT (i+1)%n #define THINKING 0 #define HUNGRY 1 #define EATING 2 typedef int semaphore; int state[n]; semaphore mutex = 1; semaphore s[n]; void philosopher(int i) { while (TRUE) { think(); take_forks(i); eat(); put_forks(i); } } void take_forks(int i) { down(&mutex); state[i] = HUNGRY; test(i); up(&mutex); down(&s[i]); } void put_forks(int i) { down(&mutex); state[i] = THINKING; test(left); test(right); up(&mutex); } void test(int i) { if (state[i] == HUNGRY && state[left]!= EATING && state[right]!= EATING) { state[i] = EATING; up(&s[i]); } }
O problema do barbeiro que dorme um barbearia tem: um barbeiro uma cadeira para cortar cabelo n cadeiras para clientes se não há clientes, o barbeiro senta-se na cadeira de cortar cabelo e dorme quando um cliente chega, ele acorda o barbeiro se outros clientes chegam, eles sentam-se nas cadeiras (se houver cadeiras vagas) ou vão embora (se não há cadeiras) como programar o barbeiro e os clientes?
Uma solução para o problema do barbeiro que dorme #define CHAIRS 5 semaphore customers = 0; sempahore barbers = 0; semaphore mutex = 1; int waiting = 0; void Barber(void) { while (TRUE) { down(customers); down(mutex); waiting = waiting - 1; up(barbers); up(mutex); cut_hair(); } } void Customer(void) { down(mutex); if (waiting < CHAIRS) { waiting = waiting + 1; up(customers); up(mutex); down(barbers); get_haircut(); } else up(mutex); } }
Exercício Fazer uma solução para o problema do barbeiro considerando que não há cadeiras de espera
O problema dos leitores/escrevedores um conjunto de leitores e escrevedores podem ter acesso a um conjunto compartilhado de dados quando um escrevedor quer escrever, ele tem que ter acesso exclusivo aos dados vários leitores podem ler os dados ao mesmo tempo como programar os leitores e escrevedores?
Uma solução para o problema dos escritores/escrevedores semaphore mutex = 1; semaphore db = 1; int rc = 0; void reader(void) { while (TRUE) { down(&mutex); rc = rc + 1; if (rc == 1) down(&db); up(&mutex); read_database(); down(&mutex); rc = rc -1; if (rc == 0) up(&db); up(&mutex); use_data_read(); } } void writer (void) { } while (TRUE) { } think_up_data(); down(&mutex); write_database(); up(&mutex);