Gli inesistenti sistemi “Amiga NG”

La bancarotta di Commodore ha lasciato infranti i cuori di tantissimi appassionati di quella meravigliosa piattaforma hardware e software che per tanti anni li aveva deliziati: l’Amiga.

Gruppi di appassionati e pure le aziende che hanno rilevato quella vecchia hanno cercato di colmarne il vuoto, proponendo anche nuovo hardware su cui far girare il s.o. o una sua riscrittura.

Il progetto più antico (iniziato appena un anno dopo) in tal senso è rappresentato da AROS (inizialmente acronimo di Amiga Replacement Operating System. Poi divenuto AROS Research Operating System), il cui obiettivo era (e rimane) quello di offrire una reimplementazione delle API del s.o. dell’Amiga in versione 3.1 (l’ultima di casa Commodore), che fosse compatibile a livello sorgente per tutte le piattaforme hardware e anche a livello binario con le sole macchine originali (basate sui processori della famiglia Motorola 68000).

Il secondo in ordine di tempo è stato MorphOS, anch’esso sviluppato da un gruppo di appassionati (per macchine basate su processori PowerPC), i quali hanno anche “preso in prestito” alcuni componenti di AROS, ma rispetto a quest’ultimo rimane principalmente chiuso (i sorgenti dei componenti sviluppati internamente non sono di dominio pubblico).

L’ultimo arrivato è AmigaOS 4, il cui sviluppo è stato commissionato dall’azienda che all’epoca aveva rilevato gli asset rimasti della ex-Commodore, con l’obiettivo di portare il s.o. originale su macchine anch’esse dotate di processori PowerPC.

Tutte e tre le piattaforme (ma l’hardware, rispetto alle macchine originali, ovviamente è cambiato. A parte per AROS, che riesce a girare anche su quelle) nascono, quindi, per dare una sorta di linea di continuità a ciò che l’Amiga ha rappresentato, fornendo un ambiente molto simile agli utenti che vi sono ancora rimasti ancora legatissimi e che si sono distribuiti, più o meno, in queste tre fazioni.

Sebbene ognuno abbia apportato miglioramenti al s.o. originale (che, comunque, nel frattempo ha ricevuto anch’esso degli aggiornamenti, nuove versioni, e diverse innovazioni abbastanza importanti anche da terze parti), c’è da dire che, purtroppo, si portano anche dietro dei grossi problemi che non sono mai stati risolti.

Protezione della memoria

Il più importante dei quali riguarda sicuramente la protezione della memoria. Se MacOS è stato reso famoso dalle famigerate “bombe“:

Windows dalle “schermate blu“:

e Unix viene preso da “attacchi di panico“:

il s.o. dell’Amiga ci ha lasciato in eredità e nell’immaginario collettivo le famose “Guru Meditation“:

le quali tante volte erano originate dalla totale mancanza di protezione della memoria in questo s.o.

Qualunque applicazione, infatti, poteva accedere tranquillamente a ogni zona di memoria, sia di altre applicazioni sia del s.o. (più informazioni sotto), coi problemi che si possono facilmente immaginare.

C’è da dire che il s.o. dell’Amiga era in ottima compagnia, perché era una problematica comune a tutti i s.o. “mainstream” dell’epoca: DOS, MacOS, GEM (Atari ST), Windows, funzionavano tutti esattamente allo stesso modo, e lo sono stati per lungo tempo (fatta eccezione per Windows, che già dalla seconda versione traeva vantaggio dalla modalità protetta. Ovviamente soltanto per i processori che la supportavano).

Alcuni dei s.o. “post-Amiga” (preferisco chiamarli così anziché “NG”) offrono una blanda protezione, intercettando accessi in scrittura alle porzioni di memoria dove è stato caricato del codice, ma si tratta di poca roba rispetto al problema generale che rimane in piedi.

