Sei sulla pagina 1di 36

Scrivere un sistema operativo

di: Antonio Mazzeo pubblicato il: 24-04-2001 Introduzione: In questo corso cercheremo di creare un nostro piccolo sistema operativo, con il solo scopo di capire quanto possa essere difficile progettarne uno e allo stesso tempo renderlo efficiente.
Introduzione
La realizzazione di un sistema operativo un impresa ardua con diversi ostacoli posti sulla strada. Gi anni addietro mi ero avviato in questa avventura, ma la quantit irrisoria di informazioni di cui ero in possesso sull argomento, lo studio, ed altre motivazioni mi fecero abbandonare questo progetto. Oggi sono convinto che giunto il momento di tirare dal cassetto questo vecchio ed arduo progetto, ma per far s che questa impresa sia condotta a termine scriver diverse lezioni sull argomento, ed accettando in qualsiasi momento eventuali vostri suggerimenti e/o critiche, al fine di poter realizzare un sogno covato sin dall di 15 anni. et

Lo scenario attuale
Chiunque vorr intraprendere questa strada insieme con me deve avere conoscenze indispensabili per proseguire lungo questo cammino. I moderni sistemi operativi tendono ad avere kernel di dimensioni a dir poche assurde, che necessitano di svariate decine di megabyte di memoria ram per girare, e come se non bastasse di una buona fetta di spazio su disco fisso per un installazione decente. Tutti quanti hanno ben presente Microsoft Windows, e chi ha buon memoria, ricorder che all uscita di Windows 95 sulla scatola del prodotto vi era scritto Requisiti hardware minimi: cpu INTEL 80386DX 33Mhz, 4 Mb di memoria RAMe se non ricordo male circa 100Mb di spazio sul vostro disco fisso, e almeno un altra fetta per il file di swap. Beh, Microsoft ci aveva visto giusto, il suo sistema operativo poteva girare sui gloriosi 386 che avessero almeno 4Mb di ram, peccato che per farlo funzionare in modo decente di memoria ram come minimo ne serviva 16 MB e...la cpu? Beh..ho avuto modo di usarlo su un P100 in modo decente, su un 486dx2 mi ha dato l impressione che stava un po stretto. Con una cpu del 300% pi veloce di quella dichiarata e col 400% di RAM in confronto a ci che Microsoft andava urlando, il suo sistema operativo girava in modo apprezzabile. L introduzione delle cosiddette GUI (Graphics Interface User) ha semplificato la vita a chiunque a digiuno di computer, facilitandone l approccio e la possibilit di poter realizzare qualcosa di concreto sin dal 1 giorno d utilizzo, senza dover stare dietro a dei prompt tremendi che non tollerano una sola lettera sbagliata. Ho avuto modo di visionare il BeOS, forse uno dei sistemi operativi che meritino ancora di portare questo nome, gira in modo eccellente sul mio PC, chiede soltanto 40Mb di spazio che poi arriva a circa 100Mb installando i relativi compilatori, ma non mi pare di aver visto qualche altro sistema operativo moderno che si accontenti di cos poco spazio su disco fisso.

Orientamento di questo corso


Per poter proseguire in questa opera, necessario conoscere bene l architettura dove si andr a scrivere il sistema operativo. Nelle successive lezioni tratteremo lo sviluppo di un sistema operativo per piattaforme INTEL o compatibile, essendo le pi diffuse in mercato, pertanto chiunque fosse interessato a seguire il corso, dovr avere delle conoscenze di base del set di istruzioni di queste CPU, ovvero per dirla breve del linguaggio Assembler. Oltre al linguaggio Assembler sar utilizzato anche il linguaggio C. La scelta di questo linguaggio dettata da motivazioni abbastanza semplici, la disponibilit di compilatori ANSI C per quasi tutte le piattaforme e per quasi tutti i vari microprocessori esistenti in commercio, permette la scrittura di un codice facilmente portatile, qualora il sistema operativo dovesse essere convertito nel minor tempo possibile su altre piattaforme incompatibili. In questo modo cercheremo di realizzare un astrazione dall hardware, ricorrendo al linguaggio assembler soltanto nelle poche sezioni dove esso realmente indispensabile.

1 di 36

I principali compilatori che utilizzeremo per questo corso saranno il Turbo Assembler della Borland facilmente reperibile sulla rete [1], ed un compilatore ANSI C che generi codice per piattaforme x86. Al momento la scelta di quest ultimo compilatore ricaduta sul CM32, compilatore che ho trovato nel CD-ROM allegato a [2], ma anche scaricabile dall indirizzo ftp://ftp.nsk.su (ftp://ftp.nsk.su/). Oltre a questi due compilatori, indispensabile avere documentazione completa sul funzionamento dei vari componenti hardware di un personal computer, in quanto deve essere lo stesso sistema operativo a fornire un interfaccia comune al software, dando cos la possibilit a chi si appresta a sviluppare applicativi per il nostro sistema operativo di non doversi interfacciare con l hardware stesso per gestirlo, ma attraverso un set di specifiche API gestire periferiche a volte totalmente incompatibili tra di loro. Obiettivi indispensabili per il nostro sistema operativo saranno dati da una velocit d esecuzione, da una dimensione ridotta, da efficienza, e da un quantitativo irrisorio di memoria. Questa introduzione termina qui. Nella prossima lezione tratteremo due applicativi indispensabili per un sistema operativo, di dimensioni talmente ridotte, ma senza i quali nessun sistema operativo sarebbe in grado di avviarsi, il BOOT SECTOR e il MASTER BOOT RECORD (MBR).

Bibliografia Riferimenti

[1] Developing Your Own 32-Bit Operating System Richard A. Burgess Sams Publishing.

[2] http://playtools.cjb.net (http://playtools.cjb.net/) contiene nella sezione compilatori un link al Turbo Assembler 5 di Borland.

Lezione 1: Il Boot Sector e il Master Boot Record.


Introduzione
In questa lezione tratteremo di due piccoli software, delle dimensioni ridotte, ma la cui presenza essenziale affinch il sistema operativo venga caricato in modo corretto. La maggior parte di voi avr sicuramente sentito parlare di Boot Sector e Master Boot Record (da ora in poi identificato col termine di MBR), due termini che identificano due zone ben precise del disco fisso e di un floppy disk. Si tende ad identificare come boot sector il 1 settore di un floppy disk o il 1 settore di ogni partizione presente in un disco fisso, mentre si identifica col termine di MBR il 1 settore di un disco fisso. Perch questa differenza tra boot sector e MBR? Ci che differenzia questi due settori e il loro contenuto. Nel 1 troviamo un microprogramma avente lo scopo di caricare il sistema operativo, mentre nel 2 troviamo un altro microprogramma avente lo scopo di identificare la partizione attiva su un determinato disco e di caricare di conseguenza il relativo boot sector in memoria per caricare cos il sistema operativo. Tratteremo il contenuto e la codifica dei relativi microprogrammi nei paragrafi successivi, dopo aver trattato in modo veloce il funzionamento di una CPU in modalit reale

La modalit reale
E necessario innanzitutto dire che un microprocessore a 32bit che supporta il set di istruzioni x86 pu lavorare principalmente in due modalit differenti: la modalit reale e la modalit protetta. In questa lezione tratteremo esclusivamente la modalit reale, molto pi semplice della modalit protetta, priva di complicazioni derivanti dall impiego della memoria e soprattutto la modalit con la quale una cpu viene costretta a lavorare dal momento in cui riceve il segnale di #RESET o subito dopo l accensione. La modalit reale non altro che un modo per gestire la memoria presente sulla macchina. Una cpu a 32bit in modo reale risulta essere come una vecchia cpu a 16bit pi veloce e con un set di istruzioni pi ampio rispetto ai predecessori. Preciso comunque che il modo nativo di una CPU a 32bit la cosiddetta modalit protetta, ma dato il periodo in cui venne lanciato sul mercato il primo processore a 32bit (il 386, a met degli anni i progetti 80), di INTEL preferirono mantenere la compatibilit con l architettura precedente, onde garantire che tutto il software fino ad allora esistente sul commercio potesse girare in modo corretto anche sui nuovi processori. La modalit reale venne introdotta negli anni 70, con i processori a 16bit di INTEL, i quali avevano un limite di indirizzamento max della memoria di 1Mb, tant vero che lo stesso Bill Gates consider tale quantitativo di memoria pi che sufficiente per qualsiasi tipo di applicazione. In modalit reale ogni singolo byte viene indirizzato attraverso una coppia di puntatori, chiamati SEGMENTO ed OFFSET. Questa coppia di puntatori viene combinata dal processore in un particolare registro interno di 20bit, ottenendo di conseguenza l indirizzo lineare del byte da indirizzare in memoria. Per poter indirizzare la memoria vennero creati appositi registri:

2 di 36

CS Code Segment Questo registro, combinato insieme al registro IP (Istruction Pointer) fornisce l indirizzo della prossima istruzione che la cpu dovr eseguire. A differenza di tutti gli altri registri, questa coppia l unica che non pu essere modificata attraverso le normali istruzioni, ma viene modificata indirettamente dalle istruzioni di salto (condizionale e non), dalle istruzioni per chiamare routine e per terminarle (CALL, RET, RETF) e dalle istruzioni di interrupt. (INT, IRET). DS Data Segment Normalmente questo registro viene utilizzato in coppia col registro SI, per indicare qual il segmento dove sono memorizzati i dati del software in memoria. ES Extra Segment normalmente in coppia al registro DI, ed utilizzato per indirizzare dati in memoria. SS Stack Segment Questo registro, insieme al registro SP (Stack Pointer) fornisce l indirizzo di stack nella memoria. Lo stack viene normalmente utilizzato per salvare il contenuto di registri, ma col passare del tempo i compilatori incominciarono ad usare lo stack per il passaggio dei parametri ad eventuali routine richiamate nel codice. La modifica del contenuto dei registri SS ed SP deve essere fatta con particolare attenzione, essendovi in una cpu a 16bit un solo stack. Un esempio di codice vi mostrer il perch necessario porre attenzione alla modifica di questi due registri. Esempio: Contenuto di SS:SP 8000:8F00 Indirizzo assoluto -> 88F000 Interrupt attivi Istruzione da eseguire: MOV AX, 7000 MOV SS, AX - Subito dopo questa istruzione lo stack punta all indirizzo assoluto 78F000, dal contenuto
identificato!

non meglio

MOV SP, 9000 Ipotizziamo che nel momento in cui il processore si accinge ad eseguire l istruzione MOV SP, 9000 arriva al processore un segnale di interrupt proveniente dal TIMER, e dato che i segnali IRQ (Interrupt Request Hardware) hanno priorit assoluta la CPU trasferisca il controllo al relativo indirizzo, salvando tutti i registri nello stack, si potrebbe sovrascrivere dei dati, o nel peggiore dei casi del codice applicativo, bloccando di conseguenza la macchina. Pertanto, il codice corretto : CLI > Disabilita gli interrupt hardware MOV AX, 7000 MOV SS, AX MOV SP, 9000 STI > Riabilita gli interrupt hardware Questo ci offre un minimo di sicurezza, garantendoci che per un breve istante il processore ignori la maggior parte dei segnali di IRQ, ma purtroppo ci non avviene per i segnali NMI (Non Maskerable Interrupt), aventi una priorit molto pi alta dei segnali di IRQ e quindi non vengono ignorati affatto dalla CPU anche se il flag Interrupt disabilitato tramite l istruzione CLI. Avremo modo comunque di trattare degli NMI successivamente. Oltre a queste coppie di registri, in una cpu x86 sono presenti altri registri, che per non vengono trattati in quanto il lettore che si appresti a seguire questo corso deve avere una conoscenza minima del linguaggio assembler. La transizione di un indirizzo espresso sottoforma di SEGMENTO:OFFSET ad indirizzo LINEARE pu essere riassunta brevemente come segue: Ipotizziamo di avere la coppia DS:SI con i seguenti valori, DS = 10h e SI = 1000h Si divide il contenuto di SI per 10h Il risultato sar addizionato al registro segmento, e il resto va a costituire l offset della pagina da indirizzare. Risultato della divisione: 1000h / 10h = 100h Resto della divisione: 1000h % 10h = 0h Il contenuto del registro segmento viene fatto scorrere a sinistra di 4bit, pertanto il nostro 10h diventa di conseguenza 100h, a cui viene sommato il risultato della divisione, ovvero 100h. Abbiamo pertanto come indirizzo di segmento 200h, mentre come indirizzo di offset abbiamo 0h.

3 di 36

Combinando 200h con 0h otteniamo 2000h, che non altro che l indirizzo assoluto del nostro byte in memoria. La nota pi interessante di questo particolare indirizzamento di memoria data dal fatto che un singolo byte pu essere indirizzato a volte anche da (2^16)-16 combinazioni di valori di SEGMENTO:OFFSET. Spiegata semplicemente questa modalit di memoria andiamo ad osservare come si presenta il 1 megabyte di memoria dopo l accensione. La coppia di registri CS:IP punta all indirizzo F000:FFF0, che anche l entry point del BIOS. Quest indirizzo di conseguenza contiene un istruzione di JUMP al codice effettivo del BIOS. Il BIOS (Basic Input Output System) non altro che un insieme di routine software che fornisce supporto per gestire la tastiera, i supporti di massa (siano essi floppy disk che hard disk), porte seriali, porte parallele, tastiera, timer, video e numerosi altri servizi. Oltre a fornire questa interfaccia, compito del BIOS quello di verificare il corretto funzionamento dei dispositivi hardware essenziali in un computer, e di segnalare eventuali errori all utente. E necessario conoscere inoltre dove tali informazioni vengono registrate in memoria, dato che ci permettono di risparmiare tempo e di conoscere informazioni quali l ammontare della memoria RAM presente sul proprio pc, il tipo di scheda video e il tipo di monitor, eventuali porte COM, porte LPT, e molto altro ancora. Una volta che il diagnostico di sistema termina l esecuzione viene invocato l interrupt software 19h, meglio conosciuto come interrupt di boot-strap. Compito di questo interrupt determinare quali sono i dispositivi di boot da controllare, e di trovare il settore di avvio o di caricare il codice dell MBR. Qualora non venga trovato nessun floppy disk nei disk driver presenti sulla macchina, e non sia presente nessun disco fisso sulla macchina, il controllo viene trasferito ad un applicativo ROM, oramai scomparso da circa 15 anni dai PC e sostituito da un semplice messaggio di testo che ci avverte dell assenza di un qualsiasi sistema da cui caricare un eventuale sistema operativo.

Il boot sector
Iniziamo col dire che sia il Boot Sector che il Master Boot Record hanno una dimensione standard di 512 byte, convenzionalmente la dimensione di un settore. Il boot sector si viene a trovare, come ho gi detto prima, nella posizione avente coordinate cilindro 0, faccia 0, settore 1. Questo vale per tutti i dischi, ad eccezione del fatto che nei dischi fissi esso prende il nome di MBR, di cui tratteremo al capitolo successivo, ma in un disco fisso noi possiamo trovare fino a 16 settori identificati col nome di boot sector. Come lo stesso nome dice, il boot sector un settore di avvio, ovvero ha il compito di caricare in memoria il sistema operativo, o nel caso quest ultimo fosse molto grande, le procedure di inizializzazione del sistema operativo, che di solito comprendono il gestore di memoria, un file system, un interprete di comandi e delle semplici routine per gestire i principali componenti hardware. Il boot sector che andremo ad analizzare in questo corso, fa parte del progetto Prometeo, ovvero il sistema operativo su cui sto lavorando al momento. Incominciamo subito col dire che quando un boot sector viene caricato, esso viene caricato all offset 0000:7C00 della memoria, ovvero al byte assoluto 7C00. Il contenuto dei registri della CPU sar di tipo indefinito, ad eccezione dei registri di segmento, che hanno tutti lo stesso valore, 0. Innanzitutto, come potrete vedere troviamo subito un istruzione di SHORT JUMP, dato che in un boot sector vi definita anche una struttura dati che racchiude le propriet del disco che si sta leggendo. Questa struttura dati non necessaria, ma per mantenere una compatibilit con altri sistemi operativi, oramai risulta presente in qualsiasi floppy troviate in circolazione, affinch un qualsiasi sistema operativo non riporti un errore quando va a leggere il floppy disk. Analizziamo tale struttura:
bsOEM db 'PROMETEO' ; Qui viene inserito il nome del sistema operativo, di una lunghezza max di 8 byte. bsSECTSIZE dw 0200h ; Questa word riporta la dimensione dei settori presenti sul dispositivo, settato a 512 per default. bsCLUSTSIZE db 1 ; Questo valore, che qui viene riportato a 1 indica quanti settori del dispositivo fanno parte di un ; particolare cluster. Il cluster l unit minima di allocazione in un file system quali FAT12, FAT16, FAT32 ; ed NTFS. In ext2 (il file system di Linux) questa unit prende il nome di blocco. bsRESSECT dw 0 ; Indica il numero di settori riservati prima del file system, di solito settati a 0 ma con un valore maggiore ; nel remoto caso in cui un boot sector sia molto pi grande del solito. bsFACTCNT db 1 ; Indica il numero di copie della tabella d allocazione dei file presenti sul disco o nella partizione. ; Al momento, non ancora stato stabilito quale sia il file system utilizzato normalmente da Prometeo, ; ma per compatibilit mettiamo 1.

4 di 36

bsROOTSIZE dw ? bsTOTALSECT dw ? bsMEDIA con db ?

; Indica la dimensione della ROOT del disco, in settori. Tale voce fa parte dei file system di Microsoft. ; Indica il numero totale di settori totali presenti sul disco. Qualora il disco sia superiore a 32mb questo ; valore sar 0. ; Indica il tipo di supporto, e il tipo di FILE SYSTEM adottato. Ogni file system viene contrassegnato ; un valore di identificazione. ; Indica la dimensione della FAT. ; Indica il numero di settori per traccia ; Indica il numero di facce ; Indica il numero di settori nascosti. ; Indica il numero di settori qualora l unit sia superiore a 32mb. ; Questo numero, identifica l unit dalla quale stato caricato il boot sector. Nei floppy disk posto a ; mentre negli hard-disk il 7bit (equivalente all dato che si parte a contare da 0!) viene settato a 1. 8, ; Non usato ; Indica se il dispositivo avviabile o meno, cio se contiene un sistema operativo da caricare o no! ; Qui viene memorizzato il codice seriale a 32bit del disco, calcolato in base al giorno e all della ora ; formattazione del dispositivo. ; Ora in un sistema operativo tradizionale quale MS-DOS qua andrebbe una stringa di 11 byte

bsFATSIZE dw ? bsTRACKSECT dw ? bsHEADCNT dw ? bsHIDENSECT dd ? bsHUGESECT dd ? bsDRIVENUM db ? 0. bsRESERV db ? bsBOOTSIGN db ? bsVOLID dd ?

contenente ; il nome del volume, ma al momento in Prometeo stato sostituito con strutture pi importanti. bsCYLTOTAL dw ? ; Indica il numero di tracce bsHEADTOTAL db ? ; Indica il numero di facce bsSECTTOTAL db ? ; Indica il numero di settori per traccia bsRESERVED db 7 dup (?) ; 7 byte riservati bsFILESTYPE db 8 dup (?) ; La stringa contenente il nome del file system. bsCYLSTART dw ? ; Il primo cilindro da dove caricare il codice di loader - Prometeo bsHEADSTART db ? ; Prima faccia dove caricare il codice di loader - Prometeo bsSECTSTART db ? ; Primo settore dove caricare il codice di loader - Prometeo bsCODESIZE dw ? ; Numero di settori da caricare in memoria Prometeo Max 32Mb di codice!

_start: db 0eah dw offset _main, 07c0h

Questa prima istruzione, subito la struttura dati ci permette di riallineare il registro CS, in modo da avere il codice di Prometeo iniziante a 0 e non a 7C00h. Semplicemente, come spiegato in alto, un byte pu essere puntato con diversi valori, e CS assumer il valore di 7C0h
_main: lea si, Prometeo call PrintString

Stampa una stringa di benvenuto a video. Il codice di PrintString non utilizza altro che il servizio 0E dell interrupt 10h. Carichiamo in CS:SI la stringa da stampare e la stampiamo. Per maggiori informazioni su tale servizio vi rimandiamo all Interrupt List di Ralph Brown.
cmp byte ptr cs:[bsBOOTSIGN], 00h ; The disk is bootable ? je not_bootableos jmp bootableos

Controlliamo se il supporto avviabile o meno, se non avviabile andiamo in un frammento di codice che ci dice che non c nessun sistema operativo sul floppy, e che la procedura di boot sar riavviata, con un warm-boot attraverso una chiamata all INT19.
mov ax, 1000h ; Segment to load Prometeo CODE startup mov es, ax xor bx, bx

Al momento Prometeo viene caricato all indirizzo 10000, mentre nei giorni avvenire probabilmente sar spostato all indirizzo 600 della memoria. Una procedura che qui non riportiamo ha il compito di caricare il numero di settori, attraverso la funzione 02 dell interrupt 13, e una volta che tale codice viene caricato, viene trasferito il codice ad esso inserendo nello stack l indirizzo ed eseguendo un istruzione RETF. Nel caso si verificasse qualche errore durante il caricamento, questa procedura resetta la macchina.
db 0eah ; COLD-RESET dw 0fff0h ; dw 0f000h ; ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

5 di 36

PrintString: mov ah, 0eh mov bx, 0007h LoadCharacter: mov al, byte ptr cs:[si] cmp al, 00h je _endPrintString int 10h inc si jmp LoadCharacter _endPrintString: ret

Ecco qui in alto il codice che stampa a video i relativi messaggi. Il codice del boot-sector di Prometeo ha una dimensione piuttosto ridotta al momento, tanto vero che bisogna aggiungere 126 byte per arrivare a scrivere in modo corretto la WORD contenente la SIGNATURE, che non altro che una coppia di byte che indicano se il settore o meno avviabile. Tale SIGNATURE al momento viene totalmente ignorata dai BIOS per quanto riguarda un floppy disk, infatti potrete controllare voi stessi con un vostro boot-sector con e senza SIGNATURE. La SIGNATURE deve trovarsi negli ultimi 2 byte del settore, e deve essere AA55. Comunque un bene inserirla, in quanto se il BIOS la ignora per i boot sector dei floppy, cos non avviene per quanto riguarda il MASTER BOOT RECORD e di conseguenza lo stesso MBR ne controlla la presenza nei boot sector delle partizioni.

Il Master Boot Record


Come ho gi detto prima, l MBR il primo settore assoluto di un disco fisso. Ha lo scopo di memorizzare un apposita struttura della lunghezza di 64 byte composta da 4 strutture dati identiche, con l unica differenza che hanno valori differenti. Tale struttura dati la seguente: Partition STRUC Boot db ? BegHead db ? BegSeCyl dw ?

SysCode db ? EndHead db ? EndSeCyl dw ? StarSec dd ? SizeSec dd ? Partition ENDS

Indica se questa la partizione da dove caricare il boot-sector o no! Indica la prima faccia della partizione. Indica la combinazione settore/cilindro che costituisce il contenuto del registro CX per il servizio 02 dell interrupt 13. I 6 bit meno significativi identificano il 1. settore, mentre gli altri 10 bit identificano la prima traccia della partizione. Codice del file system presente in questa partizione. Ultima faccia della partizione. Ultimo settore e ultimo cilindro. Numero assoluto del primo settore. Numero assoluto di settori presenti nella partizione.

Come al solito l MBR viene caricato allo stesso offset del BOOT-SECTOR, quindi a carico del programmatore far si che l MBR si sposti in un altra locazione di memoria affinch possa caricare nella locazione in cui si trova lui al momento il codice del boot sector.
cli xor ax, ax mov ss, ax mov sp, 7c00h mov si, sp push ax pop ds sti

Queste prime righe, modificano il valore dei registri dello stack, come potete vedere durante tale operazione gli interrupt vengono disabilitati.
mov di, 0600h mov es, ax mov cx, 100h repne movsw

6 di 36

Contiene l offset dove sar spostato il codice. Indirizzo assoluto 600 della memoria. Trasferisce 512 byte e poi un bel FAR JUMP ed eccoci al codice vero e proprio.
db 0EAh dw offset continue dw 0060h

Questo semplice ciclo controlla in memoria quale sia la partizione che contiene il sistema operativo. Se tale partizione non presente allora stampa a video un messaggio di errore, altrimenti provvede al caricamento della stessa.
continue: lea si, partition_table mov cx, 4 @1: cmp byte ptr ds:[si], 80h je find_ptable add si, ax ; AX contiene la lunghezza della struttura cio 16 loop @1 find_ptable: mov bx, 7c00h mov ax, 0201h mov dx, word ptr ds:[si] mov cx, word ptr ds:[si+SECYL] int 13h jc load_panic mov ax, 07c0h mov es, ax lea di, signature mov ax, word ptr es:[di] cmp ax, word ptr cs:[di] je valid_boot_sector

Una volta identificata la partizione corretta il sistema provvede a trasferire il controllo all offset assoluto 7C00, dopo essersi accertato che la SIGNATURE sia corretta .
valid_boot_sector: xor ax, ax mov es, ax mov ax, 07c00h push es push ax retf

Il solito salto attraverso l dello stack. All uso indirizzo 1BE della partizione troviamo le 4 strutture contenenti le informazioni circa le partizioni, e infine la SIGNATURE.
Table1 Table2 Table3 Table4 Partition <0, 0, 0, 0, Partition <0, 0, 0, 0, Partition <0, 0, 0, 0, Partition <0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0> 0, 0> 0, 0> 0, 0> ; Partition 1 ; Partition 2 ; Partition 3 ; Partition 4

signature: dw 0AA55h

Nel caso in cui nel campo SYSCODE della struttura dati della partizione si dovesse trovare il valore 05, ovvero Partizione Estesa, noi ritroveremmo nel relativo boot sector un settore simile all contenenti le mbr solite informazioni su altre 4 partizioni. Purtroppo, i livelli di annidamento che esistono oggi non ci permettono di avere in un segmento di una partizione estesa ulteriori partizioni estese, quindi il limite massimo di partizione su un disco fisso di 4x4, ovvero 16 partizioni. C comunque da specificare che i software quali LILO BOOT LOADER, OS/2 BOOT MANAGER, e altri software di questo genere, che permettono il caricamento di pi sistemi operativi, vengono inseriti nei settori che fanno parte della traccia 1 faccia 0, che di norma viene riservata per il MBR, di lunghezza max di 1 settore, ma per la struttura dei moderni dischi fissi, a volte pu arrivare anche una lunghezza di 32Kb, spazio pi che sufficiente per un software che ha il compito di far scegliere all utente la partizione da cui effettuare il boot.

Conclusioni
7 di 36

Spero che questa introduzione del boot sector e del master boot record sia stata pi che sufficiente. Qualora avreste dubbi in merito, vi ricordo il mio indirizzo email: blackcod@tin.it (mailto:blackcod@tin.it). Vi anticipo sin da adesso che la prossima lezione sar tendenzialmente indirizzata alla modalit protetta, in quanto la conoscenza di questa modalit della cpu alla base del memory manager che vi permetter di indirizzare sulla vostra macchina sino a 4 Gb di memoria fisica e la stratosferica cifra di 64 TB (Tera byte, equivalente a 1024 GB!) a patto che abbiate un dispositivo di massa che abbia questa capienza.

Lezione 2: Affronteremo in questa lezione la modalit protetta delle CPU a 32bit, indispensabile in un sistema operativo moderno per gestire la memoria.
Introduzione
In questa lezione affronteremo l argomento modalit protetta, cercando di spiegarne il funzionamento e l utilit di questa modalit, introdotta la prima volta nel 1986 con il rilascio della cpu 80386 da parte di INTEL. Nel libro [1] troviamo subito scritto quanto segue: 80386 un potente microprocessore a 32bit ottimizzato L per i sistemi operativi multitasking e progettato per applicazioni che necessitano di prestazioni molto elevate. I registri ed i cammini dei dati a 32bit sono in grado di gestire quantit che rappresentano indirizzo o dati a 32bit. Il processore pu indirizzare fino a 4 gigabyte di memoria fisica e 64 terabyte (2^46 byte) di memoria virtuale. I mezzi di gestione della memoria onchip comprendono i registri per la conversione dell indirizzo, un avanzato hardware multitasking, un meccanismo di protezione e il sistema di paginazione della memoria virtuale. Speciali registri di debugging forniscono breakpoint di dati e di codice perfino nel software basato su ROMQueste poche righe di testo ci fanno subito capire quali siano le reali potenzialit . di questa cpu, e cosa permette di fare ad un sistema operativo. Iniziamo subito con l analizzare le novit introdotte nelle cpu a 32bit.

Le novit delle cpu a 32bit.


Per mantenere una compatibilit con l intero parco software gi disponibile in passato INTEL decide di mantenere l intera compatibilit col set di istruzioni della famiglia x86, aggiungendo per al nuovo processore e ai processori successivi delle nuove istruzioni, cosa che oggi con la velocit con la quale vengono sfornati dai diversi produttori le cpu rende la vita un po difficile. I registri I registri del nuovo processore vengono classificati in diverse categorie, secondo la loro utilit. Registri generali EAX, EBX, ECX, EDX, EBP, ESP, ESI, EDI. Questi registri sono la versione a 32bit dei gi noti registri AX, BX, CX, DX, BP, SP, SI, DI. Infatti possibile accedere ai primi 16bit del registro semplicemente usando il vecchio nome e cos via anche per le due diverse parti a 8bit. Registri segmento Sono i soliti registri CS, DS, ES, SS, ma vengono affiancati questa volta da due nuovi registri: FS e GS. In modalit reale essi contengono il segmento, che combinato assieme all offset (come gi visto nella lezione 1) ci permettono di ottenere l indirizzo lineare del byte interessato entro il 1 megabyte di memoria. Nella modalit protetta essi contengono invece il valore del selettore, che affronteremo pi avanti. Nemmeno l dei registri segmento cambiato, infatti SS viene combinato insieme al registro ESP uso per accedere allo stack, e CS insieme al registro EIP per indicare la prossima istruzione in memoria da eseguire. Registro dei flags Il registro dei flags controlla certe operazioni ed indica lo stato della CPU nel momento in cui viene letto tale valore. Discuteremo di questo registro in un paragrafo successivo. Registri di gestione della memoria Questi registri identificano strutture di dati che controllano la gestione segmentata della memoria. Essi sono GDTR Global Descriptor Table Register LDTR Local Descriptor Table Register IDTR Interrupt Descriptor Table Register TR Task Register 80386 sono CR0, CR2 e CR3. Il registro CR0 contiene i Registri di controllo I registri di controllo dell flags di controllo del sistema, che controllano o indicano le condizioni applicabili al sistema nel suo complesso e non ad un singolo task. Il registro CR2 viene impiegato per gestire gli errori di pagina qualora vengano utilizzate tabelle di pagine per convertire gli indirizzi. Il registro CR3 consente al processore di individuare l elenco di tabelle di pagine per il task corrente. Registri di debugging Questi registri forniscono un supporto al programmatore per l esecuzione del debugging del proprio applicativo. Tratteremo questi registri e il loro impiego al momento opportuno. Registri di test Questi registri non fanno parte dell architettura standard del sistema.

8 di 36

Le nuove istruzioni Ovviamente, l introduzione di queste novit a livello di registro e di memoria sarebbero state inutili se non fosse stato ampliato il set di istruzioni, in modo da permettere al programmatore di sfruttare appieno le novit del processore. Le istruzioni della cpu vengono classificate secondo quanto segue: 1 Istruzioni di trasferimento dei dati 1.1 Istruzioni generali di trasferimento dei dati 1.2 Istruzioni di manipolazione dello stack 1.3 Istruzioni di conversione del tipo 2 Istruzioni di aritmetica binaria 2.1 Istruzioni di addizione e sottrazione 2.2 Istruzioni di confronto e d inversione di segno 2.3 Istruzioni di moltiplicazione 2.4 Istruzioni di divisione 3 Istruzione di aritmetica decimale 3.1 Istruzioni di correzione BCD impaccato 3.2 Istruzioni di correzione BCD non impaccato 4 Istruzioni logiche 4.1 Istruzioni di operazione booleane 4.2 Istruzioni di test e modifica bit 4.3 Istruzioni di scansione di bit 4.4 Istruzioni di scorrimento e rotazione 4.5 Istruzioni di assegnazione condizionata di byte 4.6 Istruzioni di test 5 Istruzioni di trasferimento del controllo 5.1 Istruzioni di trasferimento incondizionato 5.2 Istruzioni di trasferimento condizionato 5.3 Interruzioni generate dal software 6 Istruzioni di conversione di stringa e di carattere 6.1 Prefissi di ripetizione 6.2 Indicizzazione e controllo dei flags di direzione 6.3 Istruzioni di stringa 7 Istruzioni per linguaggi con struttura a blocchi 8 Istruzioni di controllo dei flags 8.1 Istruzioni di controllo dei flags di riporto e di direzione 8.2 Istruzioni di trasferimento di flags 9 Istruzioni d interfaccia del coprocessore 10 Istruzioni relative ai registri segmento 10.1 Istruzioni di trasferimento per registri segmento 10.2 Istruzioni di trasferimento del controllo di tipo FAR 10.3 Istruzioni di puntamento ai dati 11 Istruzioni miscelanee 11.1 Istruzioni di calcolo dell indirizzo 11.2 Istruzioni di nessuna operazione 11.3 Istruzioni di conversione 12 Istruzioni di sistema Essendo lo scopo di questo corso scrivere un sistema operativo e non imparare il linguaggio assembler, sar trattato l delle istruzioni qualora esse siano fondamentali per il sistema operativo. I primi 11 blocchi uso di istruzione vengono utilizzati da colui che scrive un applicativo, mentre il 12 blocco di istruzioni invece riservato al programmatore di sistema. Infatti, uno dei punti principali di questa nuova architettura la possibilit di definire quali siano i privilegi di cui in possesso un applicazione, e di conseguenza dare la possibilit al sistema operativo di essere informato qualora un applicazione violi tali privilegi permettendogli di terminare l applicazione senza che quest ultima possa causare danni alle altre applicazioni che sono in esecuzione.

Gestione della memoria in modalit protetta


Fatta questa introduzione sul processore a 32bit, passiamo a trattare la gestione della memoria in modalit protetta. Un vantaggio della modalit protetta quella di dare ad ogni applicativo sino a 4Gb di memoria RAM, anche se in realt sulla macchina sono presenti soltanto 4Mb di memoria RAM. Come gi detto in precedenza, i registri di segmento in modalit protetta indicano un descrittore, ovvero una determinata struttura caricata in una apposita tabella. Incominciamo ad analizzare il contenuto di un descrittore:

9 di 36

Un descrittore una struttura di 64bit contenente le seguenti informazioni: Base 31-24 Base 15-0 Tabella 1 G D 0 Avl LSEG1619 P DPL A0 A1 A2 A3 A4 Base 16-23 Limite 0-15

Il bit 0, ovvero il primo bit di questa struttura contenuto in basso a destra, e fa parte del limite segmento (Limite 0-15). Questo dovuto al byte order di INTEL usato nei suoi processori. LIMITE: I due campi Limite 0-15 e Limite 16-19 (LSEG1619 nella tabella) costituiscono il limite segmento. Il limite indica la dimensione del segmento. Concatenando i due campi contenuti nel descrittore otteniamo un indirizzo a 20bit. Questo indirizzo pu assumere un valore da 0 a (2^20 1). BIT DI GRANULARITA : Nel descrittore questo bit indicato con la lettera G ed indica in che modo deve essere interpretato il campo limite. Qualora questo bit non sia settato, il contenuto di LIMITE viene interpretato come unit in byte, quindi si ha un limite massimo di 1.024.768 1, mentre se il bit G settato il limite sar considerato come il numero di unit da 4kb, ed in tal caso avremo un max di 1.024.768 x 4096 che uguale a 2^32 byte, 1 ovvero 4Gb di memoria virtuale. BASE: Questo valore identifica la locazione del segmento entro lo spazio d indirizzi lineare da 4Gb. In parole semplici, identifica l indirizzo assoluto in memoria dove si trova il 1 byte del segmento. DPL: Questi 2 bit indicano il livello di privilegio del descrittore e di conseguenza i relativi permessi concessi al codice o ai dati in esso contenuto. L accesso a un segmento deve avvenire qualora il CPL sia uguale o minore del DPL. Bit di presenza del segmento: Questo bit indicato con la lettera P nella tabella 1 identifica un descrittore valido o meno. Qualora un descrittore con il bit P settato a 0 venga caricato in un registro SEGMENTO viene generata un eccezione da parte della CPU. Bit di accesso avvenuto: Questo bit indicato nella tabella 1 con la sigla A4. Questo bit settato qualora il descrittore sia caricato in uno dei registri SEGMENTO, e pu essere utile ad un sistema operativo per verificare quali siano i descrittori pi usati, e quindi da immagazzinare in memoria oppure da tenere salvati su disco. Questo bit ha validit qualora il bit A0 sia settato (indicando di conseguenza un descrittore usato per codice o dati applicativi), mentre se non settato esso indica un descrittore speciale. Default bit: Questo bit identificato con la lettera D nella tabella 1 indica se le istruzioni contenute nel descrittore hanno una dimensione di default da 16 bit (flags D=0) oppure hanno una dimensione per default da 32 bit (flags D=1). Bit disponibile: Identificato con la sigla Avl questo bit a disposizione del sistemista. Bit CODICE/DATI: Identificato nella tabella 1 con la sigla A1, qualora il bit A0 sia settato, indica un segmento codice (A1 = 1) o un segmento dati (A1 = 0). Bit conformante: Se il bit A0 uguale a 1: Se il bit A1 settato: A2 = 1 Segmento Conformante A2 = 0 Segmento non conformante Se il bit A1 non settato: A2 = 1 - Espansione verso il basso

10 di 36

Un segmento conformante un segmento nel quale, se viene trasferito il controllo da un altro segmento con maggiori privilegi, fa si che il CPL non venga modificato, e quindi il codice in esso contenuto ha privilegi maggiori. Un segmento con espansione verso il basso un qualsiasi offset che non sia compreso tra 0 e LIMITE genera una eccezione, altrimenti gli indirizzi fuori dal limite sono considerati validi, e modificando nel descrittore il limite si pu modificare la quantit di memoria da allocare dinamicamente nel descrittore. Bit A3: Se il bit A0 uguale a 1: Se il bit A1 settato: A3 = 1 Il segmento leggibile Se il bit A1 non settato: A3 = 1 Il segmento scrivibile. Una volta descritto il contenuto di un descrittore, passiamo ad analizzare il contenuto della tabella dei descrittori globali e locali (GDT ed LDT). Queste due tabelle vengono gestite tramite le istruzioni LGDT, SGDT, LLDT, SLDT. Le istruzioni LGDT ed SGDT permettono di accedere alla tabella GDT. LGDT carica nel registro LGDTR l indirizzo lineare della memoria dove risiede l elemento 0 della GDT. Per l esecuzione di questa istruzione indispensabile avere un CPL uguale a 0. L istruzione SGDT invece permette di ottenere l indirizzo lineare del primo elemento della GDT. L indirizzo lineare caricato nel registro GDTR rappresenta l offset reale del primo elemento della GDT. Le istruzioni LLDT ed SLDT forniscono l accesso alla LDT ed al registro LDTR. Il primo elemento della GDT (GDT[0]) non viene usato dal processore. Un registro segmento in modalit protetta contiene il numero del descrittore caricato, il livello di privilegio del descrittore e un bit indicante se tale descrittore fa riferimento alla GDT o alla LDT. Indice nella GDT o nella LDT (dal bit 3 al bit 15) Tabella 3 - Formato del selettore Il processore, quando deve accedere a un byte situato in memoria compie i seguenti passi. Ricorreggere la sezione dove viene trattato il selettore, e scrivere quanto segue: Copia in un registro interno il valore del descrittore; Verifica quale tabella deve controllare tramite il bit 2 (0 GDT, 1 LDT); Verifica se il descrittore scelto sia un descrittore valido attraverso il bit P del descrittore stesso, se il bit P posto a 0 genera una eccezione, altrimenti continua; Verifica che vi siano i relativi privilegi per accedere a questo descrittore, confrontando il CPL con il DPL del descrittore scelto, altrimenti genera un eccezione; Legge dal descrittore il bit G e il LIMITE, al fine di verificare che l offset richiesto rientri dentro il valore max del LIMITE (correttamente calcolato nel caso in cui vi sia attiva la granularit); Se l offset supera il LIMITE viene generata un eccezione; Aggiunge all indirizzo BASE l offset, e nel caso in cui la paginazione sia disattivata richiede il byte BASE+OFFSET alla memoria RAM. GDT(0) 1 bit LDT(1) DPL 2 bit

La tabella IDT
Il registro IDTR contiene l indirizzo della tabella vettori di interrupt. In modalit reale questa tabella era costituita da vettori di 4 byte per un numero massimo di 256 elementi, e si trovava all indirizzo 0 della memoria. In modalit protetta, ogni task pu avere una sua tabella vettori di interrupt. Gli elementi possono essere fino a un massimo di 256 elementi, ed ognuno di essi rappresentato da una struttura di 64bit cos costituita: Offset 16-31 Selettore Tabella 2 P DPL Offset 0-15 0 1 1 1 T 0 RES

11 di 36

SELETTORE: Indica il descrittore dove si trova il codice da eseguire. OFFSET: Combinando insieme i 2 elementi offset otteniamo l indirizzo del codice da eseguire dentro il selettore prescelto. Bit P: Indica se l interrupt presente o meno. DPL: Indica il livello di privilegio dell interrupt. T: Indica se si tratta di una trappola (T=1) oppure di una interruzione (T=0). L accesso alla IDT avviene tramite le istruzioni LIDTR e SIDTR. Anch esso contiene un indirizzo lineare in memoria. Quando si verifica un errore, la cpu in modo protetto lo comunica al sistema operativo generando una eccezione. Le eccezioni sono delle interruzioni che mandano in esecuzione i relativi interrupt. Pertanto, i primi 17 interrupt sono riservati al codice di sistema operativo: IDT[00] Errore di divisione IDT[01] Eccezione di debugging IDT[02] Non usato IDT[03] Breakpoint / Debugging IDT[04] Overflow IDT[05] Verifica dei confini IDT[06] Codice operativo non valido (istruzione sconosciuta!) IDT[07] Coprocessore non disponibile (e di conseguenza, qui bisognerebbe agganciare un emulatore) IDT[08] Doppio difetto IDT[09] Superamento del segmento di coprocessore IDT[0A] TSS non valido IDT[0B] Segmento non presente IDT[OC] Eccezione di stack IDT[0D] Protezione generale IDT[0E] Difetto di pagina IDT[0F] Non usato IDT[10] Errore di processore I rimanenti interrupt possono essere definiti dal sistemista o dall applicativo.

Il registro di controllo CR0, CR2 e CR3


Questo uno dei pi importanti registri delle cpu a 32bit. Permette di controllare lo stato del processore stesso, e di modificarne il funzionamento. E utilizzabile attraverso una variante dell istruzione MOV. Esempio:
; Legge il contenuto di CR0 MOV EAX, CR0 ; Scrive il contenuto di EAX in CR0 MOV CR0, EAX

Il contenuto del registro CR0 il seguente: Bit 0 Protection Enable Se questo bit settato, la CPU lavora in modalit protetta, altrimenti in modalit reale. Bit 1 Math Present Se questo bit settato, presente nel sistema un coprocessore matematico 287, 387 o superiore. Bit 2 Emulation Questo bit indica se il coprocessore matematico deve essere emulato. Bit 3 Task Switched Questo bit viene attivato ogni qualvolta avviene una commutazione di task. Bit 4 Extension Type Questo bit indica il tipo di coprocessore matematico presente. Bit 31 Paging Questo bit indica se attiva la paginazione o meno. Per questo particolare modo di

12 di 36

gestire la memoria si rimanda al relativo paragrafo. Il registro CR2 contiene l indirizzo lineare in cui si verifica un errore qualora sia attiva la paginazione. Si rimanda al relativo paragrafo per ulteriori chiarimenti. Il registro CR3 viene utilizzato quando utilizzata la paginazione. Esso contiene l indirizzo lineare dell elenco delle tabelle di pagina per il task corrente.

La paginazione
Qualora la memoria fisica non fosse sufficiente a contenere tutto il codice in esecuzione e i relativi dati, necessario trasferire il contenuto della stessa memoria RAM su un dispositivo di massa, e ricaricarlo in memoria quando lo stesso viene richiesto dall applicativo. Questo compito completamente a carico del sistema operativo, come la gestione della memoria stessa in modo efficiente. I processori a 32bit vengono in aiuto ai programmatori di sistema operativo, mettendo a disposizione un interessante sistema in modalit protetta per gestire la memoria virtuale, dando cos la possibilit di mettere a disposizione degli applicativi una relativamente infinita di memoria. Questo meccanismo viene chiamato paginazione, e consiste nel suddividere la memoria virtuale in paragrafi da 4Kb ognuno, i quali possono essere situati in memoria, oppure su disco, e quando necessario accedere a un paragrafo non presente nella memoria, il microprocessore attraverso il meccanismo delle eccezioni comunica tale richiesta al sistema operativo, il quale ha la possibilit (se prevista dagli sviluppatori stessi!) di caricare in memoria i dati richiesti, in un area disponibile, e di rieseguire l istruzione che ha richiesto tali dati. Per poter usare la paginazione, consultare il paragrafo relativo ai registri di controllo. La divisione della memoria in pagine da 4Kb avviene attraverso l di pagine stesse, che hanno il compito uso di contenere apposite tabelle che descrivono la memoria stessa. La quantit di memoria indirizzata in un singolo segmento pu arrivare fino a 4Gb, quindi la divisione della stessa memoria in paragrafi da 4Kb porta ad avere 1.048.762 pagine. Non tutte queste pagine per sono effettivamente utilizzate, quindi la maggior parte di esse non esisteranno ne in memoria ne su disco. Queste pagine vengono indirizzate da 2 voci presenti in 2 tabelle diverse. Una prima tabella primaria contiene l indirizzo delle relative 1.024 tabelle secondarie, le quali contengono le informazioni relative ai dati stessi. L indicizzazione di queste tabelle avviene attraverso il registro CR3. Ogni task pu avere le sue tabelle di memoria, oppure si pu avere una sola tabella per tutti i task che girano sulla macchina. Questa scelta ovviamente da attribuirsi al programmatore di sistema.

Passiamo ora ad analizzare le 2 tabelle. Tutte queste tabelle sono allineate sui confini di pagina, ovvero si trovano in locazioni di memoria allineate a multipli di 4Kb. Questo fa si che il microprocessore possa gestire in modo pi efficace la memoria virtuale. Gli elementi della tabella primaria sono come gi detto 1.024. Ognuno di questi elementi ha una dimensione di 32bit (DWORD) e contengono l indirizzo della relativa tabella secondaria. Le tabelle secondarie contengono come le tabelle primarie anch esse 1.024 elementi, le quali stavolta per puntano direttamente al relativo paragrafo. Le voci di entrambe le tabelle sono cos composte: Gli elementi delle tabelle di livello 1 puntano all'indirizzo effettivo della tabella. Ecco il grafico che ne rappresenta la descrizione.

Indirizzo frame di pagina Tabella 4 Elemento che descrive una pagina

DISP

RIS

RIS

U/S

R/W

Bit 0 P Indica se tale pagina esiste. Se il valore del bit 0 la pagina non esiste, ed il sistema operativo genera un eccezione, indicando nel registro CR2 il relativo indirizzo a cui si tentato di accedere. Bit 1 - R/W - Permessi di lettura/scrittura nella pagina. 0 se si hanno solo permessi di lettura, indispensabile per proteggere del codice, mentre se settato si hanno anche permessi di scrittura nella pagina. Bit 2 - U/S - Utente/Supervisore Bit 3, 4 - Riservati Bit 5 - A - Accesso avvenuto Bit 6 - D - Scrittura effettuata

13 di 36

Bit 7, 8 - Riservati Bit 9,10, 11 - Disponibili al sistema operativo per inserire informazioni sulla pagina. Bit 12..31 - Indicano l'indirizzo frame di pagina, allineato su confine da 4Kb. Il microprocessore, onde evitare di accedere continuamente a queste tabelle, utilizza una cache interna dove registra gli indirizzi delle pagine a cui si accede pi frequentemente, consultando ed aggiornando tali dati quando costretto a consultare le tabelle di paginazione.

Il multitasking
Altra novit del microprocessore a 32bit stata l introduzione del multitasking, ovviamente non nel senso letterario della parola. Infatti i task continuano ad essere eseguiti singolarmente, data la presenza di un singolo microprocessore nella quasi totalit dei sistemi in circolazione. Il supporto al multitasking da parte del microprocessore dato dalla possibilit dello stesso a salvare informazioni relative a un determinato task in esecuzione, e attraverso un opportuno descrittore, di ripristinare l esecuzione di un altro task. Ogni task deve avere indispensabilmente una struttura nella quale il processore salva i dati relativi all esecuzione del task stesso. Questa struttura ha una dimensione di 104 byte, ed cos composta:

Descrizione del segmento di stato del task nelle cpu a 32bit: BASE MAPPA I/O Riservati Riservati Riservati Riservati Riservati Riservati Riservati 15 1 - Riservati LDT GS FS DS SS CS ES EDI ESI EBP ESP EBX EDX ECX EAX EFLAGS EIP C 3( d i ob s d l l c d p g ed l s! R i i z a e ee o i a i e t k) nr z l n e n a Riservati SS2 ESP2 Riservati SS1 SS0 Retrolegamento a TSS precedente T

ESP1 Riservati ESP0 Riservati

Il bit T causa un eccezione di debugging qualora il task riceva il controllo nel caso sia settato. I campi SS0-ESP0, SS1-ESP1, SS2-ESP2 identificano i relativi stack per i livelli di privilegio 0, 1 e 2. Il registro di TSS viene definito dentro un descrittore della GDT stessa. Il campo BASE, LIMITE, DPL, G, P, hanno la stessa funzionalit del segmento DATI o CODE, ma con una piccola variante per il campo LIMITE. Esso non pu assumere un valore inferiore a 103, essendo 104 la dimensione minima di un TSS. I bit identificati nel descrittore come A0, A1, A2, A3, A4 assumono in un descrittore di TSS i seguenti valori: A0 = 0, A1 = 1, A2 = 0, A3 = B, A4 = 1.

14 di 36

Il bit A3 consente al processore di rilevare un tentativo di commutare a un task gi attivato. Infatti A3 assume il valore 1 nel caso in cui il task sia attivo. La modifica dei dati contenuti nel descrittore del TSS deve avvenire tramite un altro descrittore, che condivide la stessa area di memoria, ma che la descrive come una semplice area dati. Le istruzioni messe a disposizione dal processore per la memoria protetta sono LTR ed STR. LTR l istruzione che carica nel registro TR il task da eseguire, puntando al relativo TSS e di conseguenza Il registro TR identifica il Task Register, e precisamente il task in corso. L di questa istruzione deve uso avvenire con CPL settato a 0. L istruzione STR invece permette di leggere il valore contenuto nel registro TR. Non una istruzione privilegiata. Il registro TR (Task Register) composto da due porzioni, una parte visibile la quale pu essere modificata dalle istruzioni, e una parte invisibile che non leggibile. La base mappa I/O una speciale area dati che contiene riferimenti relativi ai descrittori di porta di task, i quali di solito vengono collocati nello spazio successivo al TSS stesso. Un descrittore di porta di TASK cos configurato: Non usato Non usato Selettore

P DPL 0 Non usato

Il descrittore della porta di task fornisce un riferimento protetto indiretto al TSS. Il campo DPL controlla il diritto di usare il descrittore per causare una commutazione di task. Per causare una commutazione di task si pu usare un descrittore di porta di TASK se si hanno i relativi permessi, oppure tramite istruzioni JMP, CALL, INTERRUPT, ed eccezioni.

Il modo virtuale 8086


Il modo virtuale 8086 una modalit introdotta da INTEL per permettere ad applicativi progettati per l esecuzione in modalit reale di girare anche in modalit protetta senza generare eccezioni per l accesso a locazioni di memoria di cui non si posseggono i relativi diritti. Per accedere al modo virtuale 8086 necessario creare un TSS nel quale viene settato nel campo EFLAGS il bit VM. Dovendo creare un sistema operativo totalmente nuovo, il problema della modalit virtuale 8086 non si presenta, pertanto rimando il trattamento di questa modalit a una lezione successiva.

Conclusioni
Tutti gli argomenti trattati in questa lezione, sono un po difficili, pertanto, nelle lezioni successive, saranno ritrattati quando ci accingeremo ad analizzare il memory manager, lo scheduler e i servizi del sistema operativo.

Bibliografia
(1) INTEL CORPORATION - "Il manuale INTEL 80386 per il programmatore", Intel Corporation, 1986

Lezione 3: PIC8259A, CMOS, DMA, PIT8253.


Introduzione
Tra i principali compiti di un sistema operativo ritroviamo quello di gestire l hardware presente in un PC al fine di mettere a disposizione un interfaccia comune per la gestione di qualsiasi componente al software che andr a girare sul s.o. stesso. Questo quanto accade oggi in sistemi operativi compatibili allo standard POSIX. Ma ovviamente per dare un interfaccia cos comune ad ogni componente necessario prima programmarlo nel migliore dei modi. In questa lezione affronteremo la gestione di 4 componenti hardware, riservandoci di trattare in lezioni successive altri componenti hardware.

Basic Input Output System (il BIOS)


Come stato gi detto nella prima lezione, all avvio un microprocessore x86, manda in esecuzione le

15 di 36

istruzioni che si trovano all indirizzo 0FFFF0h. Il caricamento del diagnostico e di tutto ci che contenuto nella ROM un compito che spetta al controller. In questa lezione ci limiteremo a trattare alcuni componenti essenziali che si trovano su un pc, e data la loro natura, il codice di gestione degli stessi deve essere integrato nel kernel del sistema operativo. Tornando al BIOS possiamo dire che data la natura differente dei vari componenti hardware presenti su un pc, e di conseguenza la differente natura nel gestire gli stessi, ha fatto si che fosse necessario un sistema standard. Il principale compiti di un BIOS, oltre a diagnostico di sistema, quello di fornire una interfaccia fatta di servizi software ad un sistema operativo per la gestione dei diversi componenti. Oltre al BIOS integrato su una scheda madre possibile trovare ulteriori estensioni su controller SCSI, schede video, schede di rete ed altri componenti. Il compito che spetta al BIOS di sistema quello di provvedere all inizializzazione di tutti gli altri BIOS. Di solito viene riservata una determinata locazione di memoria dentro un PC per caricare i vari firmware, questa locazione si trova tra l indirizzo 0C0000 e l indirizzo 0FFFFF. Data l impossibilit di usufruire dei servizi del BIOS da un sistema operativo che giri in modalit protetta indispensabile scrivere il codice per gestire i vari componenti hardware presenti su una macchina.

Programmable Interrupt Controller 8259A


In cima alla lista dei componenti da gestire troviamo il PIC8259A, un controller il cui compito quello di notificare alla CPU eventi proveniente da dispositivi hardware attraverso linee IRQ. In un pc troviamo due PIC8259A, ognuno dei quali gestisce 8 linee di comunicazione. Ogni linea viene identificata con la dicitura IRQx (dove x assume un valore da 0 a 7). Essendoci 2 controller, i primi 8 vengono identificati come IRQ master, mentre gli altri 8 vengono identificati come IRQ slave. Alle linee master troviamo collegati i seguenti dispositivi:
IRQ0 IRQ1 IRQ2 IRQ3 IRQ4 IRQ5 IRQ6 IRQ7 System Timer Keyboard Cascade Interrupt COM2/COM4 COM1/COM3 LPT2 FDC LPT1

Alle linee slave invece troviamo i seguenti dispositivi:


IRQ0 IRQ1 IRQ2 IRQ3 IRQ4 IRQ5 IRQ6 IRQ7 Real Time Clock Chip Networking adapter Non usato Non usato Non usato Floating Processor Unit HDC Non usato

Per questione di comodit da ora in poi ci riferiremo alle linee master usando le diciture IRQ0...IRQ7 mentre alle linee slave usando IRQ8..IRQ15. Ovviamente alcune di queste impostazioni possono essere cambiate. Prendete esempio da una scheda audio, a volte troverete nelle impostazioni come IRQ utilizzato il 5. Questo perch difficilmente vi una seconda porta parallela su una macchina, e quando essa presente se ne pu tramite appositi jumper (o attraverso il software stesso, dialogando col controller stesso) modificare l utilizzato. IRQ L utilizzo da parte di pi dispositivi dello stesso IRQ causa dei conflitti a volte non facilmente risolvibili, che danno l impressione che il nostro ultimo acquisto non funziona! Spesso questo accade con la famigerata tecnologia Plug & Play (inserisci e vai!). L ultimo di questi conflitti che ho avuto su una mia macchina stato tra una scheda audio PNP e una scheda ethernet anch essa PNP. Entrambe avevano avuto la bella idea di utilizzare l IRQ10 per comunicare col processore, e nonostante abbia tolto tutti i componenti del PC, bloccato attraverso il BIOS l impiego di alcuni canali IRQ (il 10 per primo!) ho dovuto optare per una soluzione poco ortodossa, o la scheda di rete o la scheda audio. Ho tolto quest ultima, rimpiazzandola con un vecchio modello ISA. La comunicazione tra la cpu ed i due controller 8259A avviene attraverso 4 porte di I/O (2 per ogni PIC). Le porte di I/O sono la 20h e la 21h per il controller master, e la A0h e la A1h per il controller slave. Il PIC8259A accetta due tipi di comandi:
ICW Initialization Command Words; OCW Operation Command Words.

I comandi ICW sono 4 e vengono spediti uno in coda all seguendo l altro ordine numerico.

16 di 36

ICW1 Porta 20h e A0h Bit 0 Se settato indica al controller che non sar trasmesso ICW4; Bit 1 Se settato indica che si tratta del controller master, altrimenti del controller slave; Bit 2 Se settato indica che i vettori di interrupt hanno dimensione da 8 byte, altrimenti da 4 byte; Bit 3 Se settato forza setta il modo level triggered (PS/2) altrimenti lo imposta sul modo edge triggered ; Bit 4 Bit settato se si tratta di un comando ICW1; Bit 5, 6, 7 Non settati; ICW2 Porta 21h e A1h Bit 0, 1, 2 Non definiti; Bit 3, 4, 5, 6, 7 Specifica il vettore di interrupt per ogni IRQ partendo dall IRQ0, assegnando l interrupt successivo agli irq successivi; ICW3 Porta 21h e A1h Bit 0, 1, 2, 3, 4, 5, 6, 7 Indica a quale IRQ collegato il canale slave sul canale master. Nell architettura AT viene collegato sul canale IRQ2 (si invia il valore 04 sul controller master, mentre si invia il valore 02 sul controller slave). ICW4 Porta 21h o A1h (se specificato nel bit 0 in ICW1) Bit 0 Settato; Bit 1 Seleziona il metodo per terminare un interrupt: Se settato viene impostato in AUTO MODE, ovvero il PIC imposta il relativo bit dopo aver inviato al processore il segnale di INTERRUPT, mentre in NORMAL MODE il processore deve comunicare attraverso OCW2 la fine dell interrupt; Bit 2, 3 Buffered mode; Bit 4 Settato Special fully neested mode (SFNM) , non settato SEQUENTIAL MODE; Bit 5, 6, 7 Impostati a 0 OCW1 Porta 21h e A1h Bit 0, 1, 2, 3, 4, 5, 6, 7 Ognuno di questi bit corrisponde allo stato del relativo IRQ. Se il bit azzerato, i segnali proveniente dall vengono ignorati, altrimenti se impostato vengono elaborati. IRQ OCW2 Porta 20h e A0h Bit 0, 1, 2 Determina il livello prioritario; Bit 3, 4 Azzerati; Bit 5 Settato se si comunica la terminazione di un interrupt; Bit 6 Non settato Priorit Rotate one settato la priorit impostata nei primi 3 bit; , se Bit 7 Se settato, viene settata la priorit secondo il bit 6, altrimenti non viene modificata. OCW3 Porta 20h e A0h inviato al PIC per leggere lo stato dell dell e della MHI (Mask Hardware ISR, IRR Interrupt) Bit 0 Viene impostato secondo relativa necessit; Bit 1 Se settato indica che il bit 0 specifica quale registro leggere (0 ISR, 1 IRR); Bit 2 Se settato il PIC entra in modo POLLING; Bit 3 Settato; Bit 4 Non settato; Bit 5 - Viene impostato secondo relativa necessita; Bit 6 Se settato il bit 5 controlla la modalit della maschera (1 ON, 0 OFF) e tutte le richieste vengono elaborate secondo i privilegi; Bit 7 Non usato. Ecco delle semplici routine per gestire il PIC8259, sia il canale MASTER, che il canale SLAVE. Ls t 0 o t e i rm t pra et n di I 85 ia 1 R ui d Po e o e l gso e ePC 29 to n e i
; ; ; ; Prometeo Programmable Interrupt Controller 8259 (C) 2001 Mazzeo Antonio e-mail: blackcod@tin.it

.386p .model flat, stdcall .CODE

17 di 36

; Initialize the PIC8259 ; On entry - No param ; On exit - No param __InitIRQHandler proc push eax cli ; Set ICW1 mov al, 00010101b ; ICW4 PRESENT bit0 ; Interrupt vector are 8bytes long bit2 ; level-triggered mode bit3 (not set!) ; costant bit4 out 20h, al ; Set ICW2 mov al, 10h ; IRQ0 -> INT10h out 21h, al ; Set ICW3 mov al, 04h ; Slave PIC is connected to IRQ2 in AT architecture! out 21h, al ; Set ICW4 mov al, 00001b ; Bit0 - It's always forced! ; Bit1 - 0 Normal End Of Interrupt ; - 1 Auto End Of Interrupt ; Bit2 - 0 Buffered mode ; Bit3 - 0 " " ; Bit4 - 0 Sequential Mode ; - 1 Special Fully Nested Mode ; Bit5,6,7 - 0 out 21h, al ; Slave PIC! See Master PIC for other info! mov al, 00010101b ; ICW1 out 0a0h, al mov al, 18h ; IRQ8 -> INT18h - ICW2 out 0a1h, al mov al, 02h ; ICW3 out 0a1h, al ; Slave PIC is connected to IRQ2 mov al, 01h ; ICW4 out 0a1h, al sti pop eax ret __InitIRQHandler endp ; Enable an IRQ handler! ; On entry: ; WORD - IRQ To enable ; On exit: ; EAX -> 0 __EnableIRQHandler proc push ebp mov ebp, esp push ebx xor eax, eax mov ax, word ptr ss:[ebp+08h] mov ebx, eax cmp al, 8h ; Verifica che il bit7 sia attivo o meno! jge _slaveIRQEnable ; Si tratta di un IRQ slave in al, 021h ; Read the Mask Register! movzx eax, al bts eax, ebx out 021h, al jmp endOfEIH _slaveIRQEnable: in al, 0a1h

18 di 36

sub ebx, 8h movzx eax, al bts eax, ebx out 0a1h, al endOfEIH: pop ebx xor eax, eax mov esp, ebp pop ebp ret __EnableIRQHandler endp ; Disable an IRQ handler! ; On entry ; WORD - IRQ to disable __DisableIRQHandler proc push ebp mov ebp, esp push ebx xor eax, eax mov ax, word ptr ss:[ebp+08h] mov ebx, eax cmp al, 8h ; Verifica che il bit7 sia attivo o meno! jge _slaveIRQDisable ; Si tratta di un IRQ slave in al, 021h ; Read the IMR movzx eax, al ; Clear eax btr eax, ebx ; Disable the IRQ out 021h, al ; Disable it! jmp endOfDIH _slaveIRQDisable: in al, 0a1h movzx eax, al ; sub ebx, 8h ; BL now assume the value of IRQ to disable! btr eax, ebx ; out 0a1h, al ; Disable IT! endOfDIH: pop ebx xor eax, eax mov esp, ebp pop ebp ret __DisableIRQHandler endp ; Verifica quale sia l'IRQ da terminare e lo termina ; On entry ; WORD - Irq da terminare __EndOfIRQ proc push ebp mov ebp, esp mov ax, word ptr ss:[ebp+08h] cmp al, 08h jge _slaveEOI mov al, 20h out 20h, al jmp _EndOfIRQProcedure _slaveEOI: mov al, 20h out 0a0h, al _EndOfIRQProcedure: mov esp, ebp pop ebp ret __EndOfIRQ endp END

19 di 36

CMOS
La memoria CMOS una memoria tampone che contiene alcune importanti informazioni sulla configurazione della macchina. Queste informazioni vengono mantenute anche se il PC spento per un lungo periodo, essendo la memoria alimentata da una batteria che si trova sulla scheda madre. L accesso alla memoria CMOS avviene tramite due porte di I/O. La porta 70h utilizzata per indirizzare il byte interessato, e la porta 71h per leggere o modificare il relativo byte. Non vi nessun controllo sul byte indirizzato nella memoria CMOS, infatti la porta 70h una porta a sola scrittura, quindi prima di leggere o modificare qualsiasi byte contenuto nella memoria importante disabilitare gli interrupt attraverso l istruzione CLI e riabilitarli successivamente attraverso l istruzione STI. Questa coppia di istruzioni purtroppo non ha nessun effetto sulle interrupt NMI (Non Maskerable Interrupt). La memoria CMOS ha una dimensione standard di 64 byte, ma col passare del tempo i produttori di schede madri ne hanno aumentato la capacit a 128 byte. Ecco una tabella riassuntiva del contenuto della CMOS: Indirizzo 00 0E OF 10 11 12 13 14 15 17 19 1A 1B 21 2E 30 32 33 34 40 Lunghezza 0E 01 01 01 01 01 01 01 02 02 01 01 06 0D 02 02 01 01 0C 40 Contenuto Real Time Clock Information Diagnostic state byte Shutdown state byte Type of the floppy disk drives Reserved Type of hard disk drive Reserved Equipment set byte Base memory size Extended memory size (above 1M) Type of hard disk drive C Type of hard disk drive D Reserved Reserved CMOS Checksum Size of extended memory above 1M Current century Additional information Reserved Estensioni della memoria CMOS da parte dei produttori di scheda madri.

Il contenuto di questi byte che non portano la dicitura Reserved standard, mentre il contenuto degli altri byte di solito varia a secondo del produttore della scheda madre. Ecco due semplici funzioni per accedere alla memoria CMOS sia in lettura che in scrittura. Ls t 0 u z n pregr e ci r nl m m r C S ia 2 F ni i e l ee sr ee ea e oi MO to o g v l a
; On entry: ;WR Bt t ra OD ye o ed ; On exit: ; AL - Byte __ReadCMOSByte PROC PUSH EBP MOV EBP, ESP MOV AX, WORD PTR SS:[EBP+08] CLI OUT 70h, AL IN AL, 71h STI MOV ESP, EBP POP EBP

20 di 36

RET __ReadCMOSByte ENDP ; On entry: ; WORD ;LwBt Bt t wie o ye ye o rt ;Hg Bt Vlet wie ih ye au o rt ; On exit: ; AX 0 __WriteCMOSByte PROC PUSH EBP MOV EBP, ESP MOV AX, WORD PTR SS:[EBP+08] CLI OUT 70h, AL XCHG AH, AL OUT 71h, AL STI MOV ESP, EBP POP EBP XOR AX, AX RET __WriteCMOSByte ENDP

Direct Memory Access (DMA)


Come il nome stesso ci dice, il DMA ha il compito di favorire lo scambio di dati tra un device e la memoria centrale del sistema, senza sovraccaricare il processore durante queste operazioni. Per fare un esempio pratico di come viene utilizzato il sistema DMA pensate ad una scheda audio, oppure a un moderno controller EIDE. Una scheda audio in grado di riprodurre dei suoni senza che il processore invii ogni singolo bit alla scheda, ma semplicemente limitandosi a caricare i dati nella memoria centrale e attraverso il DMA comunicarli alla scheda audio. Nell architettura AT noi troviamo due controller DMA, ognuno dei quali dotato di 27 registri classificati in 12 categorie differenti, pi sistemi per il controllo del dispositivo stesso. Principalmente un controller DMA lavora in due fasi, identificate come ciclo idle e ciclo working. Le operazioni che portano un controller dal ciclo idle al ciclo working generano 7 stati differenti. Il 1 di questi stati detto stato idle (SI) ed uno stato passivo. Il controller in questo stato pu essere programmato dal processore, impostando i canali da utilizzare per il trasferimento, le modalit di trasferimento, le operazioni da compiere e gli indirizzi di memoria che coinvolgeranno tali operazioni. Una volta che il dispositivo stato programmato, entra nello stato S0, dove vengono compiute le seguenti operazioni: Il controller invia un segnale HOLD al processore; Attende un segnale HLDA dal bus di sistema; Ricevuto il segnale il controller provvede a sconnettere il processore dal bus; Il controller incomincia ad eseguire i suoi compiti, entrando cos negli stati S1, S2, S3, S4. Il controller DMA pu compiere in automatico le proprie operazioni se viene impostato in modalit di auto inizializzazione. Impostato in tale modalit una volta che vengono trasferiti gli indirizzi dei blocchi di memoria da trasferire, esso pu svolgere il proprio compito senza che gli sia comandato. E possibile impostare la priorit con la quale vengono notificati al processore i segnali provenienti dai canali. Le modalit disponibili sono: Fixed Priority La priorit non viene mai variata, al canale 0 del controller slave viene assegnata la massima priorit, mentre la pi bassa priorit viene assegnata al canale 4 del controller master. Rotating Priority Ad ogni canale viene assegnata una priorit iniziale, ed ogni volta che esso segnaler un evento al processore la sua priorit sar impostata a 0, aumentando di volta in volta quando gli altri canali saranno impostati a 0. La massima priorit sar destinata al canale che non segnaler mai nessun evento. DMA Channel Modes (Modalit di trasferimento dei dati) Il controller DMA dispone di diverse modalit per il trasferimento dei dati: Single Transfer Mode In questa modalit il controller riconnette il processore al bus di sistema ad ogni ciclo di trasferimento, e comunicher la terminazione del suo compito inviando un segnale quando il bit TC

21 di 36

(Termination Counter) sar settato; Block Transfer Mode Questa modalit molto pi veloce della precedente, in quanto il controller riconnette al bus di sistema il processore soltanto quando avr terminato il proprio compito; Demand Transfer Mode In questa modalit il controller viene pilotato nelle sue attivit attraverso le porte di I/O. E molto utile nei trasferimenti di piccoli buffer tra device e memoria centrale. Ad ogni trasferimento il processore prende il controllo per preparare i trasferimenti successivi; Cascade Mode Questa modalit prevede l utilizzo di un canale DMA per permettere al 2 controller di comunicare con il processore, proprio come avviene con i PIC8259A. Il 2 controller DMA prende il nome di controller MASTER, ed connesso al processore. Il 4 canale del chip viene utilizzato per connettere il controller SLAVE al processore, ottenendo in questo modo canali DMA separati. DMA Transfer Type (Tipi di trasferimento dati) Esistono 4 modalit di trasferimento dati: Read type E trasferimento in modalit lettura, ovvero i dati vengono trasferiti dalla memoria centrale un ad un device attraverso le relative porte di I/O; Write type I dati vengono trasferiti dal device attraverso le porte di I/O alla memoria centrale; Memory to memory transfer type Questa modalit viene utilizzata per trasferire blocchi di memoria da una locazione della memoria centrale ad un locazione; altra Verify transfer mode Questa una modalit utilizzata per verificare che il trasferimento possa avvenire in modo corretto, infatti il controller eseguir tutti gli stadi intermedi che danno luogo ad un trasferimento dati, ma nessun dato sar realmente trasferito. DMA Register and Commands (Registri del controller e suoi comandi) In questo paragrafo tratteremo i registri di un controller DMA e il suo contenuto: Current Address Register Questo registro contiene l indirizzo corrente del blocco di memoria che sar interessato nelle operazioni successive; Base Address Register - L indirizzo base corrisponde ad ogni registro indirizzo corrente. Pu essere programmato solo via software ed indica il punto da cui iniziare a trasferire i dati. Il contenuto definisce l indirizzo entro un segmento di 64Kb di memoria; Page Register La memoria del sistema viene vista dal controller DMA come pagine da 64Kb, quindi opportuno indicare in questo registro a quale pagina si riferisce il valore da noi indicato nel registro base; Current Word Counter Questo registro presente per ogni canale DMA. Il registro contiene il numero di byte da manipolare meno 1. Quando questo registro raggiunge il valore massimo 0FFFF (65535 + 1 = 655336, la dimensione di una pagina) viene generato un segnale TC (Termination Counter); Base Word Counter Questo registro viene utilizzato per l inizializzazione del canale, ed ogni volta che un valore viene scritto nel registro Current Word Counter suo valore viene automaticamente copiato il dall hardware in questo registro; Mode Register Questo registro viene utilizzato per settare le modalit del controller: Bit 0, 1 Numero del canale selezionato; Bit 2 Se settato l auto-inizializzazione permessa, altrimenti negata; Bit 3 Indica la direzione, se settato il contatore viene decrementato ad ogni ciclo di trasferimento, altrimenti viene incrementato; Bit 4, 5 Indica il tipo di trasferimento; Bit 6, 7 Indica la modalit dell operazione (00 verifica, 01 scrittura, 10 lettura, 11 memoria memoria); Command Register Questo registro specifica i comandi per l operazione da eseguire: Bit 0 Trasferimento memoria-memoria, se settato permesso, altrimenti negato; Bit 1 Bloccaggio dell indirizzo del canale 0, se settato permesso, altrimenti negato; Bit 2 Se settato il controller viene bloccato, altrimenti no; Bit 3 Indica il tempo da impiegare, se settato viene utilizzato un tempo ristretto ( ignorato se il bit 0 settato); Bit 4 Priorit (0 fissa, 1 rotatoria); Bit 5 Modo Extended write permessa, 0 negata) (ignorata se il bit 3 settato); (1 Bit 6 Livello DREQ (0 alto livello, 1 basso livello); Bit 7 Livello DAC (1 alto livello, 0 basso livello); Status Register Questo registro riflette lo stato di tutti i canali nel controller; Mask Register Questo registro fornisce una maschera per l utilizzo dei canali DMA. I bit impostati a 1 da 0 a 3 indicano i servizi per i canali del controller slave, ed i restanti bit indicano i servizi per i canali del controller master; Request Register Questo registro viene utilizzato per gestire le richieste software o hardware; Temporary Register Questo registro viene impiegato nei trasferimenti da memoria a memoria come registro tampone;

22 di 36

Indirizzi delle porte di I/O per i controller DMA Ecco una tabella riassuntiva dei principali indirizzi per gestire i vari registri dei controller: DMA1 000 DMA2 0C0 Tipo accesso R/W di Destinazione Canale 0 Memory Address Register. Contiene una word e deve essere letta o scritta attraverso due operazioni consecutive. Canale 0 Transfer Count Register Canale 1 Memory Address Register Canale 1 Transfer Count Register Canale 2 Memory Address Register Canale 2 Transfer Count Register Canale 3 Memory Address Register Canale 3 Transfer Count Register DMA Status Register DMA Control Register Request Register Software DRQn Request Read Control Register Write 1 bit of DMA Mask Register DMA Mode Register Reset DMA Byte Pointer Trigger to the low byte. Read the Temporary Register DMA Master Clear Command Read the Mode Register Counter DMA Reset Mask Command DMA General Mask Register

001 002 003 004 005 006 007 008 008 009 009 00A 00A 00B 00C 00D 00D 00E 00E 00F

0C2 0C4 0C6 0C8 0CA 0CC 0CE 0D0 0D0 0D2 0D2 0D4 0D4 0D6 0D8 0DA 0DA 0DC 0DC 0DE

R/W R/W R/W R/W R/W R/W R/W R W R W R W R/W W R W R W R

Timer
L architettura AT mette a disposizione un chip (l 8253 Programmable Interval Timer) che ha il compito Intel di fornire 5 canali differenti per il timer al PC. Ognuno di questi canali ha un suo specifico impiego. Canale 0 Utilizzato per l aggiornamento del clock di sistema. Viene generato normalmente 18.2 volte al secondo, e viene segnalato attraverso l IRQ8. Canale 1 Viene utilizzato per il rinfresco della memoria DMA. La modifica di questo canale potrebbe causare errori nei contenuti della RAM, in quanto essi tendono col passare del tempo a perdere il valore registrato, quindi necessario un aggiornamento ogni determinato intervallo di tempo. Canale 2 Viene utilizzato per la generazione di suoni attraverso lo speaker di sistema. Canale 3 Utilizzato per il timer Watchdog. Canale 4 Utilizzato per il timer SlowDown. Ogni canale accetta dei segnali di input e genera dei segnali di output: Clock frequency input Utilizzato per stabilire la frequenza del timer, che di solito settata a 1.19318 Mhz. Questo segnale non pu essere modificato per il canale 1, che indispensabili al pc. Control input gate Permette di controllare il timer, non disponibile per il canale 0 e 1. Output signal out Questo segnale connesso all IRQ0 del controller PIC8259A. Viene utilizzato per il rinfresco della DRAM e nel bus di sistema. Ogni canale del PIT ha diverse modalit di uso: Modo 0 Interrupt on Terminal Count Viene utilizzato per segnalare lo scadere di un determinato periodo di tempo attraverso la generazione di un interrupt. Quando il contatore raggiunge il valore 0, esso riprende il conteggio dal valore settato; Modo 1 Programmable One-Show Viene utilizzato per segnalare lo scadere di un determinato periodo di tempo una sola volta; Modo 2 Rate Generator Il segnale viene generato attraverso la divisione di un numero N per il valore contatore; Square Wave Rate Generator Viene utilizzato per la generazione di onde quadre, e di solito trova impiego nell del speaker; uso Software Triggered Strobe Viene utilizzato per generare una pausa attraverso un valore precaricato dal

23 di 36

software; Hardware Triggered Strobe Utilizzato per generare una una pausa attraverso un segnale proveniente da un device; Porte di I/O per il timer I/O Port 040 041 042 043 048 049 04A 04B Modalit R/W R/W R/W W R/W R/W R/W W Destinazione Counter 0, PIT#1 System Timer Counter 1, PIT#1 Refresh Timer Counter 2, PIT#1 Speaker Timer Mode Control Register Counter 0, PIT# Watchdog Timer Counter 1, PIT#2 not used Counter 2, PIT#2 Slowdown Timer Mode Control Register

Comandi per i contatori Valore Comando 0 Immagazzina il valore del contatore 0 1 Legge e scrive soltanto i primi 8 bit del contatore 0 2 Legge e scrive soltanto gli ultimi 8 bit del contatore 0 3 Legge e scrive prima i primi 8 bit del contatore 0 e poi gli ultimi 8 bit 4 Immagazzina il valore del contatore 1 5 Legge e scrive soltanto i primi 8 bit del contatore 1 6 Legge e scrive gli ultimi 8 bit del contatore 1 7 Legge e scrive prima i primi 8 bit del contatore 1 e poi gli ultimi 8 bit 8 Immagazzina il valore del contatore 2 9 Legge e scrive i primi 8 bit del contatore 2 A Legge e scrive gli ultimi 8 bit del contatore 2 B Legge e scrive prima i primi 8 bit del contatore 2 e poi gli ultimi 8 bit C-F Comandi Counter Status Reading Mode Installation Commands Valore 0 1 2, 6 3, 7 4 5 Funzione Modo 0 scelto Modo 1 scelto Modo 2 scelto Modo 3 scelto Modo 4 scelto Modo 5 scelto

Word di controllo del timer La word di controllo stabilisce a quale timer si riferiscono le operazioni che vengono inviate. Questa word (che in realt un byte, in quanto costituita da soli 8 bit) composta cos come segue: Bit 0 Se settato, indica i numeri espressi sottoforma di numero BCD, altrimenti come numero binario; Bit 1, 2, 3 Utilizzati per stabilire il modo scelto; Bit 4, 5 Utilizzato per indicare le operazioni (0 Counter Latching, 1 Read/Load LSB, 2 Read/Load MSB, 3 Read/Load LSB e MSB); Bit 6, 7 Utilizzato per indicare il contatore scelto;

Conclusioni
Gli argomenti trattati in questa lezione sono stati un po complessi di quanto stato trattato nelle 2 pi lezioni precedenti. Posso immaginare quali siano i problemi ai quali andr incontro chi completamente a digiuno di tali materie, ma purtroppo questo non che l inizio. Ritornando sull argomento kernel e sui device driver che lo completeranno avremo modo di notare che l argomento non si esaurisce cos semplicemente. Se il tempo lo permetter, nella prossima lezione tratteremo la gestione del principale dispositivo di input, la tastiera, e cercando inoltre di definire in linee generali il kernel per quanto concerne il memory manager e il multitasking. Sar anche dato ampio spazio a problemi quali algoritmi di scheduling, condivisione di risorse,

24 di 36

e quanto altro sar ritenuto necessario trattare. Spero comunque che troviate queste lezioni interessanti.

Bibliografia
(1) Autori Vari - "Assembler Language Master Class", Wrox, 1995.

Lezione 4: Gestione della memoria.


Introduzione
L argomento sul quale ci soffermeremo in questa lezione la gestione della memoria. Nelle prime 2 lezioni stato trattato questo argomento, introducendo le principali modalit che un processore della famiglia x86 mette a disposizione del sistema operativo per gestire la memoria. L argomento non si esaurisce certamente l, essendo stata quella soltanto un analisi del processore, quindi in questa lezione cercheremo di analizzare quali sono i compiti di un memory manager, analizzando i diversi schemi per gestire la memoria tramite un software Principalmente possiamo classificare in 2 famiglie: 1) Sistemi che gestiscono soltanto la memoria fisica; 2) Sistemi a paginazione con l di swapping. uso La prima famiglia comprende i gestori di memoria di pi semplice realizzazione, mentre la seconda famiglia comprende tutti i gestori di memoria che vengono utilizzati nei moderni sistemi operativi, i quali necessitano di una parte del disco fisso per simulare e mettere a disposizione del software una memoria virtualmente infinita. Analizzeremo ovviamente entrambi i modelli di gestione della memoria, trattando i vari schemi che vengono impegnati in queste due classi.

Gestione della memoria


In questa famiglia troviamo i primi gestori di memoria, realizzati ed impiegati nei primi computer. Nei personal computer questi gestori di memoria sono utilizzati in quei sistemi operativi di fascia bassa, che necessitano di pochi requisiti per poter funzionare correttamente. Monoprogrammazione senza swapping o paginazione Questo a mio avviso il modello pi semplice per gestire la memoria di un calcolatore. La sua semplicit dovuta al fatto che tutta la memoria fisica presente viene utilizzata per eseguire un solo applicativo alla volta, al quale viene riservata tutta la memoria disponibile sul sistema. In questo modello di memoria spesso viene utilizzata una tabella come indice per tenere traccia della memoria disponibile e di quella allocata. Ad ogni elemento della tabella viene assegnato un blocco di dimensioni fisse della memoria (es. in ms-dos corrisponde a 16byte). Quando un applicativo viene caricato in memoria il gestore provvede a trovare un blocco di memoria sufficiente a tenere l applicativo. Possiamo inoltre riscontrare spesso che alcuni blocchi di memoria sono riservati, tipo la cima della memoria oppure la base, occupata dal kernel del s.o. oppure da un eventuali firmware presente sulle macchine (tipo i BIOS, oppure eventuali blocchi di memoria riservati come i 128Kb riservati per la memoria video). Questo metodo stato impiegato in gestori di memoria all inizio in CP/M e DOS. Purtroppo l evoluzione dei microprocessori e la crescente quantit di memoria disponibile lo rendono praticamente inutilizzabile in quei sistemi operativi dove spesso vengono caricati pi applicativi in memoria. Personalmente credo comunque che sia utile in architetture proprietarie, dove la memoria disponibile relativamente piccola e non vi la possibilit di disporre di unit dove effettuare lo swapping dei dati presenti nella memoria. Multiprogrammazione con partizioni In alcune macchine negli anni passati, era necessario avere pi processi caricati contemporaneamente in memoria, affinch non venisse perso inutilmente del tempo di elaborazione in attesa di un qualche segnale di I/O da parte di un processo. Lo schema pi semplice per permettere di avere pi processi contemporaneamente caricati in memoria consiste nel suddividere la memoria fisica presente sulla macchina in partizioni aventi dimensioni identiche. Ovviamente, eventuali processi che richiedono una quantit di memoria pi grande di quella messa a disposizione del sistema non potrebbero essere eseguiti, onde evitare questo inconveniente la memoria viene divisa in partizioni avente dimensione variabili. Una eventuale coda in cui vengono inseriti i processi da eseguire provvede ad assegnare ad ogni processo una

25 di 36

partizione disponibile. Anche questa ultima soluzione per pu presentare eventuali inconvenienti. Ipotizziamo di avere una memoria di 1Mb divisa in 4 partizioni da 100Kb, 250Kb, 500Kb, 150Kb. I processi da caricare in memoria sono p(n, x) dove n identifica il numero del processo ed x il quantitativo espresso in Kb di memoria richiesta. I processi presenti nella coda sono p(1, 80), p(2, 140), p(3, 90), p(4, 180), p(5, 400). Il sistema viene avviato, i processi p(1), p(2), p(3) vengono caricati correttamente in memoria in quanto le prime 3 partizioni sono sufficientemente grandi per contenerle. Il processo p(4) invece viene scartato in quanto richiede una quantit di memoria che non disponibile, e quindi si salta al caricamento di p(5) il quale viene anche scartato di conseguenza per insufficienza di memoria. Bisogna pertanto far attendere il processo p(4) e p(5) affinch le 2 partizioni in grado di contenerle vengono liberate (la 2 e la 4 partizione). Ci controproducente, in quanto il processo p(2) viene caricato in un blocco che supera di ben 90Kb la memoria da lui richiesta, ed il processo p(3) addirittura in un blocco avente una dimensione ancora pi maggiore con una perdita di memoria di 410Kb. L ideale sarebbe quello di creare pi code di processi, dove vengono stabilite le dimensioni massime per un processo. Pertanto la distribuzione dei processi verrebbe cos fatta: 1 partizione: p(1), p(3); 2 partizione: p(2), p(4); 3 partizione: p(5); 4 partizione: nessun processo. Questo sistema ancora controproducente, in quanto la 4 partizione rimasta libera, mentre si pu caricare il processo p(4) nella partizione vuota, occupando quindi il processore per la maggior parte del suo tempo di attivit. Ipotizziamo comunque di avere in ingresso altri 6 processi numerati da 5 a 10 aventi tutta una dimensione talmente piccola da poter essere caricati nella 1 partizione. Le partizioni 2 e 3, quando i relativi processi hanno terminato l elaborazione, vengono inutilizzate. Questo problema viene risolto aggiungendo una 3 informazione nella coda, un numero k che indica quante volte il processo stato scartato per l elaborazione. Quando il processo stato scartato un numero max di volte gli viene assegnata una delle partizioni attualmente disponibile sul sistema. Anche questo modello di memoria oggi stato prevalentemente abbandonato dai programmatori di sistemi operativi. Rilocazione e protezione I problemi derivanti dall impiego del modello sopra esposto sono molto comuni. Ogni software non pu sapere in quale partizione verr eseguito, in quanto non tutte le macchine hanno lo stesso quantitativo di memoria e non tutte le partizioni hanno le stesse dimensioni. Pertanto, quando un processo viene creato dal programmatore, viene sviluppato con indirizzi relativi, cio il processo stesso viene implementato presupponendo che sar eseguito nella partizione 1 avente inizio all indirizzo di memoria 0. Sar compito del linker quello di creare una tabella delle istruzioni relative, e sar compito del loader ricorreggere tutte quelle istruzioni che fanno riferimento a locazioni relative, inserendo il corretto valore. Ls t 1 Sa s n d u a tn a ia cni e i n s ig to o r
ScanString proc push ebp mov ebp, esp mov ax, [ebp+08] mov esi, [ebp+0a] push ax pop ds mov ecx, -1 xor eax, eax cld rep scasb neg ecx sub ecx, 1 mov eax, ecx mov esp, ebp pop ebp ;Trialeaoain dlasrnafnh nn emn lbrzoe el tig ic o ; DS:[ESI] non uguale a 0 ;Crc i sltoedoiie aia l eetr rgn ;Crc lofe doiie aia fst rgn

26 di 36

ret ScanString endp mov ds, @data mov esi, offset Nome call ScanString

Questo piccolo processo dovr essere modificato in 2 punti. Il 1. identificato dall istruzione mov ds, @data, in quanto non possibile conoscere il segmento dove sar eseguito, e il secondo punto l istruzione mov esi, offset Nome in quanto non si pu conoscere quale partizione sar assegnata. Prima dell esecuzione, il software che si preoccuper di caricare l applicativo corregger le due istruzioni sostituendo i relativi indirizzi con quelli effettivi. Questo sistema viene utilizzato nei file eseguibili dell MS-DOS, dove presente una tabella di elementi da rilocare data la dimensione dei segmenti in modalit reale nei processori x86 e la dimensione degli applicativi. Comunque, ci non risolve il problema principale, ovvero impedire al processo p(3) di accedere alla memoria del processo p(1) affinch ne sovrascriva le istruzioni e i dati causando danni irreparabili (in alcuni s.o. che non sto qui ad elencare equivale al blocco della macchina), quindi viene aggiunta un ulteriore campo nelle informazioni del processo, un numero identificativo. Il processore quando accede in memoria per compito di un processo verifica che questo numero sia identico a quello assegnato alla partizione, e nel caso ci non sia vero viene generata una eccezione. In alcune CPU il processo di rilocazione veniva eseguito dal processore stesso, senza l intervento del loader. Venivano memorizzate nelle informazioni della partizione l indirizzo BASE della partizione stessa e un indirizzo LIMITE. Per qualsiasi accesso alla memoria viene sommato l indirizzo BASE e verificato il tutto con l indirizzo LIMITE, se questo controllo viene superato una eccezione viene sollevata dal processore ed elaborata dal s.o. Questo modello ci ricorda il comportamento dei processori x86 a 32bit operanti in modalit protetta. A titolo informativo la prima CPU ad adottare uno schema simile a questo stata impiegata nel CDC 6600, il primo super computer prodotto. Swapping Si fa ricorso all dello swapping quando la memoria fisica presente su una macchina non sufficiente uso per poter tenere caricati in memoria tutti i processi che sono stati mandati in esecuzione. Per rimediare a questo problema si pensato di utilizzare la memoria dei dischi come supporto alla memoria RAM. Purtroppo l impiego del disco per contenere quei dati che non possono essere caricati in memoria causa il rallentamento dell esecuzione dei processi. Il pi semplice schema di swapping consiste nel caricare un processo dal disco in memoria, eseguirlo per un dato periodo di tempo, salvarlo sul disco e procedere con i processi successivi. Questo schema non sembra essere utilizzato nei sistemi operativi, ed ovviamente vi sono tutte le ragioni per non farlo. Un sistema del genere venne integrato in una distribuzione DOS della Digital Research (DR-DOS 6) e veniva utilizzato per avere a disposizione un ambiente multi-tasking, con risultati assai sgradevoli. Il principale sistema di swapping oggi adoperato costituito dalla memoria virtuale. Grazie ai supporti forniti dal microprocessore possibile eseguire un applicativo caricando solo una parte del codice che lo compone. Riprenderemo lo swapping nei paragrafi successivi.

Meccanismi per gestire la memoria


Compito del memory manager come gi illustrato quello di fornire la memoria all applicativo che la richiede, ed allo stesso tempo tenere traccia di quali blocchi di memoria sono occupati e di quali sono liberi. Per svolgere questo compito vengono utilizzati due meccanismi diversi: Gestione della memoria con le bit map Gestione della memoria con le liste Procediamo ad esaminare questi due meccanismi. Gestione della memoria con le bit map La gestione della memoria attraverso le bit map viene adoperata quando la memoria presente sul sistema viene suddivisa in blocchi uguali. Ad ognuno di questi blocchi corrisponde un determinato flag di identificazione nella tabella delle bit map. Il valore del flag indica lo stato del blocco di memoria, se disponibile o stato assegnato. Ipotizziamo di avere la seguente bit map: 1111 000 0101 0011 0111 1100 0000 1110 Bit0 Bit4 Bit8 Bit12 Bit16 Bit20 Bit24 Bit28 Ognuno di questi elementi rappresenta una pagina di 32Kb. In tutto abbiamo 32 elementi per un totale di 1Mb di memoria indirizzata.

27 di 36

