Win32 Assembly

Capitolo 2: Introduzione

Nell'estate del 1981 inizia l'era dei Personal Computers con l'entrata in commercio del mitico PC IBM XT 8086; si tratta di un computer basato sulla CPU Intel 8086 con architettura a 16 bit. Come sappiamo, questa CPU e' dotata di registri a 16 bit, Data Bus a 16 bit e Address Bus a 20 bit; con un Address Bus a 20 bit questa CPU e' in grado di gestire sino a 220 byte di memoria RAM (1 Mb), cioe' tutti gli indirizzi fisici che vanno da 0 sino a 1048575. Con i suoi registri a 16 bit pero', l'8086 e' in grado di gestire solo numeri interi compresi tra 0 e 65535; per permettere all'8086 di accedere a tutto il Mb di RAM, si rappresentano gli indirizzi fisici sotto forma di indirizzi logici. Prima di tutto, il Mb di RAM del computer viene suddiviso in blocchi da 65536 byte ciascuno (64 Kb) chiamati segmenti; un qualunque indirizzo fisico del computer viene allora rappresentato sotto forma di indirizzo logico costituito da una coppia seg:offset. La componente seg di questa coppia e' un numero a 16 bit che indica il numero di segmento della RAM a cui vogliamo accedere (0, 1, 2, 3, ..., 65535); la componente offset di questa coppia e' un numero a 16 bit che indica uno spiazzamento all'interno di seg (0, 1, 2, 3, ..., 65535). Si tratta dello stesso sistema che usa un postino per suddividere la citta' in vie (seg) e numeri civici (offset); in questo modo il postino puo' distinguere tra il n.40 di via Pascoli e il n.40 di via Carducci. Per poter indirizzare esattamente 1048576 byte di RAM, si e' deciso di far partire i vari segmenti da indirizzi fisici multipli di 16 byte (paragrafi); in questo modo il segmento n.0 parte dall'indirizzo fisico esadecimale 00000h, il segmento n.1 parte da 00010h, il segmento n.2 parte da 00020h e cosi' via sino all'ultimo segmento n.65535 che parte da FFFF0h. In pratica e' come avere 65536 segmenti da 16 byte ciascuno per un totale di:
65536 x 16 = 1048576 byte
Grazie a questo accorgimento, la CPU converte facilmente un indirizzo logico in un indirizzo fisico. La formuletta che consente alla CPU di convertire un indirizzo logico seg:offset in un indirizzo fisico a 20 bit e':
(seg x 16) + offset
Questo modo di accedere alla RAM prende il nome di modalita' di indirizzamento reale 8086. Il termine "reale" si riferisce al fatto che gli indirizzi logici specificati dal programmatore corrispondono ad indirizzi fisici realmente esistenti in memoria; per parecchio tempo la modalita' reale e' stata un vero e proprio incubo per i programmatori costretti a spezzettare i loro programmi in tanti blocchi da 64 Kb ciascuno.
L'IBM XT 8086 e' un computer destinato non solo alle universita' o ai grandi centri di ricerca, ma anche ad un mercato piu' vasto formato soprattutto dalle piccole e medie aziende; l'obiettivo fondamentale quindi e' quello di diffondere l'uso del computer anche tra un pubblico privo di conoscenze di informatica. Per consentire a chiunque di utilizzare un computer senza conoscerne il funzionamento interno, la IBM equipaggia l'XT 8086 con un apposito Sistema Operativo (SO); il SO e' un software che ha lo scopo di creare un'interfaccia tra l'utente e il computer. L'utente dialoga con il computer impartendogli dei comandi formati da termini come DIR, MKDIR, COPY, DEL, etc; il SO riceve questi comandi, li traduce nel linguaggio binario del computer e li fa eseguire dall'hardware. Un'altro compito fondamentale di un SO e' quello di far girare i programmi; in questo modo gli utenti possono anche scrivere o acquistare i programmi piu' adatti alle loro esigenze, facendoli eseguire dal computer.
La scelta dell'IBM cade sul SO proposto da Bill Gates e Paul Allen che poco tempo prima avevano creato una piccola software house chiamata Microsoft; il SO di Gates e Allen viene chiamato MS-DOS (Microsoft Disk Operating System). Questo nome deriva dal fatto che all'epoca, la configurazione standard dell'XT 8086 era formata dal PC con 1 Mb di RAM, scheda video in modalita' testo (alfanumerica) e memoria permanente formata da un lettore di floppy disk da 5"1/4, o al massimo "ben" due lettori di floppy disk; gli hard disk da pochi Mb rappresentavano ancora un lusso che solo pochi fortunati potevano permettersi. Il DOS organizza il disco come se fosse un armadio da ufficio suddiviso in tanti ripiani; ogni ripiano rappresenta una cartella (directory). Le varie cartelle contengono al loro interno svariati documenti ciascuno dei quali viene chiamato file (archivio); in questo modo l'utente puo' organizzare il proprio lavoro come se avesse a che fare con l'archivio di un ufficio.
Dal punto di vista dei programmi (e dei programmatori), il DOS organizza la RAM suddividendola in due blocchi principali; il primo blocco occupa i primi 640 Kb e rappresenta la memoria convenzionale (Conventional Memory), mentre i restanti 384 Kb formano la memoria superiore (Upper memory). La memoria superiore e' riservata al SO che la utilizza in particolare per accedere alle memorie periferiche; bisogna ricordare infatti che l'8086 con un Address Bus a 20 linee puo' vedere solo 1 Mb di RAM e quindi qualsiasi altra memoria periferica deve essere mappata in questo stesso Mb (I/O Memory Mapped). Il DOS crea nella memoria superiore diverse "finestre" (Frame Windows) attraverso le quali puo' accedere ad altre memorie esterne come la memoria video (VRAM), la ROM-BIOS, etc; a causa dei registri a 16 bit della CPU 8086, queste finestre non possono essere piu' grandi di 64 Kb, e questo significa che il problema della segmentazione della memoria si ripercuote anche sulle memorie esterne. Per lasciare piu' spazio ai normali programmi, la memoria superiore viene anche utilizzata dai Device Drivers (piloti di dispositivo); i piloti di dispositivo sono dei programmi residenti in memoria che permettono al DOS di pilotare diverse periferiche collegate al computer. Per quanto riguarda la memoria convenzionale, i primi Kb sono occupati dai vettori di interruzione e dall'area dati del BIOS; subito dopo troviamo alcune decine di Kb riservati alla parte del DOS residente in memoria. In definitiva, per i normali programmi rimangono a disposizione circa 600 Kb di memoria convenzionale; questo fatto spinge Bill Gates a pronunciare una delle sue frasi piu' celebri: "640 Kb rappresentano una quantita' enorme di memoria, piu' che sufficiente per qualunque applicazione presente e futura!"
Uno degli aspetti piu' scomodi del DOS e' rappresentato dalla sua interfaccia in modalita' testo; dopo aver acceso il computer l'utente si trova davanti uno schermo nero con un cursore bianco lampeggiante che viene chiamato prompt del DOS. Lo schermo viene trattato dal DOS come se fosse il foglio di una macchina da scrivere, o per meglio dire, il foglio di una telescrivente (l'antenata della stampante); il prompt rappresenta la testina della telescrivente che puo' muoversi in orizzontale e andare a capo grazie ad una sorta di carrello virtuale. Non a caso, il tasto [Invio] (o [Enter]) viene chiamato "ritorno carrello" perche' premendolo si manda a capo il prompt posizionandolo all'inizio di una nuova linea; questo tipo di interazione tra utente e computer e' piuttosto grossolano ed ha anche il difetto di costringere l'utente stesso a leggere grossi manuali per poter imparare tutti i comandi del DOS. Questo problema riguarda praticamente tutti i SO dell'epoca anche perche' l'hardware disponibile non consente ancora l'applicazione di una soluzione alternativa ideata ben dieci anni prima; questa soluzione alternativa si chiama Interfaccia Grafica Utente o GUI (Graphic User Interface).
Nei primi anni 70 un gruppo di ricercatori dei laboratori Xerox di Palo Alto (California) mette a punto un progetto destinato a sconvolgere completamente il mondo dei computers; secondo questo progetto l'utente interagisce con il computer attraverso uno schermo grafico che mostra tutta una serie di oggetti tra i quali figurano finestre, menu di scelta, pulsanti, icone, immagini, etc. L'oggetto piu' importante di una GUI e' sicuramente la finestra (window) che rappresenta una sorta di "schermo virtuale"; un SO capace di far girare piu' programmi contemporaneamente, associa una finestra a ciascun programma in modo che ogni programma abbia a disposizione il suo schermo virtuale attraverso il quale dialogare con l'utente. I ricercatori della Xerox mettono a punto anche un dispositivo di puntamento corrispondente all'attuale mouse, che permette all'utente di muoversi agevolmente tra gli oggetti dello schermo; tutti questi concetti oggi possono apparire abbastanza ovvi, ma negli anni 70 vengono accolti come una vera e propria rivoluzione.
La prima applicazione pratica di queste ideee che ottiene un enorme successo commerciale e' la GUI che viene fornita con i celebri Apple Macintosh; anche la Microsoft comincia a studiare una propria GUI e nel 1985 mette in commercio Windows 1.0. Si tratta non di un SO ma di una semplice GUI che si appoggia sul DOS; l'utente infatti avvia Windows dal DOS digitando WIN.COM e premendo [Invio] come se avesse a che fare con un qualunque altro programma DOS. In sostanza l'unico miglioramento apportato da Windows 1.0 e' rappresentato dall'utilizzo di una interfaccia grafica che consente all'utente di dialogare con il computer in modo molto piu' semplice ed intuitivo; per il resto Windows 1.0 mantiene tutti i pregi e i difetti del DOS legati anche alla modalita' di indirizzamento reale 8086.
L'irruzione della grafica nel mondo dei PC mette in risalto non solo le scarse prestazioni delle CPU 8086 ma anche l'assoluta insufficienza dell'unico Mb di memoria disponibile; la caratteristica fondamentale degli oggetti grafici come le icone, i menu e soprattutto le immagini, e' proprio quella di richiedere notevoli quantita' di memoria. Per cercare di risolvere questi problemi, cominciano a comparire sul mercato costosissime schede hardware chiamate espansioni di memoria; grazie a queste schede anche le CPU a 16 bit possono disporre di ben 32 Mb di memoria oltre al solito Mb di RAM; per poter accedere a questa memoria esterna chiamata memoria espansa, la CPU utilizza la solita tecnica dell'I/O memory mapped. In sostanza, viene creata nella RAM una frame window da 64 Kb che puo' essere fatta "puntare" dai programmi alla zona desiderata della memoria espansa; anche in questo caso quindi si nota che i programmi possono vedere al massimo 64 Kb di memoria espansa alla volta. Per mettere ordine nel mondo delle schede di espansione di memoria, nasce un consorzio di aziende formato da Lotus, Intel e Microsoft (LIM); il lavoro svolto dal consorzio LIM porta alla nascita dello standard EMS (Expanded Memory Specifications). Lo standard EMS definisce l'interfaccia di programmazione (API) attraverso la quale i programmi possono accedere alla memoria espansa; per maggiori dettagli sullo standard EMS si veda la sezione Assembly Avanzato.
Nel 1987 la Microsoft annuncia l'uscita di Windows 2.0 che oltre a presentare diversi miglioramenti estetici rispetto a Windows 1.0, offre anche il supporto completo della memoria espansa; in questo modo, le applicazioni per Windows 2.0 possono beneficiare di una notevole quantita' di memoria rispetto all'unico Mb di RAM sfruttabile con Windows 1.0. Ogni applicazione riceve da Windows 2.0 un'area di memoria espansa riservata in esclusiva all'applicazione stessa e protetta quindi da tentativi di "intrusione" da parte di altre applicazioni; pur permanendo il problema della segmentazione a 64 Kb della memoria, bisogna dire che l'avvento della memoria espansa rappresenta un notevole passo avanti per il mondo dei PC.
La memoria espansa risolve parzialmente il problema della penuria di memoria, ma non puo' certo offrire nessun miglioramento sul piano delle prestazioni del PC; per migliorare le prestazioni del PC e' necessario disporre di CPU molto piu' potenti dell'8086 e di un metodo per superare il problema della segmentazione a 64 Kb della memoria. Consapevole di questo fatto la Intel scatena un autentico uragano sul mondo dei PC producendo in rapida sequenza tre nuove CPU chiamate 80186, 80286 e 80386; l'80186 passa quasi inosservata in quanto si tratta di una versione piu' sofisticata dell'8086. La vera novita' arriva invece con l'80286 che e' una CPU dotata di registri a 16 bit, Data Bus a 16 bit e soprattutto Address Bus a 24 bit; con un Address Bus a 24 bit l'80286 puo' gestire sino a 224 byte di memoria RAM fisica, pari a 16777216 byte (16 Mb). L'aspetto importante e' dato dal fatto che non si tratta di memoria periferica distinta dal solito Mb di memoria base, ma di un vero e proprio prolungamento della memoria RAM che viene chiamato memoria estesa; questo significa che una CPU 80286 accede a questi 16 Mb specificando l'indirizzo lineare a 24 bit 0, 1, 2, ..., sino a 16777215. L'obiettivo fondamentale della Intel e' quello di mantenere la compatibilita' con l'enorme quantita' di software scritta per le CPU 8086; per questo motivo, appena si accende il computer l'80286 viene inizializzata in modalita' di emulazione dell'8086. Questa modalita' viene ottenuta disabilitando la linea A20 dell'Address Bus; in questo modo si impedisce ai programmi di accedere agli indirizzi fisici superiori a FFFFFh, proprio come accade con l'8086. In alternativa alla modalita' reale la CPU 80286 dispone anche di una nuova modalita' operativa chiamata modalita' protetta; questo nome deriva dal fatto che in questa modalita' l'80286 permette ad un SO di far girare "contemporaneamente" piu' programmi con meccanismi di protezione che impediscono ai programmi stessi di interferire tra loro. In modalita' protetta i programmi hanno la possibilita' di sfruttare tutti i 16 Mb di memoria fisica indirizzabile dalla CPU superando in questo modo il limite dei 640 Kb imposto dalla modalita' reale; questo obiettivo viene raggiunto grazie ad un uso differente dei registri di segmento. In modalita' protetta i registri di segmento vengono chiamati selettori e il loro contenuto assume la struttura mostrata in Figura 1. I 13 bit piu' significativi del registro di segmento contengono un numero compreso tra 0 e 8191. Questo numero rappresenta un indice che seleziona uno tra gli 8192 possibili elementi presenti in una tabella chiamata descriptor table (tavola dei descrittori); ciascun elemento di questa tabella viene chiamato appunto descrittore e contiene la descrizione completa del segmento di programma puntato dal registro di segmento. Questa descrizione comprende il tipo di segmento (scrivibile, leggibile, eseguibile), le dimensioni del segmento (segment limit) e soprattutto l'indirizzo di memoria da cui parte il segmento stesso (base address); nel caso delle CPU 80286 questo indirizzo e' formato da 24 bit. Ogni volta che la CPU deve accedere ad un offset all'interno di un segmento di programma, somma quest'offset al base address del segmento stesso per ottenere l'indirizzo lineare a 24 bit; se la CPU incontra ad esempio il codice macchina di una istruzione del tipo:
mov ax, [bx]
somma l'offset contenuto in BX al base address del segmento di programma puntato da DS ottenendo un indirizzo fisico a 24 bit; a questo punto la CPU accede a quell'indirizzo, legge 16 bit di dati e li trasferisce in AX. Come si puo' facilmente intuire, l'80286 con i suoi registri a 16 bit permette di specificare offset compresi tra 0 e 65535; questo significa che neanche questa CPU riesce a risolvere il problema della segmentazione a 64 Kb della memoria. In pratica, quando si opera in modalita' protetta, la CPU 80286 permette di scavalcare la barriera dei 640 Kb e di accedere ad un massimo di 16 Mb di RAM; questi 16 Mb vengono pero' suddivisi in tanti blocchi da 64 Kb analogamente a quanto succede con l'8086.
Un'altra importante caratteristica dell'80286 e' il supporto per la cosiddetta memoria virtuale; in pratica, anche se il computer non dispone fisicamente di tutti i 16 Mb di RAM, l'80286 permette ugualmente di simulare la memoria sfruttando l'hard disk. In questo modo e' possibile eseguire programmi che richiedono piu' memoria di quella fisicamente disponibile; il trucco consiste nello scaricare sull'hard disk quei segmenti di programma che in un dato momento non sono necessari per l'esecuzione del programma stesso. Per sapere se un segmento di programma si trova in memoria o sull'hard disk si utilizza un bit del descrittore di segmento; questo bit viene indicato con P e prende il nome di present bit (bit di presenza). Se P=1, il corrispondente segmento di programma e' in memoria; se invece P=0 allora il corrispondente segmento di programma e' sull'hard disk. Se si prova ad accedere ad un segmento di programma che ha P=0, la CPU genera un errore (eccezione di segmento non presente); il SO intercetta questo errore, scarica sull'hard disk un segmento di programma non piu' necessario e carica in memoria il segmento di programma richiesto dal precedente tentativo di accesso.
Tutte queste caratteristiche della CPU 80286 sono state concepite espressamente per favorire la realizzazione di SO capaci di far girare piu' programmi contemporaneamente; i SO di questo tipo vengono definiti multitask per indicare la capacita' di eseguire piu' compiti (task) alla volta. Naturalmente con un'unica CPU a disposizione e' possibile eseguire un solo programma alla volta; per ottenere il multitasking si utilizza un noto espediente chiamato time sharing (ripartizione del tempo). In sostanza, il SO fa girare uno alla volta tutti i programmi assegnando a ciascuno di essi un piccolo intervallo di tempo di esecuzione; in questo modo l'utente ha l'impressione che i vari programmi stiano girando contemporaneamente.
I SO multitasking sfruttano ampiamente gli altri due campi presenti nel selettore di segmento descritto in Figura 1; il bit in posizione 2 del selettore viene indicato con TI e rappresenta il Table Indicator (indicatore del tipo di tabella). Se TI=0 allora il selettore punta ad una Global Descriptor Table (GDT); se invece TI=1 allora il selettore punta ad una Local Descriptor Table (LDT). Generalmente il SO riserva per se stesso la GDT ed assegna ad ogni programma in esecuzione una propria LDT; queste tabelle vengono inizializzate dal SO e caricate in memoria con le apposite istruzioni LGDT (Load GDT) e LLDT (Load LDT) disponibili solo in modalita' protetta.
I due bit in posizione 0 e 1 nel selettore di Figura 1, vengono indicati con RPL e rappresentano il Request Privilege Level (livello di privilegio); l'RPL indica il livello di privilegio assegnato al segmento di programma puntato dal campo indice del selettore. In questo modo, il SO puo' implementare un meccanismo che protegge determinati segmenti di programma dai tentativi di intrusione da parte di altri segmenti di programma meno privilegiati; infatti, se da un segmento di programma si prova ad accedere ad un'altro segmento di programma piu' privilegiato, la CPU genera un errore (violazione di privilegio). Con i due bit del campo TI possiamo formare i quattro numeri 0, 1, 2 e 3; per convenzione, il livello 0 rappresenta il privilegio piu' alto (ring 0) mentre il livello 3 rappresenta il privilegio piu' basso (ring 3). Per questo motivo i SO riservano per se stessi i livelli di privilegio piu' alti e assegnano ai normali programmi i livelli di privilegio piu' bassi; in questo modo si evita che un normale programma possa danneggiare il SO. La Figura 2 mostra un esempio sulla ripartizione dei livelli di privilegio in un SO; la parte piu' privilegiata e' quella colorata in rosso che rappresenta il kernel. Il termine kernel deriva dal tedesco e significa 'nucleo' per indicare la parte piu' interna del SO che gestisce direttamente l'hardware del computer; si tratta chiaramente della componente piu' delicata di un SO e deve quindi godere del massimo livello di protezione dalle interferenze esterne. Il livello di privilegio 1 viene in genere assegnato ai servizi di sistema; si tratta dell'insieme di servizi (gestione files, gestione memoria, etc) che i SO mettono a disposizione dei normali programmi. Il livello di privilegio 2 viene in genere assegnato alle estensioni del SO; si tratta di programmi che pur non facendo parte del SO ne estendono le capacita' fornendo ulteriori servizi per le normali applicazioni. Un esempio pratico e' rappresentato dai device drivers attraverso i quali i SO possono pilotare periferiche collegate al computer; il livello di privilegio piu' basso (3) viene naturalmente assegnato ai normali programmi utilizzati dall'utente.
In base alle considerazioni appena svolte, si deduce che l'80286 e' in grado di indirizzare sino a 1 Gb di memoria virtuale; osserviamo infatti che con i 13 bit del campo indice del selettore di segmento possiamo rappresentare 213=8192 descrittori di segmento differenti. Attraverso il campo TI del selettore possiamo specificare se i descrittori si trovano nella GDT o nella LDT; con queste due tabelle possiamo gestire in totale:
2 x 8192 = 16384
descrittori differenti, e sicome ogni segmento di programma e' grande 65536 byte, otteniamo una memoria virtuale totale pari a:
16384 x 65536 = 1073741824 byte (1 Gb).

Per superare anche il problema della segmentazione a 64 Kb della memoria e' stato necessario attendere l'arrivo della CPU 80386; si tratta di una CPU dotata di registri a 32 bit, Data Bus a 32 bit e Address Bus a 32 bit. Con un Address Bus a 32 bit l'80386 puo' indirizzare sino a 232 byte di memoria fisica (4 Gb); questa CPU estende e potenzia la modalita' protetta dell'80286 ed apre veramente una nuova era per i PC. In modalita' protetta il base address di un descrittore di segmento dell'80386 e' un indirizzo a 32 bit, ma la cosa piu' importante e' che anche i registri dell'80386 sono a 32 bit; con un registro a 32 bit possiamo specificare un offset che puo' assumere tutti i valori compresi tra 0 e 4294967295. Tutto cio' significa che anche l'80386 gestisce la memoria in modo segmentato, con la differenza pero' che con questa CPU i segmenti sono virtualmente da ben 4 Gb ciascuno! Un'altra caratteristica che testimonia l'abisso che esiste tra l'80286 e l'80386 e' che l'80386 sfruttando l'hard disk e i segmenti da 232 byte e' in grado di gestire sino a:
2 x 213 x 232 = 246
byte di memoria virtuale pari a 64 Tb (terabyte)!
La memoria virtuale viene gestita anche attraverso un sofisticato meccanismo chiamato paginazione della memoria; in pratica lo spazio di indirizzamento virtuale dell'80386 viene suddiviso in tanti blocchi da 4 Kb chiamati pagine di memoria. Cosi' come accade per i segmenti virtuali dell'80286, anche le pagine dell'80386 possono trovarsi sull'hard disk; attraverso il bit di presenza (P bit) il SO puo' scambiare pagine in memoria con pagine sull'hard disk.
Come accade per tutte le CPU Intel e compatibili, anche l'80386 appena si accende il computer viene inizializzata in modalita' reale; in questo modo viene garantita la compatibilita' con tutto il software scritto per le CPU precedenti. Proprio per quanto riguarda la compatibilita' con i vecchi programmi, l'80386 offre una caratteristica veramente rivoluzionaria rappresentata dalla cosiddetta modalita' virtuale 8086 (V86) che permette a questa CPU di eseguire programmi DOS senza uscire dalla modalita' protetta; nel caso dell'80286, se ci troviamo in modalita' protetta e vogliamo eseguire un programma DOS, dobbiamo prima tornare in modalita' reale. La modalita' V86 dell'80386 crea una macchina virtuale 8086 costituita da un'area di memoria virtuale da 1 Mb; un programma DOS gira in questo Mb virtuale credendo di trovarsi in un tipico ambiente DOS. Il Mb virtuale creato dalla modalita' V86 puo' trovarsi in qualunque area della memoria; l'aspetto straordinario della modalita' V86 e' che un SO multitask puo' creare piu' macchine virtuali facendo girare piu' programmi DOS contemporaneamente.

Per poter sfruttare tutta la potenza delle nuove CPU la Microsoft nel 1990 annuncia l'uscita di Windows 3.0; oltre ai soliti miglioramenti estetici, questa nuova versione di Windows mette a disposizione dell'utente ben tre differenti modalita' operative chiamate modo reale, modo standard e modo avanzato. Nel modo reale Windows funziona come i suoi predecessori comportandosi come una normale GUI che si appoggia sul DOS; in questa modalita' quindi e' possibile eseguire un programma alla volta (in modalita' reale) ed e' possibile indirizzare solo 1 Mb di memoria RAM fisica piu' l'eventuale memoria espansa.
Il modo standard equivale alla modalita' protetta dell'80286; in questa modalita' Windows prende il controllo del computer e si comporta come se fosse un vero SO mentre in realta' non lo e'. Grazie alla modalita' protetta dell'80286, Windows e' in grado di indirizzare 16 Mb di memoria fisica e 1 Gb di memoria virtuale; in questo modo diventa possibile far girare piu' programmi contemporaneamente come in un vero SO multitask. Il problema principale del modo standard e' legato al fatto che se tra i vari programmi in esecuzione ci sono anche programmi DOS, allora Windows deve continuamente commutare tra la modalita' reale e la modalita' protetta con notevole calo delle prestazioni; inoltre se un programma DOS va' in crash, manda in crash tutta la sessione Windows facendoci perdere tutto il lavoro che stavamo svolgendo e che, come al solito, ci eravamo dimenticati di salvare.
Il modo avanzato equivale alla modalita' protetta dell'80386; in questo caso la situazione migliora enormemente grazie alle sofisticate caratteristiche di questa CPU. Nel modo avanzato Windows e' in grado di indirizzare 4 Gb di memoria fisica e 64 Tb di memoria virtuale; grazie anche alla tecnica della paginazione della memoria il multitasking diventa molto piu' affidabile perche' puo' beneficiare di meccanismi di protezione piu' severi. La modalita' V86 inoltre, permette l'esecuzione di piu' programmi DOS contemporaneamente eliminando la necessita' di commutazioni continue tra modalita' reale e modalita' protetta; in questo modo un programma DOS che va' in crash viene chiuso da Windows senza danneggiare gli altri programmi in esecuzione.