C’è da dire che più di tanto non è possibile fare, se l’obiettivo è quello di preservare larga compatibilità con le applicazioni esistenti per il s.o. Amiga, poiché quest’ultimo è basato pesantemente sul meccanismo di passaggio di messaggi, che in questa piattaforma significa semplicemente condividere la loro area di memoria.

Implementare una completa protezione della memoria è, pertanto, impossibile, e richiederebbe un drastico cambiamento che scuoterebbe il s.o. alle fondamenta e che, evidentemente, i progettisti di questi s.o. non hanno voluto affrontare.

Tracciamento delle risorse

Un’altra rogna che viene trascinata fin dagli albori, e che è intimamente legata a quanto scritto qui sopra, è quella della gestione delle risorse, riferendosi con ciò alle entità (file, cartelle, dispositivi, memoria, schermi, finestre, ecc.) messe a disposizione dal s.o., le quali vengono richieste dalle applicazioni.

Prendendo l’esempio più semplice, quello dell’apertura di un file:

#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
    FILE* fp = fopen("example.txt", "w+");
    if (!fp) {
        perror("File opening failed");
        return EXIT_FAILURE;
    }
    fputs("Hello, world!\n", fp); 
    fclose(fp);
    return EXIT_SUCCESS;
}

sorgono problemi se, per qualche motivo, il file non venga chiuso correttamente dall’applicazione (tramite la funzione fclose) alla sua uscita, perché il file risulta bloccato fino a quando il sistema non viene riavviato.

Un s.o. ben strutturato si prende cura, invece, di tutti questi casi, poiché tiene traccia di tutte le risorse che gli sono state richieste e che ha allocato per ogni applicazione, liberando tali risorse automaticamente nel caso in cui la loro esecuzione termini (naturalmente o brutalmente).

Un s.o. post-Amiga non ha quest’automatismo, in quanto non sa se una determinata risorsa sia stata condivisa con altre applicazioni, ad esempio. Oppure se magari l’applicazione ha installato un cosiddetto hook per controllare parti del sistema, e in questo caso anche liberare d’imperio la memoria che era stata allocata per caricare il codice dell’applicazione potrebbe portare a dei disastri.

Il problema, però, è che se l’applicazione non ha liberato correttamente tutte le sue risorse prima della sua uscita, queste rimarranno in una sorta di “limbo”: inutilizzate, ma che comunque pesano nel sistema. Si tratta di quello che in gergo viene chiamato leak.

E’ facile immaginare che, a lungo andare, l’accumulo di leak nel tempo porti il sistema a divenire inutilizzabile, richiedendone il riavvio per ripartire da una configurazione completamente libera e con tutte le risorse a disposizione.

Concetto di processo e thread

Legato in parte ai due precedenti punti è il concetto di processo e thread (e anche fiber nei s.o. più moderni). All’avvio di un’applicazione le viene associato un processo (e in alcuni sistemi anche un thread, chiamato anche thread principale) e, quindi, uno spazio d’indirizzamento dove risiedono codice e dati da utilizzare.

Un processo gira in uno spazio d’indirizzamento isolato da tutti gli altri processi, ma lo condivide con tutti i thread (se sono presenti) che crea (per semplificare, diciamo che i thread normalmente si differenziano soltanto per lo stack utilizzato per la loro esecuzione, ed eventualmente per i loro dati privati).

L’idea è, quindi, duplice: da una parte quella di isolare completamente la memoria in modo da evitare crash dovuti a funzionamenti non corretti, mentre dall’altra quella di condividere risorse. Il tutto, ovviamente, in maniera solida e ben controllata (il che, sia chiaro, non mette automaticamente al riparo da problemi).

I sistemi post-Amiga non offrono null’altro rispetto all’originale: esiste un solo processo che gira nel sistema, quindi con un solo spazio d’indirizzamento che è condiviso da tutti i thread (che nel gergo di Amiga vengono chiamati task).

Un gran calderone, insomma, che rende il sistema estremamente fragile e non lo rende adatto a un uso più “mainstream“.

Multicore

