Nel precedente articolo abbiamo parlato del KERNAL, un rudimentale OS di Commodore, ed abbiamo spiegato come esso implementi una forma di astrazione dell’hardware.
Tramite tale astrazione, il software può accedere alle funzioni base della macchina, controllarne le periferiche di I/O e gestirne le risorse, senza tuttavia conoscerne i dettagli implementativi. In termini tecnici, quella che si presenta al programmatore è una Interfaccia.
Studiando attentamente le funzioni del KERNAL ci si accorge facilmente che manca una precisa definizione del concetto di programma.
In poche parole, possiamo collocare dati ed istruzioni in modo sostanzialmente arbitrario. Le uniche funzioni offerte dal KERNAL per caricare i programmi, lavorano su array di byte “grezzi”.
Il programmatore può, quindi, avvalersi del KERNAL per impartire comandi all’hardware, ma è costretto a gestire manualmente la struttura dei suoi programmi, compresa la collocazione in memoria di codice, strutture dati, ecc…
Basta un banale cambiamento nella mappa di memoria, ad esempio portando il programma da un computer all’altro o semplicemente inserendo una espansione di RAM, per infrangere l’illusione di portabilità promessa dall’astrazione del KERNAL.
Il problema può essere parzialmente aggirato tramite la tecnica di rilocazione del codice, ma è una operazione tutt’altro che triviale e resta comunque a carico del programmatore. Esistono degli assemblatori che producono codice rilocabile, ma bisogna comunque produrre un compilato per ogni singola configurazione di memoria che si intende supportare.
Riassumendo in poche parole, abbiamo a disposizione un’interfaccia hardware che, in teoria, dovrebbe consentire lo sviluppo di codice per tutti i computer che implementano il KERNAL. In pratica ciò non è possibile, perché non vi è alcuna interfaccia software che garantisca una struttura standard per il codice e i dati in memoria.
Per superare questo ostacolo bisogna introdurre un nuovo livello di astrazione: il task.
Il task è una entità che definisce un nuovo tipo di interfaccia, cioè una serie di funzioni ed una struttura ben definita per rappresentare i programmi.
Se prima il Sistema Operativo aspettava “passivamente” di essere usato dal codice, con l’introduzione del concetto di task sarà in grado di controllare “attivamente” tale codice.
Quali sono, in concreto, le caratteristiche di un task?
Innanzitutto il task deve contenere le informazioni basilari riguardo la posizione in memoria di dati e codice. Quindi una prima, rudimentale, versione del task potrebbe essere costituita da 4 dati fondamentali: inizio e fine del segmento di codice, inizio e fine del segmento di dati.
Una volta stabilito questo standard, è possibile integrare nell’OS le funzionalità necessarie per rilocare il programma, cioè modificarne i riferimenti agli indirizzi di memoria, in modo da rendere il codice indipendente dalla Memory Map del sistema.
Per fare questo c’è bisogno di un’ulteriore informazione: la lista di tutti i riferimenti da modificare.
Questa viene generata dal compilatore (o dall’assembler), ma vedremo i dettagli di tale tecnica in uno dei prossimi articoli.
Al lettore più attento salterà subito all’occhio un particolare: dal momento che possiamo rilocare il codice per adattarlo a qualsiasi mappa di memoria, non possiamo rilocare tanti task in modo da averne in memoria più di uno contemporaneamente?
La risposta è si ed è effettivamente la base per la multiprogrammazione, cioè l’esecuzione di più task sulla stessa macchina. Il sistema dovrà tenere traccia del numero e della posizione in memoria dei vari task, pertanto saranno necessarie ulteriori informazioni per gestirli.
Ad esempio, sarà necessario assegnare un ID univoco a ciascun task, il quale verrà utilizzato come identificativo per l’assegnazione delle risorse di I/O, files, segmenti di memoria, ecc…
Nel prossimo articolo approfondiremo l’argomento studiando un caso concreto, un classico del mondo accademico: UNIX. Introdurremo i concetti di user e system mode, vedremo cos’è il context di un task e approfondiremo la questione relativa alla gestione della memoria e delle risorse.