Baixe o app para aproveitar ainda mais
Prévia do material em texto
T164s Tanenbaum, Andrewa S. Sistemas operacionais [recurso eletrônico] : projeto e implementação / Andrew S. Tanenbaum, Albert S. Woodhull ; tradução João Tortello. – 3. ed. – Dados eletrônicos. – Porto Alegre : Bookman, 2008. Editado também como livro impresso em 2008. ISBN 978-85-7780-285-2 1. Sistemas operacionais. I. Woodhull, Albert S. II. Título. CDU 004.4 Catalogação na publicação: Mônica Ballejo Canto – CRB 10/1023 78 SISTEMAS OPERACIONAIS 2.1.7 Threads Nos sistemas operacionais tradicionais, cada processo tem um espaço de endereçamento e um único fl uxo de controle. Na verdade, essa é praticamente a defi nição de processo. Con- tudo, freqüentemente existem situações em que é desejável ter vários fl uxos de controle no mesmo espaço de endereçamento, executando quase em paralelo, como se fossem processos separados (exceto quanto ao espaço de endereçamento compartilhado). Normalmente, esses fl uxos de controle são chamados de threads, embora algumas pessoas os chamem de pro- cessos leves. Uma maneira de “enxergar” um processo é como um modo de agrupar recursos rela- cionados. Um processo tem um espaço de endereçamento contendo texto e dados do progra- ma, assim como outros recursos. Esses recursos podem incluir arquivos abertos, processos fi lhos, alarmes pendentes, rotinas de tratamento de sinal, informações de contabilização e muito mais. Colocando-os juntos na forma de um processo, eles podem ser gerenciados mais facilmente. O outro conceito que um processo tem é o de fl uxo de execução, normalmente deno- minado apenas por thread. Uma thread tem um contador de programa que controla qual instrução vai ser executada. Ela possui registradores, os quais contêm suas variáveis de tra- balho correntes. Possui uma pilha, que contém o histórico de execução, com um bloco para cada função chamada, mas das quais ainda não houve retorno. Embora uma thread deva ser executada em algum processo, a thread e seu processo são conceitos diferentes e podem ser tratados separadamente. Os processos são usados para agrupar recursos; as threads são as entidades programadas para execução na CPU. O que as threads acrescentam no modelo de processo é o fato de permitir que várias execuções ocorram no mesmo ambiente de processo de forma bastante independente umas da outras. Na Figura 2-6(a), vemos três processos tradicionais. Cada processo tem seu próprio espaço de endereçamento e uma única thread de controle. Em contraste, na Figura 2-6(b), ve- mos um único processo com três threads de controle. Embora, nos dois casos, tenhamos três threads, na Figura 2-6(a) cada uma delas opera em um espaço de endereçamento diferente, enquanto na Figura 2-6(b) as três compartilham o mesmo espaço de endereçamento. Como exemplo de onde múltiplas threads poderiam ser utilizadas, considere um pro- cesso navegador web. Muitas páginas web contêm diversas imagens pequenas. Para cada imagem em uma página web, o navegador deve estabelecer uma conexão separada com o site de base da página e solicitar a imagem. Muito tempo é gasto no estabelecimento e na liberação de todas essas conexões. Com múltiplas threads dentro do navegador, várias ima- Thread Thread Núcleo Núcleo Processo 1 Processo 1 Processo 1 Processo Espaço de usuário Espaço de núcleo (a) (b) Figura 2-6 (a) Três processos, cada um com uma thread. (b) Um processo com três threads. CAPÍTULO 2 • PROCESSOS 79 gens podem ser solicitadas ao mesmo tempo, na maioria dos casos aumentando bastante o desempenho, pois com pequenas imagens o fator limitante é o tempo de estabelecimento da conexão e não velocidade da linha de transmissão. Quando múltiplas threads estão presentes no mesmo espaço de endereçamento, alguns dos campos da Figura 2-4 não são por processo, mas por thread, de modo que é necessária uma tabela de segmentos separada, com uma entrada por thread. Entre os itens por thread es- tão o contador de programa, os registradores e o estado. O contador de programa é necessário porque, assim como os processos, as threads podem ser suspensas e retomadas. Os registra- dores são necessários porque quando as threads são suspensas, seus registradores devem ser salvos. Finalmente, assim como os processos, as threads podem estar no estado em execução, pronto ou bloqueado. A Figura 2-7 lista alguns itens por processo e por thread. Itens por processo Espaço de endereçamento Variáveis globais Arquivos abertos Processos fi lhos Alarmes pendentes Sinais e rotinas de tratamento de sinal Informações de contabilização Itens por thread Contador de programa Registradores Pilha Estado Figura 2-7 A primeira coluna lista alguns itens compartilhados por todas as threads em um processo. A segunda lista alguns itens privativos de cada thread. Em alguns sistemas, o sistema operacional não está ciente da existência das threads. Em outras palavras, elas são gerenciadas inteiramente em espaço de usuário. Quando uma thread está para ser bloqueada, por exemplo, ela escolhe e inicia seu sucessor, antes de parar. Vários pacotes de threads em nível de usuário são de uso comum, incluindo os pacotes PO- SIX P-threads e Mach C-threads. Em outros sistemas, o sistema operacional está ciente da existência de múltiplas threads por processo, de modo que, quando uma thread é bloqueada, o sistema operacional escolhe a próxima a executar, seja do mesmo processo, seja de um diferente. Para fazer o escalonamen- to, o núcleo precisa ter uma tabela de threads listando todas as threads presentes no sistema, análoga à tabela de processos. Embora essas duas alternativas possam parecer equivalentes, elas diferem considera- velmente no desempenho. A alternância entre threads é muito mais rápida quando o geren- ciamento de threads é feito em espaço de usuário do que quando é necessária uma chamada de sistema. Esse fato é um argumento forte para se fazer o gerenciamento de threads em espaço de usuário. Por outro lado, quando as threads são gerenciadas inteiramente em espaço de usuário e uma thread é bloqueada (por exemplo, esperando uma E/S ou o tratamento de um erro de página), o núcleo bloqueia o processo inteiro, pois ele nem mesmo está ciente da existência de outras threads. Esse fato, assim como outros, é um argumento para se fazer o gerenciamento de threads no núcleo (Boehm, 2005). Como conseqüência, os dois sistemas estão em uso e também foram propostos esquemas mistos (Anderson et al., 1992). Independentemente das threads serem gerenciadas pelo núcleo ou em espaço de usuá- rio, elas introduzem muitos outros problemas que deve ser resolvidos e que alteram consi- deravelmente o modelo de programação. Para começar, considere os efeitos da chamada de sistema fork. Se o processo pai tiver múltiplas threads, o fi lho também deverá tê-las? Se não, o processo poderá não funcionar corretamente, pois todas podem ser essenciais. 80 SISTEMAS OPERACIONAIS Entretanto, se o processo fi lho recebe tantas threads quanto o pai, o que acontece se uma for bloqueada em uma chamada read, digamos, a partir do teclado? Agora, duas threads estão bloqueadas no teclado? Quando uma linha é digitada, as duas threads obtêm uma cópia dela? Só o pai? Só o fi lho? O mesmo problema existe com conexões de rede abertas. Outra classe de problemas está relacionada ao fato das threads compartilharem muitas estruturas de dados. O que acontece se uma thread fecha um arquivo enquanto outra ainda está lendo esse arquivo? Suponha que uma thread perceba que há muito pouca memória e co- mece a alocar mais memória. Então, no meio do caminho, ocorre uma alternância de threads e a nova thread também percebe que há pouca memória e também começa a alocar mais me- mória. A alocação acontece uma ou duas vezes? Em quase todos os sistemas que não foram projetados considerando threads, as bibliotecas (como a função de alocação de memória) não são reentrantes e causarão uma falha se for feita uma segunda chamada enquanto a primeira ainda estiver ativa. Outro problema estárelacionado com o informe de erros. No UNIX, após uma chamada de sistema, o status da chamada é colocado em uma variável global, errno. O que acontecerá se uma thread fi zer uma chamada de sistema e, antes que possa ler errno, outra thread fi zer uma chamada de sistema, apagando o valor original? Em seguida, considere os sinais. Alguns sinais são logicamente específi cos a uma thre- ad, enquanto outros, não. Por exemplo, se uma thread chama alarm, faz sentido o sinal resul- tante ir para a thread que fez a chamada. Quando o núcleo está ciente das threads, ele normal- mente pode garantir que a thread correta receba o sinal. Quando o núcleo não está ciente das threads, o pacote que as implementa deve monitorar os alarmes sozinho. Existe uma compli- cação adicional para as threads em nível de usuário, quando (como no UNIX) um processo só pode ter um alarme pendente por vez e várias threads chamam alarm independentemente. Outros sinais, como SIGINT, iniciado pelo teclado, não são específi cos de uma thread. Quem deve capturá-los? Uma thread específi ca? Todas as threads? Uma thread recentemente criada? Cada uma dessas soluções tem problemas. Além disso, o que acontece se uma thread altera as rotinas de tratamento de sinal sem informar as outras threads? Um último problema introduzido pelas threads é o gerenciamento da pilha. Em muitos sistemas, quando ocorre estouro da pilha, o núcleo apenas fornece mais pilha automaticamen- te. Quando um processo tem múltiplas threads, ele também deve ter múltiplas pilhas. Se o núcleo não estiver ciente de todas essas pilhas, ele não poderá aumentá-las automaticamente em caso de falta de pilha. Na verdade, ele nem mesmo percebe que uma falha de memória está relacionada com o crescimento da pilha. Certamente esses problemas não são insuperáveis, mas eles mostram que apenas intro- duzir threads em um sistema existente, sem um reprojeto substancial do sistema, não funcio- nará. No mínimo, a semântica das chamadas de sistema tem de ser redefi nidas e as bibliotecas precisam ser reescritas. E todas essas coisas devem ser feitas de tal maneira que permaneçam compatíveis com os programas já existentes, para o caso limite de um processo com uma só thread. Para obter informações adicionais sobre threads, consulte Hauser et al. (1993) e Marsh et al. (1991). 2.2 COMUNICAÇÃO ENTRE PROCESSOS Freqüentemente, os processos precisam se comunicar com outros processos. Por exemplo, no shell, em um pipe a saída do primeiro processo deve ser passada para o segundo processo e assim sucessivamente, em seqüência. Portanto, há necessidade de comunicação entre os pro- cessos, preferivelmente de uma maneira bem-estruturada que não utilize interrupções. Nas
Compartilhar