Una caratteristica straordinaria (per il tempo) e che ha rappresentato forse IL punto di forza dell’Amiga è quella dei coprocessori integrati, Blitter e Copper, a cui la CPU poteva scaricare un bel po’ di lavoro, in quanto erano di gran lunga più veloci nel portare a termine determinati compiti.

I computer moderni sono da tempo coadiuvati da dispositivi del genere che assolvono a diversi compiti (non solo grafica, ma anche riproduzione & conversione di video, audio, USB, “dischi”, ecc., a cui ultimamente s’è aggiunta anche l’IA), ma già da parecchi anni anche la CPU è stata “sdoppiata” portando ai cosiddetti sistemi multicore (CPU che integrano decine o anche centinaia di core).

I s.o. “mainstream” hanno colto abbastanza rapidamente l’opportunità di sfruttare più potenza di calcolo dal numero di processori installati in un sistema, utilizzandoli per lo più in modalità SMP (anziché AMP o BMP).

Cosa che, purtroppo, non è successa coi sistemi post-Amiga, a parte un esperimento degli sviluppatori di AROS, i quali hanno introdotto l’SMP:

Clicca per ingrandire

Sfortunatamente al momento non risulta stabile e, inoltre, non è pienamente compatibile con l’ambiente del s.o. (sempre in versione 3.1). Per cui, almeno per ora, rimane un esercizio.

D’altra parte, e per come funziona il s.o. dell’Amiga, non c’è molto che si possa fare: ci sono problematiche intrinseche la cui risoluzione dovrebbe comportare il ripensamento di alcune parti (in maniera incompatibile col software esistente) e, al contempo, la modifica delle applicazioni che ne facciano uso.

64 bit

Quello che non è un esperimento ed è diventato, invece, una versione concreta e utilizzabile è il supporto ai 64 bit, che AROS ha introdotto con un port per l’architettura x64 (la stessa e l’unica per cui è stato implementato e funziona l’SMP), il quale ha finalmente consentito di superare la barriera dei 2GB del s.o. originale e poterne sfruttare diverse centinaia.

In questo caso è stato più semplice, in quanto AROS, a livello di obiettivi, si prefigge di essere compatibile soltanto a livello sorgente con le applicazioni Amiga quando deve girare su architetture diverse dall’originale (mentre lo scopo è di esserlo anche a livello binario per i processori Motorola 68000), per cui dovrebbe bastare una ricompilazione per ottenere dei binari per AROS/x64.

Ovviamente il tutto assumendo che le applicazioni siano ben scritte e, quindi, non facciano alcuna assunzione sulla dimensione di certi tipi di dati (in particolare i puntatori). Applicazioni ben scritte, sì, ma che dovrebbero essere la norma (sebbene la realtà ci abbia spesso mostrato tutt’altro).

Gli altri s.o. post-Amiga non offrono nulla del genere, e sono limitati a 1 o 2GB di memoria indirizzabile (a seconda della loro implementazione), ricalcando più o meno fedelmente i limiti del s.o. dell’Amiga.

C’è da dire che si sarebbero potuti indirizzare fino a 4GB di memoria, se non fosse che i progettisti dell’originale non avessero peccato di eccessiva (nonché totalmente fuori luogo) mania di ottimizzazione estrema, utilizzando il bit più significativo dei 32-bit dei puntatori per segnalare errori.

L’esempio per eccellenza è rappresentato dall’API AllocEntry():

RESULTS
    memList -- A different MemList filled in with the actual memory
        allocated in the me_Addr field, and their sizes in me_Length.
        If enough memory cannot be obtained, then the requirements of
        the allocation that failed is returned and bit 31 is set.

        WARNING: The result is unusual!  Bit 31 indicates faulure.

Poiché la funzione restituisce un puntatore, una soluzione di gran lunga migliore sarebbe stata quella di utilizzare il bit 0 (il quale non può essere mai a 1, quando si alloca della memoria) per segnalare un problema, per poi shiftare di un posto a destra il risultato e verificare il tipo di memoria che ha provocato il fallimento della richiesta.

