Caricare documenti e articoli online 
INFtub.com è un sito progettato per cercare i documenti in vari tipi di file e il caricamento di articoli online.


 
Non ricordi la password?  ››  Iscriviti gratis
 

APPUNTI DI SISTEMI DI ELABORAZIONE - IL PROCESSORE INTEL 80286

tecnica




APPUNTI DI SISTEMI DI ELABORAZIONE 1

PARTE DUE


CAPITOLO CINQUE: il processore INTEL 80286.......... ..... ...... .......... ..... ...... .......... ..... ...... ..................

5.1. Introduzione.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... .............

5.1.1 I registri principali.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ......

5.1.2 Indirizzamento della memoria.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... ............



5.2 Organizzazione della memoria.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... ...

5.2.1 Struttura del descrittore di segmento.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ......

5.2.2 Generazione degli indirizzi (1).......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... ..........

5.2.3 Gli attributi di un segmento.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... ................

5.2.4 Global Descriptor Table e Local Descriptor Table.......... ..... ...... .......... ..... ...... .......... ..... ...... .......

5.2.5 Generazione degli indirizzi (2).......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... ..........

5.3 Meccanismi di protezione.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... ...............

5.3.1 Livelli di privilegio.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... .............................

5.3.2 Codici di privilegio.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... ............................

5.3.3 Regole di protezione per l' 80x86.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... ....

5.3.4 Descrittori di controllo.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... ......................

5.3.5 Descrittori di flusso (Gate Descriptor).......... ..... ...... .......... ..... ...... .......... ..... ...... ..........................

Descrittore di oggetto del sistema

5.3.7 Riassunto sui tipi di descrittore.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... .......

5.3.7 Chiamate a subroutine intratask.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... .....

5.3.8 Coinvolgimento dello stack nei trasferimenti di controllo.......... ..... ...... .......... ..... ...... ......................

5.3.9 Restrizione del privilegio.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... ..................

5.3.10 Task management e trasferimenti intertask.......... ..... ...... .......... ..... ...... .......... ..... ...... ................

5.3.11 Task nesting.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... ........

5.4 I/O Privilege Level.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... ...

5.5 Gestione degli interrupt e delle trap.......... ..... ...... .......... ..... ...... .......... ..... ...... ....................

5.5.1 Interrupt e 929h76j trap.......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... .......... ..... ...... .....

5.5.2 Il ruolo dello stack nella gestione di trap e interrupt.......... ..... ...... .......... ..... ...... .......... ..... ......


CAPITOLO CINQUE: il processore INTEL 80286



5.1. Introduzione


Ogni microprocessore INTEL dal 286 al Pentium contiene due architetture distinte e incompatibili. Da una parte il comportamento compatibile con l'8086, dall'altra la capacità di gestire il multitasking mediante meccanismi di protezione della memoria.

Il 286 (come 386, 486 e Pentium) ha quindi due modi di funzionamento:

-modo REALE, compatibile con l'8086

-modo PROTETTO

In modo protetto può indirizzare 1 Gbyte di memoria logica, 16 Mbyte di memoria fisica (24 fili di indirizzo), offre meccanismo per il multitasking, attua la segmentazione della memoria.

Nel 286 il DMA controller è integrato e questo, a differenza dell' 8086, ha un solo canale (il secondo canale nell'8086 serviva per la comunicazione con il coprocessore matematico, che qui è visto come un dispositivo di IO).


5.1.1 I registri principali


Sono presenti più registri di segmento rispetto all'8086, e di conseguenza il formato delle istruzioni è più lungo (come vedremo più avanti, dal 286 in poi il codice operativo di un'istruzione assembler non individua univocamente la semantica di tale istruzione: la semantica cambia a seconda del prefisso dell'istruzione, cioè del segmento al quale si applica).


IP

 










Figura 1: i registri del 286

Il registro dei FLAG del 286 è del tutto compatibile con quello dell'8086, ma contiene 3 bit nuovi:





NT: Nested Task (1 bit)= viene settato automaticamente quando si verifica un task switch che implica un ritorno (CALL o interrupt)

IOPL: I/O Privilege Level (2 bit): indica il livello di privilegio minimo che il codice in esecuzione deve possedere per poter eseguire un'istruzione di I/O.

La funzione di entrambi questi flag sarà chiarita in seguito.


Nel 286 il registro MSW (Machine Status Word)  contiene 4 bit importanti:






TS : task switch = viene settato a 1 ogni volta che avviene uno switch di task

PE : protected enable = se vale 1 il processore passa al modo protetto (settabile via software); se vale 0 il processore è in modo reale (resettabile solo via hardware). Vedi fig.2.


EM MP

0 0 Non c'è il coprocessore mat.; NO emulazione

1 0 Non c'è il coprocessore mat.; SI emulazione

1 C'è il coprocessore mat.


Simulazione software del coprocessore: quando il microprocessore incontra un'istruzione per il coprocessore (istruzioni che iniziano con un carattere di ESCAPE), salta a una routine di calcolo.


Figura 2: switch tra modo protetto/reale

5.1.2 Indirizzamento della memoria


L'indirizzo fisico su 24 bit viene calcolato in modo indiretto a partire dall'indirizzo logico su 30 bit; l'indirizzo logico viene ottenuto mediante giustapposizione del registro di segmento (selettore nella terminologia 286) di 16-2=14 bit, con il registro di offset di 16 bit.


Figura 3: l'indirizzo logico


I due bit "mancanti" hanno uno scopo differente che sarà chiaro in seguito.

I 14 bit del selettore costituiscono un  indice in una certa tabella, nella quale si trova l'indirizzo fisico (su 24 bit) del segmento; a questo indirizzo viene ancora sommato l'offset per avere l'indirizzo effettivo.








Figura 4: calcolo indirizzo


5.2 Organizzazione della memoria


Distinguiamo innanzitutto tra memoria logica e memoria fisica. La prima è quella indirizzabile tramite l'indirizzo logico su 30 bit, la seconda è quella indirizzabile dal bus di 24 bit (quella presente effettivamente nel sistema può essere di meno). Di seguito ci riferiremo sempre alla memoria logica, a meno che non sia indicato diversamente.

