mirror of
https://github.com/Steffo99/appunti-magistrali.git
synced 2024-11-27 20:34:18 +00:00
Importa Architettura dei calcolatori da Steffo99/appunti-universitari
This commit is contained in:
parent
a8b8ad3a87
commit
2e748ea187
18 changed files with 684 additions and 0 deletions
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
# Assemble RISV-V assembly (.s) files
|
||||
riscv64-unknown-elf-as -g -o "$1.o" "$1.riscv"
|
||||
# Link RISC-V object (.o) files
|
||||
riscv64-unknown-elf-ld -o "$1.elf" "$1.o"
|
||||
|
||||
exit $?
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
riscv64-unknown-elf-objdump "$1.elf"
|
||||
|
||||
exit $?
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
# Compile using g++
|
||||
riscv64-unknown-elf-g++ -o "$1.elf" "$1.cc"
|
||||
|
||||
exit $?
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
# Compile using g++
|
||||
riscv64-unknown-elf-gcc -o "$1.elf" "$1.cc"
|
||||
|
||||
exit $?
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
riscv64-unknown-elf-gprof "$1.elf"
|
||||
|
||||
exit $?
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
riscv64-unknown-elf-readelf "$1.elf" -a
|
||||
|
||||
exit $?
|
17
2 - Architettura dei calcolatori/0 - Script forniti/run.sh
Normal file
17
2 - Architettura dei calcolatori/0 - Script forniti/run.sh
Normal file
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
# Sintassi: ./run.sh nomefile
|
||||
# Non mettete l'estensione!
|
||||
# Potrebbe esserci bisogno di installare xterm prima
|
||||
# Se non funziona, installatelo con
|
||||
# sudo apt install xterm
|
||||
|
||||
echo "Cleaning phase"
|
||||
rm "$1.o"
|
||||
rm "$1.elf"
|
||||
echo "Compilation phase"
|
||||
riscv64-unknown-elf-as -g -o "$1.o" "$1.riscv"
|
||||
riscv64-unknown-elf-ld -o "$1.elf" "$1.o"
|
||||
echo "Debugging phase"
|
||||
xterm -e "qemu-riscv64 -g 1234 \"$1.elf\"" &
|
||||
xterm -e "riscv64-unknown-elf-gdb --ex=\"target remote localhost:1234\" \"$1.elf\"" &
|
||||
wait
|
|
@ -0,0 +1,24 @@
|
|||
# Architettura dei Calcolatori
|
||||
|
||||
Docente: [**Andrea Mariongiu**](mailto:andrea.mariongiu@unimore.it)
|
||||
|
||||
Crediti: **9 CFU** (72 ore di lezione)
|
||||
|
||||
### Materiale
|
||||
|
||||
Libri:
|
||||
- `ISBN9780128122761`
|
||||
- `ISBN9788871924618`
|
||||
|
||||
**[Sito web](http://algo.ing.unimo.it/people/andrea/Didattica/Architetture/index.html)**
|
||||
|
||||
[Slides 2017](http://dolly.fim.unimore.it/2017/course/view.php?id=57)
|
||||
|
||||
### Esame
|
||||
|
||||
- Prova scritta
|
||||
- Serie di domande a risposta singola, multipla e libera, simile a Programmazione 1
|
||||
- Rimane valida fino alla fine della sessione seguente
|
||||
- Prova orale
|
||||
- Può essere sostituita con lo sviluppo di un _progettino_
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# I principi dell'architettura dei calcolatori
|
||||
|
||||
## Gli otto grandi principi
|
||||
|
||||
- Progetta per la **Legge di Moore**
|
||||
- **Astrai** per semplificare il design
|
||||
- Velocizza i **casi più comuni**
|
||||
- Sfrutta il **parallelismo**
|
||||
- Instruction level parallelism
|
||||
- In una sola CPU vengono realizzati più calcoli in parallelo
|
||||
- Sfrutta le **pipeline**
|
||||
- Pipelining
|
||||
- Separare le azioni del processore in fasi
|
||||
- Il fetch è separato dal decoding, mentre decodifico un'istruzione posso già fetchare quell'altra
|
||||
- **Prevedi** le istruzioni successive
|
||||
- **Gerarchizza** le memorie per velocità di accesso
|
||||
- La CPU cerca sempre di accedere alle memorie più veloci
|
||||
- Rendi affidabile con la **ridondanza**
|
||||
|
||||
### Parallelismo
|
||||
|
||||
> Chiedo a 100 persone di fare una moltiplicazione ciascuna invece che fare 100 moltiplicazioni io da solo
|
||||
|
||||
### Pipelining
|
||||
|
||||
![Senza](https://upload.wikimedia.org/wikipedia/commons/2/2c/Nopipeline.png)
|
||||
|
||||
![Con](https://upload.wikimedia.org/wikipedia/commons/2/21/Fivestagespipeline.png)
|
||||
|
||||
### Gerarchia delle memorie
|
||||
|
||||
> Hit or miss? I hope they never miss.
|
||||
|
||||
La CPU prova sempre ad accedere alle memorie più in alto di questa lista, effettuando un **hit** se trova i dati che le servono in una data memoria ed effettuando un **miss** se invece non trova i dati necessari e deve accedere a uno strato inferiore.
|
||||
|
||||
- [Cache](https://it.wikipedia.org/wiki/Cache) (piccolissima e velocissima)
|
||||
- [Random Access Memory](https://it.wikipedia.org/wiki/RAM) (piccola e veloce)
|
||||
- [Solid State Drive]() (medio e di media velocità)
|
||||
- [Hard Disk Drive](https://it.wikipedia.org/wiki/Disco_rigido) (grosso, ma lento)
|
||||
- [Tape storage](https://en.wikipedia.org/wiki/Magnetic_tape_data_storage) (enorme, ma lentissimo)
|
||||
|
||||
I _miss_ rallentano l'esecuzione dei programmi, in quanto causano ritardi nel ritrovamento dei dati, passando da _meno di 1 µs_ per le cache a _più di 1 ms_ per gli hard disk.
|
||||
|
||||
Inoltre, esistono due tipi di cache, anch'essi gerarchizzati:
|
||||
|
||||
- [SRAM](https://it.wikipedia.org/wiki/SRAM)
|
||||
- [DRAM](https://it.wikipedia.org/wiki/DRAM)
|
|
@ -0,0 +1,41 @@
|
|||
# Dentro il processore
|
||||
|
||||
## Come sono fatti i circuiti integrati?
|
||||
|
||||
Sono composti da un layer di silicio (semiconduttore), più delle maschere di conduttori (µfili metallici), isolanti (plastica e vetro) e transistor.
|
||||
|
||||
### Processo di produzione
|
||||
|
||||
- Lingotto di silicio
|
||||
- Tagliato in _wafer_ da 2mm
|
||||
- Creazione dei _patterned wafer_
|
||||
- Testing dei _patterned wafer_
|
||||
- Taglio i _tested wafers_
|
||||
- Creo un package con i _tested dies_
|
||||
- Testo i _packaged dies_
|
||||
- Spedisco i _tested packaged dies_ ai clienti.
|
||||
|
||||
### Lo yield
|
||||
|
||||
Lo yield è quanti _tested packaged dies_ riesco a produrre da un singolo lingotto di silicio.
|
||||
|
||||
## Come definiamo la performance di un processore?
|
||||
|
||||
Ci sono varie metriche per definirla:
|
||||
|
||||
- **Response time**: tempo impiegato a compiere un'operazione
|
||||
- **Throughput**: operazioni fatte in un'unità di tempo
|
||||
|
||||
### Il clock
|
||||
|
||||
Il clock della CPU è il timer che scandisce le operazioni della CPU.
|
||||
|
||||
Ha, ovviamente, un periodo e una frequenza costanti.
|
||||
|
||||
### Il CPU time
|
||||
|
||||
Il tempo impiegato da una CPU a compiere una determinata operazione è dato da `numero di cicli di clock richiesti * periodo di clock`, ed è detto **CPU time**.
|
||||
|
||||
### Il Cost per instruction (CPI)
|
||||
|
||||
Alcune istruzioni di una CPU potrebbero richiedere più di un ciclo di clock.
|
165
2 - Architettura dei calcolatori/1 - Appunti/03 - Assembly.md
Normal file
165
2 - Architettura dei calcolatori/1 - Appunti/03 - Assembly.md
Normal file
|
@ -0,0 +1,165 @@
|
|||
# Assembly
|
||||
|
||||
Useremo il RISC-V.
|
||||
Esso è little-endian, ed è indirizzato a gruppi di 8 bit.
|
||||
|
||||
## Registri
|
||||
|
||||
RISC-V ha a disposizione **32** registri da 64 bit, che vanno da `x0` a `x31`.
|
||||
|
||||
Un dato a 32 bit viene chiamato **word**, uno a 64 bit viene chiamato **doubleword**.
|
||||
|
||||
- `x0`: costante
|
||||
- `x1`: indirizzo risultato
|
||||
- `x2`: indirizzo della cima dello stack
|
||||
- `x3`: indirizzo dell'area di memoria con variabili globali
|
||||
- `x4`: indirizzo dell'area di memoria specifica del thread attivo
|
||||
- `x5`: registro temporaneo
|
||||
- `x6`: registro temporaneo
|
||||
- `x7`: registro temporaneo
|
||||
- `x8`: frame pointer
|
||||
- `x9`: registro salvato, può essere copiato in memoria
|
||||
- `x10`: argomento o risultato delle funzioni
|
||||
- `x11`: argomento o risultato delle funzioni
|
||||
- `x12`: argomento delle funzioni
|
||||
- `x13`: argomento delle funzioni
|
||||
- `x14`: argomento delle funzioni
|
||||
- `x15`: argomento delle funzioni
|
||||
- `x16`: argomento delle funzioni
|
||||
- `x17`: argomento delle funzioni
|
||||
- `x18`: registro salvato, può essere copiato in memoria
|
||||
- `x19`: registro salvato, può essere copiato in memoria
|
||||
- `x20`: registro salvato, può essere copiato in memoria
|
||||
- `x21`: registro salvato, può essere copiato in memoria
|
||||
- `x22`: registro salvato, può essere copiato in memoria
|
||||
- `x23`: registro salvato, può essere copiato in memoria
|
||||
- `x24`: registro salvato, può essere copiato in memoria
|
||||
- `x25`: registro salvato, può essere copiato in memoria
|
||||
- `x26`: registro salvato, può essere copiato in memoria
|
||||
- `x27`: registro salvato, può essere copiato in memoria
|
||||
- `x28`: registro temporaneo
|
||||
- `x29`: registro temporaneo
|
||||
- `x30`: registro temporaneo
|
||||
- `x31`: registro temporaneo
|
||||
|
||||
## Operazioni
|
||||
|
||||
### Operazioni aritmetiche
|
||||
|
||||
Sono sempre formate da tre operandi: registro a cui il risultato verrà assegnato e gli argomenti dell'operazione.
|
||||
|
||||
- `add`: addizione
|
||||
- `sub`: sottrazione
|
||||
- `addi`: addizione di costanti
|
||||
|
||||
#### Esempio
|
||||
|
||||
```assembly
|
||||
add a, b, c
|
||||
```
|
||||
|
||||
##### Equivalente Python
|
||||
|
||||
```python
|
||||
a = b + c
|
||||
```
|
||||
|
||||
### Operazioni di trasferimento dati
|
||||
|
||||
Permettono di caricre / scaricare dati dalla memoria.
|
||||
|
||||
### Operazioni logiche
|
||||
|
||||
Consentono di effettuare operazioni di logica.
|
||||
|
||||
- `and`
|
||||
- `or`
|
||||
- ...
|
||||
|
||||
### Operazioni di shift
|
||||
|
||||
Consentono di spostare i bit di un registro di alcune posizioni, moltiplicando o dividendo effettivamente per potenze di 2.
|
||||
|
||||
### Operazioni condizionali
|
||||
|
||||
Consentono di saltare ad un'altra serie di istruzioni se certe condizioni si verificano.
|
||||
|
||||
- `blt`: minore di, signed
|
||||
- `bge`: maggiore di, signed
|
||||
- `bltu`: minore di, unsigned
|
||||
- `bgeu`: maggiore di, unsigned
|
||||
|
||||
### Operazioni non-condizionali
|
||||
|
||||
Consentono di saltare ad un'altra serie di istruzioni in qualunque caso.
|
||||
|
||||
#### Chiamare una procedura
|
||||
|
||||
> `jal x1, ProcedureLabel`
|
||||
|
||||
Salva l'indirizzo dell'istruzione successiva in x1, poi salta a ProcedureLabel.
|
||||
|
||||
#### Ritornare da una procedura
|
||||
|
||||
> `jalr x0, 0(x1)`
|
||||
|
||||
Ritorna all'indirizzo puntato da `x1`.
|
||||
|
||||
## Memoria
|
||||
|
||||
Per accedere a una posizione in memoria, si usa `offset(indirizzo)`.
|
||||
|
||||
##### Esempio
|
||||
|
||||
`64(x22)` accede al 64° byte dopo la posizione di memoria archiviata in `x22`.
|
||||
|
||||
## Segni
|
||||
|
||||
E' possibile usare rappresentazione standard o a complemento-a-2.
|
||||
|
||||
Possiamo usare la **sign extension** quando abbiamo un dato che vogliamo portare a un numero di bit maggiore di quello che è.
|
||||
|
||||
##### Esempio
|
||||
|
||||
```
|
||||
[0]101 0101 --> 0000 0000 [0]101 0101
|
||||
[1]100 0000 --> 1111 1111 [1]100 0000
|
||||
```
|
||||
|
||||
### Operazioni di sincronizzazione
|
||||
|
||||
- `lr`: load reserved
|
||||
- `sc`: store conditional
|
||||
|
||||
#### Load reserved
|
||||
|
||||
```
|
||||
lr rd, (rs1)
|
||||
```
|
||||
|
||||
> Non ho la minima idea di come funzioni.
|
||||
|
||||
Controlla se `rs1` è bloccato; se sì, mette un valore != 0 in `rd`...?
|
||||
|
||||
> Forse... crea un lock in `rd`?
|
||||
|
||||
#### Store conditional
|
||||
|
||||
```
|
||||
sc rd, rs2, (rs1)
|
||||
```
|
||||
|
||||
> Non ho la minima idea di come funzioni.
|
||||
|
||||
Copia il dato da `rs2` a `rs1`, se non è bloccato, e metti `x0` in `rd`...?
|
||||
|
||||
> Carica un dato... se ho ancora il lock, altrimenti, metti `x0` in `rd`.
|
||||
|
||||
#### Unlock
|
||||
|
||||
```
|
||||
sd x0, 0(x20)
|
||||
```
|
||||
|
||||
## Operazioni floating point
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# Definisci delle costanti
|
||||
.equ _SYS_EXIT, 93 # Syscall exit
|
||||
.equ _SYS_WRITE, 64 # Syscall write
|
||||
|
||||
# Inizio del programma
|
||||
.global _start
|
||||
|
||||
# Variabili statichie inizializzate a 0
|
||||
.section .bss
|
||||
|
||||
# Dati modificabili
|
||||
.section .data
|
||||
|
||||
# Dati in sola lettura
|
||||
.section .rodata
|
||||
msg: .string "Hello mondo!\n"
|
||||
|
||||
# Testo del programma
|
||||
.section .text
|
||||
_start:
|
||||
# Chiamata a WRITE(stream, primocarattere, lunghezza)
|
||||
li a0, 0 # Print to stdout
|
||||
la a1, msg # Stampa il messaggio in msg
|
||||
li a2, 13 # Stampa 13 caratteri dopo msg
|
||||
li a7, _SYS_WRITE # Seleziona la syscall WRITE
|
||||
ecall
|
||||
|
||||
li a7, _SYS_EXIT
|
||||
ecall
|
|
@ -0,0 +1,33 @@
|
|||
# Che bello! Adoro l'assembly!
|
||||
# Questo programma funziona, è verificato!
|
||||
.global _start
|
||||
|
||||
# La sezione .data contiene i dati del programma
|
||||
.section .data
|
||||
|
||||
# Creo una variabile con etichetta "ciao"
|
||||
ciao: .string "0\n\0"
|
||||
|
||||
# La sezione .rodata contiene i dati del programma in sola lettura
|
||||
.section .rodata
|
||||
|
||||
# La sezione .text contiene il testo del programma
|
||||
.section .text
|
||||
|
||||
# Creo una label _start che funzionerà da entrypoint
|
||||
_start:
|
||||
li t1, 10 # Voglio ripetere il ciclo 10 volte
|
||||
li a7, 64 # PRINT(stream, indirizzo, numerobytes) è la syscall 64
|
||||
li a0, 0 # stream è il primo argomento: vogliamo scrivere su stdout (0)
|
||||
la a1, ciao # indirizzo è il secondo argomento: partiamo dall'etichetta ciao
|
||||
li a2, 2 # numerobytes è il terzo argomento: vogliamo stampare 2 bytes
|
||||
print:
|
||||
ecall # PRINT(a0, a1, a2)
|
||||
lbu t0, 0(a1) # Carica il byte con il 0
|
||||
addi t0, t0, 1 # Aggiungici 1
|
||||
sb t0, 0(a1) # Ricaricalo in memoria
|
||||
addi t1, t1, -1 # Togli 1 dal numero di cicli rimasti
|
||||
bne t1, zero, print # Se rimangono dei cicli, torna indietro e falli!
|
||||
end:
|
||||
li a7, 93 # Tutti i programmi terminano chiamando la syscall 93, EXIT()
|
||||
ecall # EXIT()
|
|
@ -0,0 +1,40 @@
|
|||
# Che bello! Adoro l'assembly!
|
||||
# Funziona!
|
||||
.global _start
|
||||
|
||||
# La sezione .data contiene i dati del programma
|
||||
.section .data
|
||||
|
||||
# La sezione .rodata contiene i dati del programma in sola lettura
|
||||
.section .rodata
|
||||
|
||||
# La sezione .text contiene il testo del programma
|
||||
.section .text
|
||||
|
||||
# Creo una label _start che funzionerà da entrypoint
|
||||
_start:
|
||||
li s0, 5 # Scrivi il numero 5
|
||||
li s3, 3 # COSTANTE: 3
|
||||
|
||||
# Pushing phase
|
||||
pushing:
|
||||
sb s0, 0(sp) # Metti nello stack s0
|
||||
addi sp, sp, 1 # Aumenta di 1 lo stack pointer
|
||||
addi s2, s2, 1 # Aumenta di 1 la dimensione dello stack
|
||||
addi s0, s0, -1 # Diminuisci di 1 il numero da fattorializzare
|
||||
bgeu s0, s3, pushing # Continua a pushare finchè non arrivi a 2
|
||||
li s1, 2
|
||||
|
||||
# Popping phase
|
||||
popping:
|
||||
addi sp, sp, -1 # Diminuisci di 1 lo stack pointer
|
||||
# Attenzione: diminuisco lo stack pointer PRIMA di caricare dallo stack, altrimenti caricarei il nulla
|
||||
lb s0, 0(sp) # Prendi dallo stack il prossimo numero
|
||||
addi s2, s2, -1 # Diminuisci di 1 la dimensione dello stack
|
||||
mul s1, s1, s0 # Effettua la moltiplicazione
|
||||
bne s2, zero, popping # Se ci sono altri valori nello stack, continua a moltiplicare
|
||||
|
||||
# Ending phase
|
||||
ending:
|
||||
li a7, 93
|
||||
ecall
|
|
@ -0,0 +1,48 @@
|
|||
# Che bello! Adoro l'assembly!
|
||||
# Ommioddiofunzionawowimawizard
|
||||
.global _start
|
||||
|
||||
# La sezione .data contiene i dati del programma
|
||||
.section .data
|
||||
|
||||
primo: .word 1, 2, 3, 4, 5
|
||||
secondo: .word 1, 2, 3, 4, 5
|
||||
|
||||
# La sezione .rodata contiene i dati del programma in sola lettura
|
||||
.section .rodata
|
||||
|
||||
# La sezione .text contiene il testo del programma
|
||||
.section .text
|
||||
|
||||
# s0: primo[s0]
|
||||
# s1: secondo[s0]
|
||||
# s2: monomio
|
||||
# s3: indirizzo da cui prendere il valore
|
||||
# s4: indice
|
||||
# s5: dimensione array
|
||||
# a0: somma
|
||||
|
||||
_start:
|
||||
li s2, 0
|
||||
li a0, 0
|
||||
li s4, 0
|
||||
li s5, 5
|
||||
|
||||
somma:
|
||||
la s3, primo
|
||||
add s3, s3, s4
|
||||
lw s0, 0(s3)
|
||||
|
||||
la s3, secondo
|
||||
add s3, s3, s4
|
||||
lw s1, 0(s3)
|
||||
|
||||
mulw s2, s1, s0
|
||||
add a0, a0, s2
|
||||
addi s4, s4, 4
|
||||
addi s5, s5, -1
|
||||
bne zero, s5, somma
|
||||
|
||||
_end:
|
||||
li a7, 93
|
||||
ecall
|
|
@ -0,0 +1,57 @@
|
|||
# Appunti
|
||||
|
||||
## Registri
|
||||
|
||||
I registri sono divisi in tre tipi: di **proprietà del chiamante**, di **proprietà del chiamato** (anche se in realtà è solo una convenzione) e... gli **altri**.
|
||||
|
||||
Tutti i registri sono grandi 64 bit: possono quindi contenere una _doubleword_.
|
||||
|
||||
### Proprietà del chiamante
|
||||
|
||||
Questi registri **non devono essere sovrascritti** quando viene chiamata una funzione, o se vengono sovrascritti, **devono essere ripristinati** prima che la funzione restituisca.
|
||||
|
||||
Essi sono:
|
||||
- `s1..s11`: **Saved** registers, registri generici che possono essere usati per qualsiasi cosa.
|
||||
- `sp`: **Stack Pointer**, il primo indirizzo dello stack disponibile per la scrittura. Va _decrementato_ quando si aggiunge qualcosa allo stack.
|
||||
- `fp`: **Frame Pointer**, punta all'inizio dello stack.
|
||||
|
||||
### Proprietà del chiamato
|
||||
|
||||
Questi registri **possono essere sovrascritti** quando viene chiamata una funzione: bisogna salvarli in memoria prima di chiamarla!
|
||||
|
||||
Essi sono:
|
||||
- `t0..t6`: **Temporary** registers, registri generici che possono essere usati per qualsiasi cosa.
|
||||
- `ra`: **Return Address**, l'indirizzo a cui continuare l'esecuzione del programma dopo aver terminato una funzione. _Solitamente si usa solo con `jal` e `jalr`!_
|
||||
- `a0..a7`: Function **Arguments**. Se all'interno di una funzione, contengono gli argomenti che sono stati passati. Se all'esterno di una funzione, contengono i valori restituiti dall'ultima funzione chiamata.
|
||||
|
||||
### Altri
|
||||
|
||||
- `zero`: **Zero**, registro che vale sempre 0, anche dopo essere stato sovrascritto da altro. Come `/dev/null` su Linux.
|
||||
- `gp`: **Global Pointer**, punta a un indirizzo _[(?)](https://groups.google.com/a/groups.riscv.org/forum/#!topic/sw-dev/60IdaZj27dY)_
|
||||
- `tp`: **Thread Pointer**, punta al thread-local storage _[(?)](https://groups.google.com/a/groups.riscv.org/d/msg/sw-dev/cov47bNy5gY/zLnlKkw9CQAJ)_
|
||||
- `pc`: **Program Counter**, punta all'istruzione che sta venendo eseguita.
|
||||
|
||||
## Comandi preprocessore
|
||||
|
||||
Scrivendo file assembly per RISC-V, è possibile aggiungere direttive per il preprocessore.
|
||||
|
||||
Per ora ho scoperto queste:
|
||||
|
||||
- `.global INDIRIZZO`: definisce l'indirizzo a cui inizia il programma. _(Forse setta il `gp` a quell'indirizzo?)_
|
||||
- `.equ NOME, VALORE`: definisce una costante con uno specifico nome. _Il nome delle costanti dovrebbe iniziare sempre con un underscore per convenzione._
|
||||
- `.section [.data|.rodata|.bss|.text]`: marca l'inizio di una specifica sezione del programma. Le sezioni sono:
|
||||
- `.data`: Variabili del programma
|
||||
- `.rodata`: Variabili sola lettura del programma
|
||||
- `[.bss](https://en.wikipedia.org/wiki/.bss)`: Variabili statiche non costanti non inizializzate a nessun valore. _A volte, vengono inizializzate a 0. Solo a volte. Dipende dall'OS._
|
||||
- `.text`: Il testo del programma, con le istruzioni in assembly.
|
||||
- `.TIPOVARIABILE`: crea una variabile di quel tipo e la inizializza a qualcosa.
|
||||
|
||||
# Link utili
|
||||
|
||||
- [algo.ing.unimo.it](http://algo.ing.unimo.it/people/andrea/Didattica/Architetture/index.html)
|
||||
- [wikichip.org](https://en.wikichip.org/wiki/risc-v)
|
||||
- [en.wikiversity.org](https://en.wikiversity.org/wiki/Computer_architecture)
|
||||
- [it.wikiversity.org](https://it.wikiversity.org/wiki/Materia:Architetture_degli_elaboratori)
|
||||
- [stackoverflow.com](https://stackoverflow.com/questions/tagged/riscv)
|
||||
- [softwareengineering.stackexchange.com](https://softwareengineering.stackexchange.com/questions/tagged/assembly)
|
||||
- [rv8.io](https://rv8.io/isa.html)
|
133
2 - Architettura dei calcolatori/1 - Appunti/09 - Memorie.md
Normal file
133
2 - Architettura dei calcolatori/1 - Appunti/09 - Memorie.md
Normal file
|
@ -0,0 +1,133 @@
|
|||
# Memorie
|
||||
|
||||
La _memoria_ di un calcolatore solitamente è composta da molteplici strati, realizzati per velocizzare gli accessi.
|
||||
|
||||
Ogni strato offre lettura più veloce, ma è più costoso da realizzare e ha consumi più alti.
|
||||
|
||||
In ordine di velocità, sono:
|
||||
|
||||
- Registers
|
||||
- Cache L1
|
||||
- Cache L2
|
||||
- Cache L`X`
|
||||
- Random Access Memory
|
||||
- Solid State Drive
|
||||
- Hard Disk Drive
|
||||
|
||||
## Cache
|
||||
|
||||
### Cache L1
|
||||
|
||||
Piccola, ma miss penalty bassa
|
||||
|
||||
### Cache L2
|
||||
|
||||
Molto grande, ma miss penalty alta
|
||||
|
||||
### Direct mapping
|
||||
|
||||
Una cache _direct mapped_ associa a ogni indirizzo di RAM un **indice** uguale agli **N bit meno significativi** dell'indirizzo dell'inferiore, e un **tag** uguale agli **N bit più significativi**.
|
||||
|
||||
Se durante un accesso _il tag di un blocco è diverso dal tag dell'indirizzo a cui vogliamo accedere_, si ha un cache miss.
|
||||
|
||||
> Il dato di cui abbiamo bisogno è contenuto nell'indirizzo `0x0ABC` della RAM.
|
||||
> Il suo indice sarà `0xBC`, e il suo tag sarà `0x0A`.
|
||||
> Essendo la cache vuota all'inizio, viene immediatamente caricato.
|
||||
>
|
||||
> Voglio poi accedere all'indirizzo `0x0BBC` della RAM.
|
||||
> Controlliamo il blocco con indice `0xBC`: il suo tag è `0x0A`, ma noi stiamo cercando `0x0B`! Si ha quindi un cache miss, e devo andare a prendere dalla memoria il dato che sto cercando e scriverlo sulla RAM.
|
||||
|
||||
Inoltre, in ogni blocco di memoria della cache è presente **un bit di validità**, che rappresenta se il dato in cache è stato inizializzato o no: parte da `0` e viene impostato a `1` quando viene caricato dalla RAM un dato nel relativo blocco.
|
||||
|
||||
> Un esempio potrebbe essere un processore con blocchi di memoria da 32 bit e indirizzi a 64 bit: la cache, contenente 1024 blocchi di memoria, avrà index a 12 bit, e di conseguenza il tag sarà grande 52 bit.
|
||||
|
||||
#### Dimensione blocchi
|
||||
|
||||
Fare blocchi grandi o fare blocchi piccoli ha significative differenze sulla velocità della cache:
|
||||
|
||||
Avere blocchi più grandi significa che ci saranno meno blocchi in tutta la cache, quindi:
|
||||
|
||||
```diff
|
||||
+ Riduce il miss rate per il principio di località spaziale
|
||||
- Avendo una quantità minore di blocchi, aumenta la possibilità di conflitto
|
||||
- La miss penalty è più alta
|
||||
# La miss penalty è compensabile con tecniche come early restart o critical-word-first
|
||||
```
|
||||
|
||||
Con blocchi più piccoli, invece
|
||||
```diff
|
||||
+ Miss penalty minore
|
||||
+ Più blocchi significa meno conflitti
|
||||
- E' possibile che abbia bisogno di fare più di una richiesta alla cache, rallentandola significativamente
|
||||
```
|
||||
|
||||
#### Scrittura tramite cache
|
||||
|
||||
Quando scrivo in memoria un dato presente nella cache, l'informazione presente nella cache diventa errata.
|
||||
|
||||
Si può risolvere questo problema con una politica di riscrittura:
|
||||
- Write-through
|
||||
- Write-back
|
||||
|
||||
##### Write-through
|
||||
|
||||
_Quando **viene scritto** su dato in cache_, aggiorna tutte le memorie che lo contengono.
|
||||
|
||||
```diff
|
||||
+ Non necessita di ulteriore memoria di cache
|
||||
+ Caricare dati è veloce
|
||||
+ Se si verifica un write miss non ha per forza bisogno di portare in cache il dato da scrivere
|
||||
- I write richiedono molto più tempo, soprattutto se ripetuti
|
||||
```
|
||||
|
||||
|
||||
##### Write-back
|
||||
|
||||
Scrivi la modifica di dato solo nella cache, e _marca il blocco come **dirty**_.
|
||||
|
||||
Quando un blocco dirty viene **sovrascritto**, aggiorna le memorie che lo contengono.
|
||||
|
||||
```diff
|
||||
+ I write richiedono poco tempo, anche se ripetuti
|
||||
- Caricare dati dalla RAM è più lento
|
||||
- Necessita di memoria da dedicare al dirty bit
|
||||
- Devo fetchare obbligatoriamente i dati da sovrascrivere
|
||||
```
|
||||
|
||||
##### Miglioramenti alle policy
|
||||
|
||||
E' possibile utilizzare un **write buffer** invece che fare attendere il tempo di scrittura alla CPU. I dati saranno scritti successivamente, ma prima che questi vengano utilizzati.
|
||||
|
||||
```diff
|
||||
+ Il processore non ha bisogno di fermarsi per le write
|
||||
- Il buffer potrebbe riempirsi, neganode i vantaggi
|
||||
- Il buffer utilizza memoria che forse sarebbe stata più utile come cache
|
||||
```
|
||||
|
||||
### Fully associative
|
||||
|
||||
Ogni dato può essere messo in qualunque indirizzo della cache.
|
||||
|
||||
Richiede che l'indirizzo di origine venga salvato assieme al dato, e tanti comparatori.
|
||||
|
||||
### Set associative
|
||||
|
||||
Divido tutti i blocchi di cache in vie.
|
||||
|
||||
Ogni via può contenere `n` dati.
|
||||
|
||||
Un set è l'insieme dei dati che hanno lo stesso index ma sono in vie diverse.
|
||||
|
||||
Ogni dato può essere messo in qualunque indirizzo del set a cui appartiene.
|
||||
|
||||
Identifico il numero di set di appartenenza facendo `indirizzo % numerodiset`.
|
||||
|
||||
Una cache `1`-way Set Associative è una cache Direct Mapped, mentre una cache `numeroentries`-way Set Associative è una cache Fully Associative.
|
||||
|
||||
Quando non c'è spazio in un set, rimpiazzo un dato secondo la politica **Least Recently Used**, rimuovendo il dato usato meno recentemente. Posso usare anche la politica **Random**, se ho un'alta associatività.
|
||||
|
||||
#### Performance
|
||||
|
||||
Più una cache è associativa, più il miss rate sarà ridotto, ma l'associatività richiede un maggior numero di comparatori e potenzialmente più ritardi nella restituzione del dato.
|
||||
|
||||
Inoltre, la percentuale di miss non diminuisce linearmente con il numero di vie: dopo un certo numero di vie, i guadagni sono molto ridotti.
|
|
@ -0,0 +1,21 @@
|
|||
# Meltdown
|
||||
|
||||
Esempio circa comprensibile:
|
||||
|
||||
```c++
|
||||
int meltdown() {
|
||||
bool array[2] = {false, true};
|
||||
|
||||
if( qualcosa_che_restituisce_false() ) {
|
||||
//Questo viene predetto
|
||||
//Durante le predizioni non ci sono segfault!
|
||||
int protetto = *(indirizzo_bersaglio);
|
||||
//Se il primo bit di protetto è 1, verrà caricato "true" in cache, altrimenti, verrà caricato "false", ANCHE SE E' SOLO UNA PREDIZIONE!
|
||||
//Dopo essere stato caricato, NON VIENE RIMOSSO, anche se la predizione è sbagliata.
|
||||
int caricato = array[protetto & 1];
|
||||
}
|
||||
|
||||
if( in_cache(true) ) cout << "Il primo bit del bersaglio è 1.\n";
|
||||
else cout << "Il primo bit del bersaglio è 0.\n";
|
||||
}
|
||||
```
|
Loading…
Reference in a new issue