In ogni caso la frittata ormai è fatta e permangono quei pesanti limiti, per cercare di mitigare i quali uno degli altri due s.o. post-Amiga ha introdotto un meccanismo molto simile al bank switching (in voga nei sistemi a 8 bit, verso la prima metà degli anni ’80), il quale prevede di riservare delle aree di memoria per le applicazioni che lo richiedono, sulle quali poi mappare sistematicamente (in base alle richieste delle applicazioni) memoria (fisica) che si trova oltre lo spazio d’indirizzamento normalmente indirizzabile.

Si tratta di una brutta pezza, insomma, a una problematica che si trascina ormai da tantissimo tempo, e che probabilmente farà storcere il naso ai tanti amighisti che erano abituati a prendere in giro i PC per l’utilizzo della memoria segmentata (mentre le loro macchine consentivano di indirizzare tutta la memoria linearmente e direttamente).

Lo Stack

Un’altra brutta gatta da pelare, che non ha mai trovato alcuna soluzione, è quella della gestione dello stack. Ogni applicazione ha bisogno di una porzione di memoria dedicata allo stack, dove vengono conservate le variabili locali delle funzioni e gli indirizzi di ritorno quando vengono effettuate delle chiamate:

La natura dello stack è, quindi, estremamente dinamica, e il puntatore alla sua cima (chiamato, in gergo, puntatore allo stack) si sposta continuamente a seconda della profondità delle chiamate a funzioni/metodi, e soltanto alla partenza di un’applicazione è ovviamente posizionato all’indirizzo più alto possibile.

Il fatto che sia una struttura dinamica è ottimo per il tipo di uso e funzionamento che se ne fa, ma rappresenta anche un’arma a doppio taglio nel caso in cui l’esecuzione vada fuori controllo e, ad esempio, il processore cominci a utilizzare memoria più in alto rispetto alla posizione iniziale dello stack.

Si tratta di un caso abbastanza raro, mentre molto più frequente e più facile che capiti è il caso opposto, ossia che si consumi troppa memoria dello stack andando molto in basso nelle locazioni di memoria, cosa che può capitare nel caso in cui un programma faccia troppo utilizzo della ricorsione, ad esempio.

Un s.o. moderno è in grado di intercettare il primo caso e bloccare l’applicazione, mentre nel secondo caso può estendere la memoria dello stack, aggiungendo memoria in basso, quando si accorge che l’applicazione ne ha usato più di quella che inizialmente era stata allocate per lei.

Si tratta di un’operazione abbastanza veloce e semplice da implementare per un s.o. se consideriamo come viene generalmente mappata la memoria di un’applicazione:

Il grosso problema dell’Amiga è che, purtroppo, non può utilizzare un modello simile a questo, avendo l’intero spazio d’indirizzamento condiviso da tutti i task/thread e anche dal s.o.. Per cui quando un’applicazione viene caricata da quest’ultimo, i suoi blocchi di memoria vengono allocati arbitrariamente dove capita.

Per questo motivo ogni applicazione ha bisogno di specificare quanta memoria dev’essere allocata per il suo stack, in modo che possa girare correttamente, ma si tratta comunque di un’informazione che viene impostata dallo sviluppatore in base a quello che egli pensa possa essere il consumo dello stack.

Purtroppo si possono intuire facilmente quali grossi problemi possano capitare in questo caso. Il primo è che non sia possibile aumentare la dimensione dello stack nel caso si fosse superata la quantità di memoria allocata.

Il secondo, ma legato sempre al primo, è che il processore continuerà tranquillamente la sua esecuzione come se nulla fosse, cominciando a utilizzare la memoria sotto quella allocata e che, però, potrebbe essere già utilizzata per qualunque altra cosa (altre applicazioni o anche il s.o..), con risultati disastrosi (e a volte molto difficili da individuare, poiché spesso non è facile accorgersi che ci siano problemi e, soprattutto, che siano causati proprio dall’insufficienza di stack).

