Notas da Aula 1 - Fundamentos de Sistemas Operacionais 1. Conceitos Básicos Um Sistema Operacional pode ser visto sob dois pontos de vista diferentes. Por um lado, podese dizer que um SO é um software que provê acesso ao hardware da máquina a todas as aplicações dos usuários. Nesta definição, o trabalho do sistema operacional é simplificar o uso dos dispositivos de hardware para os programas das aplicações. Desta forma, as aplicações não precisam conter código específico para manipular todos os dispositivos acessados por elas. Por outro lado, um SO pode ser visto como um software responsável por gerenciar e dividir os recursos da máquina entre os processos de aplicação. Neste caso, a tarefa do Sistema Operacional é fazer um compartilhamento justo dos recursos, não permitindo que certas aplicações monopolizem o uso do computador. Embora as duas definições sejam diferentes, elas apresentam um fator em comum. Ambas definem um Sistema Operacional como um software. De fato, um SO nada mais é que um programa, um conjunto de instruções a serem executadas pelo computador. É importante notar que outros recursos como documentações e definições de APIs não fazem parte do SO, já que estes componentes não são software. Outros componentes que às vezes são erradamente entendidos como parte do SO são as interfaces com usuário (gráficas, como um gerenciador de janelas, ou de linha de comando) e utilitários de configuração do sistema. Embora geralmente distribuídos e instalados juntamente com o Sistema Operacional, estes componentes são apenas aplicações, assim como navegadores Web e leitores de e-mail. Para simplificar o acesso aos dispositivos de hardware pelas aplicações, o SO implementa funções conhecidas como Chamadas de Sistema. Estas chamadas de sistema realizam operações como abertura de arquivos, envio de mensagens pela rede e impressão de dados na tela. De modo geral, chamadas de sistema são uma maneira de aplicações acessarem serviços providos pelo Sistema Operacional. Os serviços mais comuns fornecidos por um Sistema Operacional são os de execução de operações de Entrada e Saída (E/S, ou I/O da sigla em inglês). Operações de E/S são aquelas que realizam troca de dados entre o processador e dispositivos periféricos do hardware, ou seja, dispositivos conectados ao processador através de algum barramento. São exemplos de tais dispositivos: ● disco rígido; ● placa de vídeo; ● teclado; ● mouse; ● leitor de CD; e ● pendrive. Note que o acesso à memória principal não é considerado uma operação de E/S, enquanto o acesso a dispositivos de memória secundária (disco rígido, pendrive, etc) é. Uma característica importante das operações de entrada e saída é que elas são consideradas lentas, em comparação com a velocidade de execução de instruções do processador. Outra característica é a grande variabilidade na velocidade dos dispositivos de E/S. Por exemplo, um disco rígido é um dispositivo mais rápido que uma porta serial. A velocidade das operações de E/S é um fator relevante, pois, em geral, os programas que requisitam uma operação deste tipo precisam aguardar que ela seja completada para que possam prosseguir nas suas execuções. O kernel ou núcleo de um Sistema Operacional é a parte do SO que implementa as suas funcionalidades básicas. Por exemplo, a escolha de qual processo deve utilizar o processador em cada momento (operação chamada de Escalonamento) e as rotinas de tratamento de interrupções são fundamentais para o funcionamento do sistema. O kernel é o primeiro programa carregado quando o SO é iniciado. Ele é armazenado como um único arquivo salvo em disco e lido durante o ligamento da máquina. Sistemas operacionais mais antigos mantinham toda a sua funcionalidade (mesmo as rotinas menos importantes) implementada neste único programa. O problema desta abordagem é que ela impõe que todo código que pode precisar ser executado pelo SO esteja presente no arquivo do kernel e seja carregado durante o ligamento da máquina. Isto torna o kernel muito maior do que o necessário, fazendo com que códigos que nunca serão usados sejam carregados em memória. Por exemplo, um determinado Sistema Operacional pode dar suporte à utilização de um mouse. Se o cunjunto de instruções utilizadas para manipulação do mouse estiver no kernel, mesmo que este SO seja carregado em uma máquina sem mouse este trecho de código ficará carregado em memória, desperdiçando recursos. A solução encontrada para este problema foi a criação de módulos carregáveis dinamicamente. Ao invés de manter todas as rotinas do SO em um único arquivo (o kernel), as funcionalidades opcionais podem ser implementadas em arquivos auxiliares, denominados módulos. Durante o carregamento (ou mesmo durante o uso normal da máquina), o SO detecta quais funcionalidades devem estar presentes em memória e lê os arquivos dos módulos correspondentes. Outra possível solução é a implementação de determinados serviços do SO como aplicações comuns, muitas vezes chamadas de daemons. Quando todas as funções de um Sistema Operacional estão no kernel, diz-se que o SO utiliza um Kernel Monolítico. Por outro lado, quando o SO utiliza um kernel que contém apenas as funcionalidades mais básicas, deixando outros serviços implementados como módulos ou aplicações, diz-se que o SO utiliza um Micro-kernel. Um conceito importante em Sistemas Operacionais é o de Processo. Um processo pode ser definido como uma instância de um programa em execução. Um programa é o código que deve ser executado para resolver um determinado problema de um usuário (ou seja, a aplicação do usuário). Quando o usuário pede que o SO execute seu programa, o Sistema Operacional reserva uma área de memória contendo as instruções especificadas pelo código do programa, bem como as variáveis manipuladas por ele. Neste momento, o programa entra em execução, contendo, além das instruções especificadas no arquivo executáve, o estado das variáveis e registradores do processador. A este conjunto (do código a ser executado e do estado atual), dá-se o nome de processo. Note que existem várias diferenças entre processo e programa. Enquanto um programa é uma entidade estática (uma vez compilado, o programa não se altera, a menos que seja recompilado), o processo é dinâmico, sofrendo constantes alterações. Isso porque as variáveis e registradores são alterados a medida que a execução acontece. É interessante notar também que um mesmo programa (em um único arquivo, por exemplo) pode ser executado várias vezes simultaneamente em uma mesma máquina. Cada uma das execuções corresponde a um processo diferente (com estados possivelmente diferentes), porém o programa é um só. Nos Sistemas Operacionais mais modernos, cada processo tem o seu Espaço de Endereçamento. Um espaço de endereçamento é um conjunto de posições de memória disponíveis para uso pelo processo. O processo utiliza seu espaço de endereçamento para armazenar informações como variáveis e instruções a serem executadas (código do programa). Em geral, o espaço de endereçamento de um processo não pode ser acessado por outros processos (nem para leitura, nem para escrita). Outra característica dos espaços de endereçamento é que, em alguns SOs, eles são acessados através de endereços lógicos. Ou seja, quando um processo tenta acessar a posição de memória 100, este endereço sofre um processo de tradução e é mapeado para um endereço real na memória física (por exemplo, o endereço lógico 100 pode ser mapeado para a posição 4865 da memória física). Este processo de tradução será estudado em mais detalhes no Capítulo 6. O Escalonador é a parte do Sistema Operacional responsável pela escolha da ordem de execução dos processos. É do escalonador a tarefa de compartilhar o processador entre os vários processos da melhor maneira possível. Esta tarefa é importante porque, em geral, existem mais processos carregados em um computador que processadores disponíveis para utilização. Logo, é preciso que o SO (ou especificamente, o escalonador) tome decisões de gerência relacionadas à CPU. Um escalonador pode trabalhar de acordo com uma série de objetivos. Por exemplo, ele pode objetivar reduzir o tempo de resposta médio dos processos. Ou seja, reduzir o tempo que os processos levam (em média) entre o pedido do usuário (e.g., usuário pede a abertura de uma nova aba em um browser) e a conclusão da requisição (e.g., nova aba é aberta e exibida para o usuário). Outros critérios que podem ser adotados pelos escalonadores incluem maximizar a vazão (i.e., número de processos executados por unidade de tempo) e atender a prioridades (e.g., alguns processos têm maior prioridade de execução que outros). 2. Histórico dos Sistemas Operacionais Nos primeiros computadores, o conceito de Sistema Operacional não existia. Os computadores eram grandes equipamentos compartilhados por diversos usuários em uma mesma empresa ou entidade. Quando um usuário desejava utilizar o computador para resolver alguma tarefa, ele requisitava uma janela de tempo para utilizar a máquina (dias ou semanas, por exemplo). Quando a máquina estava disponível, o usuário realizava a programação (muitas vezes através de patch-panels) e iniciava a execução. Durante todo o período de execução do programa, a máquina ficava bloqueada para os demais usuários. Ao final, o usuário recolhia o resultado da sua computação e um novo usuário podia utilizar o computador. Como sempre havia um único programa em execução na máquina (um único processo) e a tarefa de programação exigia grande conhecimento do funcionamento do computador, não fazia sentido ter um Sistema Operacional. Uma primeira evolução deste modelo foi a criação dos Sistemas em Lotes (ou Batch). Agora, ao invés de esperar que a máquina ficasse disponível para iniciar o processo de programação, o usuário trazia seu programa já pronto em alguma mídia, como um cartão perfurado. Estes cartões eram entregues a um funcionário responsável pelo gerenciamento do uso da máquina. Cada cartão representava o programa ou a tarefa que o usuário desejava executar. Neste tipo de sistema, estas tarefas eram conhecidas como Jobs. Quando um job em execução na máquina terminava, o funcionário se encarregava de inserir o próximo cartão perfurado no leitor para colocar o programa em execução. De certa forma, este funcionário atuava como um sistema operacional primitivo, gerenciando os recursos do computador. Mais tarde, este processo foi automatizado, com o surgimento dos Monitores Residentes. O monitor residente é um programa que ficava permanentemente carregado (ou residente) na memória da máquina. Sua tarefa era acionar uma leitora que lia os dados do próximo job na fila (por exemplo, uma leitora de cartões perfurados que automaticamente enfileirasse os cartões) para carregar este próximo job em memória. Uma vez que o job estivesse completamente carregado, o monitor residente passava o controle da máquina para o job. Quando a execução do job terminava, o controle da máquina voltava para o monitor, que repetia o ciclo. Neste caso, o monitor residente funcionava como um sistema operacional, embora oferecesse poucos serviços. Uma evolução dos sistemas em batch veio com os Sistemas em Batch Multiprogramado. Os sistemas batch originais apresentavam um grande problema de eficiência na utilização do processador. Como havia apenas um programa do usuário carregado em memória por vez, quando este programa realizava alguma operação de E/S, o processador ficava ocioso. Dados os longos tempos necessários para acesso a dispositivos de E/S, percebeu-se que este tempo ocioso poderia ser utilizado para a execução de outros programas. Assim, em um sistema batch multiprogramado, quando o programa atualmente em execução requisita uma operação de E/S, o Sistema Operacional coloca outro processo em execução. Quando a operação de E/ S termina, o processo atualmente no processador é retirado e o processo original volta à sua execução. Note que este ainda é um sistema batch. Ou seja, se um programa atualmente em execução nunca requisita uma operação de E/S, ele fica com o processador até o fim da sua execução. Repare ainda que um erro de programação neste tipo de sistema pode comprometer toda a máquina. Se o programa atualmente em execução entra em loop infinito, por exemplo, o SO nunca o irá retirar do processador, fazendo com que os outros programas não sejam executados. Neste caso, é necessária a intervenção do usuário. Outro problema dos sistemas batch era o baixo tempo de resposta médio dos processos. Como um programa na fila precisa aguardar que todos os outros na sua frente terminem suas execuções, ele demora muito tempo até que o usuário comece a receber as respostas às suas requisições. Para solucionar este problema, foi criado um novo tipo de sistema: os sistemas baseados em timesharing (compartilhamento de tempo). Em sistemas de timesharing, assim como nos sistemas batch multiprogramados, existem vários processos carregados em memória simultaneamente. No entanto, uma vez que um processo ganha o controle do processador, ao invés de aguardar que ele termine ou faça uma requisição de E/S, o Sistema Operacional dá a ele uma quantidade limitada de tempo de execução, chamada slice ou fatia de tempo. Se ao final do seu slice, o processo não terminou sua execução, o SO o retira do processador e coloca outro em seu lugar. Eventualmente, este processo ganha uma nova oportunidade de usar o processador (um novo slice) e continua sua execução do ponto onde havia parado. Repare que um processo pode ser muito curto e utilizar menos que um slice de tempo. Neste caso, quando o processo termina sua execução, o controle do processador automaticamente volta para o SO, que coloca um novo processo em execução. Em geral, no entanto, o slice é um intervalo relativamente curto, fazendo com que cada processo precise de vários slices para concluir sua execução. A grande vantagem dos sistemas baseados em timesharing é o seu melhor tempo de resposta. Como os processos se alternam rapidamente na utilização do processador, o usuário tem a impressão de que os vários programas estão sendo executados simultaneamente. Quando um usuário faz uma requisição (e.g., pressiona um botão), o processo encarregado por responder a ela (e.g., abrindo uma nova aba) rapidamente terá acesso ao processador para executar a tarefa. Nos sistemas do tipo batch, este processo teria que esperar vários outros processos terminarem totalmente suas execuções até que pudesse utilizar o processador. 3. Tipos de Sistemas Existem vários tipos de sistemas de computação diferentes, cada um adequado a diferentes tarefas. Como estes tipos de sistemas são diferentes uns dos outros, muitas vezes são necessários sistemas operacionais com características diferentes para gerenciá-los. ● Sistemas Multitarefa vs. Sistemas Monotarefa Sistemas multitarefa podem ter vários programas (processos) carregados em memória e prontos para execução simultaneamente. Eles se opõem aos sistemas monotarefa, nos quais há apenas um programa apto à execução por vez. ● Sistemas Monousuário vs. Sistemas Multiusuário Sistemas monousuário permitem que apenas um usuário utilize a máquina por vez. Por outro lado, sistemas multiusuário permitem que vários usuários utilizem a máquina simultaneamente. Note que um sistema multiusuário é necessariamente multitarefa, mas um sistema multitarefa pode ser monousuário. ● Sistemas Distribuídos vs. Sistemas Centralizados Sistemas distribuídos são caracterizados pela existência de vários processadores conectados através de algum tipo de rede. Eles se opõem aos sistemas centralizados, nos quais existe um único processador ou vários processadores que compartilham memória. A grande diferença, portanto, entre sistemas distribuídos e sistemas centralizados está na presença ou não de memória compartilhada entre os vários processadores. A maior parte dos Sistemas Operacionais são projetados para sistemas centralizados. Sistemas distribuídos, em geral, utilizam computadores rodando sistemas operacionais centralizados. A utilização da capacidade do sistema distribuído é feita através de aplicações especializadas que sabem da natureza distribuída do sistema. No entanto, existem alguns sistemas operacionais especificamente desenvolvidos para sistemas distribuídos. Neste caso, o Sistema Operacional tenta tornar transparente para o usuário o fato do sistema ser distribuído. Ou seja, o objetivo é fazer com que o usuário acesso o sistema como se ele fosse centralizado. ● Sistema Paralelo vs. Sistema Monoprocessado Um sistema paralelo se caracteriza pela existência de vários processadores. Estes vários processadores dão a estes sistemas a capacidade de executar várias tarefas simultaneamente. Eles se opõem aos sistemas monoprocessados, nos quais existe um único processador. Sistemas paralelos estão cada vez mais comuns, com o advento dos processadores de vários núcleos. Estes sistemas, no entanto, apresentam gerenciamento bem mais complexo que os monoprocessados. Existem dois tipos diferentes de sistema paralelo: os sistemas Symmetric Multiprocessing (SMP) e os sistemas Asymmetric Multiprocessing. Nos sistemas SMP, todos os processadores disponíveis são usados para realizar qualquer tipo de tarefa (por exemplo, executar processos dos usuários e realizar tarefas de gerenciamento do SO). Nos sistemas assimétricos, por outro lado, existem processadores especializados em determinadas tarefas. Por exemplo, alguns processadores podem ser dedicados a apenas processar tarefas do Sistema Operacional. Processadores com múltiplos núcleos geralmente se encaixam na categoria SMP. ● Sistemas de Tempo Real Sistemas de tempo real são aqueles nos quais as aplicações executadas têm prazos máximos de conclusão. Se uma aplicação não termina sua execução no seu prazo pré-determinado, há alguma penalidade associada. Um exemplo de um sistema de tempo real é o sistema de frenagem de um carro (o ABS, por exemplo). Quando o motorista pisa no pedal do freio, o sistema de freio precisa executar uma rotina que calcula a pressão que deve ser aplicada ao disco de freio de cada roda, de modo otimizar o processo de frenagem (por exemplo, para evitar o travamento das rodas). O processo responsável por esta tarefa tem um tempo máximo de execução permitido. Este tempo não pode ser excedido, pois o carro precisa reduzir sua velocidade. Os sistemas de tempo real são classificados em dois tipos: hard real time e soft real time. No primeiro tipo, prazos não podem ser perdidos sob nenhuma hipótese (e.g., sistema de frenagem de um carro). No segundo tipo, existe uma tolerância para perda de prazos (e.g., sistema de transmissão de vídeo, no qual os quadros devem ser exibidos até um determinado prazo).