|
Universidade Federal de Minas GeraisDepartamento de Ciência da ComputaçãoSistemas Operacionais 2013/1 |
|
Este trabalho tem por objetivo fazer com que os alunos tenham contato com o tipo de código usado em simuladores e apliquem os conceitos de memória virtual vistos em sala de aula.
Neste trabalho você deve implementar um simulador de memória virtual. Ao contrário dos trabalhos anteriores, este programa não se relaciona diretamente com aspectos do sistema operacional, mas dentro dele você deverá implementar uma réplica das estruturas de um mecanismo de gerência de memória virtual. De certa forma, este será um trabalho de programação ``mais tradicional''.
Seu programa deverá ser implementado em C (não serão aceitos recursos de C++, como classes da biblioteca padrão ou de outras fontes).
O simulador receberá como entrada um arquivo que conterá a sequência de endereços de memória acessados por um programa real (na verdade, apenas um pedacinho da sequência total de acessos de um programa). Esses endereços estarão escritos como números hexadecimais, seguidos por uma letra R ou W, para indicar se o acesso foi de leitura ou escrita. Ao iniciar o programa, será definido o tamanho da memória (em quadros) para aquele programa e qual o algoritmo de substituição de páginas a ser utilizado. O programa deve, então, processar cada acesso à memória para atualizar os bits de controle de cada quadro, detectar falhas de páginas (page faults) e simular o processo de carga e substituição de páginas. Durante todo esse processo, estatísticas devem ser coletadas, para gerar um relatório curto ao final da execução.
O programa, que deverá se chamar tp3virtual, deverá
ser iniciado com quatro argumentos:
tp3virtual lru arquivo.log 4 128
Esse argumentos representam, pela ordem:
Ao final da simulação, quando a sequência de acessos à memória terminar, o programa deve gerar um pequeno relatório, contendo:
Um exemplo de saída poderia ser da forma (valores completamente fictícios):
prompt> tp3virtual lru arquivo.log 4 128
Executando o simulador...
Arquivo de entrada: arquivo.log
Tamanho da memoria: 128 KB
Tamanho das páginas: 4 KB
Tecnica de reposicao: lru
Paginas lidas: 520
Paginas escritas: 352
|
Como mencionado anteriormente, cada linha contém um endereço de memória acessado, seguido das letras R ou W, indicando um acesso de leitura ou escrita, respectivamente. Por exemplo, as linhas a seguir foram retiradas de um dos arquivos utilizados:
0785db58 W 000652d8 R 0005df58 W 000652e0 R 0785db50 W 000652e0 R 31308800 R 00062368 R |
Os arquivos fornecidos representam o registro de todas as operações de acesso à memória observadas durante a execução real de alguns programas considerados significativos de classes de programas reais:
A leitura do arquivo pode ser feita com o comando scanf(), como no
trecho a seguir:
unsigned addr;
char rw;
...
fscanf(file,"%x %c",&addr,&rw);
|
Como visto em sala, para se identificar a página associada a um endereço,
basta descartar os
bits menos significativos do endereço. Se a página
fosse fixada em 4 KB,
seria sempre 12. Entretanto, para se implementar
um tamanho de página variável,
deve ser calculado a cada execução.
Para simplificar, vamos assumir que o tamanho da página será sempre
fornecido como uma potência de 2 (não é necessário checar). O código para
se determinar
pode ser:
unsigned s, page_size, tmp;
/* Derivar o valor de s: */
tmp = page_size;
s = 0;
while (tmp>1) {
tmp = tmp>>1;
s++;
}
|
Nesse caso, para um endereço addr, a página pode ser determinada
simplesmente fazendo-se page = addr >> s;
Como os endereços são de 32 bits nos arquivos fornecidos, para páginas de 2 KB (as menores que precisam ser consideradas), podemos ter até 21 bits de identificação de página, isto é, uma tabela de página de mais de dois milhões de entradas. Se cada entrada da tabela tiver um inteiro para identificar a página física, teremos um vetor de 8 MB. Não será muito eficiente, mas para fins do simulador, essa organização é aceitável. Vocês não devem se preocupar em montar uma tabela de páginas hierárquica, como seria realmente implementada na prática; isso geraria uma complexidade adicional que não seria muito útil. Vocês podem também implementar uma tabela de páginas reversa, ou uma tabela por hash, caso se sintam inspirados.
A estrutura de dados para representar cada quadro físico deve conter campos para registrar atributos como página referenciada, instante do último acesso, página alterada, etc. (os detalhes são parte da implementação e vão depender da forma como vocês implementarem cada algoritmo).
Os detalhes de outras estruturas de dados que podem vir a ser usadas para os algoritmos são de livre escolha dos implementadores. Vocês devem documentar no relatório como cada algoritmo foi implementado e certificar-se de que o desempenho final do simulador não seja demasiado lento (preferivelmente na ordem de segundos, não dezenas de minutos).
Vocês podem utilizar um contador que inicie em zero e seja incrementado a cada acesso à memória, como a forma de manter o tempo de cada acesso/leitura/escrita, quando necessário.
Para a implementação da política aleatória, as funções random() e srandom() podem ser usadas para se controlar o gerador de números aleatórios. Para se gerar um número entre 0 e n, a expressão (random() % n) é suficiente. Note-se que enquanto houver páginas vazias na memória elas devem ser preenchidas; apenas quando a memória estiver completamente ocupada uma página aleatória deve ser substituída.
Apesar da versão a ser entregue precisar gerar apenas o relatório final descrito anteriormente, recomenda-se fortemente que o programa tenha um modo ``depuração'' onde ele escreva uma linha (ou mais) descrevendo o que é feito a cada acesso à memória. Assim, utilizando-se um arquivo de teste reduzido (e possivelmente escrito especialmente para testar cada caso de operação) você pode acompanhar a operação do programa passo-a-passo. Para isso, é permitido prever um quinto parâmetro, opcional, que seja definido quando se deseje que o programa tenha esse comportamento com saída detalhada. A forma desse parâmetro é de escolha dos desenvolvedores. Pode ser uma palavra, como debug, pode ser qualquer coisa (apenas para configurar um quinto argumento), ou pode ser um número, que pode ser usado para especificar um grau de detalhamento (números maiores geram mensagens de depuração mais detalhadas, por exemplo).
Você deve entregar no moodle um arquivo .zip ou .tgz contendo o(s) arquivo(s) contendo o código fonte do programa (.c e .h), um Makefile e um relatório sobre o seu trabalho. Não inclua os arquivos de teste no processo de entrega!
O relatório deve conter:
Essa análise de desempenho é uma parte importante do trabalho e será responsável por uma fração significativa da nota (+/- 40%). Em diversos momentos precisamos comparar algoritmos, determinar o que esperar em diferentes condições. Seu relatório deve avaliar o comportamento dos três algoritmos de reposição de página para os quatro programas, em duas situações: quando o tamanho da memória cresce, com páginas de 4 KB, e quando o tamanho da memória fica constante, mas o tamanho das páginas varia de 2 KB a 64 KB (em potências de 2).
Finalmente, embora você possa desenvolver o seu código em qualquer sistema que quiser, certifique-se que ele execute corretamente na máquina virtual com o sistema operacional Linux que foi distribuída no início do curso. A avaliação do seu funcionamento será feita naquele ambiente.
Este trabalho não é tão complexo quanto pode parecer à primeira vista. O código neste caso pode vir a ser mais extenso que o do TP3, mas ainda não deve ser excessivamente longo. Evite optar por estruturas de dados excessivamente complexas em uma primeira implementação. Use a noção de tipos abstratos de dados (TADs) para definir cada estrutura de dados que seja necessária e comece com uma implementação simples. Certifique-se de que a lógica do programa esteja funcionando. Se, depois disso, o desempenho com os arquivos de teste fornecidos for muito ruim, só então considere o uso de estruturas mais sofisticadas (elas não serão necessariamente necessárias).