Accesso alle strutture del s.o. (e mancanza di astrazione)

D’altra parte questa è conseguenza del fatto che il s.o. Amiga non abbia sostanzialmente alcuna forma di protezione e che l’accesso e l’uso della memoria sia “aperto” a chiunque, anche per quanto riguarda le strutture dello stesso s.o..

Tali strutture sono, di fatto, pubbliche. Le loro specifiche e i relativi dettagli sono, infatti, stati pubblicati da Commodore e messe a disposizione degli sviluppatori, con alcuni casi in cui alcuni dettagli (ad esempio il funzionamento dello schedulatore dei task) che non stati pubblicati e, quindi, riservati al solo uso interno.

L’esempio più eloquente è rappresentato dalla libreria Exec, che è il cuore dell’intero s.o. e i cui dettagli della sua struttura principale sono interamente visibili e accessibili (ad esempio qui). Alcuni campi sono stati marcati come privati, ma sono stati utilizzati ugualmente dalle applicazioni mancando delle API per alcuni scopi (ad esempio non ce ne sono per conoscere l’elenco dei task che stanno girando).

Ovviamente il problema non riguarda soltanto Exec, ma è endemico dell’intera piattaforma: la mancanza di apposite API e/o l’aver pubblicato parecchi dettagli sulle strutture interne del s.o. è stata la principale causa del blocco della sua evoluzione.

Per fare un esempio pratico, vediamo l’API che viene utilizzata per aprire una finestra:

NAME
    OpenWindow -- Open an Intuition window.

SYNOPSIS
    Window = OpenWindow( NewWindow )
    D0                   A0

    struct Window *OpenWindow( struct NewWindow * );

All’apparenza è molto semplice, perché tutti i dettagli (attributi e strutture varie) sono nascosti all’interno dell’unico parametro di tipo NewWindow. Quest’ultimo, però, contiene diversi elementi nonché puntatori ad altre strutture dati, di cui però non è noto il proprietario né se tali strutture siano state condivise con altre applicazioni (il che è importantissimo per le problematiche della gestione delle risorse di cui abbiamo parlato prima).

Comunque l’aspetto più importante è appresentato dal valore restituito da tale funzione, il quale è un puntatore alla struttura di tipo Window. Il s.o. ha, quindi, condiviso con l’applicazione la struttura interna che egli stesso utilizza per gestire le finestre, riportando pubblicamente ogni singolo dettaglio.

Vediamo, adesso, come un s.o. ben progettato consente di aprire una finestra senza, però, rilasciare informazioni sui dettagli interni. Ad esempio, con Windows 1.0 (rilasciato lo stesso anno dell’Amiga: il 1985), il quale utilizza le famose API WIN16 (che sono state rilasciate come standard ECMA):

HWND CreateWindow(LPCSTR lpszClassName, LPCSTR lpszWindowName, DWORD dwStyle,
                  int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu,
                  HINSTANCE hInstance, LPVOID lpCreateParams);

27.2 Description
The CreateWindow() and CreateWindowEx() functions are used to make a window. CreateWindowEx() differs from CreateWindow() by one extra parameter - extended window style.

Come si può notare, una parte dei dati relativi alle risorse utilizzate (finestra “genitrice”, menu, ecc.) nonché lo stesso valore restituito dalla funzione sono oggetti astratti (“opachi”), chiamati in gergo handle, i quali non riportano nessuna informazione né dettagli relativi alle loro strutture interne.

In particolare va sottolineato come qualunque risorsa utilizzata sia rappresentata nonché gestita dal s.o. sempre e soltanto come handle e non direttamente come puntatore alla struttura di cui si hanno informazioni pubbliche.

Ciò costringe gli sviluppatori a dover utilizzare apposite API per poter richiedere informazioni di un certo tipo (getter, in gergo) oppure per richiederne il cambiamento (setter, sempre in gergo):