Il 286 attua la segmentazione della memoria: ogni segmento è lungo 2^16 byte = 64 Kbyte e possono esserci fino a 2^14 = 16K segmenti, per un totale di 2^30 = 1 Giga byte.  I segmenti sono tra di loro adiacenti e ciascuno di essi possiede una serie di attributi. Nell'ottica a oggetti, ogni segmento è un oggetto con certi attributi.


8 byte

 

64 Kbyte

 
Mem.logica  tabella descrittori

A ciascun segmento di memoria è associato un oggetto di tipo descrittore, di 8 byte, che raccoglie le caratteristiche e gli attributi del segmento. Quindi abbiamo 2^14 descrittori di segmento che stanno dentro una tabella (la tabella citata nel paragrafo precedente). A questa tabella si accede sempre con l'indirizzo scritto nel selettore di segmento (appunto di 14 bit).


Esistono anche descrittori che non sono relativi a segmenti di memoria, ma ad "oggetti" di sistema, cioè descrivono certe strutture importanti del sistema, per esempio la tabella dei descrittori di segmento.


Esiste un terzo tipo di oggetto descrittore, chiamato GATE e riguarda i meccanismi di controllo per il passaggio tra procedure all'interno di un processo utente (intratask) o tra processi utente (intertask).


Approfondiremo nei paragrafi successivi il ruolo di questi descrittori nella gestione della memoria.



5.2.1 Struttura del descrittore di segmento



Incominciamo a vedere in questo paragrafo la struttura di un descrittore di segmento.








Figura 5: struttura di un descrittore di segmento


RESERVED: questa word non contiene nulla nel 286, ma dal 386 in poi contiene informazioni sull'architettura a 32 bit. Per assicurare la compatibilità con il 386, occorre che questa word sia sempre posta a zero.

Il terzo byte ha una struttura che rimane costante in ogni descrittore di segmento (vedi fig.6).


 

Figura 6: byte 3 del descrittore


P : Present = vale 1 se il segmento corrispondente è presente nella memoria fisica

DPL: Descriptor Privilege Level = indica il livello di privilegio del segmento corrispondente (ne parleremo più avanti)

Il bit 3 indica il tipo di descrittore: se vale 1 si tratta di un descrittore di segmento

TYPE = contiene gli attributi del segmento (ne parliamo tra poco)

A : Accessed = vale 1 se è avvenuto un accesso al segmento


BASE : questi 24 bit contengono l'indirizzo fisico di base del segmento

LIMIT : questi 16 bit indicano la quantità di memoria occupata nel segmento


5.2.2 Generazione degli indirizzi (1)


Vediamo come un descrittore di segmento viene coinvolto nella generazione dell'indirizzo fisico.

Consideriamo ad esempio la seguente istruzione assembler:


MOV AX, [BX]


In questa istruzione, il selettore implicito è DS; tramite DS accediamo alla tabella dei descrittori, andando a selezionare un preciso descrittore di segmento.


Figura 7: calcolo dell'indirizzo fisico


Qui leggiamo i dati relativi al segmento selezionato e in particolare BASE e LIMIT. Se l'offset relativo a [BX] non eccede il limite, allora si somma BASE con OFFSET, in modo da ottenere l'indirizzo fisico da mandare sull'Address bus.

Questa operazione è preliminare a qualunque ciclo di bus, compresi i cicli di fetch. Nota che l'accesso alla tabella dei descrittori comporta 4 cicli di bus (deve leggere 4 word e ha parallelismo 16), quindi l'unico modo per mantenere il sistema efficiente consiste nell'utilizzare una cache.

Conviene che i descrittori dei segmenti attivi siano memorizzati in una cache interna al 286, in modo che i dati relativi siano immediatamente disponibili. Nel 286 possiamo avere al massimo 4 segmenti attivi, perché abbiamo 4 selettori : CS, DS, ED, SS. Quindi per ciascuno di questi registri c'è una cache associata di 6 byte che memorizza un descrittore (vedi fig. 8).


  Figura : i selettori con relative cache


Ogni volta che uno di questi registri viene modificato, la CPU si occupa automaticamente di caricare nella cache corrispondente il nuovo descrittore di segmento.

5.2.3 Gli attributi di un segmento


Analizziamo meglio quel byte all'interno del descrittore di segmento che descrive gli attributi e le caratteristiche del segmento (vedi fig.6 e fig.9).



  Figura : attributi di un segmento di codice



L' uno a sinistra di C indica che si tratta di un descrittore di segmento di codice.

L' uno a destra di DPL indica che si tratta di un descrittore di segmento (se fosse zero sarebbe un descrittore di controllo, che vedremo più avanti)

R = 1 se è leggibile anche in cicli di bus che non siano cicli di fetch (non è mai scrivibile !)

C = Conforming: bit utile nella gestione dei meccanismo di privilegio (lo vediamo più avanti)


  Figura : attributi di un segmento di dati



Lo zero a sinistra di ED indica che si tratta di un segmento di dati

L' uno a destra di DPL indica che si tratta di un descrittore di segmento

ED = indica la direzione di espansione del contenuto del segmento (quindi se è lo stack o un normale segmento dati)

W = 1 se il segmento è anche scrivibile (è sempre e comunque leggibile)


5.2.4 Global Descriptor Table e Local Descriptor Table


Finora abbiamo parlato della tabella dei descrittori di segmento come se fosse l'unica presente nel sistema. In realtà la situazione è più complicata e l'affrontiamo in questo paragrafo.

Per gestire più processi utente in multitasking, la memoria è organizzata nel seguente modo.

Innanzitutto si distingue tra memoria di sistema e memoria utente: la prima è riservata ai processi di sistema, la seconda ai processi utente. La memoria di sistema ha una dimensione (logica) di 0.5 Gbyte ed è suddivisa in 2 =8192 segmenti. Ogni processo utente ha a disposizione una memoria logica di 0.5 Gbyte, anch'essa divisa in 8192 segmenti.



