Baixe o app para aproveitar ainda mais
Prévia do material em texto
1 SO II 2-B 1 SO II 2-B 2.4 Problemas clássicos de IPC 2.4.1 O jantar dos filósofos Em 1965, Dijkstra propôs e resolveu um problema de sincronização que ele chamou de Jantar dos Filósofos. Desde então, toda vez que alguém inventa uma nova primitiva deve apresentar uma demonstração de como ela pode ser usada para resolver este problema. O sistema é bastante simples: 5 filósofos estão sentados ao redor de uma mesa, cada um com um prato de macarrão a sua frente. Entre cada 2 filósofos existe um garfo. Os filósofos podem estar em um de dois estados possíveis: pensando ou comendo. Para comer o macarrão, o filósofo precisa de dois garfos. Quando um filósofo fica com foem, ele tenta obter dois garfos. Se conseguir, ele pode comer. Se um ou os dois garfos não estiverem disponíveis (estão com algum outro filósofo), é preciso esperar. Uma solução óbvia é esta: Infelizmente, esta solução não funciona. Suponha que todos os 5 tentem pegar o garfo esquerdo simultaneamente. Nenhum deles poderá pegar o garfo direito e temos um deadlock. Uma tentativa de melhorar esta solução é colocar um teste no 2 o . take_fork. Se ele não estiver disponível, o 1 o . garfo é solto. Isso também não funciona. 2 SO II 2-B 2 SO II 2-B Suponha que todos peguem o garfo da esquerda. Testando o da direita, todos verificam que ele não está presente e soltam o 1 o . em seguida, todos tentam novamente resultando num impasse. Esta situação é chamada de starvation. Uma outra possibilidade é que os processos esperem um período de tempo aleatório antes de tentar de novo. A chance de dois processos escolherem o mesmo período é bem menor. Há aplicações que funcionam dessa forma (p. ex.: Ethernet). Porém, algumas aplicações podem precisar de uma solução que funcione sempre (p. ex.: controle de usina nuclear!). Uma solução que não possui deadlocks nem starvation é proteger os 5 comandos que seguem a chamada think() com um semáforo binário. Antes de tentar pegar o primeiro garfo, o processo chama um down sobre mutex. Depois de soltar os garfos, o processo chama um up. Esta solução tem um problema prático: apenas um processo pode comer por vez. Com 5 processos, pelo menos 2 poderiam comer por vez. Uma solução livre de deadlocks e que permite o máximo de paralelismo pode ser vista a seguir. 2.4.2 Leitores e escritores Este problema modela o acesso a bancos de dados. Com múltiplos processos acessando um BD, é aceitável que vários deles possam acessar simultaneamente desde que sejam apenas de leitura. Porém, quando um processo acessa o BD para atualização, nenhum outro pode acessar simultaneamente, nem mesmo os de leitura. Esse problema apresenta duas possibilidades: Suponha que alguns leitores estão acessando o BD e chega um processo de atualização; Este último é bloqueado – enquanto houver processo de leitura, o que atualiza ficará esperando indefinidamente; Caso cheguem mais processos de leitura, o que atualiza continuará esperando (virtualmente para sempre); Essa situação indica que os processos de leitura têm prioridade sobre os de atualização. Mas pode-se dar prioridade para os processos de atualização: Supondo que o processo de atualização bloqueie a entrada de novos processos de leitura; Nesse caso, os processos de leitura atuais eventualmente terminarão, liberando o acesso ao processo de atualização. 3 SO II 2-B 3 SO II 2-B 2.4.3 O barbeiro dorminhoco Este problema apresenta a seguinte situação: Há uma barbearia com um barbeiro, uma cadeira de barbeiro e N cadeiras de espera; Se não há clientes, o barbeiro senta na cadeira de barbeiro e dorme; Quando chega o primeiro cliente, ele acorda o barbeiro, ocupa a cadeira e tem o seu cabelo cortado; Caso cheguem outros clientes, eles devem ocupar as cadeiras de espera; Por fim, se chega um novo cliente e há alguém sendo atentido pelo barbeiro e todas as cadeiras de espera estão ocupadas, ele deve deixar o sistema; Quando o barbeiro termina de atender o cliente atual, este sai da cadeira e vai embora; Nesse caso, um dos clientes da espera é escolhido; Caso não haja + nenhum, o barbeiro volta a dormir. Este modelo é semelhante a vários problemas onde se apresenta uma fila para acessar algum tipo de serviço. 4 SO II 2-B 4 SO II 2-B 5 SO II 2-B 5 SO II 2-B 2.5 Escalonamento de Processos Scheduler: Há situações em que mais de um processo têm condições de execução em um momento; Deve-se decidir qual deles vai tomar posse do processador; Quem toma essa decisão é o Scheduler (Escalonador). Num sistema batch, temos o algoritmos mais simples de escalonamento. Um job é lido da unidade de fita; O job é carregado na memória e executa até o fim; Quando um job termina, o sistema carrega outro. Num sistema timesharing multi-usuário, o algoritmos é bem mais complexo: Pode haver vários processos prontos; Vários usuários esperam que suas tarefas sejam completadas; Há processos rodando em background (ex.: mail). Objetivos de um algoritmo de escalonamento: 1. Justiça: cada processo recebe um quantum adequado; 2. Eficiência: a finalidade do escalonamento é manter a CPU ocupada ~100%; 3. Tempo de resposta: deve ser baixo para os usuários interativos; 4. Turnaround: o tempo que um processo batch fica no sistema deve ser o menor possível; 5. Throughput: alto número de jobs processados por hora; Objetivos conflitantes: Usuários interativos x batch; Jobs devem ser rodados entre 0:00 h. e 6:00 h.; A CPU é finita; Se alguém é favorecido, outro é prejudicado; Síndrome do “cobertor curto”; Elementos complicadores: Processos são únicos; Como as pessoas, cada um possui uma “personalidade” diferente dos demais; Há processos que sempre estão esperando I/O (I/O bound), enquanto outros usam a CPU por horas (CPU bound); O comportamento de um processo é imprevisível. Relógio = Auxiliar do processador no controle de tempo. Interrupção por tempo: 50 ou 60 Hz. O relógio interrompe o processador 50 ou 60 vezes por segundo. Tick: período entre uma interrupção por tempo e a seguinte. Unidade de Fita Computador Job 6 SO II 2-B 6 SO II 2-B Preempção: suspensão temporária de um processo que poderia continuar executando. O processo deixa o processador e fica numa fila de processos “prontos para executar”. Um sistema preemptivo é o contrário de um sistema batch “rodar até o fim” (não preemptivo). Objetivos de todos os sistemas: Justiça; Política seguida por todos; Balanceamento equilibrado. Para sistemas batch: Maximizar o throughput (jobs por hora); Minimizar o turnaround (tempo entre submissão e término); Manter a CPU ocupada o máximo de tempo. Para sistemas interativos: Minimizar o tempo de resposta; Dar tratamento proporcional a todos os usuários. Para sistemas de tempo real: Atender aos deadlines – evita a perda de dados; Evitar a degradação de sistemas multimídia. 2.5.2 Escalonamento em sistemas batch First-Come First-Serve (FCFS) O método mais simples. Os processos ocupam a CPU na ordem em que entram no sistema. Método da fita de jobs (ou leitora de cartões perfurados). Fácil de programar e de entender. Com multiprogramação, toda vez que o processo em execução bloqueia (p. ex. I/O), o próximo da fila é executado. Quando um processo bloqueado torna-se pronto, vai para o fim da fila. A desvantagem desse sistema é não reconhecer a diferença entre processos CPU bound e I/O bound. Menor job primeiro A maioria dos algoritmos é projetada para sistemas interativos; Este é um algoritmo projetado para jobs batch onde o tempo de execução é conhecido. Ex.: Considere uma companhiade seguros em que os jobs possuem certas quantidades de solicitações por parte dos clientes. O tratamento de uma solicitação é conhecido, portanto o tempo de execução do job pode ser determinado. 4 jobs, A, B, C e D; tempo de execução de 8, 4, 4 e 4 minutos, respectivamente. Rodando os jobs nesta ordem, obtemos turnarounds de 8, 12, 16 e 20 minutos - média de 14 minutos. Com menor job primeiro, obtemos 4, 8, 12 e 20 - média de 11 minutos. O motivo é fácil de verificar. Ex.: 4 jobs com tempo de execução de a, b, c e d, respectivamente; O job 1 termina no tempo a; O job 2 termina no tempo a + b; 7 SO II 2-B 7 SO II 2-B O job 3 em a + b + c; O job 4 em a + b + c + d; A média é (4a + 3b + 2c + d)/4; O job 1 contribui com 4 vezes o seu tempo para a média. Este algoritmo pode ser usado para sistemas interativos, considerando um job uma requisição. Basta conhecer os tempos de execução das requisições. Próximo menor tempo restante Uma variação do Menor Job Primeiro é este método, que dá preferência para o job que possui o menor tempo restante de execução. O tempo de execução deve ser conhecido antecipadamente. Novos jobs podem suspender o processo em execução caso seu tempo restante seja menor. Escalonamento em 3 níveis Até agora, tem-se assumido que a memória é suficiente para todos os processos prontos do sistema. Se a memória for insuficiente, é preciso manter alguns processos em disco. Implicações: o tempo para trazer um processo do disco para a memória e vice-versa é algumas ordens de magnitude maior do que uma troca de contexto. O escalonador escolhe quem roda e quem vai para disco ou quem fica na memória. Processos novos ainda passam por uma fila de admissão. Critérios: 1. Quanto tempo faz desde o último swap? 2. Quanta CPU o processo consumiu? 3. Qual é o tamanho do processo (os pequenos não precisam passar por isso)? 4. Qual é a prioridade? 8 SO II 2-B 8 SO II 2-B 2.5.3 Escalonamento em Sistemas Interativos Escalonamento Round Robin Cada processo recebe um quantum: período de tempo em que ele pode utilizar o processador; No final do quantum, se o processo ainda estiver executando, ele sofre preempção e a CPU é entregue ao próximo processo; Caso o processo corrente solicite um I/O e bloqueie, a CPU é entregue ao próximo processo. Ex.: Quantum de 20 ms, troca de contexto de 5 ms (25%) Quantum de 500 ms, troca de contexto de 5 ms (1%) No caso 1, 25% do tempo da CPU é perdido. No caso 2, apenas 1% do tempo da CPU é perdido. Entretanto, com 10 usuários o 10 o poderia ter um tempo de resposta de 5 segundos. Escalonamento com Prioridades No método Round Robin, todos os processos são iguais. Na vida real, quase sempre estamos inseridos em hierarquias: Reitoria, centro, departamento, professor, estudante. Funcionamento: Cada processo recebe uma prioridade; O processo “pronto” com maior prioridade é aquele que executa. Para evitar que um processo execute indefinidamente, o escalonador pode reduzir sua prioridade a cada tick; Quando a prioridade do processo atual ficar abaixo da prioridade de outro processo pronto, ocorre preempção. Comando nice: um usuário pode reduzir a prioridade de seu processo caso ele não seja importante – “legal” para os outros usuários (ninguém usa!). Prioridade dinâmica: Cada processo recebe uma prioridade que representa o inverso da fração utilizada de seu quantum; Ex.: se ele utilizar 2 ms de um quantum de 100 ms, sua prioridade será 50; Ex.: se usar 50 ms, sua prioridade será 2; Ex.: se 100%, prioridade 1. Processo I/O bound: Processa para solicitar novo I/O; Este processo deve ter maior prioridade para que suas requisições de I/O sejam feitas em paralelo com a execução de outros processos; Caso não tenha prioridade, este processo utilizará a memória do sistema por muito tempo, consumindo recursos. 9 SO II 2-B 9 SO II 2-B Classes de prioridades: Enquanto houver processos prontos na classe 4, os processos das classes abaixo nunca serão executados. Múltiplas filas Baseado em exemplo antigo: CTSS, 1962. 7094 só podia manter um processo por vez na memória; Os gerentes desse sistema concluíram logo que era mais interessante dar um quantum grande aos processos do que dar quantuns pequenos e fazer mais operações de swap; Divisão em classes: Classe mais alta = 1 quantum; Classe seguinte = 2 quantuns; Etc. Ex.: a tarefa de um processo consiste de 100 quantuns. Executa 1 quantum na classe mais alta; Executa 2 quantuns e passa para a classe abaixo; Executa 4, 8, 16 e 32 quantuns; Dos últimos 64, executa apenas 37 e termina; Tudo isso com apenas 6 trocas de contexto + a carga inicial do programa. Próximo Menor Processo O método de Menor Job Primeiro produz o menor tempo de resposta médio para sistemas batch. Se pudéssemos usar um método desse tipo para sistemas interativos, teríamos o menor tempo de resposta para requisições. O problema reside em determinar qual é o tempo necessário para processar uma requisição. Uma forma de determinar isso é estimar baseado nos tempos passados, mas usando pesos, de forma que as medidas + recentes sejam mais importantes. Considere o seguinte exemplo: Uma requisição tem seu tempo estimado em T0; Um novo atendimento determina que o tempo gasto foi T1; Usando uma fórmula de peso a.T0 + (1 – a).T1, para um valor de a de ½ podemos obter uma série como segue: T0, T0/2 + T1/2, T0/4 + T1/4 + T2/2, T0/8 + T1/8 + T2/4 + T3/2 A técnica de estimar valores em uma série com peso, em que os valores mais recentes são + importantes, é chamada de aging (envelhecimento). 10 SO II 2-B 10 SO II 2-B Escalonamento garantido Abordagem: fazer promessas aos usuários sobre o desempenho do sistema - com n usuários, cada um tem garantido a utilização de 1/n do tempo de CPU. Para alcançar este objetivo, o sistema precisa saber quanta CPU cada job do usuário já consumiu desde o login e quanto tempo se passou desde o login de cada usuário. Com esses valores, pode-se computar a taxa de consumo de CPU por usuário. Em seguida, o sistema pode fazer a correção da utilização dando preferência ao usuário com menor taxa de consumo. Uma idéia parecida pode ser aplicada em sistemas de tempo real, comparando os deadlines que cada processo precisa atender. O sistema pode dar preferência ao job com deadline de mais risco de ser perdido. Escalonamento de loteria Waldspurger e Weihl, 1994: Os processos recebem tickets de loteria para vários recursos, inclusive CPU; O escalonador sorteia um ticket ao acaso; O processo que possuir este ticket ganha acesso ao recurso; Cada ticket vale uma certa quantidade do recurso; Processos de mais importância podem receber tickets extras; Ex.: um período de tempo dividido em 100 tickets. Se um processo tiver 20 tickets, tem 20% do tempo. A consumação é aleatória. A chance de um processo ser escolhido no sorteio é proporcional ao número de tickets que ele possui; Processos cooperativos podem partilhar tickets; Ex.: um cliente faz uma requisição a um servidor e bloqueia. Ele empresta seus tickets para aumentar a chance do servidor ser selecionado; Quando o servidor responder, devolve os tickets ao cliente. Método interessante para adequar necessidades de taxas diferentes. Ex.: vídeo - 10, 20 e 25 frames/segundo. Escalonamento de compartilhamento justo Suponha que dois usuários de um sistema tem a mesma prioridade. O usuário A executa 9 processos e o usuário B apenas 1. Sem outras informações, supondo que os processos sejam todos semelhantes, o usuário A vai obter 90% do uso da CPU, enquanto B vai ficar com os 10% restantes. Para evitaresta situação, alguns sistemas verificam a quem pertence um processo quando a CPU é alocada. Considere um usuário 1 com 4 processos: A, B, C e D; e um usuário 2 com o processo E. Um sistema Round Robin que use este método poderia produzir a seguinte sequência de alocação: 11 SO II 2-B 11 SO II 2-B A E B E C E D E A E B E C E D E ... Caso haja interesse em dar o dobro de CPU de B para A, a sequência seria: A B E C D E A B E C D E ... 2.5.4 Escalonamento em Tempo Real Casos em que uma resposta atrasada é tão ruim quanto nenhuma. Exemplos: Conversão dos dados de um CD em música; Monitoramento de pacientes; Piloto automático Controle de um reator nuclear. Tipos: Hard real time: deadlines rigorosos; Soft real time: perder algum deadline é aceitável; Eventos periódicos ou aperiódicos. Ex.: um sistema possui m eventos periódicos; o evento i ocorre no período Pi e necessita de Ci segundos de tratamento; a carga só pode ser manipulada se o sistema atender à regra: m Ci -- 1 i=1 Pi Um sistema de tempo real que atende este critério é chamado de escalonável. Ex.: 3 eventos com períodos de 100, 200 e 500 mseg Tratamento em 50, 30 e 100 mseg. Escalonável: 0,5 + 0,15 + 0,2 < 1 Algoritmos de tempo real: Dinâmicos: decisões durante a execução Estáticos: decisões antes da execução Algoritmos dinâmicos: 1) Taxa monotônica: Liu e Layland, 1973, clássico Prioridade proporcional à freqüência. Ex.: um processo que roda a cada 20 mseg > PRI 50 Outro roda a cada 100 mseg > PRI 10 Faz preempção de qualquer processo para rodar o mais alto. Algoritmo ótimo! 2) Primeiro deadline primeiro: processos que tratam eventos ficam na fila de prontos. A fila é classificada por deadline. 3) Menor margem. Ex.: tratamento de 200 mseg, deadline de 250 mseg, margem de 50 mseg 12 SO II 2-B 12 SO II 2-B Necessidades de tempo real: SO pequeno (microkernel); Tempo de interrupção pequeno; Troca de contexto rápida; Interrupções desabilitadas por pouco tempo; Manuseio de vários timers em mili ou microsegundos. 2.5.5 Política x mecanismo Em algumas situações, um processo pode ter vários filhos. Ex.: um banco de dados, onde cada filho atende uma requisição. O pai, no caso, deve ter uma boa idéia da importância de cada um de seus filhos (inclusive se algum deles é crítico). Nenhum dos algoritmos vistos faz perguntas aos processos usuários para tomar decisões. Assim, dificilmente fará as melhores escolhas. Solução: separar o mecanismo (que fica no kernel) da política (o processo de escolha). Ex.: no caso do BD, o pai pode definir a prioridade de seus filhos, mas o escalonador fará as escolhas quando for necessário. 2.5.6 Escalonamento de threads Num sistema de multiplos processos, que admite múltiplas threads, há 2 níveis de paralelismo: processos e threads. Quando o sistema implementa threads no nível do usuário, o kernel não toma conhecimento de sua existência. Ele continua escalonando apenas processos. O escalonador interno de cada processo decide qual thread deve executar. Como não há interrupção por tempo para threads, a thread selecionada pode executar indefinidamente. Eventualmente ela usará todo o quantum do processo. Nesse caso, o kernel selecionará outro processo para execução. Quando esse processo retomar a CPU, aquela thread continuará sua execução até que eventualmente termine. Porém, seu comportamento anti-social não afetará outros processos. O escalonador do kernel dará a cada um o tempo que ele achar justo, independente do que acontece dentro daquele processo. Agora, considere que as threads são cooperativas. Nesse caso, a situação pode ser a da figura acima. Por outro lado, quando as threads são implementadas no kernel ele escolhe que thread vai executar em determinado momento, independente do processo a que pertence. É claro que, se houver interesse, o kernel pode considerar esse dado para tomar sua decisão. 13 SO II 2-B 13 SO II 2-B A principal diferença entre threads no nível do usuário e no kernel é relativo ao desempenho: Fazer uma troca de threads num sistema de threads no nível do usuário significa gastar apenas algumas instruções; No nível do kernel, significa uma troca de contexto completa (se o processo for diferente), mudar o mapa de memória e invalidar o cache, o que é várias ordens de magnitude + lento; Por outro lado, num sistema no nível do kernel o I/O de uma thread não bloqueia as demais. Como o kernel sabe que o custo para a troca de contexto é muito alto, ele pode usar essa informação e escolher uma thread do mesmo processo em vez de uma thread de um processo diferente (desde que seja possível e interessante). Uma vantagem para threads no nível do usuário é que o escalonador interno do processo pode escolher as threads pelo que elas fazem (ele sabe!). No nível do kernel, o máximo que é possível fazer é dar + prioridade a uma delas, que será escolhida por causa disso. Sockets Chamadas para os protocolos orientados a conexão: Server Cliente Bloqueia até estabelecer a conexão com o cliente Estabelece conexão requisição Processa requisição resposta Chamadas para os protocolos sem conexão: Server socket() bind() listen() accept() read() socket() connect( ) write() write() read() socket() 14 SO II 2-B 14 SO II 2-B Cliente Bloqueia até receber do cliente Processa requisição sd = int socket (int family, int type, int protocol); Family: AF_UNIX protocolo interno Unix AF_INET protocolo Internet AF_NS protocolo Xerox NS AF_IMPLINK camada IMP link Type: SOCK_STREAM stream socket SOCK_DGRAM datagram socket SOCK_RAW raw socket SOCK_SEQPACKET sequenced packet socket SOCK_RDM reliably delivered message socket Protocol = 0 Combinações válidas de family e type: AF_UNIX AF_INET AF_NS SOCK_STREAM Sim TCP SPP SOCK_DGRAM Sim UDP IDP SOCK_RAW IP Sim SOCK_SEQPACKET SPP int bind (int sockfd, struct sockaddr *endereço, int tamanho); struct sockaddr { u_short sa_family; /* família de endereços: AF_xxx */ char sa_data[14]; /* até 14 bytes de endereço */ }; int connect(int sockfd, struct sockaddr * server, int tamanho); int listen(int sockfd, int backlog); backlog = 5, em geral. int accept(int sockfd, struct sockaddr * peer, int * tamanho); peer é o cliente. int sendto (int sockfd, char * buff, int nbytes, int flags, struct sockaddr * to, int tamanho); int recvfrom (int sockfd, char * buff, int nbytes, int flags, struct sockaddr * to, int tamanho); bind() recvfrom() sendto() socket() bind() sendto() recvfrom( ) 15 SO II 2-B 15 SO II 2-B flags = 0 bzero(char * dest, int nbytes);
Compartilhar