WORD GetClassWord(HWND hWnd, int nIndex);
WORD SetClassWord(HWND hWnd, int nIndex, WORD wNewWord);

24.2 Description
The GetClassWord() and SetClassWord() functions retrieve and set a word value at the specified offset from extra memory associated with the class associated with the specified window.

Il tutto avviene, quindi, in maniera assolutamente controllata (nonché “opaca”) dal s.o., senza che l’applicazione possa accedere a dettagli interni né tanto meno cambiarli come le pare, contrariamente a quanto, purtroppo, avviene col s.o. dell’Amiga (e “successori”).

Modalità kernel/supervisore… e altro

L’estrema libertà offerta dal s.o. si può anche notare da un altro aspetto non di poco conto e che non salta immediatamente all’occhio: quello di poter passare tranquillamente dalla modalità utente a quella supervisore/kernel (e viceversa, ma… solo se si vuole!).

Exec mette, infatti, a disposizione delle apposite API allo scopo, SuperState() e UserState(), le quali consentono di passare dall’uno all’altro. Inutile rimarcarne l’estrema pericolosità, quando ben sappiamo quanto, invece, i s.o. moderni si sforzino di isolare il più possibile i due mondi e di regolare in maniera ferrea i passaggi dall’uno all’altro.

Sulla stessa scia è la presenza di API che possono disabilitare (Forbid()) e riabilitare (Permit()) il multitasking, e persino disabilitare (Disable()) e riabilitare (Enable()) le interruzioni. Se passare in modalità supervisore era già roba da stracciarsi le vesti, potete immaginare quanto potere e quanti danni possano fare queste quattro API (le quali sono anche il principale problema per l’implementazione dell’SMP).

Sfortunatamente si tratta di situazioni che a volte non si possono evitare, proprio a causa della mancanza di altre API per consentire di effettuare alcune operazioni (getter, setter, enumeratori/iteratori), costringendo, pertanto, le applicazioni a ricorrervi e, ogni volta, mettendo in serio pericolo la stabilità del sistema.

Si può già vedere come l’apparente libertà offerta dal s.o. dell’Amiga nasconda, invece, una generalizzata penuria di API che mancano all’appello, vuoi per non curanza dei suoi progettisti, vuoi per far fronte alla cronica mancanza di memoria / risorse per la loro implementazione.

Che possa essere una generale predisposizione degli architetti che l’hanno concepito e poi implementato è un dubbio più che lecito, che affiora anche quando si incontrano altre API, come quelle relative alla gestione dei segnali, ad esempio.

Infatti nella loro documentazione si legge questo:

Before a signal can be used, it must be allocated with the AllocSignal()
function.  When a signal is no longer needed, it should be freed for reuse
with FreeSignal().

    BYTE AllocSignal( LONG signalNum );
    VOID FreeSignal( LONG signalNum );

AllocSignal() marks a signal as being in use and prevents the accidental
use of the same signal for more than one event.  You may ask for either a
specific signal number, or more commonly, you would pass -1 to request the
next available signal.  The state of the newly allocated signal is cleared
(ready for use).  Generally it is best to let the system assign you the
next free signal.  Of the 32 available signals, the lower 16 are reserved
for system use.  This leaves the upper 16 signals free for application
programs to allocate.  Other subsystems that you may call depend on
AllocSignal().

Da prestare attenzione alle parti evidenziate. Andando, però, a leggere quella di FreeSignal():

NAME
    FreeSignal -- free a signal bit

SYNOPSIS
    FreeSignal(signalNum)
               D0

    void FreeSignal(BYTE);

FUNCTION
    This function frees a previously allocated signal bit for reuse.
    This call must be performed while running in the same task in which
    the signal was allocated.