Sia nella memoria di sistema che nella memoria utente, per ogni segmento esiste un descrittore, e questi descrittori sono raccolti in tabelle. La tabella che raccoglie gli 8192 descrittori della memoria di sistema si chiama Global Descriptor Table (GDT). Le tabelle che raccolgono gli 8192 descrittori di ogni processo utente si chiamano Local Descriptor Table. A sua volta, ciascuna delle LDT ha un descrittore memorizzato nella GDT (vedi fig.11).


Il processo di sistema si trova sempre nella memoria fisica, essendo sempre attivo, mentre i processi utente si trovano in parte nella memoria fisica e in parte su disco. Analogamente la GDT si trova sempre in memoria fisica, mentre le varie LDT possono trovarsi anche su disco.


Il registro GDTR (Global Descriptor Table Register) è lungo 24+16 bit perché contiene l'indirizzo fisico di base della GDT (e sono 24 bit), più la dimensione della GDT (e sono 16 bit).


GDTR

 

















Figura 11: modello di memoria logica



Il registro LDTR (Local Descriptor Table Register) è lungo invece 16 bit e contiene l'indirizzo logico del descrittore della LDT corrente.

Il registro GDTR contiene l'indirizzo fisico su 24 bit appunto perché la GDT si trova sempre in memoria fisica. Invece il registro LDTR contiene un indirizzo logico di 16 bit, il quale è un indice che punta a un descrittore presente nella GDT. Questo descrittore descrive la LDT attiva in quel momento. Per rendere più rapidi gli accessi, tale descrittore è sempre caricato nella cache di 6 byte associata al registro LTDR:


Figura : il registro LDTR


5.2.5 Generazione degli indirizzi (2)


Per fissare le idee, rivediamo ancora la generazione degli indirizzi, che risulta essere un po' più complicata rispetto al paragrafo 5.2.2. Riprendiamo l'analisi della struttura del selettore: si era detto che i primi 14 bit formano l'indice che punta al descrittore voluto; in realtà l'indice è formato solo da 13 bit, che corrispondono a 8192 possibili descrittori. Il 14° bit (bit 13 nella figura  13), indica se bisogna cercare il descrittore nella GDT o nella LDT del processo attivo.


Figura 13

TI=0 accede alla GDT

TI=1 accede alla LDT attiva


Esaminiamo il caso in cui TI = 0.

Con i 13 bit prelevati dal selettore si forma un indice che seleziona uno degli 8192 descrittori della GDT. Per ottenere l'indirizzo fisico del descrittore, il numero D va moltiplicato per 8 e sommato all'indirizzo fisico di base della tabella, contenuto nel registro GDTR  (vedi fig.14).












Figura 14: calcolo indirizzo in GDT


Una volta ottenuto il descrittore di segmento, per ottenere l'indirizzo fisico del segmento si procede come già visto nel par.5.2.2.


Esaminiamo il caso in cui TI = 1; in questo caso occorre fare un passaggio in più.

Noi vogliamo ottenere un certo descrittore di segmento; questo descrittore si trova nella LDT del processo attivo; quindi dobbiamo conoscere l'indirizzo di base della LDT attiva e l'indice, all'interno di questa, del descrittore desiderato. L'indice è scritto nei 13 bit del selettore, mentre l'indirizzo di base della LDT attiva si trova nel descrittore della LDT attiva (ogni LDT ha un descrittore) e questo, infine, si trova dentro la GDT (fig.15).


Descrittore di segmento desiderato

 









Figura 15: trovare la LDT attiva


Per raggiungere il descrittore della LDT attiva ci occorre l'indirizzo di base della GDT e l'indice, all'interno di questa, per accedere al descrittore della LDT attiva. L'indirizzo fisico di base della GDT si trova nel registro GDTR, mentre l'indice si trova nel registro LDTR (fig. 16). Utilizzando queste due informazioni otteniamo il descrittore della LDT attiva. Tramite questo descrittore otteniamo l'indirizzo di base della LDT attiva. Avendo questo possiamo raggiungere il descrittore di segmento desiderato.









Figura 16: calcolo indirizzo in LDT


Come abbiamo visto, il descrittore della LDT attiva si trova nella cache associata al registro LDTR, il cui contenuto è aggiornato ogni volta che si cambia processo attivo: questo significa che occorre accedere alla GDT solo quando avviene un cambio di processo attivo.


5.3 Meccanismi di protezione


I meccanismi di protezione nei processori 286, 386,... sono realizzati mediante:


1- gli attributi nei descrittori di segmento

2- i livelli di privilegio


Incominciamo a vedere cosa sono i livelli di privilegio.

5.3.1 Livelli di privilegio


Esistono 4 livelli di privilegio, da 0 a 3, in ordine decrescente di privilegio (0=max privilegio).

Le due regole che seguono sono i pilastri dell'intero meccanismo di protezione:


R1 (accessibilità dei dati): un dato è accessibile solo da task aventi lo stesso livello o un livello maggiore del livello del dato (ragionando con i valori numerici: privilegio task privilegio dato)

R2 (integrità del sistema): le subroutine possono essere chiamate solo da task allo stesso livello o ad un livello minore (numericamente : livello chiamante livello chiamato)


Ogni oggetto (segmento, tabella di descrittori, controllo, subroutine di un processo...) ha un livello di privilegio; quando un oggetto accede o usa un altro oggetto, avviene sempre un confronto tra i livelli di provilegio per stabilire se l'operazione è lecita.


5.3.2 Codici di privilegio


I codici che definiscono i privilegi sono 3 e sono memorizzati in punti diversi:


DPL (Descriptor Privilege Level) si trova in quel byte di attributi all'interno dei descrittori (vedi fig. 6) e indica il livello di privilegio dell'oggetto a cui il descrittore si riferisce.

CPL (Current Privilege Level) indica il privilegio del  processo correntemente attivo e si trova in quei due bit del selettore CS che non vengono usati nella generazione dell'indirizzo


Figura 17: i bit di privilegio


RPL (Register Privilege Level) indica il privilegio richiesto e occupa quei due bit del selettore (diverso da CS) che non vengono utilizzati nella generazione dell'indirizzo.


Per capire come avvengono i controlli sui livelli di privilegio vediamo alcuni esempi (la funzione di RPL sarà chiarita più avanti).


Esempio 1


MOV BX, cost

MOV ES, BX 


La seconda istruzione modifica il selettore ES, quindi la cache collegata a ES dovrebbe essere aggiornata con il descrittore a cui punterebbe il nuovo valore di ES.

Ma prima di eseguire questa operazione, la CPU esamina il descrittore che dovrà prendere il posto

di quello attuale, e in particolare va a leggere il valore DPL in esso contenuto; inoltre legge il CPL contenuto nel selettore CS; infine esegue il confronto tra CPL e DPL.

Se DPL CPL, allora significa che il privilegio del processo corrente è maggiore del privilegio del segmento di memoria a cui accede, e questo fatto rispetta la regola R1. Se invece DPL CPL, la CPU genera una trap.


Esempio 2


CODE SEGMENT code

ASSUME CS:CODE

var1 DD 1

start: MOV AL, var1 (1)

MOV var1,AH (2)

CODE ENDS


L'istruzione (1) viene eseguita solo se il segmento di codice in cui si trova var1 è leggibile (attributo R posto a 1) e se DPL CPL.

L'istruzione (2) non viene MAI eseguita perchè non è possibile scrivere sul segmento di codice.


5.3.3 Regole di protezione per l' 80x86


Riassumiamo qui le regole fondamentali di protezione per i processi 80x86.


Il livello di privilegio di un processo in esecuzione coincide con il valore del CPL

Lo stack deve avere un livello uguale a quello del codice. Questo implica che per ciascun processo devono esistere 4 stack distinti, uno per livello di privilegio. Infatti lo stack non può essere una struttura unica, quindi visibile dai 4 livelli di privilegio, perché tale situazione violerebbe la regola R1, la quale vieta ad un task di accedere a dati di livello superiore al suo. In altre parole, i dati posti nello stack da una subroutine di livello 0 sarebbero alterabili da una subroutine di livello 3. Quando avviene una CALL ad una subroutine di livello differente da quello attuale, l'hardware aggiorna automaticamente il registro SS, in modo che punti allo stack opportuno (lo vediamo meglio più avanti).

Un dato è accessibile solo da task aventi lo stesso livello o un livello maggiore del livello del dato

Controllo del flusso: possono essere chiamate in modo diretto solo le procedure di uguale livello all'interno dello stesso task; per chiamare procedure di livello diverso oppure appartenenti a task diversi, occorre fare un salto indiretto attraverso una struttura chiamata GATE. Questo salto viene eseguito solo in seguito a controlli sui livelli di privilegio. Esistono due tipi di salto: intra e inter task. Nel primo caso si tratta di un salto ad una subroutine di diverso livello all'interno dello stesso processo (task), nel secondo caso di un salto ad una subroutine di un processo differente (vedi fig.18).


I gate sono particolari descrittori che regolano le chiamate a subroutine di livello diverso o appartenenti a task diversi. Essi fanno parte della categoria dei descrittori di controllo, descritti nel prossimo paragrafo.


a=salto diretto: intratask e intralevel

b=intratask e interlevel (gate)

c=intertask e interlevel (gate)

 









Figura 18: tipi di salto

5.3.4 Descrittori di controllo


Abbiamo già esaminato i descrittori di segmento (vedi par. 5.2.1 e 5.2.3); esaminiamo qui i descrittori di controllo perché entrano in gioco nei meccanismi di protezione.

I descrittori di controllo si dividono in due categorie: i descrittori di flusso e i descrittori di oggetti di sistema.

Tra i descrittori di flusso (o Gate Descriptor) abbiamo:

- CALL GATE (coinvolti nelle chiamate intratask)

- TASK GATE (coinvolti nelle chiamate intertask)

- INTERRUPT GATE (coinvolti nelle chiamate su interrupt)

- TRAP GATE (coinvolti nelle chiamate su trap)

Tra i descrittori di oggetti di sistema abbiamo:

-LDT DESCRIPTOR (descrittore di una tabella locale dei descrittori)

-TSS DESCRIPTOR (descrittore dello stato di un task)

Ricorda che né la GDT né il vettore delle interruzioni hanno un descrittore perché sono sempre presenti in memoria fisica.


5.3.5 Descrittori di flusso (Gate Descriptor)









Figura 19: struttura del Gate descriptor


Esaminiamo i bit del generico descrittore di flusso:


P (1 bit) = 0/1 : il contenuto del descrittore non è valido/è valido

DPL (2 bit): Descriptor Privilege Level del gate

bit a 0: indica che si tratta di un descrittore di controllo e non di segmento

Type (4 bit):

4=Call Gate

5=Task Gate

6=Interrupt Gate

7=Trap Gate

Destination SELECTOR (14 bit): contiene l'indirizzo logico del descrittore del segmento di codice in cui si trova la subroutine chiamata; se type = 5, contiene l'indirizzo logico di un descrittore di TSS (sarà chiaro più avanti)

Destination Offset (16 bit) : punto di ingresso nel codice di segmento della subroutine chiamata (non usato se type=5)

Word Count (5 bit): contiene il numero di word da copiare dallo stack del chiamante nello stack del chiamato (usato solo se type = 4)

La funzione di questi campi sarà chiarita nei prossimi paragrafi, quando vedremo in quali meccanismi di protezione sono coinvolti i gate.


Descrittore di oggetto del sistema


Esaminiamo i bit del generico descrittore di sistema (vedi fig. 20):


P (1 bit) = 0/1: oggetto di sistema non presente /presente in memoria fisica

Base (24 bit):

Se type = 2: è l'indirizzo fisico di base di una tabella dei descrittori locali (LDT)

Se type = 1 o 3: indirizzo fisico di base del Task State Segment del task attivo

Limit (16 bit)

Se type = 2: limite superiore della tabella dei descrittori locale (LDT)

Se type =1 o 3: limite superiore del TSS


 

Figura 20: struttura del descrittore di sistema


Type:

1 = TSS disponibile

2 = LDT Descriptor

3 = TSS non disponibile

(Tipo 0 e da 8 a 15 : descrittore non valido)


La struttura TSS e le sue funzionalità verranno descritti più avanti.

5.3.7 Riassunto sui tipi di descrittore


Conviene a questo punto riassumere le idee sui vari tipi di descrittore visti finora, aiutandoci con lo schema di fig. 21.

















Figura 21: tipi di descrittori


5.3.7 Chiamate a subroutine intratask


Analizziamo in questo paragrafo quali meccanismi di protezione entrano in gioco nel momento in cui, durante l'esecuzione, avviene una chiamata ad una subroutine di livello di privilegio superiore rispetto al livello del chiamante (le chiamate verso livelli inferiori sono vietate, regola R2).

Immaginiamo per esempio di avere un'istruzione CALL che chiama una subroutine di livello 1, mentre il codice in esecuzione sta a livello 3. Siccome parliamo di chiamate intratask, il descrittore di controllo che viene coinvolto è il Call Gate. In effetti, l'indirizzo scritto dopo l'istruzione CALL è l'indirizzo logico (su 14 bit) di un Call Gate descriptor, e non l'indirizzo della prima istruzione della subroutine chiamata. Il Call Gate descriptor in questione contiene l'indirizzo logico (campo Destination selector, 14 bit) del descrittore di segmento di codice in cui si trova la subroutine chiamata.



 

Code Segment

Descriptor

 

 

 

 

CALL GATE

Descriptor

 








Figura 22: salto inter livello


Quindi, tramite il descrittore di segmento, viene calcolato l'indirizzo fisico, utilizzando come offset il contenuto del campo Destination offset (16 bit) nel Gate descriptor.


Figura 23: calcolo indirizzo tramite gate


NOTA: l'istruzione CALL SEG:OFF è composta dall'indirizzo del segmento e dalll'offset. Quest'ultimo è importante solo se la subroutine chiamata si trova allo stesso livello del chiamante. In questo caso, infatti, il processore utilizza l'indirizzo SEG per rintracciare il descrittore di segmento relativo e poi l'offset per calcolare l'indirizzo fisico all'interno del segmento. Se invece la subroutine chiamata si trova a un livello di privilegio superiore (inferiore è impossibile per la regola R2), il processore automaticamente utilizza SEG (i suoi 14 bit) per rintracciare il Gate descriptor. All'interno del Gate descriptor troverà l'offset (Destination offset). In questo caso non ha importanza il valore di OFF nell'istruzione.


Le istruzioni che implicano trasferimenti di controllo sono : JMP, CALL e RET, quindi il discorso che facciamo vale per tutte e tre le istruzioni. Esaminiamo solo i trasferimenti di tipo FAR, perché i trasferimenti di tipo NEAR non implicano l'uscita dal segmento.

Abbiamo appena visto il ruolo del Gate descriptor nelle chiamate a procedure intratask; analizziamo più in dettaglio le condizioni da rispettare e le verifiche sul livello di privilegio che vengono eseguite in questo tipo di chiamata. Vediamo, per incominciare, un esempio di trasferimento tra subroutine di pari livello.


Istruzione:   JMP SEG:OFF


OFF: offset all'interno del segmento indicato da SEG



CPL: livello di privilegio del codice attualmente in esecuzione.


Figura 24

RPL : supposto livello di privilegio del segmento di codice verso cui avviene il salto (si parla anche di livello di privilegio richiesto).

Come abbiamo detto, quando il salto non implica un cambio di livello, il Gate descriptor non viene chiamato in causa (infatti nella figura precedente SEG è direttamente l'indirizzo del descrittore di segmento).

Siccome stiamo parlando di una chiamata di uguale livello, deve essere soddisfatta la seguente relazione:  

CPL = RPL = DPL


Vediamo ora il caso più interessante, cioè una chiamata a una procedura di livello superiore.


Istruzione:   CALL SEG:OFF


I livelli di privilegio che entrano in gioco sono questi:

CPLatt : livello di privilegio del codice attualmente in esecuzione

RPL: livello di privilegio massimo che può avere il Gate descriptor

DPLg : livello di privilegio del Gate descriptor

DPLs : livello di privilegio del descrittore di segmento

CPLf : livello di privilegio del codice che viene chiamato



Regola da rispettare per accedere al Gate descriptor :


MAX (CPLatt , RPL ) DPLg


Cioè la subroutine chiamante deve avere priorità maggiore o uguale del Gate descriptor (regola R1) e contemporaneamente il livello di privilegio richiesto deve essere maggiore o uguale a quello del Gate descriptor (ricorda che i livelli di privilegio sono numericamente invertiti rispetto al grado di importanza).


Regola da rispettare per accedere al codice:


DPLs CPLatt   


Cioè la subroutine chiamata deve avere priorità maggiore della subroutine chiamante (regola R2).


Figura 25: salto inter livello e regole di protezione



Se entrambe le regole sono soddisfatte, avviene questo aggiornamento:


CPLf = DPLs


in altre parole il nuovo CPL assume il livello di privilegio del codice chiamato.


Se invece il bit di Conforming presente nel descrittore di segmento (vedi par. 5.2.3) è settato a uno, allora avviene questo aggiornamento:


CPLf = CPLatt


cioè il livello di privilegio del codice chiamato si adatta al livello del codice chiamante. L'utilità del bit Conforming sarà chiarita più avanti.

Illustriamo i concetti appena esposti con alcune figure che mostrano i 4 livelli di privilegio e i vari descrittori coinvolti nella chiamata intratask.


Figura 26: esempi corretti


Figura 27: esempi errati


Le chiamate intertask seguono un meccanismo abbastanza diverso e più complesso. In questo caso il descrittore coinvolto è un Gate descriptor di tipo 5, cioè un Task Gate. Prima di analizzare questo tipo di chiamata, occorre vedere altri concetti che sarannno utili per la comprensione dei trasferimenti intertask.


5.3.8 Coinvolgimento dello stack nei trasferimenti di controllo


Riprendiamo per un attimo la struttura del Call Gate descriptor, per spiegare la funzione di un particolare campo: il WORD COUNT.


Gate descriptor:Call Gate

Il campo WORD COUNT (5 bit) indica il numero di word che occorre trasferire dallo stack del chiamante allo stack del chiamato. Abbiamo detto che esiste uno stack per ogni livello di privilegio (quindi 4 stack), e una chiamata ad una subroutine di livello superiore implica un cambiamento di stack, e di conseguenza un aggiornamento di SS e SP, oltre, naturalmente all'aggiornamento dei registri CS e IP.


Per essere più precisi, sullo stack del chiamato vengono memorizzati in questo ordine:

i valori dei registri SS e SP al momento della chiamata (OLD SS e OLD SP)

gli eventuali parametri passati al chiamato

i valori dei registri CS e IP al momento della chiamata (che qui indichiamo con le diciture OLD CS e OLD IP)


Figura 28: funzione autocopy attiva

La funzione autocopy è attivata automaticamente insieme all'aggiornamento dei registri CS, IP, SS, SP e copia i parametri presenti sullo stack del chiamante nello stack del chiamato. A questo punto, il chiamato può copiare i parametri nei suoi registri, mediante una serie di MOV:


MOV AX, [SP+6]

MOV BX, [SP+8]

MOV CX, [SP+10]


Se invece la funzione di autocopy non è utilizzata, il chiamato deve copiare i parametri dal vecchio stack in via indiretta, utilizzando OLD SS e OLD SP (fig. 29):


LDS BX, SP[6]   (carica in BX OLD SP)

MOV AX,[BX+2]  (copia il primo parametro in AX)

MOV CX,[BX+4]  (copia il secondo parametro in CX)

MOV DX,[BX+6]  (copia il terzo parametro in DX)












Figura 29: struttura degli stack


Occorre notare che il trasferimento interlivello implica l'aggiornamento di SS e SP, e quindi i vecchi valori vengono salvati sullo stack, mentre i trasferimenti intralivello non necessitano del cambio di stack, di conseguenza SS e SP non vengono salvati.


Abbiamo visto come viene coinvolto lo stack in una chiamata ad una subroutine di livello uguale o superiore. Cosa succede quando la routine termina e il processore incontra un'istruzione di ritorno?

L'istruzione di ritorno ha la seguente forma:


RET n


dove n è opzionale e indica il numero di elementi che devono essere tolti dallo stack al momento del ritorno.

Il processore deve essere in grado di sapere se si tratta di un ritorno interlivello o intralivello. Nel primo caso occorre ripristinare i registri CS, IP, SS e SP, mentre nel secondo caso si aggiornano solo CS e IP.

Per decidere, è possibile leggere il contenuto di OLD CS sullo stack (vedi figg.28-29), e in particolare i due bit che formano il CPL.


Come sappiamo. questi due bit contengono il livello di privilegio del chiamante.



Se il CPL del chiamante è diverso dal CPL attuale, significa che è avvenuta una chiamata interlivello, quindi occorre ritornare allo stack del chiamante, cioè ripristinare i puntatori allo stack: i valori OLD SS e OLD SP vengono quindi copiati nei registri SS e SP.


5.3.9 Restrizione del privilegio


Per capire il vantaggio di avere un meccanismo per la restrizione del livello di privilegio, vediamo un esempio in cui una subroutine riesce ad accedere a un segmento dati di livello superiore, infrangendo così la regola R1.

Poniamo di avere due subroutine, una di livello 3 e un'altra di livello 0. Quest'ultima può accedere a un segmento dati di livello 1:


La procedura a livello 3 chiama, tramite Call gate, la procedura di livello 0, passandole come parametro l'indirizzo della tabella di dati D. La procedura di livello 0 accede alla tabella e restituisce i risultati tramite stack alla procedura di livello 3.



Vediamo due possibili soluzioni a questo problema.



Soluzione 1

Si utilizza il bit di Conforming presente del descrittore di segmento della subroutine chiamata. Ponendo questo bit a uno, il CPL del chiamato sarà forzato al CPL del chiamante (vedi par. 5.3.7). Nell'esempio, dopo la chiamata, la procedura di livello 0 non assume come livello di privilegio il valore 0, bensì si adatta al CPL del chiamante, in questo caso assume il valore 3. A questo punto non è più in grado di accedere alla tabella dati di livello 1.

Tale soluzione porta la subroutine chiamata ad avere la stessa visibilità della subroutine chiamante nei confronti di qualsiasi altro oggetto. Questa forte limitazione rende la soluzione molto inefficiente. Sarebbe meglio poter restringere la visibilità di una procedura soltanto a certi oggetti specificati.


Soluzione 2

Vediamo allora un modo per ottenere una restrizione di privilegio limitata e temporanea.

A questo scopo è necessario utilizzare l'istruzione:


ARPL < sel #1 > , < sel #2 >


dove ARPL = Adjust RPL

sel #1 , sel #2 = sono due selettori


Questa istruzione confronta i campi RPL dei due selettori (o il campo CPL nel caso in cui il selettore sia CS); se RPL < RPL , pone RPL = RPL e ZF = 1, altrimenti pone ZF = 0. In altre parole, questa istruzione serve ad abbassare il livello di privilegio del primo selettore (numericamente RPL1 aumenta).

Vediamo un esempio:


ES = .......01   RPL = 1 (privilegio maggiore)

CS = ......10    RPL = 2 (privilegio minore)


Se eseguiamo queste linee di codice:


MOV AX, ES

ARPL AX, CS


il risultato che otteniamo è AX = .......10, cioè  è diminuito il privilegio del selettore ES, dal valore 1 al valore 2.


Vediamo come è possibile utilizzare ARPL per restringere il privilegio di una procedura nei confronti di certi oggetti. Riprendendo il nostro esempio, vogliamo fare in modo di restringere la visibilità della procedura di livello 0 nei confronti della tabella dati D. Poniamo che, ad un certo punto della subroutine di livello 3, ci sia una chiamata alla procedura di livello 0.


procedura chiamante (livello 3)


[altre istruzioni]


CALL SEG:OFF (chiamata alla procedura di livello 0)


La procedura chiamante, tramite lo stack, passa l'indirizzo logico della tabella dati D alla procedura chiamata.












Figura 30: struttura dello stack risultante


La procedura chiamata deve copiare il parametro nei propri registri, quindi esegue le seguenti linee di codice.

MOV AX, [SP+6]

Carica in AX il parametro, cioè il selettore della tabella

ARPL AX,[SP+4]




Confronta RPL del selettore della tabella con il CPL di OLD  CS (livello del chiamante). Nell'esempio, RPL selettore = 1 e CPL di OLD CS = 3, quindi forza RPL del selettore a 3, cioè otteniamo AX = ......11

MOV DS, AX

Carica il selettore modificato in DS, in modo che DS punti alla tabella dati


A questo punto, nel momento in cui il chiamato vuole accedere alla tabella, avviene la solita verifica:


MAX (CPL, RPL ) DPL


Nell' esempio:   MAX ( 0 , 3 ) è FALSO, quindi l'accesso alla tabella è negato.


NOTA: Si definisce EPL (Effective Privilege Level) in questo modo:




EPL = MAX (CPL, RPL)


5.3.10 Task management e trasferimenti intertask


Il processore 80286 e successivi offrono dei meccanismi hardware per la gestione automatica del task switching. La struttura dati utilizzata è il Task State Segment (TSS). Esiste un TSS per ogni processo e per ogni TSS esiste un descrittore che ne raccoglie le caratteristiche. Abbiamo già visto questo descrittore di sistema nel par. 5.3.6; la fig. 31 ne mostra la struttura.

Il campo TYPE del descrittore indica se il task è disponibile (type = 1) o non disponibile (type = 3); nel primo caso il task può essere attivato da quello corrente e può avvenire un task switch, nel secondo caso no (vediamo più avanti la sua utilità).








Figura 31: TSS descriptor


Il TSS entra in gioco ogni qualvolta si verifica un trasferimento di controllo da un task a un altro (trasferimento intertask), per mezzo di una CALL, JMP, interrupt o trap. In ogni caso, questo tipo di trasferimento avviene tramite un Gate descriptor di tipo Task Gate (Interrupt Gate, Trap Gate).


Il descrittore di TSS si trova nella GDT ed è puntato dal registro TR ((Task Register, da 16 bit); per evitare troppi accesi in memoria, il descrittore del TSS del task attivo si trova anche nella cache associata al registro TR; se in TR viene caricato un valore diverso, automaticamente nella cache viene caricato il TSS Descriptor corrispondente (vedi fig. 32). Il TSS del processo attivo si trova in memoria fisica, mentre gli altri TSS possono anche trovarsi su disco.












Figura 32: ruolo del registto TR


La struttura del TSS, illustrata in fig. 33, contiene tutti i registri, i selettori, i flags, i puntatori agli stack dei quattro livelli, e due campi particolari:


il Task's LDT Selector contiene l'indirizzo logico del descrittore della LDT associata al task (tale descrittore si trova nella GDT);

il Back-Link TSS Selector contiene un puntatore al TSS descriptor del task "padre", cioè di quel task che ha generato questo task. Questo campo è importante solo quando è stata eseguita una CALL e non una JMP; infatti nel secondo caso non si ha intenzione di ritornare al task padre.








 

























Figura 33: struttura del TSS


Consideriamo una chiamata intertask e vediamo quale sia la funzione del Task Gate.


Istruzione:    CALL SEG:OFF


SEG punta a un descrittore di tipo Task Gate (i Task Gate, così come gli Interrupt e i Trap Gate, si trovano nel vettore delle interruzioni IDT), il quale contiene, nel campo DESTINATION SELECTOR (14 bit), l'indirizzo logico del TSS Descriptor relativo al processo chiamato (vedi fig 34).











Figura 34: salto inter task


Con riferimento alle figg. 32 e 34, vediamo cosa succede prima e dopo il trasferimento.


Prima:

LDTR punta al descrittore della LDT associata al task corrente

TR punta al descrittore di TSS del task corrente


Aggiornamento:

LDTR viene salvato nel primo campo del TSS corrente (Task's LDT Selector)

TR viene salvato nell'ultimo campo del TSS futuro (Back-Link TSS Selector)

Vengono salvati nel TSS corrente tutti i registri della macchina

LDTR viene caricato con il valore presente nel primo campo del TSS futuro (in questo modo viene cambiata la LDT corrente)

TR viene caricato con l'indirizzo logico del descrittore del TSS futuro (e viene aggiornata la cache relativa)

Vengono caricati tutti i registri della macchina con i valori presenti nel TSS futuro

7. In CS:IP vengono caricati i valori corrispondenti che si trovano nel TSS futuro

5.3.11 Task nesting


Quando si verifica un task switching che implica un ritorno (CALL, interrupt o trap), si ha una situazione di task annidati. In questi casi, oltre all'aggiornamento del campo Back-Link (come abbiamo già visto), avviene anche il settaggio di alcuni parametri:


Flag NT, presente nella parola dei FLAG (Nested Task)

Flag TS, presente nella MSW (Task Switch)

Bit di Busy, presente nel descrittore di TSS (è il secondo bit da sinistra del campo TYPE)


NT è utilizzato dall'istruzione IRET che deve concludere il task figlio; se NT=1, l'istruzione IRET esegue un ritorno di tipo task switching (intertask); se NT=0, esegue un ritorno normale (intratask).

TS viene settato a '1' nel momento in cui avviene un task switching.

Come abbiamo visto nel par. 5.3.10, fig. 31, il campo TYPE del descrittore indica se il task è disponibile (type = 1, cioè bit Busy azzerato) o non disponibile (type = 3, cioè bit Busy settato); nel primo caso il task può essere attivato da quello corrente e può avvenire un task switch, nel secondo caso no, perché sta aspettando la conclusione di un task figlio e quindi non può essere schedulato.


5.4 I/O Privilege Level


In generale il set di istruzioni del processore è ripartito in tre sottoinsiemi:


- le istruzioni eseguibili da tutti (indipendenti dal Privilege Level di chi le esegue)

- le istruzioni eseguibili solo da chi ha PL=0

- le istruzioni eseguibili in funzione di un parametro


Le istruzioni di I/O appartengono alla terza categoria.

Nella parola dei flag, due bit sono riservati all'IOPL (I/O Privilege Level); qui c'e' scritto il livello minimo di privilegio che il codice in esecuzione deve avere per eseguire istruzioni di I/O. Se per esempio IOPL=2, significa che solo il codice con privilegio CPL 2 può eseguire istruzioni di I/O.

Condizione: CPL IOPL

 

FLAGS

 





Il flag IOPL può essere modificato solo da istruzioni di livello 0.

Il fatto di avere i due bit dell'IOPL all'interno della parola dei flags permette di selezionare un diverso livello per ogni task.


5.5 Gestione degli interrupt e delle trap


Il registro IDTR da 24+16 bit contiene su 24 bit l'indirizzo fisico del vettore delle interruzioni (che nel 286 e successivi viene chiamato IDT: Interrupt Descriptor Table) e su 16 bit il suo limite superiore. Valgono gli stessi discrosi fatti per la GDT: anche in questo caso esiste una sola copia di vettore delle interruzioni per tutti i processi, ed è situato in una posizione nota nella memoria fisica. Questo vettore è formato da una serie di Gate descriptor che possono essere di tre tipi (vedi anche par. 5.3.4):


Task Gate (campo TYPE = 5)

Interrupt Gate  (campo TYPE = 6)

Trap Gate (campo TYPE = 7)


Con l'eccezione del Task Gate, di cui abbiamo già parlato, ogni Gate descriptor presente nella IDT contiene l'indirizzo logico di un descrittore di segmento (che si trova nella LDT o nella GDT), il quale punta al segmento di codice da eseguire in seguito all'interruzione (vedi figg.35 e 36).


IDT contiene al massimo 256 descrittori, non per mancanza di spazio, ma per compatibilità con il set di istruzioni precedente; infatti nell'istruzione INT N, il valore N è un byte.















Figura 35: struttura dell'IDT










Figura 36: salto tramite Gate

5.5.1 Interrupt e 929h76j trap


Nella tabella che segue sono elencate alcune eccezioni previste dall'80286. E' interessante notare le differenze presenti tra il modo reale e il modo protetto; nel modo protetto il numero di eccezioni è molto più elevato, soprattutto in relazione alla gestione della memoria e ai complessi meccanismi di protezione che abbiamo visto.

L'interrupt 7, Math Coprocessor not Available, è attivato quando, in presenza di istruzioni con prefisso ESC, o il bit TS è settato (cioè è avvenuto un Task Switch in precedenza), oppure il bit EM è settato (emulazione 287).

L'interrupt 8, General Double Fault, significa che una qualunque eccezione si è verificata mentre il processore stava gestendo una precedente eccezione.

L'interrupt 11, Segment not Present, significa che un segmento di codice, un segmento di dato, un TSS o una LDT non sono presenti in memoria fisica.

Interrupt 13, General Protection Violation: l'errore verificatosi può essere di diversi tipi: errore di violazione di limite, di privilegio, di accesso, di salto ad un task non disponibile.

L'interrupt 16,  Math Coprocessor Calculation Error, viene attivato quando la CPU riceve un segnale sul pin ERROR da parte del coprocessore.



Vedremo tra poco come fa la CPU a capire che tipo di errore si è verificato a fronte di un interrupt generico come ad esempio l'interrupt 13.



Num

Real Mode 80286

Protected Mode 80286


Divide Error

Divide Error


Single Step trap

Single Step trap


NMI

NMI


Breakpoint

Breakpoint


INTO Interrupt

INTO Interrupt


Bound Interrupt

Bound Interrupt


Illegal Opcode

Illegal Opcode


Coprocessor unavailable

Coprocessor unavailable


IDTR Limit too small to process exception

General Double Faults


Coprocessor operand

Coprocessor operand


Reserved

Invalid TSS


Reserved

Segment not present


Word offset=0FFF in SS

Stack Segment not present or limit violation


Word offset=0FFF in CS,DS or ES

General Protection Violation


5.5.2 Il ruolo dello stack nella gestione di trap e interrupt


Come abbiamo accennato, alcuni interrupt o trap di tipo generale sono ulteriormente specificati da un codice di errore, il quale specifica con maggior precisione il tipo di errore verificatosi.

Distinguiamo tre casi possibili.

1) Interrupt o trap senza codice di errore e senza salto di privilegio.


Lo stack della routine chiamata contiene CS:IP e il registro dei flag.


 





2) Interrupt o trap con codice di errore ma senza salto di privilegio.

Lo stack della routine chiamata contiene anche il codice specifico dell'eccezione verificatasi

 


3) Interrupt o trap con codice di errore e con salto di privilegio.

Lo stack della routine chiamata contiene anche OLD SS e OLD SP, cioè i puntatori allo stack della routine chiamante.

 


Il codice d'errore è una parola di 16 bit strutturata nel modo seguente:



INDEX: selettore che punta al descrittore in corrispondenza del quale è stato generato l'errore (es: errore sul privilegio in un descrittore di segmento)

TI: descrittore in GDT (TI=0), descrittore in LDT (TI=1)

I: eccezione causata da un elemento nel vettore delle interruzioni (I=1); eccezione causata da un elemento nella GDT o LDT (I=0)

E: evento esterno (E=1), evento interno (E=0)


Esempio: violazione sugli attributi di un descrittore di codice. Poniamo che a un certo punto CS punti a un descrittore errato (per esempio è sporco o non si tratta di un descrittore di codice).


  

Figura 37: codice d'errore

In questo caso il codice di errore sarà settato come mostra la figura 37.







Privacy




Articolo informazione


Hits: 3143
Apprezzato: scheda appunto

Commentare questo articolo:

Non sei registrato
Devi essere registrato per commentare

ISCRIVITI



Copiare il codice

nella pagina web del tuo sito.


Copyright InfTub.com 2024