Fundamentos de Sistemas Operacionais: Threads, Memória e I/O

Classificado em Computação

Escrito em em português com um tamanho de 17,12 KB

Gerenciamento de Threads e Processos

Monothread vs. Multithreads

Monothread

Neste ambiente, cada processo possui seu próprio espaço de endereçamento. Consome muitos recursos do sistema, pois o sistema precisa alocar recursos no início e no término do processo. O compartilhamento de espaço de endereçamento é um desafio, pois como cada processo tem seu próprio espaço, a comunicação entre processos fica mais lenta, sendo necessária a utilização de mecanismos como semáforos, pipes, compartilhamento de memória, etc.

Multithreads

É um ambiente com múltiplos threads, e pode ser definido como uma sub-rotina de um programa executada de forma assíncrona. Desta forma, um ambiente multithread possibilita a execução concorrente de sub-rotinas dentro de um mesmo processo.

A grande vantagem no uso de threads está no uso do espaço de endereçamento, pois as threads compartilham esse espaço dentro de um mesmo processo. Por isso, a comunicação entre elas pode ser realizada de forma rápida e eficiente, além de minimizar a alocação de recursos do sistema. O problema é que não existe proteção no acesso à memória, ou seja, um thread pode alterar dados de outros threads. Deste modo, é fundamental a implementação de mecanismos de comunicação e sincronização entre threads a fim de garantir o acesso seguro aos dados compartilhados na memória.

Tipos de Threads e Aplicações

Threads em Modo Usuário (TMU)

São implementados pela aplicação e não pelo sistema operacional. Neste modo, o sistema operacional não sabe da existência de múltiplos threads, pois a aplicação fica responsável por gerenciar e sincronizar os threads existentes.

Threads em Modo Kernel (TMK)

São implementadas diretamente pelo núcleo do sistema operacional. Desta forma, o SO pode escaloná-los individualmente e executá-los simultaneamente.

Scheduler Activations (Ativações do Escalonador)

É utilizado em threads de modo híbrido para melhorar o desempenho, evitando mudanças de modo de acesso (usuário-kernel-usuário). Esta biblioteca pode escalonar os threads conforme a necessidade sem que o kernel seja ativado.

Benefícios do Uso de Threads

  • O uso de threads pode melhorar o desempenho de aplicações paralelas em ambientes com múltiplos processadores, se a aplicação permitir que partes diferentes do seu código sejam executadas em paralelo de forma independente.
  • Em ambientes Cliente-Servidor: Melhoria no desempenho da aplicação. Além disso, a comunicação entre os threads no servidor pode ser feita através de mecanismos mais simples e eficientes.
  • Em arquiteturas Microkernel: A arquitetura microkernel utiliza processos para implementar funções relativas ao kernel do sistema operacional e possibilita um melhor desempenho em processos servidores.

Sincronização e Comunicação entre Processos

Aplicação Concorrente

É uma aplicação estruturada de maneira que partes diferentes do código do programa possam executar paralelamente. Este tipo de aplicação tem como base a execução cooperativa de múltiplos processos ou threads trabalhando em uma mesma tarefa.

Se dois ou mais processos tentarem acessar um recurso simultaneamente e não houver um gerenciamento adequado no uso de recursos compartilhados, pode ocorrer inconsistência de dados.

Para resolver problemas de compartilhamento de recursos, é necessário garantir que somente um único processo possa estar acessando o recurso por vez.

Exclusão Mútua

É uma técnica usada para impedir que dois ou mais processos acessem um mesmo recurso simultaneamente. Para isso, enquanto um processo estiver acessando determinado recurso, todos os demais processos que queiram acessá-lo deverão esperar pelo término da utilização do recurso.

Starvation (Inanição)

É a situação onde um processo nunca consegue executar sua região crítica e, consequentemente, acessar o recurso compartilhado. Para prevenir esse problema, é necessário que o SO estabeleça mecanismos de acesso que garantam o acesso ao recurso por todos os processos que solicitarem uso.

Espera Ocupada (Busy Waiting)

Ocorre toda vez que um processo não consegue entrar em sua região crítica, por já existir outro processo acessando o recurso. O processo permanece em looping, testando uma condição, até que lhe seja permitido o acesso. Dessa forma, o processo consome tempo do processador desnecessariamente, podendo ocasionar problemas ao desempenho do sistema.

Sincronização Condicional

É uma situação onde o acesso ao recurso compartilhado exige a sincronização de processos vinculada a uma condição de acesso. O processo fica bloqueado até que o recurso fique disponível. Um exemplo desse tipo de sincronização é a comunicação entre dois processos através de operações de gravação e leitura em um buffer.

Semáforos

Um semáforo é uma variável inteira, não negativa, que só pode ser manipulada por duas instruções: DOWN e UP. Podem ser classificados como contadores.