non v’è alcun riferimento né spiegazione su cosa potrebbe succedere se, per sbaglio o intenzionalmente, si chiedesse di liberare un segnale di sistema: l’API fallirebbe oppure procederebbe tranquillamente? Non è dato saperlo (anche perché la funzione non restituisce alcun risultato per l’operazione), ma se la seconda possibilità fosse quella giusta non farebbe che confermare l’assunto di cui sopra.

Il risultato, in definitiva, è il sistema estremamente veloce che ci ha deliziato per tanti anni poiché, mancando controlli e non allocando risorse in tanti casi molto comuni nonché frequenti, questa è l’impressione generale che ne è derivata.

Ciò non significa che fosse necessariamente un bene: magari lo era per l’epoca a causa della scarsità di risorse (sebbene anche altri s.o. / piattaforme si trovassero nella medesima situazione), ma le scelte effettuate dagli ingegneri che hanno lavorato alla piattaforma sono anche quelle che l’hanno sostanzialmente condannata allo stato in cui versano anche i s.o. post-Amiga che ancora oggi continuano a essere sviluppati.

Modernità non è solo andare oltre il chipset originale

C’è da dire che tanti, nonché molto pesanti, limiti del s.o. dell’Amiga sono stati col tempo superati, rendendolo anche più moderno, e si tratta di cambiamenti che sono stati incorporati fin dall’inizio nei s.o. post-Amiga.

Mi riferisco, in particolare, alla totale simbiosi del s.o. col chipset delle macchine di casa Commodore. Dunque grafica, audio, e il sistema di I/O sono stati riprogettati o hanno fatto uso di altre librerie in modo da poter utilizzare la tecnologia che è stata “presa in prestito” dagli arcinemici PC (d’altra parte non è rimasto altro da fare, una volta defunta la casa madre).

Come ben sappiamo, nel bene e nel male le API del s.o. sono sostanzialmente una mappatura 1:1 col sottostante hardware messo a disposizione dal chipset. Ciò ha reso il sistema estremamente veloce e versatile nello sfruttamento di quello che c’era sotto la “carrozzeria”, ma l’ha anche legato mani e piedi a esso.

Un chiaro esempio di questa contorta mentalità è rappresentato dalla struttura (che dovrebbe essere di sistema, ma che ovviamente è “pubblica” nel s.o. dell’Amiga) che contiene le informazioni di uno schermo, la quale integra direttamente alcune strutture critiche:

struct Screen
    {
    struct Screen *NextScreen;
    struct Window *FirstWindow;
    WORD LeftEdge, TopEdge, Width, Height;
    WORD MouseY, MouseX;
    UWORD Flags;
    UBYTE *Title, *DefaultTitle;
    BYTE BarHeight, BarVBorder, BarHBorder, MenuVBorder, MenuHBorder;
    BYTE WBorTop, WBorLeft, WBorRight, WBorBottom;
    struct TextAttr *Font;
    struct ViewPort ViewPort;
    struct RastPort RastPort;
    struct BitMap BitMap;
    struct Layer_Info LayerInfo;
    struct Gadget *FirstGadget;
    UBYTE DetailPen, BlockPen;
    UWORD SaveColor0;
    struct Layer *BarLayer;
    UBYTE *ExtData, *UserData;
    };

Mi riferisco, nello specifico, a ViewPort, RastPort, BitMap e LayerInfo, che non sono presenti sotto forma di puntatori. Questa scelta scellerata ha comportato il fatto che tutte e quattro queste strutture non possano avere delle variazioni in termini di dimensioni (quindi aggiungendo altri campi) ma debbano rimanere esattamente come sono anche per tutte le future versioni del s.o., pena la perdita di compatibilità con l’intero ecosistema.

Ciò risulta particolarmente evidente per BitMap:

struct BitMap
{
    UWORD   BytesPerRow;
    UWORD   Rows;
    UBYTE   Flags;
    UBYTE   Depth;
    UWORD   pad;
    PLANEPTR Planes[8];
};

