Multitasking in ia-32 - Spiegazioni varie...
Pubblicato da Ritz il 28/01/2001
Livello avanzato
Introduzione
Salve a tutti. Senza soffermarmi troppo nell'introduzione, vi dico subito che in questo tute vedremo come nei processori della famiglia i386 viene gestito il cosiddetto "multitasking" di cui tanto si sente parlare.
Iniziamo
Per affrontare questo tutorial vi basta una classica guida sul protected mode (ne trovate a centinaia in inglese, se ne volete una in italiano prendete la mia :P )
Nelle architetture ia32, un task e' una "unita' di lavoro che il processore puo' preparare, eseguire e sospendere". Esso puo' essere utilizzato in svariate situazioni: come processo a parte, come servizio di sistema, come interrupt handler, e cosi' via.
Il protected mode (detto anche pm, o pmode) offre al programmatore (a livello di sistema operativo) la possibilita' di eseguire piu' task "contemporaneamente" tramite alcune strutture che trattero' in seguito, non prima di aver spiegato il significato delle virgolette che ho usato.
Infatti, il multitasking inteso come capacita' di eseguire contemporaneamente piu' istruzioni a livello processore, cioe' un multasking reale, e' impossibile in architetture monoprocessore, dove cioe' c'e' solo 1 processore che si occupa di eseguire le istruzioni macchina una per volta (ovviamente).
Il multitasking come viene solitamente inteso, pero', consiste in realta' nel dividere tra i vari processi la possibilita' di utilizzare il processore stesso.
Nella pratica cio' significa che mentre stiamo eseguendo un prg qualsiasi (in un so opportuno ovviamente =) ) possiamo decidere di farne partire un altro, consci del fatto che sara' il SO stesso a decidere i tempi di esecuzione che i due dovranno osservare (attraverso appositi algoritmi di scheduling).
Fatta questa piccola nota, procediamo.
Struttura di un Task-State Segment ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^Cio' che identifica un task e' il suo Task-State Segment (TSS). Il TSS infatti e' una struttura che specifica lo spazio di esecuzione del task stesso, i suoi code/data/stack segment, i meccanismi per il task linking, e cosi' via. Ecco come esso si presenta:
typedef struct
{
/* i80386 TSS */
unsigned BackLink;
unsigned ESP0;
unsigned SS0;
unsigned ESP1;
unsigned SS1;
unsigned ESP2;
unsigned SS2;
PGTBL **PGDIR;
unsigned EIP;
unsigned EFLAGS;
unsigned EAX;
unsigned ECX;
unsigned EDX;
unsigned EBX;
unsigned ESP;
unsigned EBP;
unsigned ESI;
unsigned EDI;
unsigned ES;
unsigned CS;
unsigned SS;
unsigned DS;
unsigned FS;
unsigned GS;
unsigned LDT;
unsigned IO_bitmap_offset;
/* End of i80386 TSS. */
} TSS;
Insomma si tratta di un array di 26 elementi da 32bit ciascuno. Da notare che per il caso di elementi a 16bit (BackLink, SS0, SS1, SS2, ES, CS, SS, DS, FS, GS) il corrispondente spazio all'interno del TSS viene riempito solo per i 16 bit + bassi della "cella" mentre i rimanenti 16 bit + alti sono riservati e devono essere settati a 0. Fa eccezione l'ultimo elemento, che occupa quelli + alti lasciando rivervati i rimanenti (a parte il primo bit dell'ultimo elemento, che costituisce il flag T).
Task Switching
^^^^^^^^^^^^^^
Per cambiare task (task switching) possiamo agire in 4 modi diversi:
Metodo 1
--------
Se decidiamo di fare in questo modo, allora dobbiamo prima installare un tss descriptor della gdt (_non_ possiamo utilizzare ldt o idt) ovviamente da 64 bit.
31 24 23 22 21 20 19 16 15 14 13 12 11 8 7 0
+--------+---+---+---+-----+-------+---+-----+---+-------+--------+
| Base | G | 0 | 0 | AVL | Limit | P | DPL | 0 |1|0|B|1| Base |
+--------+---+---+---+-----+-------+---+-----+---+-------+--------+
31 16 15 0
+----------------------------------+------------------------------+
| Base Address | Segment Limit |
+----------------------------------+------------------------------+
Similmente agli altri segment descriptor, i 3 campi "Base Address" offrono un indirizzo logico del primo byte del segmento (il TSS nel nostro caso). I 2 campi "Segment Limit" offrono la grandezza del TSS stesso e seguono la regola del flag G di granularita'. Il DPL indica il livello di privilegio del segmento.
Metodo 2
--------
Tramite un task gate descriptor possiamo saltare indirettamente a un task. Esso puo' essere installato nella gdt, ldt o idt. Eccone di seguito la struttura:
31 16 15 14 13 12 11 8 7 0
+---------------------------------+---+-----+---+-------+---------+
| Reserved | P | DPL | 0 |0|1|0|1|Reserved |
+---------------------------------+---+-----+---+-------+---------+
31 16 15 0
+---------------------------------+-------------------------------+
| TSS Segment Selector | Reserved |
+---------------------------------+-------------------------------+
Unica cosa da dire e' che il TSS selecotr punta a un TSS descriptor della GDT. In questo segmento non viene usato l'RPL, mentre il DPL controlla l'accesso al tss descriptor durante lo switch. Infatti, se viene usato un jmp/call attraverso un task gate, il CPL e RPL del selettore che punta al gate devono essere minori o uguali al DPL del task gate descriptor (mentre non viene utilizzato il DPL del TSS descriptor di destinazione).
Metodo 3
--------
Non credo necessiti di spiegazioni =) .
Metodo 4
--------
In questo caso si ha il semplice ritorno al task di origine. Infatti, quando un task viene chiamato tramite call, int o exception, il processore accende il flag NT ("Nested Task") negli EFLAG. Se tale bit e' acceso, significa che siamo in un task da cui possiamo uscire attraverso il classico iret.
Task Register
-------------
Finora non abbimo ancora parlato di questo registro, che si divide in 2 parti: visibile e invisibile.
TSS +---+
+---------+ <-----| + | <------------------+
| | +---+ |
| | ^ |
| | | |
+---------+ <-------+ |
| |
Visible | Invisible |
+----------------+----------------+----------------+
| Selector | Base Address | Segment Limit | <- Task Register
+----------------+----------------+----------------+ ^^^^^^^^^^^^^^^^
| ^ ^
| | |
| +------------+ |
| | |
| GDT | |
| +----------------+ | |
| | | | |
| +----------------+ | |
| | | | |
| +----------------+ | |
+-----> | TSS Descriptor |----+----+
+----------------+
| |
+----------------+
Ma una volta che tutto e' installato correttamente, come avvine lo switch vero e proprio tra 2 task? Ecco cosa fa il processore mentre cambia per cambiare task (pasto dal solito tute sul pmode):
Task Linking
^^^^^^^^^^^^
Il BackLink del TSS e il flag NT sono utilizzati per far tornare l'esecuzione da un task. Infatti, il flag NT indica se il task corrente e' nidificato o meno. In caso affermativo, un iret avra' come consegueza il ritorno al task di livello subito piu' alto (precedente, chiamante, insomma avete capito =) ) della gerarchia, identificato a sua volta dal BackLink (selettore).
Task Address Space
^^^^^^^^^^^^^^^^^^
Ovviamente ad ogni nuovo task corrispondera' un proprio segmento code/data/stack, ed eventualmente anche una propira ldt. Se il paging non e' abilitato, tutti gli indirizzi lineari saranno mappati come fisici.Conclusioni
Bene, e' tutto anche per oggi.
Spero che questo tute vi sia stato utile e vi abbia incuriosito.
Ovviamente molte delle info in esso contenute sono state prese dai vari manuali intel disponibili per il download in http://developer.intel.com.
Un saluto a tutti, in particolare a mrcode, che mi ha convinto a scrivere questo tute. Andate a http://web.tiscalinet.it/mrcode e girate bene quel suo sito, che merita davvero!
Byzz,
Ritz
Attenzione
Questo tutorial e' stato scritto a scopo unicamente didattico. L'autore non si assume alcuna responsabilita' dell'utilizzo che ne verra' fatto.