As instruções DOWN e UP funcionam como protocolos de entrada e saída para que o processo possa entrar e sair da região crítica. O semáforo fica associado a um recurso compartilhado, indicando se o recurso está sendo utilizado ou não no momento.

Monitores

São mecanismos de sincronização de alto nível que tornam mais simples o desenvolvimento de aplicações concorrentes, evitando as chances de erros.

Deadlock

É a situação em que um processo aguarda por um recurso que nunca estará disponível ou um evento que não ocorrerá. Para que ocorra a situação de deadlock, quatro condições são necessárias simultaneamente:

  • Exclusão Mútua: Cada recurso só pode estar alocado a um único processo em um determinado instante.
  • Espera por Recurso (Hold and Wait): Um processo, além dos recursos já alocados, pode estar esperando por outros recursos.
  • Não-Preempção: Um recurso não pode ser liberado de um processo só porque outros processos desejam o mesmo recurso.
  • Espera Circular (Circular Wait): Um processo pode ter de esperar por um recurso alocado a outro processo e vice-versa.

Para prevenir a ocorrência de deadlocks, é preciso garantir que uma das quatro condições apresentadas, necessárias para sua existência, nunca se satisfaça.

Tratamento de Deadlock

Pode-se usar preempção, exclusão mútua, eliminação de processos, e se o deadlock ocorre raramente, pode-se ignorar por completo.

Gerência do Processador e Escalonamento

Política de Escalonamento

É composta por critérios estabelecidos para determinar qual processo em estado de pronto será escolhido para fazer uso do processador.

Escalonador (Scheduler)

É uma rotina do sistema operacional que tem como principal função implementar os critérios da política de escalonamento.

Dispatcher

É responsável pela troca de contexto dos processos após o escalonador determinar qual processo deve fazer uso do processador.

Métricas de Tempo

  • Tempo de Processador (Tempo de CPU): É o tempo que um processo leva no estado de execução durante seu processamento.
  • Tempo de Espera: É o tempo total que um processo permanece na fila de pronto durante seu processamento, aguardando para ser executado.
  • Tempo de Turnaround: É o tempo que um processo leva desde a sua criação até o seu término, levando em consideração todo o tempo gasto na espera para alocação de memória, espera na fila de pronto (tempo de espera), processamento na CPU e na fila de espera (como nas operações de E/S).
  • Tempo de Resposta: É o tempo decorrido entre uma requisição ao sistema ou à aplicação e o instante em que a resposta é exibida.

Escalonamento Preemptivo vs. Não-Preemptivo

  • Escalonamento Preemptivo: O sistema operacional pode interromper um processo em execução e passá-lo para o estado de pronto, com o objetivo de alocar outro processo na CPU.
  • Escalonamento Não-Preemptivo: Quando um processo está em execução, nenhum evento externo pode ocasionar a perda do uso do processador. O processo somente sai do estado de execução caso termine seu processamento ou execute instruções do próprio código que ocasionem uma mudança para o estado de espera.

Algoritmos de Escalonamento

  • Escalonamento FIFO (First-In, First-Out): É um escalonamento não-preemptivo onde o processo que chegar primeiro ao estado de pronto é o selecionado para execução. Este algoritmo é bastante simples, sendo necessária apenas uma fila.
  • Escalonamento Circular (Round Robin): É um escalonamento preemptivo, projetado especialmente para sistemas de tempo compartilhado. É semelhante ao FIFO, porém, quando um processo passa para o estado de execução, existe um tempo limite para o uso contínuo do processador denominado fatia de tempo (time-slice) ou quantum.
  • Escalonamento SJF (Shortest Job First): O algoritmo de escalonamento seleciona o processo que tiver o menor tempo de processador ainda por executar, ou seja, os processos menores são executados primeiro.
  • Escalonamento por Prioridade: É um escalonamento do tipo preemptivo, onde são executados primeiro os processos de maior prioridade de execução. Esse tipo de escalonamento não sofre com preempção por tempo.
  • Escalonamento Adaptativo: É um mecanismo onde o sistema operacional identifica o comportamento dos processos durante sua execução, adaptando as políticas de escalonamento dinamicamente.

Preempção por Tempo e Prioridade

Preempção por Tempo ocorre quando o sistema operacional interrompe o processo em execução em função da expiração da sua fatia de tempo, substituindo-o por outro processo. Preempção por Prioridade ocorre quando o sistema operacional interrompe o processo em execução em função de um processo entrar em estado de pronto com prioridade superior ao do processo em execução.

As aplicações em tempo real exigem escalonamento por prioridades, onde é possível atribuir prioridades aos processos em função da sua importância.

Gerência de Memória Principal e Virtual

As principais funções da gerência de memória são: maximizar o número de processos na memória, permitir a execução de programas maiores que a memória física, compartilhamento de dados na memória e proteção da memória utilizada por cada processo e pelo sistema operacional.

Estratégias de Alocação de Memória

Best-Fit