la quale rappresenta un framebuffer (la grafica vera e propria) e che, come si può tristemente vedere, rispecchia esattamente la natura del chipset originale, nel quale la grafica planare è conservata in singoli bitplane, di cui sono visibili gli (al massimo) 8 puntatori.

Inutile dire che una siffatta struttura non si sposi affatto con l’evoluzione dei sottosistemi grafici, che ci hanno portato all’utilizzo di grafica “packed/chunky“, superando il limite dei massimo 8 bit per pixel = 256 colori e andando ben oltre.

Situazione anche peggiore per l’audio, poiché non esistono API di alto livello per poter utilizzare i canali per riprodurre i campioni sonori e, quindi, si deve far ricorso allo specifico dispositivo (audio.device) impartendogli degli appositi comandi allo scopo.

In ogni caso siamo ancora una volta in presenza di una mappatura 1:1 col sottosistema audio dell’Amiga, in quanto si possono utilizzare i soli quattro canali a disposizione con quest’hardware, che riproducono campioni sonori a 8 bit (quindi niente 16 bit o altro), e infine anche volume (64 livelli) e frequenza (circa 28Khz massimo) devono rispecchiare i valori da impostare negli appositi registri del chip custom (Paula).

Tutte queste limitazioni sono state in buona parte risolte grazie ad hack / progetti esterni al s.o. originale, che hanno dato origine a tecnologie come RTG per la grafica e AHI per l’audio, che sono poi diventate lo standard di fatto per schede grafiche e sonore provenienti dal mondo PC (in primis).

In maniera simile sono stati superati i limiti del filesystem originale (fermo ai 32 bit e, quindi, in grado di gestire file di massimo 4GB in lunghezza. Limite che inizialmente era condiviso per l’intera dimensione dei dischi / supporti di memorizzazione di massa), è stato introdotto uno stack TCP/IP per collegarsi a internet (e quindi sono arrivati anche i browser), uno stack USB, librerie per rendere più facile la realizzazione di interfacce grafiche, ecc. ecc.

Sono stati persino realizzati dei completi rimpiazzamenti per il vecchio gestore del desktop / file manager (il Workbench), di cui degni di nota sono Scalos:

e Directory Opus (poi divenuto Magellan):

Clicca per ingrandire

S’è fatto molto, insomma, per migliorare nettamente il sistema anche senza che Commodore abbia messo mano al progetto Amiga quando poteva o dopo che l’azienda era ormai andata in bancarotta, contribuendo a rendere decisamente più gradevole e semplice l’esperienza degli utenti.

Conclusioni

Tutte queste innovazioni, però, non possono nascondere i limiti intrinseci del sistema, di cui sono stati riportati i più rilevanti nelle apposizione sezioni dell’articolo.

E’ vero che le piattaforme post-Amiga offrono molto di più rispetto al s.o. originale, ma la maggior parte di tali cambiamenti sono stati portati e apportati anche ad esso, come già detto, e non possono in alcun modo nascondere o mitigare quanto ancora oggi di estremamente limitato e limitante si portino dietro.

Sono queste le motivazioni che mi portano a rigettare l’etichetta di “Amiga NG” che è stata attribuita ai tre sistemi post-Amiga che ho citato all’inizio: non essendo svaniti i limiti intrinseci della piattaforma originale, non v’è alcun motivo per poter parlare di “Prossima (Next) Generazione”.

AROS è l’unico che abbia risolto alcuni problemi, in parte, ma è ancora troppo poco per poter parlare di “NG”.

La verità è che per andare oltre tali limiti si dovrebbe per forza di cose ripensare interamente la piattaforma Amiga, togliendo di mezzo le fondamenta su cui è basata e, quindi, portando a un sistema nuovo, e soltanto ispirato a quello che ci ha resi felici.

Qualcosa che, però, gli sviluppatori attuali non si sentono di fare, condannando, di fatto, i loro prodotti a rimanere per sempre isolati in una piccola nicchia di sistemi hobbistici (Hobby/Toy o.s.).

Press ESC to close