Al momento in cui viene inoltrata una richiesta di allocazione di memoria il gestore deve effettuare la scansione dell intera bit map per cercare il blocco in grado di soddisfare la richiesta. Qualora sia richiesta una quantit di 128Kb di memoria, abbiamo due possibilit. Utilizzare l elemento 22, 23, 24, 25 oppure i 4 elementi successivi al 24. Il dover effettuare la scansione dell intera tabella e la possibilit che il blocco che soddisfi la richiesta si possa trovare a cavallo di 2 byte rappresenta un punto a svantaggio di questo metodo, ma nonostante ci utilizzato da alcuni gestori di memoria. Altro problema che deriva dall impiego delle bit map riguarda la dimensione stessa della bit map. In questo esempio per comodit ogni elemento della bit map rappresenta un blocco da 32Kb, quindi se fosse richiesta anche solo 1300 byte si sarebbe costretti ad assegnare un intero blocco, perch non vi modo di sapere quanti dati effettivamente siano utilizzati nel blocco. Una soluzione sarebbe quello di aumentare gli elementi della bit map in modo da diminuire la dimensione di un blocco di memoria stesso. Ma questo aumenterebbe il consumo della memoria per la bit map. Infatti se nel nostro esempio la memoria richiesta dalle bit map di soli 4byte, se si scendesse ad un kb per ogni blocco si arriverebbe ad occupare gi 128byte. Non una grande dimensione, per comunque si arriverebbe a sprecare sino a un max di 1023 byte per ogni richiesta di allocazioni dati. Se scendiamo addirittura ad un paragrafo di memoria (riducendo la dimensione di un blocco a soli 16byte) la perdita sarebbe minima, ma la bit map automaticamente occuperebbe 8Kb di memoria. Gestione della memoria con le liste Un alternativa all impiego delle bit map per la gestione della memoria data dalle liste. Ogni elemento della lista rappresenta una porzione di memoria, indicando la dimensione del blocco, lo stato e i collegamenti agli elementi successivi e precedenti. Un esempio di un elemento della lista il seguente:
typedef struct memlink { bool status; // 0 free, 1 used unsigned long address; unsigned long size; struct memlink *prev, *next; } MEMLINK;

Un solo elemento della lista pu gestire tutta la memoria del calcolatore, considerando che il membro size pu avere un valore max di 2^32 (equivale a 4gb di memoria). Trovare un nuovo blocco di memoria si riduce ad una semplice scansione della lista. Le operazioni per allocare una quantit di memoria da un blocco pi grande senza sprecare un solo byte sono le seguenti: Si crea un elemento di tipo MEMLINK all indirizzo address del blocco pi grande; Il membro address del nuovo MEMLINK assume il valore del vecchio elemento MEMLINK; Il membro size del nuovo MEMLINK assume il valore del blocco di memoria da allocare pi la dimensione dell oggetto MEMLINK; Il membro prev punta all oggetto MEMLINK contenuto in prev del vecchio elemento MEMLINK; Il membro next punta al vecchio elemento MEMLINK; Il vecchio elemento MEMLINK viene aggiornato nel campo prev, size ed address e viene collocato al nuovo indirizzo address. Ovviamente per migliorare l efficienza delle liste si pu memorizzare in due liste separate la memoria libera e quella allocata. Gli algoritmi utilizzati per la scansione della lista sono i seguenti: first fit Scandisce la lista finch non viene trovato il primo elemento in grado di soddisfare la richiesta, ma a lungo andare questo algoritmo genera blocchi di memoria sempre pi piccoli, rischiando di non poter soddisfare le richieste successive. Nonostante ci esso il pi semplice da implementare ed anche il pi veloce; next fit Questo algoritmo invece tiene memorizzata la posizione dell ultimo elemento che ha soddisfatto una richiesta per l allocazione di un dato blocco, in modo da effettuare la ricerca solo nei blocchi successivi; best fit Questo algoritmo prevede una scansione dell intera lista alla ricerca del blocco che si adatta a soddisfare in modo migliore la richiesta; worst fit Questo algoritmo prevede la scansione dell intera lista alla ricerca del blocco pi grande che ci sia, in modo che spezzando tale blocco in 2 parti si generi un blocco libero che possa essere impiegato successivamente per altre richieste di memoria; quick fit Questo algoritmo invece prevede la memorizzazione di diverse liste dove gli elementi hanno una dimensione pressoch identica, in modo da velocizzare la ricerca. Ci che penalizza l impiego di questo algoritmo dovuto alla compattazione della memoria. Quando della memoria allocata viene rilasciata, se essa confinante con un blocco disponibile, si deve prevedere

28 di 36

all unione dei 2 blocchi, rimovendoli dalle rispettive liste ed inserendo il nuovo elemento nella lista appropriata.

La memoria virtuale
L impiego della memoria virtuale, come stato gi detto, dovuto alla necessit di poter utilizzare una quantit relativamente grande di memoria, senza che per essa sia necessariamente presente sulla macchina. Immaginate un processo che per la sua corretta esecuzione richieda 2Mb di memoria fisica, mentre la macchina ne pu mettere a disposizione soltanto uno. L unica alternativa che lo sviluppatore ha consiste nel suddividere il proprio processo in pi frammenti, chiamati overlay, i quali venivano caricati accuratamente dal sistema operativo ogni qualvolta era necessario, ma assicurarsi che tutti i dati di cui un determinato overlay avesse bisogno era un compito che spettava al programmatore stesso, e tale operazione era lunga e noiosa. Tutto questo si risolto introducendo la memoria virtuale. L utilizzo della memoria virtuale permette di eseguire un processo anche con una minima porzione di dati e di codice, garantendo l eventuale caricamento in memoria degli stessi all occorrenza. Purtroppo, l impiego della memoria virtuale richiede automaticamente l impiego di un disco dove memorizzare i dati non presenti nella memoria. La paginazione La maggior parte dei sistemi con memoria virtuale utilizza la tecnica della paginazione. Nella lezione 2 abbiamo trattato la paginazione di un processore x86 a 32bit, mentre in questa approfondiremo l argomento della paginazione. La memoria fisica del computer viene divisa in opportuni blocchi aventi tutti una dimensione uguale, chiamate pagine, come accade con la gestione delle bit map, ma ad ogni pagina pu corrispondere un indirizzo virtuale di memoria che non identico al suo indirizzo fisico. Infatti in presenza di paginazione tutti gli indirizzi virtuali utilizzati dalla CPU per accedere alla memoria fisica vengono tradotti da un apposito dispositivo chiamato MMU (Memory Manager Unit). Nel caso in cui ad un indirizzo virtuale non corrisponda nessun indirizzo fisico, l MMU avverte il sistema operativo generando l eccezione page fault dando cos la possibilit al sistema stesso di provvedere al caricamento dei dati stessi in una pagina libera, oppure attraverso determinate procedure al rimpiazzamento della pagina con una nuova, garantendo al processo una corretta esecuzione. La gestione della memoria virtuale necessita dell utilizzo di tabelle che descrivono le pagine stesse. Ogni elemento di queste tabelle descrive lo stato di ogni pagina presente sul sistema. Le tabelle che descrivono la memoria possono essere a un solo livello o a pi livelli. Nei sistemi in cui viene adoperato un solo livello per le tabelle di pagine, necessario avere in memoria una copia dell intera tabella, e questo ci obbliga ad occupare una quantit non indifferente di memoria solo per la tabella (un esempio pratico dato dai processori a 32bit INTEL, dove per tenere tutte le tabelle di pagine in memoria necessario dedicare 4100Kb di memoria!). L adozione di questo meccanismo un grosso problema, in quanto se la tabella ha dimensioni non indifferenti, viene posizionata in memoria, causando un rallentamento non indifferente all esecuzione dell applicativo. Nei sistemi in cui vengono adoperate tabelle a pi livelli, ogni elemento della tabella di livello inferiore permette di puntare a pi elementi. Ci permette inoltre di poter stabilire solo quali tabelle di livello superiore caricare fisicamente in memoria, dandoci cos la possibilit di risparmiare una quantit non indifferente di memoria. Tale meccanismo come abbiamo visto viene adoperato dalle cpu x86 a 32bit (per accedere all indirizzo virtuale 00400200 tale CPU carica dalla tabella di primo livello il 2 elemento, e se esso caricato in memoria carica dalla stessa tabella il primo elemento dato che 0200 rientra nell offset della prima pagina). Per aumentare la velocit d accesso alla tabella di pagine viene adoperato una particolare unit chiamata TLB (Translation Lookaside Buffer o memoria associativa) che ha il compito di mantenere in appositi registri hardware un elenco delle pagine a cui si accede molto pi frequentemente. Quando il processore deve accedere a una locazione di memoria, l MMU controlla in parallelo tutti gli elementi della TLB, e nel caso in cui tali elementi non hanno memorizzate le informazioni relative alla pagina consulta la tabella caricata in memoria, scartando un elemento della TLB per rimpiazzarlo col nuovo elemento caricato dalla memoria. In alcune architetture il caricamento degli elementi nella TLB avviene in modo trasparente al s.o. mentre in altre architetture, gli elementi della TLB devono essere caricati dal s.o. via software. Ovviamente, questo compito deve essere fatto nel minor numero possibile di cicli di clock, dato che le eccezioni dovute alla mancanza di un elemento nella TLB avvengono pi spesso delle eccezioni di pagina. Una soluzione alle tabelle di pagine data dalle tabelle di pagine inverse adoperate spesso in quelle architetture dove la quantit di memoria fisicamente indirizzabile talmente vasta che la suddivisione in pagine darebbe vita a tabelle di pagina molto grandi. Le tabelle di pagine inverse sono delle tabelle in cui gli unici elementi che figurano non sono altro che quelli effettivamente presenti, quindi non si pu utilizzare l indirizzo virtuale come indice per identificare l elemento ma bens deve essere effettuata una scansione degli elementi alla ricerca di quello corretto. Questa procedura mediante l impiego della TLB pu essere velocizzata, e attraverso appositi algoritmi di ricerca una eventuale scansione della tabella pu essere

29 di 36

effettuata in un tempo pi che ragionevole. La dimensione delle pagine un parametro che spesso ricade sul s.o. nonostante l architettura della macchina preveda pagine di dimensione differente. Onde evitare di sprecare della memoria inutilmente, spesso si tende a scegliere una dimensione di pagine ridotta. Comunque per ottenere la protezione a livello di pagine da parte dell hardware consigliato avere una dimensione di pagina logica identica alla stessa dimensione di una pagina fisica, e di scegliere sempre un multiplo di 2 per sfruttare al meglio l architettura del processore. Algoritmi per il rimpiazzamento delle pagine Quando non vi in memoria pi nessuna pagina disponibile, si pone il dubbio di determinare quale sia la pagina da eliminare dalla memoria per rimpiazzarla con quella necessaria. A questo proposito, esistono diversi algoritmi, quindi andiamo ad esaminarne alcuni: Algoritmo ottimo per il rimpiazzamento delle pagine Questo algoritmo il miglior algoritmo che possa esistere, ma impossibile da realizzare. Esso prevede una scansione dell applicativo alla ricerca delle pagine utilizzate e della frequenza dell accesso, in modo da stabilire statisticamente quale siano le pagine che si possono togliere evitando un page fault per il maggior tempo possibile. Tale algoritmo purtroppo pu essere applicato a un processo analizzando prima del caricamento il flusso stesso del codice. Dato che specifico a un determinato processo, tale algoritmo non viene implementato da nessun sistema; Algoritmo di rimpiazzamento delle pagine Not-Recently-Used I vari sistemi che utilizzano le pagine per la memoria virtuale, permettono di utilizzare alcuni bit negli elementi che descrivono le pagine per indicare un eventuale accesso alla pagina ed eventuali modifiche apportate alla stessa. Quando si verifica un eccezione, il sistema operativo effettua una scansione degli elementi nelle tabelle al fine di cercare quali pagine non siano mai utilizzate, e sceglie una di queste pagine per provvedere al rimpiazzamento. Qualora non vi siano pagine mai utilizzate, il s.o. pu scegliere invece tra quelle pagine modificate ma mai utilizzate in lettura; Algoritmo di rimpiazzamento delle pagine FIFO Questo algoritmo prevede il rimpiazzamento delle pagine attraverso una coda FIFO. Il primo elemento inserito nella coda (e in tal caso la pagina che risiede in memoria da un maggior numero di tempo) viene estratto per essere rimpiazzato in cima alla coda dal nuovo elemento. Ci per non buono, in quanto la pagina che si trova alla base della coda pu essere anche la pagina pi utilizzata dal sistema; Algoritmo di rimpiazzamento delle pagine Second Chance Questo algoritmo si rif sull algoritmo FIFO, ma dando la possibilit alle pagine utilizzate di essere conservate nel sistema. Al verificarsi di un PAGE FAULT l algoritmo verifica che l elemento base della coda non sia stato utilizzato, se invece stato utilizzato la pagina viene spostata in cima alla coda come se fosse stata appena caricata e si provvede ad esaminare l elemento successivo della coda; Algoritmo dell orologio per il rimpiazzamento delle pagine Tale algoritmo identico all algoritmo Second Chance con l unica differenza che le pagine non vengono mai spostate nella lista, ma bens viene conservato il puntatore all ultimo elemento controllato, provvedendo ad effettuare la ricerca dall elemento puntato. Quando viene individuata nella coda una pagina non adoperata di recente, essa viene rimpiazzata con la nuova pagina da caricare e il puntatore avanza all elemento successivo della coda; Algoritmo di rimpiazzamento delle pagine Least Recently Used Questo algoritmo riprende l algoritmo ottimo per il rimpiazzamento delle pagine. Consiste nel cercare la pagina meno utilizzata recentemente attraverso un riferimento di tipo cronologico o in base al numero di accessi effettuati in una determinata pagina prima del verificarsi di un eccezione di pagina. Dato che la maggior parte delle istruzioni usano un operando che spesso localizzato in memoria diventa inefficiente dovendo mantenere aggiornata la tabella dei riferimenti per la ricerca della pagina da rimuovere. In alcune forme di paginazione, quando un processo caricato non ha nessuna pagina in memoria, quindi man mano che esso sar in esecuzione attraverso i page fault il s.o. provveder a caricare tutte le pagine di cui ha bisogno in memoria. Normalmente un processo necessita di un numero molto piccolo e spesso ben identificato di pagine in memoria per un corretto funzionamento. Il numero di pagine caricate in memoria in un dato istante che appartengono ad un determinato processo prendono il nome di working set. Finch l intero working set di un processo non viene caricato correttamente in memoria il processo generer page fault. Una soluzione ideale negli algoritmi di rimpiazzamento delle pagine quella di evitare di rimpiazzare le pagine che appartengono al working set del processo in modo da ridurre il numero di page fault provocati.

La segmentazione
Oltre alla paginazione, la memoria virtuale pu essere gestita attraverso la segmentazione, che ci permette di creare spazi di indirizzamento virtuali e di tenere separati blocchi quali codice, dati, stack per diversi processi. Mantenere separati questi blocchi ci permette di far crescere la dimensione dei segmenti senza

30 di 36

mai rischiare di entrare in collisione con altri blocchi di memoria, inoltre ci ci permette di tenere del codice condiviso fra pi processi, quali il codice del s.o. che altrimenti dovrebbe essere caricato nella memoria virtuale del processo stesso. Nonostante si possano tenere separati in segmenti diversi blocchi di memoria non si deve dimenticare che essi sono fisicamente presenti nella stessa memoria, quindi possibile accedere alla memoria di un processo semplicemente ridefinendo un nuovo segmento che la comprenda utilizzando privilegi differenti. Un esempio reale ci pu essere dato dai processori x86. Nella GDT viene definito un segmento alias che comprende l intera memoria fisica del processore, mentre in altri segmenti viene incapsulato il processo. Attraverso il segmento alias nessuno ci impedisce di accedere al contenuto di tutta la memoria fisica contenuta nella macchina, in quanto nello stesso segmento la stessa memoria viene vista come un unico banco DATI con gli attributi R/W settati, con base uguale a 0 e LIMITE equivalente al corrispettivo della memoria fisica. Spesso in molte architetture si fa ricorso alla segmentazione combinata alla paginazione onde evitare che il contenuto di tutto il segmento sia realmente presente in memoria. Una delle architetture pi importanti ad adottare questo sistema fu MULTICS (girava su macchine Honeywell 6000) mentre uno dei pi diffusi nei nostri giorni il processore Pentium di INTEL del quale ne abbiamo trattato il funzionamento sia in modalit reale (Lezione 1) che in modalit protetta (Lezione 2).

Conclusioni
Abbiamo analizzato in questa lezione un argomento abbastanza interessante. Come avete avuto modo di leggere, esistono diversi sistemi per gestire la memoria, e l uso di concetti quali paginazione e segmentazione riducono le architetture verso il quale possibile fare il porting del proprio s.o. Per chiunque fosse interessato ad approfondire questo argomento, si rimanda alla lettura di (1).

Bibliografia
(1) Andrew S. Tanenbaum, Albert S. Woodhull - "Sistemi operativi - Progetto e implementazione", Prentice Hall International, 2001.

Lezione 5: I processi
Introduzione
In questo appuntamento tratteremo i processi, ovvero il modo in cui un sistema operativo affronta la gestione di pi applicativi in esecuzione su una macchina facendo credere all utente che si stanno eseguendo tutti contemporaneamente. Ci tengo a dire innanzitutto che non esiste nessun sistema operativo in grado di effettuare il multitasking in presenza di un singolo processore. Oggi il termine multitasking viene utilizzato senza rispettare affatto il suo significato. Multitasking significa poter eseguire nello stesso istante pi processi, non condividere il processore tra pi task caricati contemporaneamente nella memoria, come avviene oggi in tutti quei s.o. che vengono eseguiti su macchine con un solo processore. L esecuzione di pi processi su una macchina, in presenza di uno o pi processori, genera problemi inerenti alla risorsa pi importante, il tempo di utilizzo della CPU. Nessun programmatore sarebbe disposto a richiamare dal suo applicativo una fantomatica API di sistema avente lo scopo di sospendere l esecuzione del processo per dare la possibilit ad altri processi di essere eseguiti. Questo compito spetta allo scheduler di sistema, che stabilisce il tempo da dedicare ad un dato processo, la priorit di esecuzione degli stessi processi e l utilizzo delle risorse di sistema nel modo pi efficiente onde evitare blocchi di sistema a causa di due processi che lottano per acquisire una singola risorsa. Spesso negli applicativi che attendono un evento troviamo una simile forma di codice:
while(1) { ioctl(handle, FIONBIO, &data); if (data) break; } // Controlla se sono disponibili byte..

.... continua la normale esecuzione del processo, i dati attesi sono finalmente disponibili.

Sarete tutti d accordo che forme di codice del genere dovrebbero essere evitate quanto pi possibile, ma in quei s.o. che non permettono ad un software di sospendere l esecuzione finch un dato evento non si verifichi, da qualche parte bisogna pur bloccare l esecuzione, altrimenti si rischierebbe di giungere alla fine del processo stesso senza che nessun dato sia stato elaborato. Un alternativa al codice riportato sopra sarebbe quella di farci inviare dal kernel un segnale, qualora vi siano dei dati disponibili per l handle utilizzato, ma resta sempre il problema che il nostro software deve eseguire

31 di 36

operazioni inutili perch non vi modo di arrestare la sua esecuzione. Oltre alla gestione dei processi e al coordinamento delle risorse hardware presenti sulla macchina, il kernel deve anche mettere a disposizione mezzi di comunicazione a tali processi e permettere la condivisione di memoria.

I processi
Nei sistemi in cui disponibile un solo processore, come gi stato detto, il multitasking viene realizzato grazie alla possibilit della CPU di commutare da un processo a un altro in un tempo relativamente breve. Ovviamente la quantit di tempo dedicata ad un processo influenza la velocit di elaborazione dei processi stessi, dato che il continuo scambio da un processo ad un altro e il passaggio attraverso lo scheduler (che ha il compito di decidere quale processo eseguire successivamente) ridurr il tempo di elaborazione disponibile per i processi stessi. In un sistema dove vi la possibilit di eseguire pi processi, ognuno di questi processi ha sempre un processo padre che lo ha generato, il quale dispone di tutti i permessi per farlo terminare in qualsiasi istante, e a sua volta ogni processo figlio pu creare altri processi. I principali stati in cui si pu trovare un processo sono i seguenti: In esecuzione Quando il controllo sar trasferito al processo, esso sar in grado di continuare l elaborazione; Pronto Il processo non in elaborazione, in quanto la cpu sta elaborando un altro processo; Bloccato Il processo non pu essere eseguito, in quanto non si verificato un dato evento. Quest ultimo stato ci porta alle considerazioni fatte prima; se il processo in attesa dei suoi dati si deve bloccare, quindi le possibilit sono o il blocco automatico del processo, oppure una chiamata allo scheduler per chiedere di bloccare il processo. Ora per la possibilit di bloccare in modo automatico il processo (ad esempio quando tenta di leggere da un dispositivo seriale, ed i dati non sono ancora presenti, il processo viene bloccato) ci impedisce di eseguire ulteriori operazioni (tipo l aggiornamento di un orologio che viene visualizzato a video); attendere un segnale dallo scheduler ci obbliga a scrivere una apposita procedura che faccia da destinatario ad uno specifico messaggio. Gestione dei processi Per la gestione dei processi il sistema deve tenere un elenco dei processi stessi da gestire, attraverso un apposita tabella dei processi. Gli elementi che figurano in questa tabella sono lo stato del processo, il valore di tutti i registri e quant possa essere utile a ripristinare la situazione del processo al momento in altro cui il controllo gli sar di nuovo trasferito. Naturalmente, oltre a queste informazioni nei s.o. che implementano un modello multiprocesso gestendo la memoria tramite la paginazione, importante, prima di trasferire il controllo al processo, caricare quel minimo indispensabile di pagine affinch non provochi continui page-fault. Oltre ad una tabella per gestire un processo necessario che un s.o. fornisca anche delle strutture primitive per permettere la gestione dei thread. Il thread non altro che un piccolo processo, che gira nello spazio di indirizzamento di un processo padre che lo ha generato. Immaginate per un attimo ad un server http quale APACHE oppure IIS. Nel momento in cui l utente provvede ad effettuare una connessione al web-server, questo provvede a soddisfare la richiesta nel minor tempo possibile (banda permettendo), ma se tutte le richieste http dovessero essere effettuate da un singolo processo, il risultato sarebbe pessimo. Ecco che in questo caso l utilizzo dei thread la scelta ottimale, in quanto istanziare un nuovo processo per soddisfare una richiesta sarebbe troppo dispendioso. Ad ogni richiesta che giunge al server, la richiesta viene passata ad un thread affinch venga soddisfatta. Ovviamente, se un numero di thread fossero gi creati, ma in attesa di un compito (e quindi bloccati) l efficienza del web-server aumenterebbe. Spesso i thread possono essere gestiti al pari di un processo, mettendo a disposizione dello sviluppatore routine per la creazione, la cancellazione, l assegnazione di priorit, la modifica dello stato di un thread stesso. Tra le pi importanti librerie in circolazione per la gestione dei thread troviamo la P-thread POSIX e la C-thread di Mach. I thread POSIX sono molto conosciuti da coloro che sviluppano sotto l ambiente Linux, in quanto una semplice alternativa all impiego della fork per gestire pi operazioni contemporaneamente. La gestione dei thread a livello pratico, come ovviamente dei processi, sar affrontata in una lezione successiva. Dato atto che ogni processo pu dare vita ad un numero non meglio precisato di processi, quanti thread possono essere presenti su una macchina in un dato istante? E soprattutto, qual la soluzione pi efficace per gestire i thread, permettendo loro qualsiasi azione ma allo stesso tempo negando loro la possibilit di apportare modifiche al processo stesso al fine di provocarne un malfunzionamento?

32 di 36

Comunicazione interprocesso
Nei sistemi in cui vi sono pi processi in esecuzione indispensabile che due o pi processi possano comunicare tra loro per uno scambio di informazioni. Perch necessario che i processi comunichino tra di loro? Innanzitutto per evitare che pi processi possano tentare di accedere in un dato istante ad una determinata risorsa e quindi evitare che l uno danneggi l altro. La gestione di alcune risorse deve essere affidata a determinati processi (tipo lo spooler di stampa) e far s che il software che desideri effettuare una tale operazione debba per forza comunicare col processo piuttosto che dare lui la possibilit di accedere alla risorsa. Cosa succederebbe se due software contemporaneamente decidessero di stampare un file e non vi nessun processo che gestisca tale operazione? Uno dei due software si vedrebbe negata la propria richiesta, o nel peggiore dei casi entrambi i documenti sarebbero stampanti senza nessuna distinzione, combinando i dati di uno con i dati dell altro sulla carta! Questo genere di problemi prende il nome di corsa critica, e cercare di risolvere questo genere di problemi (che si presentano seguendo la famosa legge di Murphy (vedi Listato1)) un operazione abbastanza complessa data la manifestazione del problema in modo casuale. Una soluzione per evitare le corse critiche data dalla mutua esclusione, ovvero impedire ad ogni processo di accedere ad una determinata risorsa che stata gi impegnata da un altro processo. Evitare le corse critiche comporta conoscere quando un processo entra in una sezione critica, e conoscere la sezione critica di un processo significa evitare che un altro processo possa entrare nella propria sezione critica quando lo gi uno, e per poter fare ci bisogna che lo sviluppatore notifichi al sistema che la sezione in esecuzione da considerarsi una sezione critica. Per poter ottenere la muta esclusione necessario garantire che durante l impiego di una data risorsa nessun altro processo la possa utilizzare. Esistono diverse possibili soluzioni a questo problema. Interruzioni disabilitate Molti scheduler utilizzano le interruzioni del timer per effettuare il salto da un processo ad un altro, quindi disabilitandole si garantisce che durante una sezione critica nessun processo possa prendere il controllo del sistema; Variabili di lock Questa soluzione prevede l impiego di una variabile di stato nella quale si memorizzi se il processo in una sezione critica o meno. Quando un nuovo processo tenta di entrare in una sezione critica, se tale variabile gi posta a 1 un errore verr notificato, altrimenti la variabile sar posta a 1 ed il processo continuer la propria esecuzione nella sezione critica. Si veda il listato 1 alla fine di questa lezione per ulteriori commenti; Alternanza stretta - L alternanza stretta consiste nel far si che due o pi processi che debbano lavorare in una sezione critica si diano il cambio tramite una variabile di stato, quando uno dei processi sta lavorando, l altro processo attende che il valore della variabile di stato cambi, e viceversa; La soluzione di Peterson G. L. Peterson nel 1981 scopr un metodo pi semplice per ottenere la mutua esclusione. Prima di utilizzare delle variabile condivise ogni processo chiama una funzione enter_region. Questa chiamata lo blocca finch non gli viene dato l accesso alla sezione critica. Una chiamata alla funzione leave_region lo far uscire dalla sezione critica. Il listato 2 riporta insieme ad un commento la soluzione di Peterson; Istruzioni TSL Nei sistemi multiprocessore, quando una cpu accede alla memoria esegue un istruzione TSL (Test and Set Lock) vietando alle altre cpu l del bus per accedere alla uso memoria. Questa soluzione, insieme alla soluzione di Peterson, ha per il problema di dare vita a degli enormi cicli di attesa, sprecando il tempo macchina di elaborazione; Sleep e wakeup sleep una chiamata di sistema che fa s che il chiamante si blocchi e resti quindi sospeso fino a che un altro processo non lo risveglia, mentre wakeup una chiamata di sistema che richiede come parametro il processo da svegliare. Questo permette di bloccare un processo e di dare la possibilit ad un altro, alla fine della sua sezione critica di richiamarne un altro. Semafori Nel 1965 E. Dijkstra sugger l di una variabile intera per contare il numero di uso segnali di risveglio. Questo tipo di variabile prendeva il nome di semaforo, ed indica il numero di wakeup che sono rimasti in attesa di essere elaborati. L insieme di quelle azioni che hanno come fine il controllo del semaforo, e una sua eventuale modifica devono essere raggruppati all interno di una singola operazione, indivisibile o atomica. Le principali azioni di un semaforo sono UP (ha il compito di incrementare il valore del semaforo, ed lo stesso valore di wakeup) e DOWN (ha il compito di controllare se il semaforo non ha un valore pari a 0, ed ha le funzioni di sleep).

La maggior parte dei s. o. oggi permettono ai processi di comunicare tra loro attraverso i messaggi. Le principali funzioni per garantire lo scambio dei messaggi sono due: SEND Invia un messaggio; RECEIVE Riceve un messaggio. Entrambe queste funzioni naturalmente richiedono il destinatario e il mittente del messaggio, ed un buffer

33 di 36

contenente il messaggio da ricevere o inviare. Unico inconveniente di questo sistema costituito dal mittente e dal destinatario, che normalmente vengono espressi sottoforma di valore numerico (corrispondente alldel processo, perci ogni volta differente), il quale deve essere conosciuto a priori dal il processo. Una probabile soluzione potrebbe essere data da una famigerata funzione chiamata RegisterProcess che registra una coda per lo scambio dei messaggi con un eventuale nome. Una eventuale funzione a sua volta chiamata FindProcess otterr l eventuale id del processo, favorendo lo scambio. Questa possibile soluzione presenta un solo piccolo difetto, se 2 processi completamente differenti hanno in comune il nome, chi ci assicura che il destinatario dei messaggi sia quello corretto? Una eventuale procedura di autenticazione potrebbe risolvere il problema, ma a questo punto lo scambio dei messaggi sta diventando sempre pi complesso.

Problemi di comunicazione interprocesso


Problema dei filosofi affamati Lo stesso Dijkstra formul e risolse un problema di sincronizzazione chiamato problema dei filosofi affamati. Esponiamo questo problema: 5 filosofi sono seduti attorno a un tavolo circolare; Ogni filosofo ha un piatto di spaghetti tanto scivolosi, che necessitano di 2 forchette per poter essere mangiati; Sul tavolo vi sono in totale 5 forchette. La vita di ogni filosofo scandita da due momenti distinti, un momento in cui esso pensa, ed un momento in cui esso mangia. Quando un filosofo ha fame, prende la forchetta che sta a sinistra e quella che sta a destra del suo piatto, mangia per un po poi mette sul tavolo le due forchette. Questa soluzione sarebbe fattibile se gli altri 4 e filosofi stanno pensando, ma se in un dato istante tutti i filosofi decidessero di mangiare prendendo tutti la forchetta a sinistra del loro piatto, e non trovando la forchetta a destra si avrebbe un blocco del sistema (deadlock). Un altro metodo sarebbe quello di prendere la forchetta a sinistra, e controllare se la forchetta a destra disponibile, quindi se non lo il filosofo mette gi la forchetta a sinistra, attendendo per un tempo casuale prima di ripetere l operazione. Questa soluzione anche inefficace, perch se i 5 filosofi decidessero nello stesso istante di prendere la forchetta a sinistra, essi non troverebbero la forchetta a destra, e nessuno di loro mangerebbe. L attesa casuale potrebbe risolvere il problema, ma una soluzione inefficace, non si pu pretendere che un fattore casuale determini il funzionamento di un sistema. Risolvere questo problema attraverso l impiego dei semafori molto pi semplice. Ad ogni filosofo viene assegnato un semaforo, e ogni qualvolta che esso decider di mangiare, viene controllato lo stato dei semafori che lo circondano. Se entrambe le forchette non sono disponibili, il filosofo viene bloccato, e non pu continuare la sua operazione. Problema dei lettori e degli scrittori A differenza del problema espresso precedentemente, forse questo problema pi comune a coloro che hanno a che fare con architetture client/server per la condivisione di dati. Immaginiamo la seguente situazione: In piena stagione un villaggio turistico pu ancora ospitare una famiglia; Due famiglie, situate in due citt completamente differenti, si recano contemporaneamente in due agenzie diverse, venendo a conoscenza del posto libero nel villaggio turistico; Decidono entrambe di prenotare, cos vengono inviate dai terminali delle agenzie le due richieste, dato che il posto risultava ancora disponibile al momento dell interrogazione del database remoto; Le due richieste vengono accodate in una pila dal database, che provvede ad elaborare i due segnali; Essendo entrambe richieste di scrittura sul database, il software blocca il record elaborando la prima richiesta; Il thread che ha il compito di effettuare la seconda richiesta, all accesso al record del database viene bloccato dal semaforo, essendo sotto elaborazione un altro processo; Il primo thread termina l elaborazione, invia un segnale DOWN al semaforo, che cos permetter l elaborazione al secondo thread; Il secondo thread, trovando occupato il record ritorner un errore.

Cosa sarebbe successo se non vi fosse stato un semaforo a regolare questa operazione ? Entrambi i thread

34 di 36

avrebbero soddisfatto la richiesta, mentre solo una delle due famiglie ha realmente prenotato il posto. Problema del barbiere addormentato Un altro problema il seguente: In una bottega un barbiere serve i proprio clienti. Nella bottega vi sono un numero N di sedie, ed un unico barbiere. Se nessun cliente nella bottega, il barbiere si mette a dormire sulla poltrona. Quando entra il cliente, la prima cosa che controlla se il barbiere sta dormendo oppure no. Se il barbiere sta dormendo, il cliente lo sveglia e viene servito. Altrimenti, se un altro cliente viene servito, il cliente appena entrato controlla che vi sia una sedia libera e si siede in attesa del suo turno, altrimenti abbandona la bottega senza essere stato servito. Dato che impossibile conoscere lo stato di un semaforo (una eventuale chiamata al semaforo per richiedere una particolare risorsa pu bloccare il processo nel caso questa risorsa sia gi impegnata) in questo problema viene utilizzata una variabile alternativa, che viene aggiornata dal semaforo e che contiene il numero di clienti in attesa, in modo da sapere a priori se vi disponibile un posto oppure no.

Scheduling dei processi


In un sistema operativo dove pi processi sono eseguibili, la parte del sistema che ha il compito di scegliere quale processo deve essere mandato in esecuzione prende il nome di scheduler e l algoritmo usato per prendere questa decisione prende il nome di algoritmo di scheduling. I criteri che stabiliscono cosa costituisce un buon algoritmo di scheduling sono i seguenti: Equit Assicurarsi che ogni processo riceva una parte del tempo della CPU; Efficienza Mantenere la CPU impegnata per il 100% del tempo; Tempo di risposta Minimizzare il tempo di risposta per gli utenti interattivi; Turnaround Minimizzare il tempo di risposta che gli utenti di processi batch devono attendere per il risultato; 5) Throughput Massimizzare il numero di job elaborati in ogni ora. 1) 2) 3) 4) Ovviamente, qualsiasi algoritmo si impieghi a favore di una particolare classe di job (punto 3, risposta agli utenti interattivi) danneggia un classe di job (in questo caso la 4, o viceversa). altra Gli algoritmi di scheduling in cui l esecuzione di un processo (job) viene interrotta per favorirne un altro prendono il nome di scheduling preemptive (con prerilascio, come accade in tutti i s. o. di oggi che girano su una singola cpu) mentre invece l algoritmo che provveder a soddisfare le richieste di un processo successivo soltanto quando il processo che in elaborazione finir il suo compito prende il nome di nonpreemptive scheduling. Questi ultimi algoritmi sono di pi facile implementazione, ma nell ipotesi che i due processi fossero due software per la generazione di un frattale, il secondo processo sarebbe avviato con un ritardo molto enorme da quando il processo stesso viene avviato. Analizzeremo adesso diversi algoritmi di scheduling. Scheduling round robin Questo algoritmo uno dei pi vecchi, ed uno degli algoritmi di scheduling preemptive di pi facile implementazione. Questo algoritmo consiste nel suddividere il tempo a disposizione della CPU in quanti o frazione di tempo da dedicare ad un processo. Lo scambio del processo prende il nome di context switch, e richiede un determinato periodo di tempo per essere effettuato. Se la durata di un quanto viene fissata in un periodo di tempo molto basso, si violerebbe il 2 punto esposto in alto (efficienza) perch lo scheduler arriverebbe a richiedere pi tempo di quanto non ne rilasci ai processi. Aumentare la durata di un quanto, invece diminuirebbe la quantit di tempo dedicata al context switch, ma allo stesso tempo violerebbe il punto 3 (tempo di risposta). La durata di un quanto deve essere stabilita in un determinato intervallo di tempo in modo che non si violi nessuno dei punti sopra esposti. Scheduling prioritario Questo algoritmo riprendere l algoritmo precedente, ma con una piccola differenza. Introduce oltre al concetto di quanto il concetto di priorit, dando cos la possibilit allo scheduler di eseguire a priori sempre quei processi con priorit maggiore, e di dedicare meno quanti ai processi con priorit inferiore. Onde evitare di dedicare il 100% del tempo ai processi pi importanti, allo scadere di ogni quanto lo scheduler pu aggiornare la priorit, in modo che dopo un certo numero di quanti i processi meno importanti abbiano la possibilit di essere eseguiti. Ogni qualvolta che un processo ha raggiunto la minima priorit assoluta, lo scheduler ripristina il suo livello a quello originale. Questa soluzione evita le attese a tempo indeterminato da parte di alcuni processi (starvation). Code multiple L adozione da parte degli scheduler di livelli di priorit danno vita a diverse classi, ognuna delle quali

35 di 36

raggruppa tutti i processi aventi la stessa priorit. Man mano che l esecuzione va avanti, i processi vengono declassati, fino a ritrovarsi nella classe pi bassa (o pi alta, a secondo delle regole di scheduling) e il suo compito non si esaurito. Ovviamente, l adozione di questo modello non pu essere rispettata per quei processi che hanno bisogno di interagire con l utente. In alcuni sistemi, questi processi vengono si de-classificati, ma un input proveniente dal terminale dove sono in esecuzione li riportano nella classe a maggior priorit di esecuzione. Questo modello, se adottato in sistemi distribuiti penalizzerebbe tutti quegli utenti che non stanno davanti al terminale, e soprattutto quei processi che forniscono soltanto un output, senza richiedere alcuna forma di input dopo il caricamento in memoria. Shortest job first

36 di 36