Departamento de Ciências e Tecnologias da Informação
Arquitectura de Computadores II
Textos de apoio
– Hierarquia da Memória –
Versão 0.02a – Maio de 2010 – Tomás Brandão.
ISCTE-IUL - DCTI
2
Índice
1.
INTRODUÇÃO.................................................................................................................................5
2.
MEMÓRIAS CACHE ......................................................................................................................7
2.1.
PRINCÍPIOS DE FUNCIONAMENTO ...............................................................................................7
2.2.
CARÁCTER LOCAL DAS REFERÊNCIAS.........................................................................................8
2.3.
ESTRUTURA DE UMA CACHE .......................................................................................................8
2.3.1. Cache directa........................................................................................................................8
2.3.2. Cache completamente associativa ......................................................................................13
2.3.3. Cache associativa de N vias ...............................................................................................13
2.4.
POLÍTICAS DE ESCRITA .............................................................................................................15
2.5.
REDUÇÃO DA TAXA DE FALTAS ................................................................................................16
2.5.1. Aumento da dimensão do bloco ..........................................................................................16
2.5.2. Aumento do grau de associatividade (número de vias) ......................................................17
2.5.3. Utilização de uma “cache vítima” .....................................................................................17
2.5.4. Técnicas de compilação......................................................................................................18
3.
MEMÓRIA VIRTUAL ..................................................................................................................19
3.1.
PAGINAÇÃO..............................................................................................................................19
3.1.1. Paginação simples..............................................................................................................19
3.1.2. Tabelas de páginas .............................................................................................................21
3.1.3. Page fault............................................................................................................................22
3.1.4. Algoritmos de substituição de páginas ...............................................................................23
3.1.5. Paginação multi-nível.........................................................................................................24
3.1.6. TLB (Translation Lookaside Buffer)...................................................................................26
4.
BIBLIOGRAFIA ............................................................................................................................29
5.
LISTA DE REVISÕES...................................................................................................................30
ISCTE-IUL - DCTI
3
ISCTE-IUL - DCTI
4
1. Introdução
Ideally one would desire an indefinitely large memory capacity such that any particular word
would be immediately available.
We are forced to recognize the possibility of constructing a hierarchy of memories, each of
which has greater capacity than the preceding but is less quickly accessible.
Burks, Goldstine & von Newmann (1946)
Quando um processador executa um programa, vai lendo as instruções da memória (fetch) e
também vai lendo e escrevendo dados em memória. Sendo assim, a rapidez com que é feito o
acesso à memória é um importante factor no desempenho de um computador.
De forma a facultar um rápido acesso aos elementos guardados em memória, nos actuais
sistemas é habitual ser estabelecida uma hierarquia de memória semelhante à esquematizada na
Figura 1.
Chip do processador
CPU
Registos
Cache
Memória
Principal
Memória
Secundária
(RAM)
(Disco)
Figura 1 – Hierarquia da memória.
Nesta hierarquia, os registos do processador são os elementos de memória mais rápidos que
podem ser encontrados no sistema de memória. Por outro lado são também os elementos com
menor capacidade de armazenamento, tipicamente com capacidade para guardar uma única
palavra.
Subindo na hierarquia, o bloco que se segue é a memória cache. A memória cache consiste
numa memória rápida, mas com uma capacidade reduzida relativamente à memória principal
(tipicamente é 100 a 1000 vezes mais pequena, usando números redondos). O objectivo da
utilização de memória cache é o processador aceder mais depressa às instruções e aos dados de
ISCTE-IUL - DCTI
5
que necessita. Caso encontre o que necessita na cache, escusa de aceder à memória principal. As
memórias cache estão normalmente incluídas no próprio circuito integrado do processador.
A memória secundária, o disco rígido, serve para guardar grandes quantidades de informação.
No contexto de um sistema hierárquico de memória assume um papel importante quando o
sistema utiliza memória virtual. Nesse caso o disco poderá ser utilizado para armazenar
programas (ou partes de programas) e dados, que dadas as suas dimensões não “caberiam” na
memória principal.
Em suma, num sistema hierárquico de memória, os elementos mais próximos do processador
são os mais rápidos, mas também os com menor capacidade e custo mais elevado. Para um
sistema hierárquico cumprir os objectivos para os quais foi projectado, a maioria dos acessos à
memória devem ser feitos aos elementos que se encontram mais próximos do processador, i.e.,
acede-se muitas vezes à cache, ocasionalmente acede-se à RAM e raramente se acede ao disco.
ISCTE-IUL - DCTI
6
2. Memórias cache
2.1. Princípios de funcionamento
Tal como já foi referido, uma memória cache é uma memória de dimensões reduzidas, mas de
rápido acesso e que tipicamente se encontra incluída no mesmo circuito integrado que o
processador.
A memória cache guarda cópias de blocos da memória principal, i.e., cópias de palavras
guardadas em endereços consecutivos da memória RAM. Quando o processador necessita de
aceder à memória (para ir buscar uma instrução ou para ir buscar dados) verifica primeiro se a
palavra pretendida se encontra na memória cache. Se encontrar o que pretende, apenas acede à
cache e portanto perde pouco tempo até obter a palavra pretendida – diz-se que teve sucesso ou
que ocorreu um cache hit.
Por outro lado, quando o processador não encontra na cache os dados pretendidos, diz-se que o
bloco está em falta ou que ocorreu um cache miss. Nesse caso terá que ser acedida a memória
principal para o bloco ser copiado para a cache, e portanto perde-se mais tempo. Copia-se o
bloco de dados no qual se encontra a palavra pretendida, na esperança de que futuros acessos a
palavras incluídas nesse bloco resultem em sucesso. Deste modo consegue-se uma maior
eficiência.
Exemplo:
Admita que um sistema utiliza uma cache com um tempo de acesso de 1ns e que 90% dos
acessos resultam em sucesso (hit rate = 90%). Sabe-se também que o tempo necessário para
aceder à memória RAM (ou copiar um bloco de dados da mesma) requer 15 ns.
Qual será, em média, o tempo de acesso à memória? Ou seja, qual será o tempo que o
processador perde até aceder aos dados pretendidos?
Resposta:
Pelo enunciado do problema, pode-se concluir que 90% dos acessos à memória resultam em hits
na cache, pelo que em 90% das vezes o tempo de acesso é igual ao tempo de acesso à cache.
Nos restantes 10% dos acessos, o processador verifica em primeiro lugar a cache, mas os dados
pretendidos não se encontram lá – ocorrem misses e nesses casos o processador terá que em
seguida aceder à memória principal.
Sendo assim tem-se:
Tacesso = 0.9 × 1 ns + 0.1 × (1 ns + 15 ns) = 0.9 + 1.6 = 2.5 ns
Pelo resultado pode concluir-se que presença da memória cache faz com que o desempenho
aumente consideravelmente. Se a cache não existisse, todos os acessos demorariam 15 ns.
ISCTE-IUL - DCTI
7
Para cache anterior, e a título de exemplo, pode-se também calcular a percentagem de sucesso
(h) a partir da qual compensa utilizar a cache. Poder-se-ia escrever a seguinte inequação:
h × 1ns + (1 – h) × (1ns + 15ns) < 15 ns
⇔ h + 16 – 16 h < 15
⇔ -15 h < - 1
⇔ h > 1/15
⇔ h > 0.067
Ou seja, para uma taxa de sucesso maior do que 6.7% já compensaria utilizar esta cache.
A taxa de sucesso das caches é normalmente elevada, pois estas são projectadas de forma a tirar
partido do carácter local das referências.
2.2. Carácter local das referências
A designação carácter local das referências significa que um programa em execução tende a
referenciar (ou aceder) posições de memória que se encontram localizadas próximas umas das
outras. Essa localização pode ser vista num contexto espacial e num contexto temporal:
•
Espacial – quando uma determinada posição de memória é acedida, é provável que num
futuro próximo sejam acedidas posições de memória vizinhas. Como exemplos temos o
acesso aos elementos de uma matriz (as matrizes são colocadas em posições de memória
consecutivas), a execução de uma sequência de instruções, sem saltos pelo meio (também
estão colocadas em posições consecutivas da memória), etc.
•
Temporal – é provável que um programa aceda às mesmas posições de memória várias
vezes ao longo da sua execução. Como exemplos de situações deste género, tem-se uma
variável que é utilizada muitas vezes, uma sequência de instruções dentro de um ciclo com
muitas iterações, etc.
As memórias cache, ao guardarem blocos de memória copiados da RAM acabam por tirar
partido destas duas características – a temporal, porque guardam valores, e portanto estes podem
ser acedidos no futuro, e espacial, porque as caches guardam blocos de dados que contêm os
valores guardados em posições de memória consecutivas.
2.3. Estrutura de uma cache
A estrutura da cache varia consoante o tipo de cache em questão: cache directa, cache
completamente associativa e cache associativa de N vias.
2.3.1.
Cache directa
A cache directa é provavelmente a cache cuja organização é a mais simples. A cache directa
pode ser vista como uma tabela, composta por várias linhas, e em que cada entrada numa linha é
composta por três campos principais:
ISCTE-IUL - DCTI
8
•
bloco de dados – com capacidade para guardar uma cópia de um bloco de dados da
memória;
•
tag ou etiqueta – que serve para identificar o bloco que se encontra guardado na cache;
•
bit de validade – que indica se essa entrada da cache contém dados válidos ou não.
Na Figura 2 encontra-se esquematizada a organização de uma cache directa
Bit de
validade
Bloco de dados
Tag
Linhas
0
1
2
3
4
...
...
Figura 2 – Organização interna de uma cache directa.
Uma questão importante numa cache directa é o modo como é feito o acesso à cache. Para
ilustrar este conceito, vamos considerar uma memória principal com uma dimensão pequena –
256 Bytes, endereçáveis individualmente – e uma cache directa, também pequena, com 8 linhas
e com 4 Bytes por bloco, ilustrada na Figura 3. Se as dimensões fossem maiores, o
funcionamento seria idêntico.
Cache
8 linhas
0
1
2
3
4
5
6
7
4 Bytes / bloco
Figura 3 – Estrutura da cache utilizada neste exemplo.
A memória possui 256 Bytes, o que quer dizer que os endereços são de 8 bits. Como se encontra
organizada em blocos de 4 Bytes, possui um total de 64 blocos (de 0 a 63).
Como a cache possui 8 linhas, faz sentido utilizar 3 bits para indexar as linhas (23 = 8) e como
os blocos são de 4 bytes (4 palavras) faz sentido utilizar 2 bits para indexar cada bloco (22 = 4).
ISCTE-IUL - DCTI
9
Deste modo, quando se pretende aceder à cache, divide-se cada endereço em três partes: um
índice de palavra dentro de cada bloco, um índice de linha e uma etiqueta.
Como os endereços são de 8 bits, pode-se então dividir cada endereço da seguinte forma:
8 bits
Etiqueta
Linha
Palavra
3 bits
3 bits
2 bits
Repare que os bits da etiqueta são os que “sobram” depois de definidos quantos bits são
necessários para indexar as linhas e quantos são necessários para indexar as palavras dentro de
cada bloco. Para acesso à cache, a estruturação que é feita aos endereços é utilizada da forma
indicada na Figura 4.
Estruturação de um endereço
Etiqueta
Índice de linha
Índice de palavra
Indexa as palavras
pertencentes ao bloco
Indexa as linhas
0
1
2
3
4
5
...
Figura 4 – Estruturação de um endereço para acesso a uma cache directa.
Na Figura 5 pretende-se ilustrar o modo como os vários blocos em memória são “mapeados”
para a cache directa do exemplo anterior. Com base na figura pode-se ver, por exemplo, que o
bloco 0, composto pelos endereços 0000 0000 a 0000 0011, irá ocupar a linha 0, o bloco 1 irá
ocupar a linha 1, e assim sucessivamente. Repare que o bloco 8 também será mapeado para
linha 0 (tal como o bloco 0) e o bloco 9 será mapeado para linha 1 (tal como o bloco 1). A
etiqueta serve precisamente para distinguir os blocos que são mapeados para as mesmas linhas
da cache.
Repare que tanto o bloco 0 como o 1 têm etiqueta ‘000’ ao passo que os blocos 8 e 9 têm
etiqueta ‘001’.
ISCTE-IUL - DCTI
10
RAM
0000 0000
0000 0001
0000 0010
0000 0011
0000 0100
0000 0101
0000 0110
0000 0111
...
Bloco 0
Bloco 1
Cache
...
0010 0000
0010 0001
0010 0010
0010 0011
0010 0100
0010 0101
0010 0110
0010 0111
...
1111 1100
1111 1101
1111 1110
1111 1111
...
Bloco 8
Bloco 9
...
0
1
2
3
4
5
6
7
...
Bloco 63
Figura 5 – Mapeamento de blocos de memória para uma cache directa.
De uma forma geral, se a cache directa tiver N linhas, as linhas da cache para as quais cada
bloco de memória é mapeado repetem-se de N em N blocos.
A sequência de acções que é feita quando se acede a uma cache é a seguinte:
•
Consultar a linha indicada no endereço;
•
Nessa linha, verificar o bit de validade – se está a ‘0’ então ocorre uma falta (miss);
•
Verificar a etiqueta – se a etiqueta do bloco guardado em cache for igual à especificada no
endereço, então há sucesso (hit), caso contrário ocorre uma falta (miss);
No caso de haver uma falta, procede-se a uma transferência do bloco em falta para a cache; Em
caso de sucesso e lêem-se os dados da cache poupando-se o acesso à RAM.
ISCTE-IUL - DCTI
11
Exemplo:
Na cache do exemplo anterior, o seu conteúdo em determinada altura da execução de um
programa, poderia ser o seguinte:
Cache
0
1
2
3
4
5
6
7
•
1
0
1
1
0
1
0
1
000
xxx
000
010
xxx
111
xxx
001
M[00] a M[03]
xxx
M[08] a M[0B]
M[4C] a M[4F]
xxx
M[F4] a M[F7]
xxx
M[3C] a M[3F]
Nestas condições, o que será que acontece caso se aceda ao endereço 0x0A ?
Observando a cache, temos imediatamente a resposta a esta pergunta – ocorre um hit porque o
conteúdo do endereço 0x0A já se encontra na cache. Mas como é que o processador sabe que
ocorre um hit ?
Em primeiro lugar, são obtidos os diversos campos que compõem o endereço (de acordo com a
estruturação que já foi vista anteriormente). Assim, tem-se:
0x0A = 0000 1010 – Etiqueta 000; Linha 010 (2); Palavra 10 (2)
Seria acedida a linha 2, que está válida e cuja etiqueta é igual à do endereço a aceder – daria um
hit, pois os dados pretendidos encontram-se na cache.
•
Então e no caso de se aceder ao endereço 0x05 ?
0x05 = 0000 0101 – Etiqueta 000; Linha 001 (1); Palavra 01 (1)
Neste caso seria acedida a linha 1, que está inválida – daria um miss, pois os dados não se
encontram em cache. De seguida o bloco ao qual pertence o endereço 0x05 (M[04] até M[07])
seria carregado para a linha 1. O bit de validade seria modificado para o valor ‘1’.
•
E no caso de se aceder ao endereço 0x40 ?
0x40 = 0100 0000 – Etiqueta 010; Linha 000 (0); Palavra 00 (0)
Neste caso seria acedida a linha 0, que é válida, mas como a etiqueta do endereço a aceder é
diferente da guardada em cache, ocorreria um miss. Depois da ocorrência deste miss, o bloco ao
qual pertence o endereço 0x40 (M[40] até M[43]) seria copiado para linha 0, substituindo o
bloco que lá se encontra.
ISCTE-IUL - DCTI
12
2.3.2.
Cache completamente associativa
Numa cache completamente associativa não existe o conceito de linha. A cache completamente
associativa pode ser vista como uma cache em que um bloco copiado da memória principal
poderá ocupar qualquer entrada da cache – não existem posições pré-definidas como foi visto
com a cache directa.
A cache associativa tem no entanto uma complexidade adicional no que respeita à verificação
das etiquetas: como um bloco pode ocupar qualquer posição da cache, terão que ser verificadas
as etiquetas de todas as entradas válidas guardadas na cache... Esta verificação pode ser feita em
paralelo, à custa de mais material (muitos comparadores lógicos e portas OR a unir as suas
saídas).
A vantagem deste tipo de caches em relação às directas é o facto se poderem aproveitar todas as
entradas da cache, já que não existe nenhum mapeamento de blocos pré-definido.
Mas como acontece em qualquer cache, as caches associativas são susceptíveis de ficarem
“cheias”, isto é, com todas as suas entradas ocupadas. O que fazer então no caso da cache estar
cheia e ser acedido um bloco de memória que não se encontra na cache? Nesta situação, um dos
blocos que estão na cache terá que ser substituído pelo bloco em falta. Mas como a cache é
completamente associativa, o novo bloco poderá ocupar qualquer uma das entradas da cache,
pelo que o bloco a substituir poderá ser escolhido com algum critério. Qual deverá então ser o
bloco a ser substituído? É aqui que entra em acção o algoritmo de substituição de blocos.
Um bom algoritmo de substituição de blocos seria um algoritmo que descartasse um bloco que
tão cedo não fosse necessário, ou que nunca mais viesse a ser acedido. Mas como os
computadores não conseguem prever o futuro (pelo menos por enquanto...) podem ser seguidas
as seguintes abordagens:
•
Descartar o bloco que não é acedido à mais tempo (algoritmo LRU – least recently used)
•
Ou descartar o bloco que foi acedido menos vezes (algoritmo NFU – not frequently used)
Basicamente estes algoritmos analisam, de uma forma simplista, o comportamento do acesso
aos blocos no passado e extrapolam esse comportamento para o futuro. Muitas vezes resulta. No
entanto, como estes algoritmos obrigam a que seja guardada informação adicional (de controlo)
na cache, podem-se tornar pouco atractivos em caches de maiores dimensões. Nesses casos, a
escolha do bloco a substituir é feita de forma aleatória, uma estratégia com bons resultados em
caches grandes, e que não traz um custo adicional tão pesado.
2.3.3.
Cache associativa de N vias
Uma cache associativa de N vias é uma solução de compromisso entre uma cache directa e uma
cache completamente associativa. A cache associativa de N vias é constituída por um conjunto
de linhas, que são compostas por N entradas cada uma. A ideia consiste em mapear os blocos
ISCTE-IUL - DCTI
13
para uma dada linha, mas dentro dessa linha existe a liberdade de escolher para qual das N vias
se vai transferir o bloco de dados.
Na Figura 6 apresenta-se uma esquematização de uma cache associativa de 4 vias. Neste
esquema, por cada conjunto existem 4 vias. Um bloco que estivesse mapeado para o conjunto 0,
por exemplo, poderia ocupar uma das 4 entradas desse conjunto
Via 0
Via 1
Via 2
Via 3
...
...
...
...
0
1
2
3
4
...
Figura 6 – Estrutura de uma cache associativa de 4 vias.
A estruturação de endereços para uma cache deste género é semelhante ao que é feito na cache
directa, mas neste caso há que ter em conta que se podem encontrar N blocos em cada linha.
Exemplo:
Suponha que uma cache associativa de 8 vias tem uma capacidade útil de 256KBytes. Sabe-se
também que a dimensão de cada bloco é de 64 Bytes e que os endereços são de 24 bits. Como
será que são estruturados os endereços para aceder a esta cache ?
Para determinar o modo como é feita a estruturação dos endereços temos que determinar
quantos bits são utilizados para indexar as linha e quantos bits são utilizados para indexar cada
bloco.
Como o enunciado especifica que cada bloco é composto por 64 bytes, e assumindo que cada
palavra consiste num byte, então cada bloco contém 64 palavras. Como 64 = 26, conclui-se que
são utilizados 6 bits para indexar as palavras dentro de cada bloco.
Para determinar quantos bits são necessários para indexar as linhas, há que determinar quantas
linhas existem nesta cache. Como a cache é de 8 vias, quer dizer que cada linha tem capacidade
para guardar 8 blocos. Como cada bloco são 64 bytes, então cada linha tem capacidade para 8 ×
64 = 512 bytes. Como a cache tem uma capacidade de 256 KBytes, então terá 256K / 512 = 512
linhas. Como 512 = 29, então serão necessários 9 bits para indexar as linhas.
Como os endereços são de 24 bits, o número de bits das etiquetas será 24 – 9 – 6 = 9 bits.
Chega-se portanto à seguinte estruturação de endereços:
24 bits
ISCTE-IUL - DCTI
Etiqueta
Linha
Palavra
9 bits
9 bits
6 bits
14
2.4. Políticas de escrita
Até este ponto do texto tem-se falado da interacção processador-cache quando se pretende ler o
conteúdo de uma posição de memória. Mas o que acontece no caso de uma escrita? Será que se
escreve na cache? Ou na memória principal? Ou em ambos os lados?
Esta secção debruça-se sobre as várias hipóteses de procedimentos em caso de escrita – estas
possíveis acções designam-se por políticas de escrita.
Em primeiro lugar há que separar as políticas de escrita utilizadas em caso de sucesso (hit) ou
em caso de falta (miss).
Quando se pretende escrever uma palavra pertencente a um bloco que se encontra em cache,
tem-se um sucesso. As políticas de escrita em caso de sucesso dividem-se em dois grupos:
•
Write-back – a palavra é escrita apenas na cache; o bloco onde se encontra a palavra que foi
modificada só será escrito na memória principal quando for substituído, i.e. quando “sair”
da cache.
•
Write-through – a palavra é escrita em ambos os lados: na cache e na memória principal.
A política write-back tem a vantagem de ser mais rápida na maioria das situações, pois ao se
escrever apenas na cache, evitam-se provavelmente muitas escritas na memória principal. No
entanto, um aspecto que poderá ser problemático é o facto do conteúdo da memória principal
poder estar desactualizado. Esta situação poderá causar problemas no caso da memória principal
ser partilhada por diversos dispositivos (e.g. vários CPUs) ou no caso de existirem
transferências de dados entre a memória e um periférico.
A política write-through é à partida mais lenta, já que se acede sempre à RAM em caso de
escrita, mas tem a vantagem de ter a memória sempre actualizada com os mesmos valores que
se encontram em cache. Para aumentar a eficiência desta política, muitas vezes é utilizado um
buffer auxiliar que guarda as palavras a escrever na RAM – o objectivo desta técnica (designada
por write buffer) é fazer com que o processador não tenha que ficar à espera da escrita dos
valores na memória principal. O CPU escreve no buffer, e quando houver oportunidade (o BUS
estiver livre), transfere-se o conteúdo do buffer para a memória.
No caso de ocorrer uma falta (miss) durante uma escrita, pode-se seguir uma das seguintes
políticas:
•
Write allocate (escrita com colocação) – copia-se o bloco em falta para cache, seguindo-se
depois uma das políticas em caso de hit.
•
No write allocate (escrita sem colocação) – escreve-se apenas na memória principal, sem se
copiar o bloco em falta para a cache.
Na estratégia write allocate copia-se o bloco em falta para a cache, na esperança de que esse
bloco venha depois a ser utilizado em futuros acessos. A estratégia no write allocate assume um
maior cepticismo quanto a futuras utilizações do bloco em falta, sendo mais rápida na medida
ISCTE-IUL - DCTI
15
em que não necessita de copiar um bloco de dados da memória, mas sim escrever apenas uma
palavra, o que em princípio será mais rápido.
Para estabelecer uma política completa, combina-se uma das estratégias em caso de hit com
uma das estratégias em caso de miss. As combinações mais habituais são:
•
Write-back em conjunto com write allocate – como à partida se escreve apenas na cache,
valerá a pena copiar para lá blocos que estejam em falta.
•
Write through em conjunto com no write allocate – já que em caso de hit se escreve sempre
na memória RAM, não valerá a pena copiar o bloco em falta para cache.
Apesar destas serem as combinações mais comuns, é possível utilizar as restantes combinações.
2.5. Redução da taxa de faltas
O desempenho de uma cache está tipicamente associado a dois factores: o tempo de acesso à
cache (e comparação das etiquetas) e a taxa de sucesso. Nesta secção são discutidas algumas
estratégias que visam aumentar a taxa de sucesso.
Antes de começar, há no entanto que distinguir as causas de um cache miss:
Cold-start – o miss ocorre por o bit de validade estar a ‘0’. A situação típica é o início da
execução de um programa. Como todas as entradas da cache estão inicialmente “vazias” por
ainda não terem havido acessos, o primeiro acesso a um novo bloco de dados ou instruções irá
inevitavelmente dar um cache miss.
Colisão – apesar do bit de validade estar a ‘1’, a etiqueta do endereço a aceder não coincide com
a etiqueta do bloco guardado na cache, originando um miss. Ou seja, o bloco de dados que se
encontra na cache não é o bloco onde se encontra o endereço a aceder. Esta situação ocorre
essencialmente devido a duas causas: devido à capacidade da cache ser pequena relativamente à
da memória principal e porque blocos de memória distintos podem ser mapeados para as
mesmas linhas da cache.
2.5.1.
Aumento da dimensão do bloco
Intuitivamente poderá parecer vantajoso aumentar a dimensão dos blocos da cache, já que assim
se consegue tirar maior partido do carácter espacial das referências, diminuindo o número de
cold-start misses.
Mas assumido que a capacidade da cache se mantém, o aumento da dimensão do bloco só será
possível se houver uma diminuição do número de linhas, no caso de uma cache directa, e/ou do
número de vias, no caso de uma cache associativa. Essa diminuição do número de entradas na
cache poderá fazer com que aumente o número de colisões.
Em suma, mantendo a capacidade da cache e aumentando a dimensão dos blocos faz com que
diminuam as faltas devido a cold-start mas ao mesmo tempo podem aumentar as colisões.
ISCTE-IUL - DCTI
16
Por outro lado, com blocos maiores, o tempo de transferência de blocos entre a memória
principal e a cache (e vice-versa) também poderá aumentar, aumentando portanto a penalização
em caso de miss.
È portanto necessário ter algum cuidado com esta estratégia, pois só deverá ser vantajosa até
uma determinada dimensão de bloco.
2.5.2.
Aumento do grau de associatividade (número de vias)
Uma estratégia cujo objectivo é uma diminuição do número de colisões consiste em aumentar o
grau de associatividade da cache, ou seja, aumentar o seu número de vias. Ao ser aumentado o
número de vias, está-se a aumentar o número de hipóteses de localizações dentro da cache para
as quais um bloco poderá ser copiado.
Por outro lado, ao ser aumentado o grau de associatividade, poder-se-á estar a aumentar o tempo
de verificação das etiquetas (pois terão que ser verificadas todas as etiquetas dentro de um
conjunto), o que poderá causar um impacto negativo no tempo de acesso à cache.
Na tabela encontra-se sintetizada uma estatística referente à percentagem de faltas de várias
caches, com a mesma dimensão de bloco (16 bytes), mas em que se varia a dimensão total e o
número de vias.
Dimensão da cache
2 vias
4 vias
8 vias
16 KB
5.18 %
4.67 %
4.39 %
64KB
1.88 %
1.54 %
1.39 %
256KB
1.15 %
1.13 %
1.12 %
Tabela 1 – Percentagem de faltas (misses) em função do grau de associatividade de uma cache.1
Com base na tabela pode-se observar que à medida que aumenta o grau de associatividade da
cache, o número de faltas diminui, mas que essa diminuição tem cada vez menor significado à
medida que aumenta a dimensão da cache.
Pode-se portanto concluir que aumentar o grau de associatividade compensa desde que se
garanta que o atraso adicional no acesso à cache não prejudica o desempenho do sistema. Na
prática tende-se a projectar caches pequenas com um grau de associatividade elevado e caches
maiores com grau de associatividade mais baixa.
2.5.3.
Utilização de uma “cache vítima”
Uma “cache vítima” é uma cache de dimensões muito reduzidas, completamente associativa,
com capacidade para guardar 4 a 8 blocos. Como guarda pouca informação também é muito
rápida.
O objectivo da cache vítima é guardar alguns blocos que foram substituídos na cache principal.
Deste modo, sempre que ocorre uma falta na cache principal, verifica-se se o bloco pretendido
1
Tabela retirada do livro Computer architecture: a quantitative approach – 2nd edition.
ISCTE-IUL - DCTI
17
se encontra na vítima. No caso de se encontrar na vítima, troca-se esse bloco com o bloco a
substituir na cache principal, passando o bloco a substituir para a vítima e o bloco a aceder para
a cache principal.
No fundo está-se a dar uma segunda chance aos blocos que foram substituídos na cache
principal: em vez de serem descartados por completo na altura da substituição, poderão ainda
ser “repescados” na cache vítima.
A utilização desta técnica acaba por reduzir, ainda que de forma indirecta, os misses devido a
colisões. A única desvantagem é um ligeiro aumento do tempo de penalização no caso de
ocorrer um miss tanto na cache principal como na vítima.
2.5.4.
Técnicas de compilação
Uma outra hipótese de redução do número de faltas na cache pode ser feita através de software,
durante a compilação dos programas. Se o compilador foi projectado para ter em conta
características da cache de um determinado processador, poderão feitas optimizações que tiram
partido dessas características.
Entre o tipo de optimizações mais comuns, destacam-se os seguintes:
•
separação de matrizes (blocking);
•
fusão / separação de ciclos;
•
trocas de índices dentro de ciclos.
Estas técnicas têm em vista reduzir o número de colisões, adaptando as matrizes às dimensões
dos blocos utilizados, de forma a maximizar os acessos a um determinado bloco, antes deste ser
descartado da cache (evitando-se que seja carregado múltiplas vezes).
O detalhe destes métodos sai fora do âmbito da disciplina.
ISCTE-IUL - DCTI
18
3. Memória virtual
3.1. Paginação
3.1.1.
Paginação simples
Uma das formas mais comuns de implementação de memória virtual é a paginação. A ideia
consiste em dividir o espaço virtual em blocos de dimensão fixa designados por páginas. Do
mesmo modo, a memória física encontra-se dividida em blocos com capacidade para conter uma
página, designados por molduras ou page frames.
Cada página do espaço de endereçamento virtual poderá estar colocada numa moldura (na
RAM), num ou mais blocos do disco, ou poderá nem sequer existir fisicamente por o programa
em questão não necessitar de muita memória. Na Figura 7 pretendem-se ilustrar estes conceitos.
Espaço virtual
RAM
Página 0
Moldura 0
Página 1
Moldura 1
Página 2
Moldura 2
Página 3
Moldura 3
Página 4
Disco
Página 5
Página 6
Página 7
Figura 7 – Exemplo de mapeamento de páginas virtuais para localizações físicas.
Observando a Figura 7 podemos concluir, por exemplo, que a página 0 está carregada na
moldura 1 da RAM, a página 1 está no disco, a página 2 está na moldura 2 da RAM, etc. Mais à
frente será discutida a forma como se consegue saber em que localizações físicas (quer na RAM
quer no disco) se encontram cada uma das páginas.
Resta observar como poderá ser obtido um endereço real a partir do virtual quando é utilizada
paginação. Num esquema simples de paginação, o endereço virtual é estruturado segundo duas
partes distintas:
•
Número de página – uma sequência de bits que identifica a página.
•
Deslocamento (offset) – uma sequência de bits que indexa a posição a aceder dentro de
uma página.
Ou seja, na forma mais simples de paginação, um endereço virtual é composto por dois campos
distintos – o número de página e o deslocamento, tal como pode ser observado na Figura 8.
ISCTE-IUL - DCTI
19
Endereço virtual
Nº de página
Deslocamento
Figura 8 – Estrutura de um endereço virtual utilizado pela paginação.
Para ficar com uma ideia mais clara do que é o deslocamento, considere o exemplo ilustrado na
figura adjacente:
Deslocamento = 3
O deslocamento é semelhante ao índice de palavra
utilizado para indexar os blocos na cache.
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Página 0
Espaço virtual
Página 1
Repare na posição de memória que se encontra
sombreada (endereço 11). Neste exemplo está-se a
partir do princípio que cada página contém 8
posições diferentes. O endereço 11 corresponde ao
endereço de início da página 1, somado de 3
posições (11 = 8 + 3). O deslocamento é portanto o
número de posições que são contadas a partir do
início da página.
...
...
Vamos agora supor que o sistema utiliza endereços
virtuais de 8 bits – isto quer dizer que o espaço de
endereçamento virtual seria composto por 28 = 256 palavras. Como seria a estruturação dos
endereços virtuais?
Repare que cada página tem 8 palavras, pelo que seriam necessários 3 bits para o deslocamento
(o deslocamento seria portanto um valor entre 0 e 7) e os restantes bits especificariam o número
de página.
Para o exemplo da posição 11 teríamos:
11(10) = 0000 1011 (2)
O deslocamento é dado pelos 3 bits menos significativos, ou seja, é ‘011’ = 3;
O número de página seria dado pelos 5 bits mais significativos – ‘00001’ = 1.
Quando se utiliza paginação, é relativamente fácil a conversão de endereço virtual para real,
desde que se saiba qual o número da moldura em que se encontra determinada página.
Considere a Figura 9, que representa as páginas do espaço virtual que se encontram carregadas
em molduras, na memória RAM. Na figura encontram-se indicados os endereços de início de
cada página, assim como os endereços de início de cada moldura (representados em
hexadecimal). Repare também que o deslocamento dentro de cada página será agora dado por
12 bits (3 dígitos em hexadecimal).
ISCTE-IUL - DCTI
20
Espaço virtual
RAM
0x0000
0x0000
0x1000
0x1000
0x2000
0x2000
0x3000
0x3000
0x4000
0x5000
0x6000
0x7000
Figura 9 – Exemplo de algumas páginas carregadas em RAM.
Qual será então, por exemplo, o endereço real que corresponde ao endereço virtual 0x5f80 ?
Basta observar que a página onde se encontra o endereço 0x5f80 é a página 5, que se encontra
carregada na moldura 3. O endereço real correspondente será dado pelo endereço de início da
moldura 3, somado ao deslocamento. Será portanto o endereço real 0x3f80.
Outra forma de obter o endereço real é concatenar o número da moldura ao deslocamento, isto é
a moldura 0x3 concatenada com o deslocamento 0xf80 resulta em 0x3f80.
De forma a que o hardware consiga traduzir os endereços virtuais em reais, é necessária
existência de uma estrutura de dados que faça a correspondência entre o número de página e a
localização física da mesma. Essa estrutura de dados designa-se por tabela de páginas.
3.1.2.
Tabelas de páginas
Uma tabela de páginas é composta por um número de entradas igual ao número de páginas
existentes no espaço virtual, em que cada entrada da tabela guarda informação referente à
página correspondente. Faz por isso sentido que seja indexada pelo número de página. Em cada
uma das entradas de uma tabela de paginação podemos encontrar um descritor de página. O
descritor de página contém, entre outras informações, um campo que indica o número da
moldura em que se encontra localizada a página em questão (caso esteja carregada na RAM).
Com base numa consulta à tabela de paginação consegue-se traduzir o endereço virtual em real,
tal como se ilustra na Figura 10.
ISCTE-IUL - DCTI
21
Endereço virtual
Nº Página
Deslocamento
Tabela de páginas
0
Descritor da página 0
1
Descritor da página 1
2
Descritor da página 2
3
Descritor da página 3
N-1
Descritor da página N-1
Nº Moldura
Deslocamento
Endereço real
Figura 10 – Tabela de páginas.
A informação e a dimensão dos descritores de páginas variam de sistema para sistema, mas de
uma forma geral, possuem a seguinte informação:
•
Bit de presença – indica se a página se encontra carregada na RAM (nesse caso estará a
‘1’) ou se a página se encontra no disco (nesse caso estará a ‘0’)
•
Bits de protecção – bits de protecção do acesso à página. Por exemplo, nas páginas que
contém as instruções de um programa pode-se limitar o acesso apenas para leitura, de modo
a evitar que possam ser escritos dados nas posições de memória ocupadas pelo programa.
•
Bits de controlo – são normalmente utilizados pelo algoritmo de substituição de páginas,
para decidir qual ou quais as páginas candidatas a serem transferidas para o disco, no caso
de ser necessário espaço na RAM para carregar novas páginas.
•
Número de moldura – o índice da moldura em que se pode encontrar a página a que se
refere o descritor. No caso da página se encontrar em disco, em vez da moldura este campo
poderia conter o bloco do disco no qual se encontraria a página.
A informação que se encontra na tabela de paginação é normalmente controlada por software,
nomeadamente pelo sistema operativo. O hardware limita-se a fornecer o número da página, o
deslocamento e um endereço base da tabela de paginação, pois a tabela de paginação é mantida
na memória RAM.
3.1.3.
Page fault
Durante a execução de um programa com uma dimensão grande, ou se estiverem vários
programas a correr num sistema, poderão existir situações em que uma página necessária não se
encontre na memória RAM, estando localizada no disco. O que será que acontece quando o
processador tanta aceder a uma página que não se encontra na RAM ?
ISCTE-IUL - DCTI
22
Uma primeira hipótese seria aceder à página directamente do disco, mas tal poderia conduzir a
uma grande degradação do desempenho, pois como facilmente se deduz, um acesso ao disco
demora muito mais tempo que um acesso à memória RAM....
Outra hipótese mais eficiente seria carregar a página em falta do disco para RAM e só depois
aceder. Este é o método seguido em todas as arquitecturas que utilizam memória virtual. Para
“informar” o processador que a página pretendida não se encontra na RAM existe um
mecanismo chamado page fault ou página em falta..
Uma page fault ocorre quando se consulta a tabela de páginas e se verifica que o bit de presença
se encontra a ‘0’. Isso significa que a página a aceder não se encontra carregada na RAM.
Ao receber uma page fault, inicia-se um procedimento de procura de uma moldura na RAM que
se encontre disponível para receber a página em falta. Se todas as molduras estiverem ocupadas
é necessário correr um algoritmo de substituição de páginas, que escolhe uma página para
descartar da memória RAM, transferindo-a para o disco, libertando desta forma uma moldura.
Depois de escolhida uma moldura, dá-se início à transferência da página em falta, do disco para
a RAM. Durante o tempo em que é feita essa transferência, o processador pode dedicar-se a uma
outra tarefa.
É necessário ainda actualizar a tabela de páginas, mais concretamente actualizar o descritor da
página que veio do disco e, no caso de ser necessária uma substituição, actualizar também o
descritor da página que foi substituída.
3.1.4.
Algoritmos de substituição de páginas
Tal como foi referido na secção anterior, podem ocorrer situações em que todas as molduras da
RAM estão ocupadas por páginas, sendo necessário substituir uma delas por uma página que
estava no disco, mas que entretanto se tentou aceder.
Nessa altura entra em acção o algoritmo de substituição de páginas. O algoritmo de substituição
de páginas é geralmente da responsabilidade do sistema operativo, o que significa que é corrido
por software.
Apesar de existirem diversos algoritmos de substituição de páginas, todos eles tentam substituir
uma página com uma baixa probabilidade de vir a ser necessária a curto prazo. Como não se
consegue prever se uma página vai ter utilizações futuras, o algoritmo de substituição analisa o
comportamento das páginas, mas no passado. Um algoritmo de substituição de páginas é muito
parecido com um algoritmo de substituição de blocos numa cache associativa (que já foi visto
mais atrás).
Entre os algoritmos de substituição de páginas “clássicos” temos:
•
LRU (Least recently used) – escolhe para substituição a página que não é acedida à mais
tempo;
•
NFU (Not frequently used) – escolhe para substituição a página que foi acedida menos
vezes;
ISCTE-IUL - DCTI
23
•
FIFO (First in, first out) – escolhe para substituição a página que está carregada à mais
tempo na RAM;
•
etc.
Na disciplina de Sistemas Operativos estes (e outros) algoritmos serão estudados com maior
detalhe.
3.1.5.
Paginação multi-nível
Quando o espaço de endereçamento virtual é muito grande, é natural que exista também um
número muito elevado de páginas. Um número muito elevado de páginas significa que a tabela
de páginas tem muitas entradas e portanto poderá ocupar uma quantidade de memória RAM
considerável.
Exemplo:
Suponha que são utilizados 32 bits para os endereços virtuais de um dado sistema em que cada
palavra é um byte. Admitindo que cada página tem uma dimensão de 4KBytes, quantas páginas
existirão ?
A dimensão do espaço virtual é de 232 = 4GBytes. Logo o número de páginas é dado por
4Gbytes / 4Kbytes = 1 Mpáginas.
Se existem 1 Mpáginas, então a tabela de paginação terá 1 M entradas. Como em cada entrada
se tem um descritor com, suponhamos, 8 bytes, uma tabela de paginação ocuparia qualquer
coisa como 8MBytes...
No caso de um programa precisar de poucas páginas, esta situação poderia conduzir a uma
utilização muito pouco eficiente da memória disponível – poder-se-ia mesmo correr o risco de
ser necessária mais memória para guardar as tabelas de paginação do que o exigido pelos
próprios programas.
De forma a evitar este problema, existem esquemas de paginação multi-nível. Num esquema de
paginação multi-nível, em vez de existir uma única tabela de páginas existem várias tabelas de
páginas, mais pequenas, que são mantidas em memória à medida das necessidades do programa
em execução.
Uma esquematização desta ideia pode ser observada na Figura 11, onde se representa um
esquema de paginação a 2 níveis. Estrutura-se o endereço virtual em mais campos, mais
especificamente, subdivide-se o campo do número de página de forma a serem utilizados
índices mais curtos para indexar tabelas. Quanto às tabelas em si, estas podem ser organizadas
em níveis, em que uma tabela de nível anterior contém nas suas entradas os endereços de início
das tabelas do nível seguinte.
ISCTE-IUL - DCTI
24
Endereço virtual
10 bits
10 bits
12 bits
TP1
TP2
Deslocamento
Tabela de 1º nível
(Directoria)
Tabelas de 2º nível
(Tabelas de páginas)
0
1
2
0
1
2
1023
1023
0
1
2
1023
Nº de moldura
Deslocamento
Endereço real
0
1
2
1023
Figura 11 – Exemplo de paginação a 2 níveis.
Na figura, o endereço virtual encontra-se dividido em 3 partes: uma parte que indexa a tabela de
1º nível ou directoria (TP1), uma parte que indexa as tabelas de 2º nível (TP2) e o
deslocamento. Repare que a concatenação dos dois índices (TP1 e TP2) corresponde ao número
de página.
Para converter um endereço virtual em real procede-se do seguinte modo:
•
Com o 1º índice (TP1) acede-se à directoria, e obtêm-se uma referência para uma das
tabelas de 2º nível;
•
Referenciada a tabela de 2º nível, acede-se a esta com base no 2º índice (TP2);
•
A tabela de 2º nível irá conter o descritor de página, construindo-se o endereço real a partir
da moldura obtida nesse descritor.
Suponha agora que um determinado programa só necessita apenas de 2048 páginas. No
esquema da figura pode-se concluir que cada tabela de 2º nível tem 1024 entradas, ou seja,
contém informação respeitante 1024 páginas. Para satisfazer as necessidades do programa serão
apenas necessárias duas tabelas de 2º nível. Como essas tabelas têm que ser referenciadas, a
directoria também será necessária. Tem-se um total de 3 tabelas com 1024 entradas cada uma.
Admitindo novamente que em cada entrada estão 8 bytes, o espaço total para guardar as tabelas
seria dado por 3 × 8bytes × 1024 entradas = 24Kbytes. Comparando este valor com o obtido
ISCTE-IUL - DCTI
25
anteriormente para paginação simples (8MBytes) pode-se concluir que esta solução é vantajosa
sob o ponto de vista de utilização de recursos, especialmente quando se pretende correr
programas que usem pouca memória.
A paginação multi-nível apresenta contudo uma desvantagem: o tempo de conversão de um
endereço virtual em real é maior à medida que aumenta o número de níveis. Basta pensar que
com paginação multi-nível são feito múltiplos acessos à memória (1 por cada nível) para ir
consultando as tabelas. De forma a minorar os atrasos é normalmente utilizada uma pequena
cache especial chamada TLB (Translation lookaside buffer).
3.1.6.
TLB (Translation Lookaside Buffer)
Uma TLB consiste numa cache completamente associativa de dimensões reduzidas2
(tipicamente com 64 a 256 entradas). Esta cache é especial na medida em que não guarda dados
respeitantes ao programa, mas sim dados auxiliares à tradução de endereços virtuais em
endereços reais.
Na realidade os dados que são guardados na TLB são descritores de páginas. Sendo assim, antes
de se consultarem as tabelas de páginas, consulta-se primeiro a TLB e verifica-se se o descritor
da página pretendida se encontra lá guardado. No caso desta verificação ter sucesso, conseguese imediatamente traduzir o endereço virtual para real, dispensando-se deste modo a consulta às
tabelas de páginas e consequentes acessos à memória. No caso do descritor da página pretendida
não se encontrar na TLB, segue-se a via “normal” de tradução, acedendo-se à memória para
consultar a(s) tabela(s).
Uma TLB segue normalmente a estrutura ilustrada na Figura 12.
Etiquetas
(Nos de página)
Blocos de dados
(Descritores)
1
24
Descritor da página 24
1
56
Descritor da página 56
0
xxx
xxx
1
3
Descritor da página 3
0
xxx
xxx
0
xxx
xxx
Bit de validade
Figura 12 – Estrutura e exemplo de conteúdo de uma TLB.
Repare que o número de página é utilizado como etiqueta. Para determinar se o descritor da
página pretendida se encontra na TLB, utilizam-se os mesmos circuitos que numa cache
associativa regular, comparando-se o número da página que se pretende aceder com as etiquetas
válidas na cache.
2
Por exemplo, a maioria dos modelos do processador Intel P4 incluem duas TLBs de 64 entradas cada
uma (uma TLB é utilizada para páginas referentes a instruções, a I-TLB, e outra para páginas referentes a
dados, a D-TLB).
ISCTE-IUL - DCTI
26
Deste modo, para determinar se o descritor de uma determinada página está ou não presente na
TLB, compara-se o número da página que se pretende aceder (extraído directamente do
endereço virtual) com todas as etiquetas presentes na TLB. Estas comparações são todas feitas
em paralelo.
Para ilustrar a vantagem em utilizar uma TLB, considere o seguinte exemplo:
Suponha que o acesso à TLB requer 1 ns e que em 80% dos casos os acessos resultam em
sucesso. Admita também que uma consulta à memória requer 10 ns. Qual será o tempo médio
de conversão de um endereço virtual em real, admitindo que é utilizada:
a) paginação simples
Neste caso, em 80% dos casos o tempo será de 1 ns e nos restantes casos será dado por
um acesso em falta à TLB seguido de um acesso à RAM para consultar a tabela de
paginação. Ou seja:
Tconversão = 0.8 × 1ns + 0.2 × (1ns + 10ns) = 0.8 + 2.2 = 3ns
Quanto ao ganho resultante do uso da TLB, pode-se calcular o rácio entre o tempo que
seria necessário quando não se utiliza a TLB e o tempo resultante com a utilização da
TLB. Tem-se:
Ganho =
10ns
= 3.33
3ns
b) Paginação com 2 níveis
Neste caso, quando o descritor se encontra em falta na TLB serão necessários dois
acessos à RAM – um para consultar a directoria e outro para consultar a tabela de 2º
nível. Tem-se então
Tconversão = 0.8 × 1ns + 0.2 × (1ns + 10ns + 10ns) = 0.8 + 4.2 = 5ns
Ganho =
20ns
=4
5ns
c) Paginação com 3 níveis
Semelhante ao caso anterior, mas desta vez são necessários 3 acessos à RAM para
converter o endereço virtual em físico, quando o descritor não é encontrado na TLB:
Tconversão = 0.8 × 1ns + 0.2 × (1ns + 10ns + 10ns + 10ns) = 0.8 + 6.2 = 7ns
Ganho =
30ns
= 4.29
7ns
Pode-se observar que, para todos estes casos, o facto de utilizar uma TLB com as características
referidas tem um impacto positivo no desempenho do sistema. Esse impacto é tanto maior
ISCTE-IUL - DCTI
27
quanto o número de níveis utilizados na paginação (o que faz sentido, já que se usarem vários
níveis, são mais acessos à memória que a TLB evita em caso de sucesso).
Note também que tudo se pode complicar se existirem memórias cache à mistura…
Nesse caso, o melhor caso para acesso a um determinado endereço (por exemplo para leitura),
seria o correspondente a:
•
a página ter um descritor na TLB (hit na TLB)
•
os dados a aceder estarem em cache (hit na cache – acesso aos dados)
O pior caso seria dado por:
•
a página não tem descritor na TLB; (miss na TLB)
•
o bloco de memória em que se encontra a entrada da tabela de páginas não estar em
cache; (miss na cache – consulta da tabela).
•
o bloco de dados a aceder não se encontra em cache (miss na cache – acesso aos dados).
ISCTE-IUL - DCTI
28
4. Bibliografia
1. Arquitectura de computadores: dos sistemas digitais aos microprocessadores – 2ª
edição, Guilherme Arroz, José Monteiro e Arlindo Oliveira, 2009
2. Computer organization and design: the hardware/software interface – 4th edition,
David Patterson & John Hennessy, Morgan Kaufmann, 2008.
3. Logic and computer design fundamentals – 4th edition, Morris Mano & Charles Kime,
Prentice-Hall, 2007.
4. Computer architecture: a quantitative approach, 3rd edition, John Hennessy, David
Patterson, Morgan Kaufmann, 2003.
5. Computer organization, 5th edition, Carl Zamacher, Zvonko Vranesic, Safwat Zaky,
McGraw Hill, 2002.
6. Structured computer organization, 4th edition, Andrew Tanenbaum, Prentice-Hall
International, 1999.
ISCTE-IUL - DCTI
29
5. Lista de revisões
Versão
Autor
Data
0.01
Tomás Brandão
Jun / 2005
0.02
0.02a
Tomás Brandão
João Pedro Oliveira
Mai / 2009
Mai / 2010
ISCTE-IUL - DCTI
Comentários
Versão draft inicial;
Publicação antecipada para as avaliações 2004/05.
Correcção de gralhas; Reformulação de texto.
Correcção de gralhas.
30
Download

Hierarquia da memória