File system: aspetti implementativi
Tutte le informazioni necessarie al file system per poter operare sono memorizzate sul disco di boot:
- MBR: Settore 0 del disco, contiene la tabella delle partizioni
- Boot lock: Primo settore della partizione attiva, contiene un programma che carica il sistema operativo
- Superblock: Contenente parametri di inizializzazione per il file system
- Free-blocks: Elenco blocchi liberi
- I-nodes: Insieme degli i-node
Tecniche di allocazione
Il file system deve preoccuparsi di tenere traccia dei contenuti dei diversi blocchi del disco, così come i blocchi liberi. Esistono diverse modalità di allocazione dei file:
- Contigua: Ogni file allocato avrà ogni blocco di memoria successivo al precedente, senza buchi intermediari
- Lista linkata: Ogni blocco di memoria di un file contiene un riferimento al blocco successivo in memoria che non è necessariamente adiacente allo stesso
- FAT: Dimensione massima dei file in funzione alla dimensione del blocco
- I-node: Tabella contenente gli indirizzi di riferimento ai blocchi salvati
Directory
Strutture dati usate dal FS per associare al nome simbolico di un file il suo indirizzo fisico. Per recuperare una directory è necessario sapere dove si trova la directory root (di solito posizione fissa).
Backup
- Backup fisico => Viene effettuata la copia bit a bit del dispositivo fisico, senza consapevolezza della sua struttura logica (copiati anche i blocchi liberi). Semplice e veloce.
- Backup logico => Consapevole della struttura fisica del disco, può limitarsi a copiare determinate directory
Ottimizzazioni
Quando si effettua un’operazione su disco i dati contenuti su uno o più blocchi sono portati in memoria. La buffer cache è un'area di transito della memoria centrale al fine di ottimizzare il trasferimento dei blocchi. La buffer cache è gestita tramite una tabella hash con liste linkate per la gestione di collisioni, ordinata secondo il criterio LRU (Least Recently Used).
Consistenza file system
L’unità disco è in grado di garantire l’atomicità di una write alla volta. Viene verificata periodicamente la consistenza delle strutture dati di riferimento: questa attività viene svolta da un programma che viene eseguito periodicamente in fase di boot, o dopo un guasto o dopo spegnimenti accidentali.
Architettura IA-32
L’architettura IA-32 fornisce una serie di funzionalità per la realizzazione di software di sistema, queste funzionalità variano in funzione delle modalità con cui opera il processore:
- Real Mode: Stessa modalità operativa e funzionalità presenti nell’Intel 8086 con l’aggiunta di alcune estensioni
- Protected Mode: Modalità nativa del processore che fornisce un insieme di funzionalità predefinite per la realizzazione di sistemi multiprogrammati, garantendo retrocompatibilità.
- Virtual 8086 Mode: Consente al processore di eseguire software predisposto per l’architettura 8086 in un ambiente multiprogrammato e protetto
- System Management Mode: Modalità che consente al processore di eseguire codice memorizzato in una zona riservata della memoria nota come SMRAM (System Management RAM). Codice eseguito ad alto livello di priorità
- IA-32e Mode: Introdotta per supportare le applicazioni sia a 32 che a 64 bit
Al momento dell'accensione
Il processore è messo automaticamente in real-address mode. Il valore del flag PE nel registro di controllo CR0 determina se sta operando in real o protected mode. Il processore entra in modalità SMM ogni volta che riceve un interrupt SMI, eseguito in una delle modalità real address, e successivamente rientrerà in modalità normale.
I segmenti e descrittori
Il processo di segmentazione implementa il meccanismo di isolamento, dove sono assegnate aree di memoria disgiunte ai diversi processi caricati. Ogni processo è spezzato in due o più segmenti:
- Codice o dati
- Stack
I processi sono gestiti dal processore attraverso segmento Task State Segment (TSS). Ulteriore segmento opzionale è costituito da opportune tabelle con l’elenco degli oggetti accessibili a un processo (Local descriptor table, LDT).
Ciascun segmento deve essere provvisto di una struttura dati che ne descriva le principali proprietà, chiamata descrittore. Un descrittore di segmento fornisce al processore le seguenti informazioni:
- Base Address: Numero 32 bit che definisce l’indirizzo di partenza del segmento
- Segment Limit: Numero a 20 bit che specifica la dimensione del segmento
- Diritti di accesso: Informazioni per il controllo degli accessi e sullo stato del segmento
I descrittori sono generati dal SO o da programmi di sistema. Sono previsti una serie di descrittori speciali, non legati ai segmenti, chiamati GATE (interrupt gate, call gate, trap e task gate) => Meccanismi forniti dall'hardware per controllare l’esecuzione di codice privilegiato da parte di applicazioni che operano in user space.
Principali tabelle
- GDT (Global Descriptor Table): Tabella contenente descrittori di segmenti accessibili a tutti i processi
- LDT (Local Descriptor Table): Tabella dei descrittori di segmenti visibili solo al processo a cui la LDT è associata
- IDT (Interrupt Descriptor Table): Tabella contenente i descrittori Gates di accesso alle procedure o ai task associati agli interrupt.
Per operare al meglio con le suddette tabelle la CPU IA-32 dispone dei registri che puntano alle rispettive tabelle.
Registri di sistema
Altri registri sono usati dal processore per controllare e gestire l’intero sistema, accessibili in parte dal sistema operativo con istruzioni privilegiate; i principali sono:
- EIP
- EFLAGS
- Control Register (CR0-2-3-4)
- Segment register
Registri general purpose
Supporto ai processi
Un task è definito come un’unità di lavoro che il processore può attivare, eseguire e sospendere (usato per eseguire un programma, una routine del SO, un gestore di interrupt). Un task composto da due componenti:
- Spazio di esecuzione: Formato da uno spazio d’esecuzione, codice, stack e uno o più segmenti dati
- Task State Segment (TSS): Segmento che contiene un’opportuna struttura dati con le seguenti informazioni:
- Stato dell’ambiente di esecuzione del task
- Selettori e gli stack pointers a tre segmenti stack, uno per ogni livello di privilegio
- Le informazioni necessarie per gestire la paginazione
Elementi di linguaggio assembly
Data size da ricordare
- Byte (b): 1 byte
- Word (w): 2 bytes
- Long (l): 4 bytes
Metodi di memorizzazione
- Little endian: I bit meno significativi sono memorizzati nella parte più bassa della memoria
- Big endian: I bit più significativi vengono memorizzati nella parte bassa della memoria
Formato delle istruzioni IA32:
- Con singolo operando: opcode scr
- Con due operandi: opcode scr, dest
Caricare e memorizzare dati
I dati possono essere memorizzati in:
- Registri => Porzioni speciali di memoria accessibili direttamente dal processore
- Variabili => Memorizzate in memoria
Il processore può solo manipolare i dati presenti nei registri. Istruzione per caricare e memorizzare:
mov scr, dest
Accesso ai dati
Il processore ha diversi modi per accedere ai dati:
- Accesso per registro: Semplicemente muove i contenuti dei registri.
- Esempio movl %edx, %ecx => Con questa istruzione copiamo il contenuto EDX in ECX
- Caricamento immediato: Modo veloce per caricare un contenuto in un registro.
- Esempio movl $12, %eax => Carichiamo il valore 12 nel registro EAX
- Indirizzamento diretto: Carichiamo o salviamo da una particolare zona di memoria.
- Esempio movl 2000, %ecx => Legge i primi 4 bytes contenuti nella locazione 2000 e carica il valore nel registro ECX
- Indirizzamento indiretto: Carica o memorizza relativamente ad un contenuto di un registro, utilizzato come indirizzo di riferimento.
- Esempio movl (%eax), %ecx => Prende il contenuto di EAX, legge i 4 bytes di memoria dall’indirizzo corrispondente e poi li carica nel registro ECX
È possibile sommare anche un offset al contenuto del registro per formare l’indirizzo:
Esempio movl 8(%eax), %ecx => Uguale al caso precedente, solo che al contenuto di EAX viene sommato 8 per formare l’indirizzo da cui prelevare i 4 bytes corrispondenti.
Istruzioni aritmetiche
- add{b,w,l} source, dest dest = source + dest
- sub{b,w,l} source, dest dest = dest – source
- inc{b,w,l} dest dest = dest + 1
- dec{b,w,l} dest dest = dest – 1
- cmp{b,w,l} source1, source2 source2 – source1
Moltiplicazione => mul
Divisione => div
Istruzioni logiche
- and{b,w,l} source, dest dest = source & dest
- or{b,w,l} source, dest dest = source | dest
- xor{b,w,l} source, dest dest = source ^ dest
- not{b,w,l} dest dest = ~dest
- sal{b,w,l} source, dest dest = dest << source
- sar{b,w,l} source, dest dest = dest >> source
Per controllare se c’è stato overflow possiamo usare due istruzioni:
cmpl $0, %eax
je end_loop
Tipi di jump
- je: Salta se i valori sono uguali
- jg: Salta se il secondo valore è più grande del primo
- jge: Salta se il secondo valore è più grande o uguale del primo
- jl: Salta se il secondo valore è più piccolo del primo
- jle: Salta se il secondo valore è più piccolo o uguale al primo
- jmp: Salta senza testare nessuna condizione
Lo stack
Molti processori hanno bisogno di uno stack. Lo stack è un’area di memoria organizzata a livelli (gestita come una lista LIFO). Il comando PUSH aggiunge un elemento mentre il comando POP rimuove un dato dallo stack. Il dato rimosso è sempre l’ultimo dato ad essere stato aggiunto.
Il registro ESP contiene l’indirizzo del dato che dovrebbe essere rimosso dallo stack, ossia la cosiddetta “cima”. Il dato può essere aggiunto solo con due words (non è indirizzabile un singolo byte).
PUSH => Inserisce un doppio word sullo stack sottraendo 4 dal contenuto di ESP
Pushl src indica quindi subl $4, %esp
Movl src, (%esp)
POP => Vengono letti i due word all’indirizzo di ESP e poi sommiamo 4 sempre a ESP
Popl scr indica quindi movl (%esp), dest
Addl $4, %esp
L’80x86 prevede due istruzioni che usano lo stack per eseguire chiamate a sottoprogrammi molto semplicemente e facilmente.
Call => Istruzione con salto incondizionato al sottoprogramma e mette l’indirizzo della prossima istruzione nello stack
Ret => Istruzione che toglie l’indirizzo dallo stack e salta a quell’indirizzo.
Sono istruzioni molto importanti per maneggiare lo stack in modo accurato, soprattutto per il passaggio di parametri relativi ai sottoprogrammi. Viene introdotto un registro EBP che tiene traccia in modo relativo il punto di inizio dei parametri salvati nello stack dal sottoprogramma.
Compilazione, linking e debugging
Per assemblare un programma è sufficiente eseguire il comando:
as name.s –o name.o
As è il comando con il quale lanciamo l’assemblatore, name.s è il file sorgente, -o name.o invece specifica il nome di file di output, scritto in linguaggio macchina. Quando si hanno più programmi da compilare e da convertire in un unico file, il linker svolgerà questo compito
Per linkare il file, il comando è:
ld name.o –o name
Il programma può essere eseguito nel seguente modo: ./nomeprogramma
Possiamo utilizzare anche un debugger, il quale ha lo scopo di rilevare eventuali errori: GDB. Per eseguirlo bisogna compilare il file con il seguente comando:
as --gstabs name.s –o name.o
E infine lanciare il comando:
gdb name
Bootstrap e bootloader di JOS
All’accensione di un sistema in memoria non è presente alcun programma; i calcolatori moderni utilizzano una ROM in cui è memorizzato il codice iniziale per avviare il caricamento del sistema operativo => operazione di boot loading. Ci sono due categorie di boot loader:
- Basati su BIOS (Basic Input Output System): Software memorizzato in una memoria permanente, attaccato alla scheda madre (i chip moderni sono EEPROM). Sono 4 i software che compongono essenzialmente il BIOS:
- Il BIOS 16 bit device drivers: presenta un set di istruzioni universali per accedere ai dispositivi standard montati sulla scheda madre, e per avviare il loro lavoro
- BIOS CMOS Setup Utility: È un programma che mostra un menù a video il quale permette all’utente di modificare e settare determinati parametri. Questa funzione non viene eseguita nel normale avvio.
- POST (Power On Self Test): Controlla livelli elettrici e poi passa il controllo al BootStrap Loader
- BIOS Boot Strap Loader: Carica il MBR dall’hard disk, individuando il SO. Il BIOS inoltre costruisce una tabella per gestire gli interrupt dei dispositivi in I/O
- Basati su EFI/UEFI: rappresenta un piccolo sistema operativo, scritto in C anziché assembly, con interfaccia utente molto più accessibile.
Il processo di avvio inizia quando il power supply è acceso: vengono settati tutti i livelli elettrici, e quando sono stabili e accettabili viene inviato un segnale di Power Good alla scheda madre, e ricevuto dal processore. Dopo avere ricevuto il segnale, il processore tramite i BUS inizializza tutto l’hardware del sistema.
La figura mostra lo stato dei flag e di altri registri successivamente al power-up. Il registro di controllo di stato CR0 è 60000010H. Il processore lavora in address mode.
Stato dei registri dopo il power up
Memory map
Prima istruzione 8088: La prima istruzione ad essere fetchata ed eseguita ad hardware resettato viene localizzata ad indirizzo fisico 0xFFFF0. Questo indirizzo è a 16 bit, anche se il bus indirizzi ne supporta 20. La EEPROM contiene il codice di inizializzazione del software che deve trovarsi in questo indirizzo. Il processore è inizializzato come segue => durante il reset hardware, il segment selector nel registro CS è caricato con 0xF000 e EIP con 0xFFF0. L’indirizzo è costruito usando questi due indirizzi; questo è l’indirizzo dove la prima istruzione del BIOS è memorizzata.
Boot loader
Dopo che il POST ha completato il suo utilizzo, e l’hardware settato, viene eseguito il secondo componente ossia BIOS Boot Strap Loader: viene ricercato all’interno dell’hard disk il MBR (Master Boot Record), ossia la parte bootabile del disco. L’MBR sono i primi 512 bytes di ogni unità di memorizzazione e dopo aver identificato un sistema operativo è possibile caricarlo in memoria. (Nota: ogni partizione può essere usata per contenere diversi sistemi operativi). Quando il BIOS ha trovato il MBR carica il settore all’indirizzo fisico 0x7C00 fino a 0x7DFF.
Boot loader (Qemu 8088)
I primi PC basati su 16 bit e tecnologia di processore Intel 8088 potevano indirizzare una memoria fisica di massimo 1Mb. Lo spazio fisico era compreso tra 0x00000 e 0xFFFFF.
- 640 Kb erano marchiati come “Bassa Memoria” ed erano dedicati alla RAM
- 384 Kb, dall’indirizzo 0xA0000 fino a 0xFFFFF, erano riservati all’utilizzo di hardware speciali come il video o la ROM. La più importante area era riservata al BIOS, che occupava 64Kb dall’indirizzo 0xF0000 fino a 0xFFFFF.
La memoria massima supportata di 1Mb viene sorpassata dall’introduzione del 80286 e 80386, i quali supportano 16Mb e 4Gb di indirizzi fisici, mentre l’architettura prevede la retro-compatibilità con la tecnologia già esistente.
Real mode
Quando il processore è in real mode, è simulato l’utilizzo di un Intel 8088, in cui vengono utilizzati 16 bit di indirizzamento (mentre il processore utilizza bus da 20 bit). Occorre effettuare una traduzione:
Indirizzo fisico = 16 * segmento + offset
ES: CS costruito nel seguente modo utilizzando IP:
CS = 0xF000 IP = 0xFFF0
Utilizzo formula => 16 * 0xF000 + 0xFFF0 = 0xF0000 + 0xFFF0 = 0xFFFF0
La prima istruzione ad essere eseguita sarà: [f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
Operazioni del boot loader
- Abilitare A20
- Cambiare modalità del processore a 32bit (la low memory non è sufficiente per caricare il SO). Entrando in segmentazione (bios => Boot loader => kernel), e in seguito il kernel carica la paginazione
- Leggere il kernel dall’hard disk
A20 Enable
In linea teorica segment:offset poteva indirizzare 21 bit di indirizzi fisici, ma nel 8088 si aveva la possibilità di indirizzare solamente 20, e perciò veniva scartato il primo bit più significativo, infatti: 0xffff0+0xffff = 0x10ffef ma per l'8088 l'indirizzo fisico è 0x0ffef. Occorre riattivare la riabilitazione al 21°bit => Riattivato utilizzando i registri di controllo della tastiera (il controller riceve il tipo di comando (64) e poi il dato (60) ). Se il secondo bit della porta di output del controller della tastiera è basso il 21° bit viene cancellato, altrimenti viene considerato.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.