Tutte queste considerazioni sono vere solo da un punto di vista teorico; gli utenti e soprattutto i programmatori che hanno conosciuto Windows 3.x lo considerano come una sorta di entita' aliena scesa sulla terra per impadronirsi dei nostri computers e spargere il terrore. In effetti le vecchie versioni di Windows sono diventate tristemente famose per i continui blocchi di sistema annunciati dalla classica schermata blu; i motivi di tutta questa instabilita' sono numerosi. La prima cosa da osservare e' che tutte le versioni di Windows dalla 1.x alla 3.x vengono definite con il termine Win16 per indicare il fatto che si tratta di "ambienti operativi" a 16 bit; questo aspetto e' legato alla continua ossessione della Microsoft di voler garantire a tutti i costi la compatibilita' con il vecchio software e con il vecchio hardware. Basti pensare al fatto che Windows 3.x potrebbe girare persino su un 8086 dotato di un solo lettore di floppy disk; la conseguenza di tutto cio' e' che all'interno di Win16 e' presente una autentica bolgia infernale fatta di programmi DOS, programmi Windows, modalita' reale, modalita' protetta e chissa' cos'altro. Un'altro difetto di Win16 e' rappresentato dal meccanismo rudimentale che viene utilizzato per il multitasking; questo meccanismo viene chiamato multitasking cooperativo per indicare il fatto che Win16 si affida al "buon comportamento" dei vari programmi in esecuzione. In sostanza Win16 fa girare a turno i vari programmi sperando che ciascuno di essi gli restituisca il controllo nel piu' breve tempo possibile; se tutti i programmi si comportano "educatamente", allora questo meccanismo funziona discretamente bene. Se uno dei programmi impiega un quarto d'ora prima di restituire il controllo a Win16, allora tutto il sistema rimane bloccato per un quarto d'ora; come molti ricorderanno, in un caso del genere l'unica cosa da fare consisteva nel riavviare il computer perdendo come al solito tutto il lavoro che non era stato ancora salvato.

Tra il 1990 e il 1995 la situazione in casa Microsoft comincia decisamente ad evolversi nella direzione dei SO a 32 bit; il primo passo consiste nell'arrivo di Windows NT che e' un SO destinato all'utenza professionale e al mondo dei server. In termini di qualita' la situazione non sembra pero' migliorare; in breve tempo Windows NT balza al primo posto nella poco invidiabile classifica dei SO piu' colpiti dai virus. Per quanto riguarda il mondo di Win16, la Microsoft inizia la distribuzione di uno strumento di sviluppo software chiamato win32s; attraverso questa libreria e' possibile iniziare a sviluppare applicazioni a 32 bit destinate a girare nella modalita' avanzata di Windows 3.x. L'obiettivo della Microsoft e' chiaramente quello di dare inizio alla migrazione degli utenti e dei programmatori verso il mondo dei SO a 32 bit; verso la fine del 1995 infatti, esce sul mercato Windows 95 che viene presentato dalla Microsoft come un vero SO a 32 bit destinato al grande pubblico. Passano pochi mesi e alcuni hackers dimostrano in modo inconfutabile che in realta' Windows 95 continua ad essere una GUI che si appoggia sul DOS; infatti Windows 95 installa sul disco una sorta di versione a 32 bit del DOS che rappresenta il vero SO su cui si appoggia l'interfaccia grafica. La conseguenza pratica e' che anche Windows 95 in breve tempo diventa tristemente famoso per i continui crash; a tale proposito appare molto eloquente il giudizio di Matt Pietrek (probabilmente il massimo esperto mondiale di programmazione in ambiente Windows) che vedendo in azione Windows 95 afferma: "Windows 95 e' un'ottimo Sistema Operativo per giocare a Solitario!"
Successivamente la situazione subisce un certo miglioramento con l'arrivo di Windows 98 e Windows ME; in questi casi si puo' parlare di veri SO decentemente stabili anche se dietro le quinte continua a farsi notare la presenza del DOS.
Passando a questioni piu' tecniche, la prima cosa da dire e' che tutte queste versioni di Windows vengono classificate con il termine Win32 per indicare il fatto che si tratta di SO a 32 bit; in realta' per la solita compatibilita' verso il basso, al loro interno continuano a convivere (con molte difficolta') porzioni di codice a 16 e a 32 bit. Con l'arrivo di Win32 scompaiono sia il modo reale che il modo standard; questo significa che Win32 puo' funzionare solo in modo avanzato e quindi richiede un PC dotato di CPU 80386 o superiore. Tutto cio' non puo' che garantire una maggiore stabilita' del SO; un'altro miglioramento apportato da Win32 e' rappresentato dal meccanismo usato per il multitasking. Questa volta si tratta di un multitasking non cooperativo che non fa' affidamento sulla "buona educazione" dei vari programmi in esecuzione; in sostanza Win32 cede il controllo ad un programma e dopo un breve periodo di tempo glielo toglie per passarlo ad un altro programma. Anche questo aspetto produce un notevole aumento della stabilita' e dell'efficienza del SO; infatti un programma che perde molto tempo non provoca nessun rallentamento del sistema.
Uno degli obiettivi fondamentali di Win32 consiste nell'eliminare un grave problema presente in Win16; in Win16 i vari programmi in esecuzione possono vedersi l'uno con l'altro e per risparmiare memoria condividono tra loro molte risorse. Questa situazione crea due gravi conseguenze; la prima conseguenza e' legata al fatto che i vari programmi potendosi vedere tra loro, possono anche danneggiarsi l'uno con l'altro compromettendo cosi' il meccanismo di protezione. La seconda conseguenza e' legata al fatto che la condivisione delle risorse crea il fenomeno degli orfani; con questo termine si indicano quelle risorse che rimangono in memoria anche quando non vengono piu' utilizzate. Questo fenomeno puo' verificarsi sia quando un programma termina dimenticandosi di rimuovere le risorse caricate in memoria, sia quando un programma va' in crash lasciando la memoria piena di "rifiuti". A causa della confusione che regna al suo interno, Win16 non e' assolutamente in grado di individuare il programma che non ha ripulito la memoria; anche se individuasse il "resposabile", Win16 non potrebbe fare niente perche' le risorse rimaste in memoria potrebbero essere condivise con altri programmi ancora in esecuzione. Tra le varie risorse si possono citare in particolare le DLL (Dynamic Link Libraries); le DLL sono librerie software che invece di essere collegate al programma in fase di linking, vengono collegate in fase di esecuzione rendendo cosi' piu' "snello" il programma stesso. Se due programmi devono utilizzare la stessa DLL, invece di caricare in memoria due copie della DLL ne viene caricata una sola da condividere tra i due programmi; Win16 utilizza un contatore per sapere quanti programmi stanno utilizzando la stessa DLL. Se uno dei programmi termina, il contatore viene decrementato di 1; quando il contatore diventa 0, Win16 scarica la DLL dalla memoria. Non ci vuole molto a capire che se uno dei programmi va' in crash, il contatore non viene aggiornato e la DLL rimane in memoria anche quando non viene piu' utilizzata; una conseguenza pratica di tutti questi problemi e' che in Win16 si verifica spesso il fenomeno della saturazione della memoria.
Per cercare di eliminare tutti questi aspetti negativi di Win16, Win32 si basa sul fatto che, al giorno d'oggi, i PC non hanno certo problemi di penuria di RAM; non sussiste piu' quindi la necessita' di risparmiare memoria attraverso la condivisione delle risorse. Eliminando la condivisione delle risorse si elimina anche la necessita' di consentire alle varie applicazioni in esecuzione di potersi vedere l'una con l'altra; in questo modo si garantisce un meccanismo di protezione molto piu' efficiente in quanto in Win32 ogni applicazione ha il suo spazio di indirizzamento privato nel quale vengono caricate anche le risorse necessarie. La Figura 3 mostra la "realta' virtuale" che Win32 crea per ogni programma in esecuzione; questo significa che indipendentemente dalla configurazione fisica del computer, una applicazione in esecuzione sotto Win32 crede di vedere la situazione di Figura 3. Come si puo' notare, i primi 4 Mb vengono riservati al codice per il supporto dei programmi DOS; ogni programma DOS gira all'interno di una macchina virtuale V86. Il DOS e' un SO monotask che consente cioe' di eseguire un solo programma alla volta; i programmi DOS hanno quindi tutto il computer a loro completa disposizione e sono liberi di accedere direttamente all'hardware del computer senza il rischio di entrare in conflitto con altri programmi. Tutto cio' non avrebbe nessun senso in un SO multitask dove girano contemporaneamente piu' programmi che devono condividere lo stesso hardware; uno dei compiti fondamentali della macchina virtuale V86 e' proprio quello di intercettare i tentativi di accesso diretto all'hardware da parte dei programmi DOS. Il SO riceve queste richieste e le esaudisce al momento opportuno in modo da non creare conflitti tra i vari programmi in esecuzione; tutti questi aspetti vengono quindi gestiti con l'ausilio del codice di supporto per i programmi DOS.
Subito dopo questo blocco da 4 Mb troviamo una vasta area di memoria virtuale riservata a ciascuna applicazione Win32 "pura"; in pratica ogni applicazione scritta espressamente per Win32, una volta che viene caricata in memoria crede di avere a disposizione un vasto spazio di indirizzamento che va' dall'indirizzo fisico 4194304 (4 Mb) all'indirizzo fisico 2147483648 (2 Gb). Quest'area di memoria contiene il blocco dati, il blocco codice e il blocco stack del programma in esecuzione, piu' le varie risorse necessarie; se si eseguono due istanze dello stesso programma, ciascuno dei due programmi vede la situazione di Figura 3 credendo quindi di essere l'unico programma in esecuzione con le sue risorse private che non vengono quindi condivise con nessuno. Se due o piu' programmi Win32 vogliono condividere delle risorse, lo devono richiedere esplicitamente; a tale proposito Win32 mette a disposizione lo spazio di indirizzamento virtuale compreso tra 2 Gb e 3 Gb. In quest'area troviamo ad esempio le DLL esplicitamente condivise dalle applicazioni Win32; sempre in quest'area sono presenti gli eventuali memory mapped files (files mappati in memoria). Si tratta dello strumento che Win32 mette a disposizione per consentire a due o piu' programmi di condividere tra loro la stessa area di memoria; i memory mapped files vengono illustrati in un apposito capitolo.
Proprio l'area di memoria compresa tra 2 e 3 Gb e' la piu' delicata per Win32; uno degli obiettivi fondamentali di Win32 e' quello di garantire la compatibilita' con le vecchie applicazioni scritte per Win16. In ambiente Win16, ogni programma che vuole allocare dinamicamente memoria, ha a disposizione due aree di memoria chiamate Local Heap e Global Heap; il Local Heap e' una piccola area di memoria riservata alle singole applicazioni nel senso che ogni applicazione ha il suo Local Heap privato. Il Global Heap invece e' una vasta area di memoria a disposizione di tutte le applicazioni; si tratta quindi di un'area di memoria visibile a qualunque applicazione Win16. Per garantire la compatibilita' con queste vecchie applicazioni, Win32 ricrea lo stesso ambiente di Win16 disponendo il Global Heap proprio nell'area compresa tra 2 e 3 Gb; tutte le applicazioni Win16 che girano sotto Win32 condividono questo spazio di memoria e sono quindi in grado di vedersi l'una con l'altra. La conseguenza di tutto cio' e' che in quest'area della memoria si ricrea la classica bolgia tipica di Win16; tanto maggiore e' il numero di applicazioni Win16 in esecuzione tanto piu' cresce l'instabilita' di Win32.
Passiamo infine all'area compresa tra 3 e 4 Gb che e' la piu' importante di tutte in quanto contiene la parte residente in memoria del kernel di Win32 (codice di ring 0); con il meccanismo della paginazione della memoria, il codice di ring 0 crea per ogni applicazione in esecuzione la "realta' virtuale" descritta dalla Figura 3 e chiamata Virtual Machine (macchina virtuale). Ogni applicazione Win32 gira quindi in una propria macchina virtuale gestita da un Virtual Machine Manager o VMM; il compito del VMM quindi e' quello di far credere ad ogni applicazione di essere l'unica in esecuzione. Un'altro componente importante del codice di ring 0 e' rappresentato dai Virtual Device Drivers o VxDs; il compito dei VxDs e' quello di far credere ad ogni applicazione di avere il controllo totale su una determinata periferica. E' chiaro che in un SO multitask non e' possibile concedere alle varie applicazioni in esecuzione l'accesso diretto alle periferiche del computer; attraverso i VxDs due o piu' applicazioni possono accedere alla stesa periferica senza entrare in conflitto tra loro.

Abbiamo visto che la necessita' di garantire la compatibilita' con le applicazioni Win16 crea parecchi problemi a Win32; in particolare Win32 e' costretto a definire una vasta area di memoria virtuale da condividere tra le varie applicazioni. Tutto cio' si ripercuote negativamente sul meccanismo di protezione che dovrebbe impedire alle applicazioni di vedersi tra loro; tanto e' vero che come molti hanno avuto modo di constatare, le schermate blu continuano ad imperversare anche su Windows 9x. Il SO Windows NT e i suoi successori Windows 2000 e Windows XP cercano di risolvere questo problema attraverso il controllo totale sui 4 Gb di memoria virtuale; di conseguenza, la condivisione della memoria subisce in questi SO drastiche limitazioni. Proprio per questo motivo, le applicazioni scritte esplicitamente per Windows NT, 2000 e XP possono anche non funzionare in Windows 9x; fortunatamente pero' grazie alla compatibilita' verso il basso, le applicazioni scitte per Windows 9x girano benissimo (o quasi) anche sulle versioni superiori di Windows.
In conclusione comunque bisogna dire che nonostante i grandi sforzi compiuti dalla Microsoft, Windows XP e compagnia non sono ancora in grado di competere con la proverbiale affidabilita' dei SO della famiglia Unix; basti pensare al fatto che esistono server Unix che ormai funzionano ininterrottamente da venti anni! Proprio per questo motivo, i SO basati su Unix (come ad esempio Linux) vengono largamente impiegati in quei settori nei quali, non essendo possibile l'intervento umano, viene richiesta una grandissima affidabilita' operativa per lunghissimi periodi di tempo; un esempio pratico abbastanza eloquente e' rappresentato dall'utilizzo di Unix/Linux per la gestione dei computers di bordo dei satelliti artificiali e delle sonde spaziali (meditate gente, meditate)!