Notas da Aula 3 - Fundamentos de Sistemas Operacionais
1. Problemas de Segurança
A multiprogramação traz uma série de benefícios, como a melhora do desempenho do sistema
e a redução do tempo de resposta (quando utilizada com o sistema de timesharing). No
entanto, existem algumas questões importantes relacionadas a segurança que devem ser
levadas em consideração em um sistema no qual os recursos são compartilhados por vários
processos.
Quando um processo de um usuário está no estado executando ele tem controle sobre o
processador. Logo, ele pode executar instruções, manipular o valor dos registradores, ter
acesso a barramentos, etc. Da mesma forma, ele tem acesso à memória: pode escrever e ler
valores. Como então evitar que o processo atualmente em execução consiga:
1. Monopolizar o processador? Isto é, como forçar que o processo eventualmente devolva
o controle da execução para o SO?
2. Monopolizar acesso a dispositivos de E/S? Ou seja, como fazer com que processos não
usem exclusivamente estes dispositivos?
3. Ler informações guardadas no espaço de endereçamento de outros processos?
4. Alterar (escrever) informações guardadas no espaço de endereçamento de outros
processos?
Os dois primeiros itens são relacionados ao compartilhamento justo de recursos. Se de alguma
forma um processo puder monopolizar o processador, por exemplo, sistemas multiusuário
provavelmente não funcionariam. Bastaria que um único usuário decidisse “sequestrar” o
processador e os demais usuários não teriam mais acesso à maquina. Os dois últimos itens
se relacionam a confidencialidade e integridade dos dados contidos em memória. Suponha
que um processo em execução pudesse ler a memória de outro processo qualquer. Isto
significa que, se um usuário do sistema está atualmente acessando sua conta de banco
através da máquina, outro usuário poderia capturar todas as informações (senhas, saldos,
movimentações, etc) apenas escrevendo um programa que fizesse leitura da memória. Talvez
até mais sério, se um processo pudesse escrever na memória de outro processo, um usuário
poderia realizar transações na conta bancária do outro, apenas manipulando valores de
variáveis.
Estas questões, portanto, trazem os seguintes requisitos de segurança a um Sistema
Operacional:
1. O SO deve ser capaz de retomar o controle do processador, uma vez que um processo
seja colocado para executar.
2. O SO deve ser capaz de impedir acessos de memória a posições externas ao espaço
de endereçamento de um processo.
3. O SO deve ser capaz de impedir acesso direto a dispositivos de E/S por parte dos
processos dos usuários.
Infelizmente, em sistemas monoprocessados, o SO não tem como realizar qualquer tipo de
ação, uma vez que um processo seja colocado para executar. Isso porque qualquer ação de
controle depende da utilização do processador (que por hipótese está em posse do processo).
Logo, a solução para estes problemas de segurança precisa obrigatoriamente passar por
algum tipo de suporte do hardware. Este suporte será dado através de 3 mecanismos básicos:
1. Temporizadores: o processador tem um relógio ou um contador que pode ser
configurado para um valor determinado. Quando o tempo pré-configurado expira, uma
interrupção é disparada.
2. Validação de endereços de memória: o processador tem um circuito auxiliar que verifica
o valor do endereço de memória acessado a cada instrução. Se este valor for inválido,
uma interrupção é disparada.
3. Instruções restritas: o processador tem um sub-conjunto de instruções cujo acesso
é permitido apenas para o SO. Se algum processo de aplicação tenta executar uma
destas instruções, uma interrupção é disparada.
Os 3 mecanismos utilizam a funcionalidade de interrupções. Uma interrupção é um sinal
recebido pelo processador que o avisa que determinados eventos ocorreram. Em geral,
existem vários tipos de interrupções diferentes, representando a ocorrência de vários tipos
distintos de eventos. O hardware reserva uma região específica da memória para guardar
uma tabela de tratadores de interrupção. Cada entrada desta tabela corresponde a uma
interrupção específica e contém o endereço de memória inicial de uma rotina de tratamento
da interrupção. Ao ser carregado, é tarefa do SO preencher as entradas desta tabela com os
endereços das suas rotinas de tratamento de interrupção. Quando uma interrupção é detectada
pelo processador, ele aborta a execução atual, salva o contexto básico (valores de alguns
registradores), consulta a tabela de tratadores de interrupção e passa a execução para o
tratador adequando (faz o registrador PC apontar para o endereço da rotina).
Os tratadores de interrupção são parte do SO. Logo, uma vez que a rotina de tratamento de
interrupção é chamada, o controle do processador já está de volta com o Sistema Operacional.
Daí para frente, o SO pode tomar as atitudes cabíveis para controlar a situação. Por exemplo,
se o SO identifica que a interrupção foi causada pelo estouro do temporizador, ele pode decidir
colocar um novo processo em execução.
Existem três tipos básicos de interrupção: as interrupções de hardware, as interrupções de
software e as interrupções geradas por erros de execução. Interrupções de hardware são
aquelas disparadas por dispositivos de hardware, como uma placa de rede ou o temporizador
do processador. Interrupções de software são causadas pela execução de uma instrução
específica do processador, em geral denominada INT. Esta instrução pode ser executada
por processos, quando necessitam da intervenção do SO. Por fim, as interrupções causadas
por erros são aquelas que indicam que algo na execução de uma instrução não ocorreu
corretamente. Por exemplo, uma instrução de divisão que recebe o denominador 0 pode
disparar uma interrupção para alertar sobre a condição de erro.
Interrupções podem ainda ter prioridades diferentes. Em geral, dispositivos de E/S diferentes
apresentam prioridades diferentes. Por exemplo, a interrupção causada pela chegada de um
novo pacote no buffer da placa de rede é provavelmente mais importante que a interrupção
causada pela mudança de posição de um mouse.
2. Modos de Execução do Processador
Para dar suporte aos requisitos de segurança dos Sistemas Operacionais, os processadores
em geral apresentam mais de um modo de execução. Tradicionalmente, existem dois modos
distintos: o modo supervisor e o modo usuário.
O modo supervisor é aquele de maior privilégio. Um processo executando em modo supervisor
tem acesso a todas as instruções do processador (o que significa acesso a todos os
dispositivos de E/S) e a qualquer posição da memória principal. Este modo é o modo padrão,
quando a máquina é ligada. A ideia é que o SO comece a ser executado neste modo e possa
fazer todas as suas tarefas iniciais. Este modo também é ativado quando um tratador de
interrupção é chamado. Ao longo de toda a execução da máquina, quando o núcleo do SO
precisa ser executado, ele sempre é executado neste modo.
Quando o SO coloca um processo de um usuário para execução, ele antes altera o modo
de execução do processador (isto é permitido no modo supervisor). Quando o processo do
usuário executa sua primeira instrução, o processador já está em modo usuário. No modo
usuário, existem instruções que não são permitidas. Por exemplo, as instruções CLI e STI,
que desativam e ativam as interrupções respectivamente, não são permitidas neste modo.
Além disso, todo acesso à memória passa a ser verificado pelo processador, analisando se o
endereço especificado pertence ou não ao espaço de endereçamento do processo.
Processadores mais modernos às vezes implementam um esquema alternativo de
diferenciação de modo de execução. Ao invés de utilizarem apenas dois modos, os
processadores apresentam vários rings de execução. No ring 0, todos os recursos da máquina
estão disponíveis. No ring 1, alguns recursos são restritos. Nos rings sucessivos, o acesso da
máquina passa a ficar cada vez mais controlado.
A objetivo original da utilização dos rings era a diferenciação de classes de processos. Este
esquema dá suporte, por exemplo, ao SO eleger alguns processos que tem um pouco mais
de privilégios que outros. Um exemplo disso são os device drivers, ou seja, processos que
realizam a manipulação de algum dispositivo de hardware específico, como uma placa de
som. No modelo de dois níveis de prioridade, estes device drivers precisam executar no modo
supervisor, já que eles precisam de acesso a dispositivos de E/S. No modelo de rings, estes
device drivers podem ser executados em um modo um pouco menos privilegiado, aumentando
a segurança do SO.
Em geral, no entanto, os Sistemas Operacionais atuais não empregam esta funcionalidade,
continuando a utilizar basicamente dois modos (o ring 0 para tarefas do SO e o ring de mais
baixo privilégio para os demais processos). No entanto, a existência destes vários níveis trouxe
benefícios para a área de virtualização de hardware. Hoje, é possível executar uma instância
de um Sistema Operacional dentro de outro SO através dos softwares de virtualização. Embora
estes softwares não sejam novos, apenas recentemente eles se tornaram realmente eficientes.
A ineficiência da virtualização era causada pelo excesso de chamadas ao SO nativo, mesmo
para tarefas relativamente simples. Como o software de virtualização era executado em modo
usuário, qualquer tarefa classificada como privilegiada requeria a intervenção do SO nativo.
Com o advento dos rings de execução, o virtualizador (ou parte dele) pode ser executado em
um modo com mais privilégios, evitando o excesso de chamadas ao SO nativo.
3. Proteção de E/S
A proteção de acesso aos dispositivos de E/S é feita de maneira muito simples. Dado que
os processos dos usuários executam em modo usuário, eles não têm acesso às instruções
necessárias para comunicação com os dispositivos (instruções IN e OUT). Isso faz com que
os processos sejam forçados a utilizar o SO como um intermediário na utilização de tais
dispositivos.
Quando um processo de um usuário precisa realizar uma operação de E/S, ele faz uma
chamada de sistema ao SO. Essa chamada, geralmente é feita através de uma interrupção
de software (instrução INT). A execução da instrução INT faz com que o processador
interrompa a execução atual e passe a execução (agora em modo supervisor) para o tratador
de interrupções do SO. O tratador de interrupções, então, verifica os parâmetros da chamada,
identifica a operação e, caso seja possível, executa o serviço requisitado. Possivelmente, o SO
coloca o processo no estado bloqueado, enquanto o mesmo aguarda o resultado da operação.
Enquanto a operação é realizada, o SO pode colocar um novo processo em execução. Quando
o dispositivo termina a operação, uma interrupção é gerada, o que coloca novamente o SO
em execução. Desta vez, o SO identifica qual processo havia requisitado a operação, o
desbloqueia e, de alguma forma, repassa os resultados da operação.
4. Proteção de Memória
Quando um processo executa em modo usuário, cada acesso à memória é verificado pelo
processador antes da execução propriamente dita. Uma maneira de fazer isso é através de
um sistema simples de duas comparações. Ao colocar um novo processo em execução, o
SO configura o valor de dois registradores específicos: o registrador base e o registrador
limite. A cada acesso à memória, um circuito auxiliar com dois comparadores faz as seguintes
verificações:
1. Se o endereço a ser acessado é menor que o registrador base, uma interrupção é
gerada.
2. Se o endereço a ser acessado é maior ou igual ao registrador limite, uma interrupção é
gerada.
Estes registradores, portanto, guardam os endereços dos extremos do espaço de
endereçamento do processo. Quando o tratador de interrupção do SO é chamado para tratar
este tipo de interrupção, ele se encarrega de tomar alguma atitude para corrigir o problema.
Em geral, a ação tomada é simplesmente o encerramento do processo com algum código
específico de erro.
Para que este simples mecanismo funcione, é preciso que o espaço de endereçamento
dos processos seja contíguo. Ou seja, o espaço de endereçamento do processo não pode
conter “buracos”, ou estar espalhado em vários pedaços da memória. Como será visto no
Capítulo 6, isso nem sempre é verdade, o que resulta na necessidade de mecanismos mais
complexos.
5. Proteção do Processador
O mecanismo de proteção do processador é bastante simples. Em um sistema baseado em
timesharing, por exemplo, o SO escolhe um valor para a fatia de tempo, ou seja, a quantidade
de tempo que cada processo tem para usar o processador. Ao colocar um novo processo
em execução, o SO configura previamente um temporizador do processador para expirar ao
término do slice. Se o processo termina sua execução antes do final do slice, ele simplesmente
dispara uma interrupção de sotfware, que eventualmente passará a execução de volta
para o SO. Por outro lado, se o slice do processo termina enquanto o processo ainda está
executando, o temporizador gera uma interrupção de hardware que faz com que a execução
volte ao SO. Neste caso, o SO pode decidir substituir o processo atualmente de posse do
processador por outro processo na fila de aptos. Desta forma, o processo que estava no
processador é colocado de volta na fila de aptos, aguardando pela oportunidade de receber um
novo slice do processador para prosseguir seu processamento.
Mesmo em um sistema que não utiliza timesharing (por exemplo, um sistema batch), o SO
pode se utilizar do temporizador do processador para evitar que processos abusem do uso
deste recurso. Por exemplo, o SO pode configurar o temporizador para, de tempos em temos,
retomar o controle da execução e verificar se o tempo máximo de uso da máquina não foi
ultrapassado (e.g., um processo executando por mais de uma semana é automaticamente
terminado).
Download

Notas da Aula 3 - Fundamentos de Sistemas Operacionais 1