Com’è ben noto, Windows permette di far girare non soltanto le applicazioni appositamente disegnate per esso, ma anche quelle per le versioni più vecchie, o addirittura sviluppate per l’ormai arcaico MS-DOS che ha dominato la scena per molti anni. Da notare che un’applicazione MS-DOS è MOLTO diversa da una per Windows: le API del sistema operativo di cui fa uso sono, infatti, poco o per nulla simili.
Tutto ciò (e molto di più) avviene proprio grazie all’utilizzo di particolari strumenti, chiamati subsystem nel gergo Microsoft.
Un subsystem è un processo che emula un ben preciso ambiente di un sistema operativo (d’ora in poi abbreviato s.o.) al quale s’interfacciano le sue applicazioni e che fanno uso dei servizi che espone. Questo significa che la relazione che lega un’applicazione scritta per Windows 3.1 al suo subsystem Win16 è di tipo client/server: quando all’applicazione serve un particolare servizio, effettua una chiamata al subsystem (tramite uno scambio di messaggi). Da evidenziare che, a parte Win32, i subsystem possono essere caricati dinamicamente, quando servono.
Un meccanismo così astratto può portare a pensare che Windows sia potenzialmente in grado di eseguire qualunque applicazione per qualunque s.o. senza alcuna emulazione di mezzo, posto che sia presente un subsystem per gestirla; quindi in maniera praticamente “nativa“. Ad esempio, sarebbe ipotizzabile l’esecuzione di applicazioni scritte per OS X clickando sull’apposito file.
La realtà, però, è ben diversa e ci riporta bruscamente coi piedi per terra. Esiste, infatti, un filo conduttore che lega tutti i subsystem, ed è il formato EXE, cioé il file dell’applicazione che contiene il suo codice e i suoi dati. Il formato EXE è stato introdotto con l’MS-DOS, e successivamente esteso per permettere di contenere codice e dati di applicazioni delle varie versioni di Windows.
Comunque il DOS e Windows non sono gli unici ad averlo adottato: l’hanno fatto anche OS/2 (progettato originariamente da IBM e MS) e… Unix! Più precisamente POSIX. Infatti MS con Windows NT ha sviluppato un sottosistema POSIX-compatibile che fa uso di una particolare estensione del formato EXE per le sue applicazioni. Entrambi i subsystem hanno avuto uno scarso successo, e infatti sono stati rimossi con l’introduzione di Windows XP.
OS/2 era presente soltanto per far girare queste applicazioni, ma questo s.o. è stato abbandonato da MS e lasciato soltanto in mano a IBM, per cui per MS aveva poco senso continuare a mantenerne il relativo subsystem. Quella di introdurre il subsystem POSIX era stata, invece, una scelta strategica di MS, che aveva bisogno che NT fosse accettato anche negli ambienti militari che richiedevano necessariamente l’aderenza allo standard POSIX. Poiché POSIX è molto ampio, ma prevede soltanto un limitato insieme di funzionalità di base che devono essere messe a disposizione, ovviamente MS implementò soltanto il minimo indispensabile rendendolo, di fatto, inutilizzabile.
D’altra parte MS non aveva interesse a espandersi nel mercato Unix. In passato l’aveva fatto con una sua versione, chiamata Xenix (che tra l’altro ebbe un discreto successo), ma decise di concentrarsi esclusivamente sul prodotto di cui deteneva tutti i diritti e che poteva quindi controllare a proprio piacimento.
Tolto il vincolo militare dell’obbligatorietà all’aderenza alle specifiche POSIX, il relativo subsystem è stato soppresso, visto che il suo mantenimento aveva un costo (seppur minimo, come vedremo dopo). E’ singolare, però, che con Vista sia stato introdotto un nuovo subsystem POSIX (che aderisce a un insieme molto più vasto delle specifiche). Questo subsystem non è immediatamente utilizzabile, ma è necessario installarlo dal pannello di controllo come componente aggiuntivo di Windows (chiamato SUA). Una volta installato, è anche possibile installare un’utilissima suite di strumenti a corredo che vanno dalla bash ai compilatori GCC e g++. Chi ha lavorato con Cygwin avrà già intuito che con questo subsystem si può anche fare a meno di questo progetto.
Tornando ai subsystem, si potrebbe pensare a questo punto che ognuno contenga il codice del rispettivo sistema operativo che emula, andando quindi ad appesantire anche notevolmente il sistema a causa dell’occupazione di memoria in particolare e del codice che deve eseguire (che a sua volta dev’essere in qualche modo “controllato” per renderlo gestibile all’interno di Windows). Oltre a ciò, tutto questo codice presente va ovviamente mantenuto e ciò, ancora una volta, costa.
Il problema è stato risolto racchiudendo tutto il codice “nativo” di Windows in appositi file e reso accessibile alle applicazioni esclusivamente tramite l’uso dei subsystem, che lo richiamano in maniera opportuna. Per essere più chiari, la funzione che crea un file su Windows è presente in copia unica ed esposta dalle API native di Windows a tutti i subsystem, che la richiamano quando le loro API hanno bisogno di eseguire un’operazione di questo tipo.
Questo vuol dire che sia l’API CreateFile() del subsystem Win32 che la create() di quello POSIX fanno entrambe uso dell’API nativa che è stata appositamente creata allo scopo. Ovviamente le API native di Windows sono più affini al subsystem Win32, e una chiamata di un’API di quest’ultimo si traduce quasi sempre in una sola chiamata a un’API nativa, mentre per gli altri subsystem probabilmente servirà un po’ di codice in più per ottenere lo stesso risultato (in gergo si parla di “wrapper“), ma in ogni caso non siamo in presenza di una riscrittura della funzionalità richiesta.
I subsystem permettono quindi di ottenere molteplici vantaggi:
- compatibilità con s.o. diversi (ma sempre legati al formato EXE);
- isolamento (le applicazioni che girano dentro un subsystem sono separate da quelle che girano in un altro);
- manutenibilità (c’è una sola copia del codice da gestire di cui fanno uso tutti i subsystem, più un’eventuale piccola parte per eseguire il wrapping ove necessario);
- estensibilità (possono essere creati altri subsystem).
Tradotto in soldoni, questo significa che in teoria (ma anche in pratica: è stato fatto con Vista, infatti) è possibile riscrivere tutto il codice delle API native senza intaccare minimamente i subsystem, grazie al mantenimento dell’interfaccia con queste ultime. Allo stesso modo, è possibile riscrivere completamente il codice di un subsystem senza avere impatti su quello delle API native.
Qualche differenza fra i vari subsystem, però, c’è, ed è anche facile intuirla: Win32 è attualmente il subsystem “standard”, cioé quello utilizzato dal s.o. e che al suo avvio viene contestualmente caricato (mentre gli altri possono essere caricati dinamicamente, come dicevo prima). Inoltre gli è demandata la gestione dell’I/O e dell’interfaccia grafica. Ciò significa che tutti gli altri subsystem che vogliono utilizzare l’I/O o renderizzare a video qualcosa, devono necessariamente passare per i servizi da lui esposti.
Questo serve non soltanto a centralizzare queste operazioni, ma anche a “omogeneizzare” l’esecuzione delle applicazioni dei diversi subsystem. Per chiarire meglio il concetto, se un’applicazione Windows 3.1 (che fa uso del subsystem Win16) decide di aprire una finestra, questa verrà in realtà gestita e renderizzata dal subsystem Win32 (a cui Win16 avrà passato pari pari tutte le relative chiamate alle sue API di I/O e grafica) in accordo con le impostazioni di sistema, dando all’utente l’idea di un sistema coerente (chi ha avuto a che fare con applicazioni Java dotate di interfaccia grafica conosce bene il problema della disomogeneità della rappresentazione dell’interfaccia utente nel s.o. su cui gira).
Il rovescio della medaglia è che se qualcosa dovesse andare storto nel subsystem Win32, l’intera interfaccia grafica diventerà inutilizzabile anche da parte degli altri subsystem. E’ un grosso e pesante limite di cui bisogna tener conto, ma al momento rappresenta la miglior soluzione per garantire la consistenza dell’interfaccia con l’utente a fronte della disomogeneità dei diversi subsystem.
In tutta questa lunga dissertazione c’è, però, un illustre assente: .NET. Questo in realtà non è un subsystem vero e proprio, in quanto poggia interamente su Win32 per la sua esecuzione e quello delle relative applicazioni che ne fanno uso. Dunque non meritava d’esser trattato. C’è, però, da sottolineare che le applicazioni .NET non “vedono” il sottostante subsystem Win32, ma soltanto l’ambiente .NET, per cui sono sostanzialmente indipendenti da Win32.
Non è, quindi, peregrina l’ipotesi di vedere invertite le parti in una futura versione di Windows, con .NET a rappresentare il subsystem “standard” e Win32 relegato al ruolo di subsystem, al pari di tutti gli altri. Ancor più interessante sarebbe l’integrazione di .NET quale sistema nativo su cui fondare l’intero s.o., perché permetterebbe di togliere di mezzo uno strato d’interfacciamento con le applicazioni, migliorando anche le prestazioni.
Ma forse sto galoppando troppo con la fantasia…