Nesta estratégia, é escolhida a melhor partição, aquela onde o programa deixa o menor espaço sem utilização. Neste algoritmo, a lista de áreas livres é ordenada por tamanho, diminuindo o tempo de busca por uma área desocupada. O ponto negativo é que este método aumenta o problema de fragmentação.

Worst-Fit

Nesta é escolhida a pior partição, onde o programa deixa o maior espaço sem utilização. Essa técnica deixa espaços livres maiores na memória, permitindo um maior número de programas utilizando a memória e diminuindo o problema da fragmentação.

Swapping

A técnica de swapping foi introduzida para contornar o problema da insuficiência de memória principal. Essa técnica é aplicada à gerência de memória para programas que esperam por memória livre para serem executados. Nesta situação, o sistema escolhe um processo residente, que é transferido da memória principal para a memória secundária (swap out), geralmente disco. Posteriormente, o processo é carregado de volta da memória secundária para a memória principal (swap in) e pode continuar sua execução como se nada tivesse ocorrido.

Na maioria das políticas, o critério de escolha considera o estado do processo e sua prioridade, buscando dessa forma identificar o processo com as menores chances de ser executado para sofrer swap-out.

Memória Virtual

Os principais benefícios da técnica de memória virtual são: possibilitar que programas e dados sejam armazenados independentemente do tamanho da memória principal, permitir um número maior de processos compartilhando a memória principal e minimizar o problema da fragmentação. O que possibilita que um programa e seus dados ultrapassem os limites da memória principal é a técnica de gerência de memória virtual que combina as memórias principal e secundária, estendendo o espaço de endereçamento dos processos.

Quando um programa é executado, as páginas virtuais são transferidas da memória secundária para a principal e colocadas em frames. Sempre que um programa fizer referência a um endereço virtual, o mecanismo de mapeamento localizará o endereço físico do frame no qual se encontra o endereço real correspondente.

Paginação e Segmentação

A principal diferença entre os dois sistemas está relacionada à forma como o espaço de endereçamento virtual está dividido logicamente. Na Paginação, o espaço de endereçamento está dividido em blocos com o mesmo número de endereços virtuais (páginas), enquanto que na Segmentação o tamanho dos blocos pode variar (segmentos).

Tabelas de Mapeamento

Tabelas de Páginas e Tabelas de Segmentação são tabelas de mapeamento, utilizadas no mecanismo de memória virtual, que possibilitam que endereços virtuais sejam traduzidos em endereços reais.

Bits de Controle

  • Bit de Validade: Indica se a página ou o segmento em questão encontra-se na memória principal.
  • Bit de Modificação (Dirty Bit): Indica se a página ou segmento foi modificado desde o momento em que foi carregado pela última vez na memória principal.

A fragmentação interna em um sistema que implementa paginação só é encontrada, realmente, na última página, quando o código não ocupa o frame por completo.

Thrashing

É a consequência da excessiva paginação/segmentação em sistemas que implementam memória virtual, levando o sistema a dedicar mais tempo com operações relacionadas à gerência da memória do que no processamento das aplicações dos usuários.

Sistema de Arquivos e Métodos de Acesso

Métodos de Acesso a Registros

  • Acesso Sequencial: A leitura dos registros é realizada na ordem em que são gravados e a gravação de novos registros só é possível no final do arquivo.
  • Acesso Direto: A leitura/gravação de um registro ocorre diretamente na sua posição, através do número do registro que é a sua posição relativa ao início do arquivo.
  • Acesso Indexado: O arquivo possui uma área de índice onde existem ponteiros para os diversos registros. Sempre que a aplicação deseja acessar um registro, deve ser especificada uma chave através da qual o sistema pesquisará na área de índice o ponteiro correspondente.

System Calls (Chamadas de Sistema)

É o mecanismo usado pelo programa para requisitar um serviço do sistema operacional, ou mais especificamente, do núcleo do sistema operacional.

Tipos de Proteção de Acesso

Existem diversos tipos de proteção de acesso, incluindo Senha de Acesso, Proteção por Grupos de Usuários e Lista de Controle de Acesso (ACL).

  • Senha de Acesso: A vantagem é a simplicidade, pois o controle resume-se ao usuário ter conhecimento da senha e, consequentemente, ter a liberação do acesso ao arquivo concedida pelo sistema.
  • Proteção por Grupos de Usuários: Oferece uma proteção em três níveis: owner (dono), group (grupo) e all (todos).
  • Lista de Controle de Acesso (ACL): Tem a vantagem de especificar individualmente para cada arquivo qual usuário e tipo de acesso é concedido.

Buffer Cache

É a técnica em que o sistema operacional reserva uma área da memória para que se tornem disponíveis caches utilizados em operações de acesso ao disco. Quando uma operação é realizada, o sistema verifica se a informação desejada se encontra no buffer cache. Em caso positivo, não é necessário o acesso ao disco. Caso o bloco requisitado não se encontre no cache, a operação de E/S é realizada e o cache é atualizado.

Entradas relacionadas: