Prévia do material em texto
SISTEMAS OPERACIONAIS AULA 3 Prof. André Roberto Guerra 2 CONVERSA INICIAL Descritos anteriormente como “dispositivos eletrônicos criados para auxiliar nas tarefas do cotidiano das pessoas”, os computadores são máquinas incríveis, com grande poder de processamento e capacidade de armazenamento. São compostos basicamente pelo hardware (dispositivos físicos) e software (tarefas e rotinas previamente programadas) de forma dependente. Sem memória de leitura/escrita de informações pela CPU, não há computador digital com programa armazenado. O hardware tem como principais componentes a CPU, as memórias e os dispositivos de E/S, e cada um deles é individualmente gerenciado e controlado pelo sistema operacional. Apresentada a gerência de processos e do processador (CPU), agora apresentaremos a gerência de memória, uma tarefa elementar que pode fazer toda a diferença na escolha do sistema operacional mais adequado aos objetivos iniciais de conveniência e eficiência. Enquanto a principal atividade de gestão de processador e de processos é o escalonamento, na gestão de memória é a alocação de memória, seguida pelas estratégias de paginação e segmentação. Contudo, de nada são válidas as explicações sobre as tarefas avançadas de gestão sem abordar os conceitos básicos. Então, vamos lá? O conteúdo desta aula contempla a definição de gestão (gerência) de memória e o seguinte roteiro: Conceitos e definições – visão geral; Tipos e padrões de memória – classificação básica; Alocação; Estratégias de paginação; Memória virtual. Bons estudos! TEMA 1 – CONCEITOS E DEFINIÇÕES – VISÃO GERAL Anteriormente definimos sistema operacional como um programa que gerencia os recursos do computador, em particular, o gerenciamento da hierarquia de memória (Stallings, 2018). Trata-se do software que executa 3 diversas funções, em especial: escalonamento de processos (já apresentado) e gerenciamento de memória, que veremos a partir de agora. Acompanhe! Para que as funções sejam executadas de modo rápido e eficiente, é necessário que o sistema operacional disponibilize o suporte adequado do hardware do processador. Quase todos os processadores dispõem desse suporte, em maior ou menor extensão, incluindo hardware de gerenciamento de memória virtual e de gerenciamento de processos, incluindo registradores de propósito especial e áreas de armazenamento temporário, além de um conjunto de circuitos para realizar tarefas básicas de gerenciamento de recursos. Fundamentais para qualquer sistema de computação, temos as memórias, em especial a memória de usuário (random access memory – RAM), memória de acesso aleatório (aleatório no sentido de que os processos podem acessar localizações de dados em qualquer ordem – Deitel; Deitel; Choffnes, 2017) ou memória principal. Memórias são recursos escassos e caros. Um desafio aos projetistas de sistemas é desenvolver sistemas operacionais leves, que ocupem a menor quantidade de bytes de memória e simultaneamente otimizem os recursos computacionais. Num sistema multiprogramável, a quantidade de memória disponível contribui diretamente para definir o número de processos que podem ser alocados e consequentes à capacidade de processamento do sistema computacional. Essa etapa é responsabilidade da gerência de memória, que tem como meta manter o maior número de processos residentes na memória principal, permitindo maximizar o compartilhamento do processador e demais recursos computacionais, auxiliando no combate à ociosidade da CPU. Tão relevante quanto quaisquer definições técnicas sobre as memórias, a gestão eficiente desse precioso recurso é fundamental para o bom desempenho de qualquer sistema operacional. Exemplo marcante no mercado é o popular Windows, que desde a sua primeira versão enfrenta esse dilema. Por definição, todas as “janelas” fechadas permanecem como “em espera”, visando uma melhor performance, mas acabam obtendo o inverso, sendo necessária uma quantidade de memória cada vez maior. Uma solução bastante comum são softwares de terceiros que “limpam” os arquivos dos processos já encerrados. 4 Em outros sistemas operacionais, essa solução está incorporada (built- in), dispensando o entendimento de temas mais complexos, atendendo objetivos básicos da conveniência e eficiência, comprovadas com a utilização de outro sistema operacional no mesmo equipamento. No primeiro sistema operacional (Kali Linux 2020) são necessários menos de 512 MB de memória RAM para o funcionamento inicial, enquanto no segundo sistema operacional (Windows 10 1909) são necessários mais de 3 GB de memória RAM para as mesmas tarefas básicas. Esse exemplo real (estudo de caso) aqui descrito será apresentado futuramente. O gerenciamento de memória é normalmente feito por software e por hardware de propósito especial. O gerenciador de memória é definido por Deitel, Deitel e Choffnes (2017) como um componente do sistema operacional responsável pelo esquema de organização da memória do sistema, com as estratégias de gerenciamento de memória, determinando como o espaço de memória disponível é alocado a processos e como responder a mudanças na utilização da memória de um processo. Ele também interage com hardware de gerenciamento de memória de propósito específico (se houver algum disponível) para melhorar o desempenho. Nesta aula e em aulas futuras, descreveremos diversas estratégias de gerenciamento e organização de memória. Deitel, Deitel e Choffnes (2017) complementam que cada estratégia de gerenciamento de memória difere quanto ao modo como responde a certas perguntas: Quando a estratégia recupera um novo programa e seus dados para colocá-los na memória? A estratégia recupera o programa e seus dados quando o sistema os requisita especificamente, ou ela tenta se antecipar às requisições do sistema? Em que lugar da memória principal a estratégia posiciona o próximo programa a ser executado e seus dados? Ela minimiza o espaço desperdiçado (compactando programas e dados a mais que puder em áreas disponíveis da memória) ou minimiza o tempo de execução (posicionando programas e dados o mais rapidamente possível)? 5 Se um novo programa ou novos dados devem ser colocados na memória principal (e se esta estiver frequentemente cheia), a estratégia substitui quais programas ou dados que já estão na memória? Ela deve substituir os mais velhos, os usados com menos frequência ou os menos usados recentemente? Há sistemas implementados que usam essas e outras estratégias de gerenciamento de memória. Portanto, trata-se de uma importante função do sistema operacional, composta por diversas rotinas e estratégias, e cada uma será descrita e analisada nos temas seguintes. Acompanhe! TEMA 2 – TIPOS E PADRÕES DE MEMÓRIA – CLASSIFICAÇÃO BÁSICA Elementar para uma boa gestão é conhecer seus componentes. Neste tema, apresentaremos os tipos e padrões das memórias, permitindo uma classificação quanto às seguintes características: Capacidade de armazenamento (tamanho em bytes); Velocidade (taxa de transferência e tempo de acesso); Custo de armazenamento (e seu respectivo custo material/monetário); Consumo de energia e volatilidade (dependência da energia). Essa classificação é simples e funcional, pois, quanto maior a capacidade de armazenamento, menores seus custos e velocidades (desempenho), e vice- versa. Veja a Figura 1: 6 Figura 1 – Hierarquia de memórias – uma classificação (o símbolo ≈ significa aproximadamente) Memória volátil: é aquela em que se perde o conteúdo com a ausência de energia elétrica; são memórias rápidas e pequenas; Registradores: são usados geralmente para endereçar a memória – são voláteis; Memória cache: o tamanho do barramento de endereçosé igual ao tamanho da palavra. Oriunda do francês cacher, que significa esconder. Contém os dados e/ou instruções mais recentemente referenciadas pelo processador. Quando a CPU precisa de uma palavra de memória, primeiro busca essa palavra no cache. Somente no caso de ela não estar armazenada lá é que a busca será na memória principal. Se uma parte substancial dos acessos for satisfeita pelo cache, o tempo médio de acesso a uma palavra em memória será pequeno, próximo ao tempo de acesso à CPU. Na execução de um programa de computador, muitas referências são feitas num pequeno conjunto de posições de memória. Cache é um dispositivo interno a um sistema que serve de intermediário entre a CPU e o dispositivo principal de armazenamento (memória principal). 7 O acesso à memória principal pode ser demorado, por isso vale a pena armazenar as informações mais procuradas num meio mais rápido. Deitel, Deitel e Choffnes (2017) apresentam nas “Reflexões sobre sistemas operacionais, caching” que os projetistas de sistemas devem equilibrar o custo e o desempenho de vários dispositivos de armazenamento para atender às necessidades dos usuários. Todos usamos caching na vida real. De modo geral, cache é um lugar para armazenar provisões que podem ser acessadas rapidamente; esquilos armazenando bolotas (frutos do carvalho) enquanto se preparam para o inverno é uma forma de caching. Guardamos lápis, canetas, grampos, fitas e clipes nas gavetas da mesa de trabalho para poder ter acesso rápido a eles quando precisarmos (em vez de buscá-los no armário de suprimentos). Sistemas operacionais empregam muitas técnicas de caching, como caching de dados e instruções para acesso rápido em memórias cache de alta velocidade, e caching de dados de discos na memória principal para acesso rápido enquanto um programa está em execução. Projetistas de sistemas operacionais devem ser cuidadosos ao usar caching, porque num sistema de computador os dados em cache são uma cópia dos dados cujo original é mantido num nível mais baixo da hierarquia da memória. A cópia em cache geralmente é aquela na qual as mudanças são feitas em primeiro lugar; desse modo, rapidamente pode ficar fora de sincronia com os dados originais, causando inconsistência. Se um sistema falhar quando o cache contiver dados atualizados, mas o original não, os dados modificados poderão se perder. Portanto, sistemas operacionais frequentemente copiam os dados em cache para o original – esse processo é denominado esvaziamento de cache. Sistemas de arquivos distribuídos muitas vezes colocam o cache tanto no servidor quanto no cliente, o que torna ainda mais complexa a consistência do cache. 2.1 Memória principal A memória principal armazena programas em execução e os dados utilizados por eles. É a memória primária, que também é volátil. A CPU processa instruções obtidas da memória principal, e os resultados são retransmitidos a ela. A unidade básica de memória é o bit (binary digit – dígito binário), uma abstração de valores 0 ou 1, pois fisicamente é mais fácil 8 distinguir entre dois valores distintos do que de mais valores – “tensão”, “corrente”. A memória é formada por um conjunto de células (ou posições), cada uma das quais podendo guardar uma informação. Todas as células de uma dada memória têm o mesmo número de bits, e os números que identificam (referenciam) a posição da célula na memória são chamados de endereços. A célula é a menor unidade endereçável da memória. Seus endereços são indexadores, pelos quais os programas podem referenciar dados na memória. Os computadores modernos agrupam as células (ou bytes) em palavras (words). Por exemplo: uma palavra de 32 bits tem 4 bytes (ou 4 células). Nesses computadores, a palavra é a parte mínima de dados que podem ser transferidos de/para a memória principal. A informação na palavra pode ser um dado ou uma instrução, portanto, “processadores de 32 bits” têm palavras e registradores de 32 bits. O número de bits do barramento de endereços em geral (mas não obrigatoriamente) é igual ao número de bits dos registradores, e as instruções são (em geral) de 32 bits. Cada instrução deve tratar palavras de 32 bits (movimentar, somar, subtrair etc.) como dados armazenados em registradores de 32 bits. Outra denominação da memória principal é a memória de acesso randômico (RAM). A célula pode ser acessada sem ter que percorrer os endereços anteriores. O tempo de acesso é praticamente o mesmo para todas as células, e é memória volátil. Recebe o nome randômico, pois os processos podem acessar localizações de dados em qualquer ordem; mas, em caso contrário, as localizações de dados num meio de armazenamento sequencial (por exemplo, fita) devem ser lidas em sequência. Diferentemente de fitas e discos rígidos, as latências da memória para cada endereço da memória principal são essencialmente iguais (Deitel; Deitel; Choffnes, 2017). 2.2 Memória secundária Sua primeira característica é denominada memória não volátil, que retém o padrão de bits original, mesmo que a energia seja desligada. É também chamada de memória endereçada sequencialmente, na qual, para obter a informação de um endereço, é necessário percorrer os endereços anteriores (por exemplo, fita magnética). 9 São memórias não endereçadas diretamente, isto é, os dados são transmitidos (enviados) para a memória primária antes de a CPU executá-los. Esse é um procedimento necessário, devido à volatilidade. Como dito, são não voláteis, o que permite guardar os dados permanentemente e, dessa forma, é possível executar programas e ler arquivos contendo os dados quando o computador for ligado novamente, garantindo o armazenamento de dados a longo prazo. São diversos tipos e modelos de memórias secundárias, entre eles os discos rígidos (HDs) convencionais e removíveis, memórias flash, discos de estado sólido (SSD), discos óticos, entre outros. Sistemas operacionais modernos utilizam a memória flash para expandir a memória principal como memória virtual, como veremos no Tema 5. As memórias secundárias são responsáveis por armazenar dados e informações (data storage), e esse amplo conceito será definido e apresentado nas próximas aulas. TEMA 3 – ALOCAÇÃO Todos os softwares do sistema de computação, desde o sistema operacional até os aplicativos, dependem de memória disponível para serem executados. Neste tópico, apresentaremos a sequência das tarefas de gestão das memórias, em especial a estratégia utilizada para evitar conflitos entre aplicações e garantir espaço para a execução, a alocação e os principais conceitos relacionados. Utilizando os mecanismos de hardware de memória, o sistema operacional prevê espaços e disponibiliza áreas de memória para os processos (ou para o próprio núcleo), conforme a necessidade. Segundo Maziero (2019), alocar memória significa reservar áreas de memória RAM para serem usadas por um processo, por um descritor de socket ou de arquivo no núcleo, por um cache de blocos de disco, entre outros. Ao final de seu uso, cada área de memória alocada é liberada (ou deveria ser) pela entidade que a solicitou e colocada à disposição do sistema para novas alocações. O alocador de memória é o mecanismo responsável pela alocação e liberação de áreas de memória. Em linhas gerais, o alocador reserva ou libera partes da memória RAM de acordo com o fluxo de solicitações que recebe (de 10 processos ou do núcleo do sistema operacional). Para isso, o alocador deve manter um registro contínuo de quais áreas estão sendo usadas e quais estão livres. Para ser eficiente, deve fazer alocações rapidamente e minimizar o desperdício de memória (Wilson et al., 1995). Considerando a atualização constante e a diversidade nos padrões de funcionamento, a complexidade da tarefa de alocação torna necessária a separação dosalocadores em: Alocador de memória física: organiza a memória física do computador, alocando e liberando grandes áreas de memória para carregar processos ou atender requisições do núcleo; Alocador de espaço de núcleo: o núcleo do sistema operacional continuamente cria e destrói muitas estruturas de dados relativamente pequenas, como descritores de arquivos abertos, de processos, sockets de rede, pipes etc. O alocador de núcleo obtém áreas de memória do alocador físico e as utiliza para alocar essas estruturas no núcleo; Alocador de espaço de usuário: um processo pode solicitar blocos de memória para armazenar estruturas de dados dinâmicas, por meio de operações como malloc e free. O alocador de memória do processo geralmente é implementado por bibliotecas providas pelo sistema operacional, como a LibC. Essas bibliotecas interagem com o núcleo para solicitar o redimensionamento da seção Heap do processo quando necessário (Maziero, 2019). Descrita por Maziero (2019), a estratégia de alocação básica traz que o problema básico de alocação consiste em manter uma grande área de memória primária (RAM) e atender a um fluxo de requisições de alocação e liberação de partes dessa área para o sistema operacional e/ou as aplicações. Essas requisições ocorrem o tempo todo, em função das atividades em execução no sistema, e devem ser atendidas rapidamente. Como resultado (efeito) das alocações e liberações, a área de memória inicialmente vazia se transforma numa sequência de áreas ocupadas (alocadas) e áreas livres, que evolui a cada nova requisição. Essas informações são geralmente mantidas em uma ou mais listas duplamente encadeadas (ou árvores) de áreas de memória. 11 3.1 Estratégias de gerenciamento e alocação de memória Deitel, Deitel e Choffnes (2017) apresentam em paralelo as estratégias de alocação e de gerenciamento de memória, projetadas para conseguir o melhor uso possível da memória principal, sendo divididas em: 1) de busca; 2) de posicionamento; e 3) de substituição. Estratégias de busca determinam quando transferir a próxima porção de um programa ou dados para a memória principal por meio do armazenamento secundário. São divididas em dois tipos: 1) estratégias de busca sob demanda; e 2) estratégias de busca antecipada. Durante muitos anos era comum empregar uma estratégia de busca sob demanda, com a qual o sistema posicionava a próxima porção do programa ou de dados na memória principal quando um programa em execução os referenciava. Projetistas acreditavam que, como em geral não podemos prever os trajetos de execução que os programas tomarão, a sobrecarga envolvida em adivinhações excederia em muito os benefícios. Atualmente, entretanto, muitos sistemas aumentaram seu desempenho empregando estratégias de busca antecipada, que tentam carregar parte de um de programa ou de dados na memória antes que sejam referenciados. Estratégias de posicionamento determinam em que lugar da memória principal o sistema deve colocar programas ou dados que chegam. São consideradas as estratégias de posicionamento first-fit (o primeiro que couber), best-fit (o que melhor couber), worst-fit (o que pior couber) e next-fit (o próximo que couber). Quando a memória estiver muito cheia para acomodar um novo programa, o sistema deverá remover parte de um programa (ou ele todo) e dos dados que residem correntemente na memória. A estratégia de substituição do sistema determina que parte remover (Deitel; Deitel; Choffnes, 2017). Vejamos alguns exemplos: Alocação contígua simples: a memória principal é subdivida em duas áreas: uma para o sistema operacional e outra para o programa do usuário; Alocação particionada estática ou fixa: a memória era dividida em pedaços de tamanho fixo, chamados partições. O tamanho das partições 12 estabelecido na inicialização do sistema era definido em função do tamanho dos programas que executariam no ambiente; o Alocação fixa com código absoluto: os programas só podem ser executados em posições físicas de memória; o Alocação fixa com código relocável: todas as referências a endereços no programa se relacionam ao início do código, e não a endereços físicos de memória. Nesse tipo de alocação, o principal problema é a fragmentação interna, os espaços que sobram nas partições ao alocar aplicações e o tamanho menor que a partição; Alocação particionada dinâmica: as partições são criadas de acordo com o tamanho dos programas que serão executados, eliminando o problema da fragmentação interna. Cada vez que surge um novo programa a ser executado, criam-se novas partições, e com isso surge um novo problema: a fragmentação externa (partições tão pequenas que não são suficientes para alocar os programas necessários). As estratégias de alocação de memória surgiram para minimizar a fragmentação externa. Ao longo da vida de um sistema, áreas de memória são alocadas e liberadas continuamente. Com isso, podem surgir áreas livres (“buracos” na memória) entre as áreas alocadas. Esse fenômeno se chama fragmentação externa, pois fragmenta a memória livre, fora das áreas alocadas. A fragmentação externa é muito prejudicial, porque limita a capacidade de alocar a memória do sistema. Além disso, quanto mais fragmentada a memória livre, maior o esforço necessário para gerenciá-la, pois mais longas serão as listas encadeadas de área de memória livre. Pode-se enfrentar o problema da fragmentação externa de duas formas: minimizando sua ocorrência, com estratégias de alocação (desfragmentando periodicamente a memória do sistema), ou permitindo a fragmentação interna (Maziero, 2019). As estratégias de alocação são, em síntese, estratégias de posicionamento anteriormente apresentadas. Vejamos: First-fit (“o primeiro que se encaixa” ou “o primeiro que couber”): consiste em escolher a primeira área livre que satisfaça o pedido de alocação. Sua vantagem é a rapidez, sobretudo se a lista de áreas livres for muito longa; 13 Best-fit (“o que melhor se encaixa” ou “o que melhor couber”): consiste em escolher a menor área possível que possa receber a alocação, minimizando o desperdício de memória. Contudo, algumas áreas livres podem ficar pequenas demais e, portanto, inúteis; Worst-fit (“o que pior se encaixar” ou “o que pior couber”): consiste em escolher sempre a maior área livre possível, de forma que a “sobra” seja grande o suficiente para ser usada em outras alocações; Next-fit (“o próximo que se encaixar” ou “o próximo que couber”): variante da estratégia first-fit que percorre a lista de áreas com base na última área alocada ou liberada, para que o uso das áreas livres seja distribuído de forma mais homogênea no espaço de memória. Diversas pesquisas demonstram que as abordagens mais eficientes são a best-fit e a first-fit, sendo esta última bem mais rápida (Johnstone; Wilson, 1999). Além dos alocadores de uso geral, pode-se desenvolver alocadores customizados para aplicações específicas. Uma técnica muito usada em sistemas de tempo real, por exemplo, é o memory pool (reserva de memória). Nessa técnica, um conjunto de blocos de mesmo tamanho é pré-alocado, constituindo um pool. A aplicação pode então obter e liberar blocos de memória desse pool com rapidez, pois o alocador só precisa registrar quais blocos estão livres ou ocupados (Maziero, 2019). TEMA 4 – ESTRATÉGIAS DE PAGINAÇÃO Apresentaremos agora quais programas e dados podem ser divididos em pedaços de tamanho fixo denominados páginas, que podem ser posicionadas em qualquer “moldura de página” disponível. Nesses tipos de sistema, as estratégias de posicionamento são triviais e gerenciadas com o auxílio de políticas de alocação e substituição de páginas. A política de alocação de páginas determina quantos frames cada processo pode manter na memória principal. São duas as estratégias utilizadas:1. Alocação fixa: cada processo tem um número máximo de páginas que pode ser utilizado durante sua execução; 2. Alocação variável: o número máximo de páginas alocadas ao processo pode variar durante sua execução. 14 Já a política de substituição de páginas consiste na atuação do sistema operacional na substituição de páginas (page out e page in) quando o processo atinge o número máximo de páginas alocadas. A estratégia working set tem o objetivo de reduzir o problema de thrashing (sucessivos page faults e I/O de páginas) e erros de page faults (páginas não encontradas na memória). A paginação de memória, especialmente a paginação em disco, consiste em técnicas para que um dispositivo de armazenamento secundário seja uma extensão da memória RAM, de forma transparente para as aplicações. Partes ociosas da memória podem ser transferidas para alguma memória secundária, liberando a memória RAM para outros usos. Caso algum processo tente acessar esse conteúdo posteriormente, ele deverá ser trazido de volta à memória, e essa transferência de dados entre memória principal e secundária é feita pelo sistema operacional, de forma transparente para os processos (Maziero, 2019). Existem diversas técnicas para usar um espaço de armazenamento secundário como extensão da memória RAM, com ou sem o auxílio do hardware. Entre elas, destacam-se a overlay, a swapping e a paging. 4.1 Overlay A técnica de overlay divide a memória em área do sistema operacional, área do módulo principal do programa do usuário e uma área de troca entre os módulos secundários do programa do usuário, denominada área de overlay. Essa organização em módulos é feita pelo desenvolvedor e permite que eles sejam carregados numa mesma região de memória em momentos distintos. Esses módulos (overlays) são gerenciados por uma biblioteca específica. Essa técnica permite executar programas maiores que a quantidade de memória disponível, e está representada pela Figura 2: 15 Figura 2 – Overlay Fonte: elaborado com base em Machado; Maia, 2007. 4.2 Swapping Swapping é uma técnica aplicada à gerência de memória para programas que esperam por memória livre para serem executadas. Nessa situação, o sistema escolhe um processo residente, que é transferido da memória principal (RAM) para a memória secundária (swap out), geralmente em disco, liberando a memória para executar outros processos. Caso esse processo seja novamente executado (entrar na fila de prontos do escalonador), ele é novamente carregado na memória principal (swap in). Essa é outra alternativa para expandir a capacidade de processamento da máquina, pois alocar um espaço em disco para transferir arquivos da memória principal para a área de swapping (e vice-versa) também permite executar processos maiores que a quantidade de memória principal (RAM) disponível. 4.3 Paging Já a técnica de paging consiste em mover páginas individuais, conjuntos de páginas ou mesmo segmentos da memória principal para a secundária (page out). Se o processo tentar acessar alguma dessas páginas mais tarde, é gerada uma interrupção de page fault (falta de página), e o núcleo do sistema operacional recarrega a página faltante na memória principal (page in). Essa é a 16 técnica mais utilizada nos sistemas operacionais atuais, por sua flexibilidade, rapidez e eficiência. 4.4 Algoritmos de substituição de páginas Como a transferência de páginas entre a memória principal e a secundária é feita pelo núcleo do sistema operacional, é responsabilidade dele escolher quais páginas retirar da memória, auxiliado por algoritmos de substituição de páginas. Assim, quando um processo tentar acessar uma página que está na memória secundária, o núcleo recebe um alerta e traz a página de volta à memória para poder ser acessada. Para cada página transferida ao disco, a tabela de páginas do processo é atualizada. Os algoritmos de substituição de páginas têm o objetivo de selecionar os frames com as menores chances de serem referenciados num futuro próximo. Com base no princípio da localidade, a maioria dos algoritmos tenta prever o comportamento futuro das aplicações em função do comportamento passado, avaliando o número de vezes que uma página foi referenciada, quando foi carregada para a memória principal e o intervalo de tempo da última referência. Segundo Maziero (2019), vários critérios podem ser usados para escolher quais páginas transferir da memória RAM para o armazenamento secundário. Alguns deles são os seguintes: Idade da página: tempo em que a página está na memória (páginas muito antigas talvez sejam pouco usadas); Frequência de acessos à página: páginas muito acessadas num passado recente possivelmente ainda o serão num futuro próximo; Data do último acesso: páginas há mais tempo sem acesso possivelmente serão pouco acessadas num futuro próximo (sobretudo se os processos respeitarem o princípio da localidade de referências); Prioridade do processo proprietário: processos de alta prioridade, ou de tempo real, podem precisar de suas páginas de memória rapidamente; se elas estiverem no disco, seu desempenho ou tempo de resposta poderão se prejudicar; Conteúdo da página: páginas cujo conteúdo seja código executável exigem menos esforço do mecanismo de paginação, porque seu conteúdo 17 já está mapeado no disco (dentro do arquivo executável correspondente ao processo). Por outro lado, páginas de dados alteradas precisam ser salvas na área de troca; Páginas especiais: páginas com buffers de operações de entrada/saída podem trazer dificuldades ao núcleo caso não estejam na memória quando ocorrer a transferência de dados entre o processo e o dispositivo físico. O processo também pode solicitar que certas páginas com informações sensíveis (como senhas ou chaves criptográficas) não sejam copiadas na área de troca, por motivos de segurança. A escolha correta das páginas a retirar da memória física é um fator essencial para a eficiência do mecanismo de paginação. Más escolhas poderão remover da memória páginas muito usadas, aumentando a taxa de faltas de página e diminuindo o desempenho do sistema. Veremos agora os principais algoritmos de substituição de páginas. O algoritmo Ótimo seleciona para substituição uma página que não será mais referenciada no futuro, ou aquela que levará o maior intervalo de tempo para ser novamente utilizada. Simplificando: a melhor página a remover da memória num dado instante é a que ficará mais tempo sem ser usada pelos processos, mas, como o comportamento futuro dos processos não pode ser previsto com precisão, esse algoritmo não é implementável. No algoritmo Aleatório (Random) todas as páginas alocadas na memória principal têm a mesma chance de serem selecionadas, inclusive os frames frequentemente referenciados, pois consiste em escolher aleatoriamente as páginas. Consome pouco recurso de memória, mas tem baixa eficiência. O algoritmo de ordem First In, First Out (Fifo) seleciona para substituição a primeira página utilizada (e que está há mais tempo na memória), e páginas mais antigas podem ser removidas para dar lugar a novas páginas. Esse algoritmo é muito simples de implementar, pois os números das páginas recém- carregadas na memória são registrados no final da lista, enquanto os números das próximas páginas a substituir na memória estão no início da lista. No entanto, não oferece bons resultados. Seu principal defeito é considerar somente a idade da página, sem levar em conta sua importância. Páginas carregadas na memória há muito tempo podem ser frequentemente acessadas. 18 Para o algoritmo Least Frequently Used (LFU), a página que tiver o contador com o menor número de referências será escolhida, ou seja, o algoritmo evita selecionar páginas bastante utilizadas. O problema é que somente aspáginas que estão há pouco tempo na memória podem ser selecionadas. O algoritmo Least Recently Used (LRU) (menos usado recentemente) seleciona a página na memória principal que está há mais tempo sem ser referenciada. É necessário que cada página tenha associado a ela o momento do último acesso, que deve ser atualizado a cada referência a um frame. Quando for necessário substituir uma página, o sistema fará a busca por um frame que esteja há mais tempo sem ser referenciado. Outra maneira de implementar o LRU é com uma lista encadeada, em que todas as páginas estariam ordenadas pelo momento da última referência, partindo do pressuposto de que páginas recentemente acessadas no passado provavelmente serão acessadas num futuro próximo, evitando removê-las da memória, mas isso gera um elevado custo de implementação. O algoritmo Not Recently Used (NRU) (ou não usado recentemente) leva em conta o bit de referência de cada página e o bit de modificação (dirty bit), que indica se o conteúdo de uma página foi modificado depois de ter sido carregada na memória. É bastante semelhante ao LRU, mas menos sofisticado. Para implementar esse algoritmo, é necessário um bit adicional, conhecido como bit de referência (BR), que indica se a página foi utilizada recentemente e está presente em cada entrada da tabela de páginas. Quando uma página é carregada para a memória principal, o bit de referência é alterado pelo hardware, indicando que a página foi referenciada (BR=1). Quando uma página for substituída, o sistema seleciona um dos frames que não tenha sido utilizado recentemente, ou seja, com o bit de referência igual a zero. Há também o algoritmo Fifo com buffer de páginas, que combina uma lista de páginas alocadas (LPA) com uma lista de páginas livres (LPL). A LPA organiza todas as páginas que estão sendo utilizadas na memória principal, podendo ser implementada como uma lista única para todos os processos, ou uma lista individual para cada processo. Independente da política utilizada, a LPA organiza as páginas alocadas há mais tempo na memória no início da lista, e as mais recentes no final. Da mesma forma, a LPL organiza todos os frames 19 livres da memória principal; as páginas livres há mais tempo estão no início, as mais recentes, no final. Sempre que um processo necessita alocar uma nova página, o sistema utiliza a primeira página da LPL, colocando-a no final da LPA. Caso o processo tenha que liberar uma página, o mecanismo de substituição seleciona o frame em uso há mais tempo na memória, isto é, o primeiro da LPA, colocando-o no final da LPL. É importante notar que a página selecionada e que entrou na LPL continua disponível na memória principal por um determinado intervalo de tempo. Caso essa página seja novamente referenciada e ainda não tenha sido alocada, basta retirá-la da LPL e devolvê-la ao processo. Nesse caso, a LPL funciona como um buffer de páginas, evitando o acesso à memória secundária. Por outro lado, se a página não for mais referenciada, com o passar do tempo chegará ao início da LPL, quando será utilizada para outro processo. Caso a página seja posteriormente referenciada, o sistema terá que carregá-la novamente da memória secundária. O algoritmo Fifo circular utiliza como base o Fifo, porém as páginas alocadas na memória estão numa estrutura de lista circular, semelhante a um relógio. Esse algoritmo é implementado com pequenas variações na maioria dos sistemas Unix. Para implementar o algoritmo, existe um ponteiro que guarda a posição da página mais antiga na lista, e cada página tem associado um bit de referência, indicando se ela foi recentemente referenciada. Quando é necessário substituir uma página, o sistema verifica se o frame apontado tem o bit de referência desligado (BR=0). Nesse caso, a página é selecionada para descarte pois, além de ser a mais antiga, não foi utilizada recentemente. Por outro lado, se a página apontada tem o bit de referência ligado (BR=1), o bit é desligado e o ponteiro é incrementado, pois, apesar de ser a página mais antiga, foi utilizada recentemente. O processo se repete até encontrar uma página com bit de referência igual a zero. Nesse algoritmo, é possível que todos os frames tenham o bit de referência ligado. Nesse caso, o ponteiro percorrerá toda a lista, desligando o bit de referência de cada página. Ao final, a página mais antiga é selecionada. O bit de referência permite conceder a cada página uma segunda chance antes de ser substituída. É possível melhorar a eficiência do algoritmo utilizando o bit de 20 modificação juntamente com o bit de referência, como apresentado no esquema NRU. TEMA 5 – MEMÓRIA VIRTUAL A memória virtual é uma técnica sofisticada e poderosa de gerência de memória, em que a memória principal e a secundária são combinadas, dando ao usuário a ilusão de existir uma memória muito maior que a capacidade real da memória principal. O conceito de memória virtual fundamenta-se em não vincular o endereçamento feito pelo programa dos endereços físicos da memória principal. Para ocultar a organização complexa da memória física e simplificar os procedimentos de alocação da memória aos processos, os sistemas de computação modernos implementam a noção de memória virtual, na qual existem dois tipos de endereço de memória distintos: 1. Endereços físicos (ou reais): são os endereços dos bytes de memória física do computador. Esses endereços são definidos pela quantidade de memória disponível na máquina. 2. Endereços lógicos (ou virtuais): são os endereços de memória usados pelos processos e pelo sistema operacional e, portanto, usados pelo processador durante a execução. Esses endereços são definidos de acordo com o espaço de endereçamento do processador. Ao executar, os processos “enxergam” somente a memória virtual. Assim, durante a execução de um programa, o processador gera endereços lógicos para acessar a memória, que devem então ser traduzidos para os endereços físicos correspondentes na memória RAM em que as informações desejadas se encontram. Por questões de desempenho, a tradução de endereços lógicos em físicos é feita por um componente específico do hardware do computador, denominado unidade de gerência de memória (memory management unit – MMU). Na maioria dos processadores atuais, a MMU se encontra integrada ao chip da própria CPU. A MMU intercepta os endereços lógicos emitidos pelo processador e os traduz para os endereços físicos correspondentes na memória da máquina, permitindo seu acesso pelo processador. Caso o acesso a determinado 21 endereço lógico não seja possível (por não estar associado a um endereço físico, por exemplo), a MMU gera uma interrupção de hardware para notificar o processador sobre a tentativa de acesso indevido. O comportamento da MMU e as regras de tradução de endereços são configuradas pelo núcleo do sistema operacional (Maziero, 2019). O mecanismo de tradução é essencial pois, sempre que o processo referenciar um endereço virtual, a unidade de gerência de memória verifica pelo bit de validade se a página que contém o endereço referenciado está ou não na memória principal. Caso a página não esteja na memória, ocorre um page fault. Para corrigir o erro, o sistema deverá transferir a página da memória secundária para a principal. Além de desacoplar os endereços lógicos dos endereços físicos e fazer a tradução entre ambos, a noção de memória virtual também permite implementar a proteção de memória do núcleo e dos processos entre si, fundamentais para a segurança e estabilidade do sistema. Para implementar a proteção de memória entre processos, o núcleo mantém regras distintas de tradução de endereços lógicos para cada processo e reconfigura a MMU a cada troca de contexto. Assim, o processo em execução, a cada instante, tem sua própria área de memóriae é impedido pela MMU de acessar áreas de memória dos demais processos. Além disso, a configuração das MMUs mais sofisticadas inclui a definição de permissões de acesso às áreas de memória. Essa funcionalidade permite implementar as permissões de acesso às diversas áreas de cada processo e impedir os processos de acessar áreas exclusivas do núcleo do sistema operacional. As principais estratégias de tradução de endereços usadas pelas MMUs são por partições, usadas nos primeiros sistemas de memória virtual, e por segmentos e por páginas, usadas nos sistemas atuais. A memória virtual por paginação é a técnica de gerência de memória com a qual o espaço de endereçamento real é dividido em blocos de mesmo tamanho chamados páginas. Já a memória virtual por segmentação é a técnica de gerência de memória com a qual o espaço de endereçamento virtual é dividido em blocos de tamanhos diferentes chamados segmentos. Enquanto na paginação existe o problema da fragmentação interna, na segmentação surge o problema da fragmentação externa. 22 A técnica de swapping apresentada é o exemplo mais popular de memória virtual, pois diversos sistemas operacionais (como o Linux) exigem (como requisito básico) a definição em disco de uma partição de swap, normalmente com o dobro do tamanho da memória principal (RAM) disponível. FINALIZANDO Os conteúdos da gerência de memória foram apresentados e definidos nesta aula. Trata-se de uma tarefa elementar que pode fazer toda a diferença na escolha do sistema operacional mais adequado aos objetivos iniciais de conveniência e eficiência. Mostramos que a gestão de memória tem como meta manter o maior número de processos residentes na memória principal, permitindo maximizar o compartilhamento do processador e demais recursos computacionais, auxiliando no combate à ociosidade da CPU. No Tema 2, apresentamos tipos e padrões de memória numa classificação básica, pois conhecer os componentes é elementar para uma boa gestão. Também mostramos os tipos e padrões das memórias, permitindo uma classificação simples e funcional, pois, quanto maior a capacidade de armazenamento, menores seus custos e velocidades (desempenho) – e vice- versa, como vimos na Figura 1. No Tema 3, descrevemos a alocação de memória, em que todos os softwares do sistema de computação, desde o sistema operacional até os aplicativos, dependem de memória disponível para serem executados. Apresentamos a sequência das tarefas de gestão das memórias, em especial a estratégia utilizada para evitar conflitos entre aplicações e garantir espaço para a execução, a alocação e os principais conceitos relacionados, ressaltando que alocar memória significa reservar áreas de memória RAM para serem usadas por um processo. Demos destaque ao item 3.1, que trata das estratégias de gerenciamento e alocação de memória: first-fit (“o primeiro que se encaixa”, ou “primeiro que couber”), best-fit (“o que melhor se encaixa”, ou “o que melhor couber”), worst-fit (“o que pior se encaixa”, ou “o que pior couber”) e o next-fit (“o próximo que se encaixa”, ou “o próximo que couber”). No Tema 4, vimos as estratégias de paginação, apresentando quais programas e dados podem ser divididos em pedaços de tamanho fixo 23 denominados páginas, que podem ser posicionados em qualquer “moldura de página” disponível e gerenciadas com o auxílio de políticas de alocação e substituição de páginas, com as técnicas overlay, swapping e paging. No item 4.4, apresentamos os algoritmos de substituição de páginas, considerando a idade da página, frequência de acessos a ela, data do último acesso, prioridade do processo proprietário, conteúdo da página e páginas especiais. Os principais algoritmos de substituição são: Ótimo; Aleatório; de ordem Fifo; LFU; LRU; NRU; Fifo com buffer de páginas; e Fifo circular. No Tema 5, falamos sobre a memória virtual, uma técnica sofisticada e poderosa de gerência de memória, em que a memória principal e a secundária são combinadas, dando ao usuário a ilusão de existir uma memória muito maior que a capacidade real da memória principal. Fundamenta-se em não vincular o endereçamento feito pelo programa dos endereços físicos da memória principal, com destaque à MMU, que permite implementar a proteção de memória do núcleo e dos processos entre si. Finalizamos o tópico apresentando as principais estratégias de tradução de endereços usadas pelas MMUs, por partições, segmentos e páginas, paginação e segmentação. Compreender esses conceitos possibilita uma melhor e mais eficiente escolha do sistema operacional, obedecendo aos critérios e às necessidades de cada software aplicativo e de cada usuário. 24 REFERÊNCIAS DEITEL, P. J.; DEITEL, H. M.; CHOFFNES, D. R. Sistemas operacionais. 3. ed. São Paulo: Pearson Brasil, 2017. JOHNSTONE, M. S.; WILSON, P. R. The memory fragmentation problem: solved? ACM SIGPLAN Notices, New York, v. 34, n. 3, p. 26-36, 1999. MACHADO, F. B.; MAIA, P. L. Arquitetura de sistemas operacionais. 4. ed. São Paulo: LTC, 2007. MAZIERO, C. Sistemas operacionais: conceitos e mecanismos. Curitiba: UFPR, 2019. STALLINGS, W. Operating systems: internals and design principles. 9. ed. London: Pearson, 2018. WILSON, P. R. et al. Dynamic storage allocation: a survey and critical review. New York: Springer, 1995. Disponível em: <https://users.cs.northwestern.edu/~pdinda/icsclass/doc/dsa.pdf>. Acesso em: 20 jun. 2020.