Articolo pubblicato sul n. 47 di MCmicrocomputer (Edizioni Technimedia Srl - Roma) nel dicembre 1985 Il Sistema Operativo Batch
Seconda puntata di Appunti di Informatica. Dopo aver visto
il mese scorso abbastanza in generale la struttura di un
sistema di calcolo, l'argomento di questo mese riguarda le
tecniche di comunicazione tra dispositivi di ingresso/uscita
(stampanti, nastri, dischi, plotter ecc.) e il calcolatore
vero e proprio. Analizzeremo il problema non tanto dal punto
di vista tecnico, cioè i vari metodi di trasmissione
dell'informazione dal punto A al punto B di un sistema di
calcolo, ma elevandoci a considerare solo gli aspetti logici
di una comunicazione. In altre parole, posto che due oggetti
possano comunicare, come sia possibile organizzare un
traffico di comunicazioni.
Comuncazione Computer-Device Come descritto il mese scorso, un sistema di calcolo è sempre formato da più parti tra loro interagenti. Ogni computer avrà certamente una memoria per contenere programmi e dati, un'unità controllo processo (CPU) per eseguire programmi e un numero più o meno esteso di dispositivi (device) di ingresso/uscita. Ad esempio la stampante è un'unità di uscita (output device), la tastiera di un terminale un'unità di ingresso (input device) infine un driver per dischi sarà un'unità allo stesso tempo di ingresso e di uscita, essendo possibili su tale dispositivo sia operazioni di lettura che di scrittura.
In generale ogni dispositivo sarà collegato sia con la CPU,
per ricevere comandi circa l'operazione da eseguire, sia con
la memoria dato che il "qualcosa" da far entrare o far
uscire deve finire o è parcheggiato in tale unità. In
alcuni personal la connessione tra dispositivi e memoria non
è diretta ma passa sempre per la CPU che riceve ad esempio
il dato richiesto e "personalmente" lo scrive in memoria.
Nei sistemi un po' più seri ciò non accade mai in quanto
la CPU, per minimizzare il costo dell'esecuzione di un
programma (centinaia di migliaia di lire al secondo), deve
sempre e solo elaborare programmi, non curandosi di
particolari tecnici come il prelevare dati dal disco. Al
punto che se un dato richiesto tarda ad arrivare (e ciò da
disco o da nastro o da qualsiasi dispositivo meccanico è
normale) la CPU molla il programma che stava eseguendo e
In figura 1 è mostrato un primo approccio per il collegamento tra computer e dispositivi. Concettualmente è il più semplice di tutti, ma anche il più costoso ed efficiente. In tale schema ogni dispositivo ha una connessione fisica con la CPU ed una con la memoria. Si noti che tali connessioni sono per così dire private: ogni connessione è adoperata da un solo dispositivo. In questo modo, nulla vieta che mentre (ad esempio) il lettore di nastri riversa un programma in memoria, la CPU tramite un plotter fa un bel disegno, come da programma in corso di esecuzione. La costosità sta nel fatto che occorre disporre di tanti collegamenti quanti sono i dispositivi i quali, dal canto loro, possono benissimo essere tanti, dislocati a notevole distanza tra loro e dal computer.
In figura 2b è mostrato un altro sistema di collegamento detto "bus ad anello". La CPU e la memoria sono collegate, come sempre, tra loro e a un processore di Input Output che gestisce le operazioni di ingresso/uscita. I dispositivi sono collegati a catena e il flusso delle informazioni circola in un solo senso (vedi frecce). Ogni messaggio per un dispositivo corre sul bus ad anello e contiene in testa il nome del destinatario. Per fare un esempio, immaginiamo di dover dialogare col dispositivo 2 che è un lettore di dischi: il nostro messaggio è "preleva il dato xxx e mandamelo". A questo dovremo aggiungere in testa il destinatario, cioè Dispositivo 2. In tale modo quando D1 riceverà il messaggio saprà che non è per lui e lo "girerà" a D2. D2 lo riconoscerà come proprio ed eseguirà l'ordine. Per risposta scriverà un nuovo messaggio, questa volta etichettato "CPU" che come prima oltrepasserà D3 e giungerà a destinazione. Semplice, no? Sincronizzazione Computer-Device Per prima cosa vedremo come funziona un semplice modello di sincronizzazione tra computer (CPU + Memoria Pricipale) e dispositivo. Occorre un sincronismo dato che generalmente i dispositivi sono assai più lenti delle CPU: pensate per un attimo ad una stampante, potrà stampare 120 caratteri al secondo... il computer gliene può passare anche 2000 nello stesso tempo.
Scherzi a parte è ovvio che se la stampa avviene a 120 cps, il computer mediamente dovrà al massimo inviarglieli alla stessa frequenza, chiedendo il permesso prima di spedire un nuovo lotto di caratteri da stampare. Su questo principio, funziona il primo meccanismo di sincronizzazione illustrato in figura 3. Computer e device sono tra loro collegati oltre che dal bus principale dove ci corrono messaggi, risposte e dati, anche da due bus di controllo (1 e 2) che servono il primo per dare il via al dispositivo e l'altro per controllare se questo è libero o sta ultimando un'operazione. Così, se il device è una stampante che stampa linee di max. 80 caratteri, la sequenza delle operazioni compiute dalla CPU per stampare un po' di linee è, tenendo sott'occhi fig. 3, la seguente:
I due processi all'interno del computer e all'interno della stampante evolvono parallelamente e in perfetto sincronismo grazie ai segnali di Busy e di Ready. Questo metodo, assai semplice, ha il considerevole svantaggio di far restare la CPU in attesa che la stampante sia pronta per ricevere altri caratteri da stampare. E questo, come detto prima, non è cosa buona per un calcolatore che si rispetti. Un miglioramento delle prestazioni si ha impegnando la CPU in qualcos'altro invece di aspettare che un dispositivo si liberi. Per esempio far andare avanti un altro programma contenuto in memoria che in quel momento non ha da far aspettare la CPU. Per migliorare la sincronizzazione tra computer e device occorre introdurre il meccanismo delle interruzioni o interrupt. Un interrupt è un segnale proveniente dall'esterno della CPU che fa interrompere l'esecuzione del programma in corso per far partire immediatamente una subroutine detta appunto di gestione delle interruzioni. E' all'interno di tale subroutine che si decide il da farsi a seconda della provenienza dell'interruzione o di altri fattori. Terminato questo "da farsi" il programma interrotto continua la sua elaborazione come se nulla fosse accaduto. Il meccanismo delle interruzioni si ampia poi col concetto di maschera e di priorità che qui illustreremo solo brevemente. Può succedere infatti che la CPU preferisca non essere disturbata da interruzioni, dovendo per esempio compiere operazioni delicate (come inviare un messaggio a un dispositivo), e allora impone una maschera all'interrupt. In questo modo l'interruzione è completamente ignorata o al più resta pendente ossia in attesa che la maschera venga tolta. Le priorità infine permettono di dare per così dire una forza variabile all'interrupt. Per esempio la CPU invece di mascherare completamente le interruzioni potrebbe porsi in stato di priorità 5. In questa maniera se l'interrupt ha una priorità maggiore di 5 viene accolto altrimenti è ignorato. Se la CPU vuole accogliere tutte le interruzioni si porrà in priorità 0; se vorrà mascherare qualsiasi interrupt si porrà in stato di priorità massima. Con questo meccanismo si possono dare priorità diverse ai vari dispositivi che generano interruzioni in modo da accogliere più sovente le richieste dai device più veloci (dischi, tamburi) e dare meno retta a device di natura lenta (stampanti, lettori di schede e di nastri).
Se qualcosa non vi è chiaro non disperate questo tipo di sincronizzazione lo vedremo meglio ora che commenteremo il funzionamento di un piccolo... Sistema Operativo Batch Dicevamo nell'introduzione che col sistema operativo Batch l'utente di un centro di calcolo per far girare i propri programmi doveva innanzitutto trasferirli su schede perforate. L'operazione veniva compiuta presso un apposito (rumorosissimo) marchingegno detto appunto perforatrice di schede. Generalmente ogni scheda conteneva una linea di programma o una linea di dati per un massimo di 80 caratteri. Finito di perforare le schede contenenti programma e dati, bisognava aggiungere alcune schede di controllo contenenti caratteri speciali (asterischi, barre o altro) per specificare alcune opzioni per la compilazione e, giustamente, per delimitare il proprio pacchetto di schede. Infatti i vari programmi (leggi: pacchetti di schede perforate) raccolti nella giornata, venivano introdotti nel lettore di schede, uno di seguito all'altro. Era il sistema che doveva riconoscere (grazie alle schede di controllo) dove terminava ogni programma per poterlo compilare . Chi legge avrà certamente notato l'uso di tempi passati dei verbi: il sistema Batch è ormai in via di estinzione, ne sarà rimasto vivo qualcuno in centri di calcolo universitari dove gli studenti vanno per farsi "le ossa". Oggi si usano prevalentemente i terminali, non importa se collegati direttamente col calcolatore (e quindi con la CPU in linea, come nei personal), o sempre stile perforatrici e schede, con la differenza che i programmi vengono salvati su disco magnetico, è possibile editarli su video e l'operatore a determinate ore del giorno prende il disco pieno di programmi da elaborare e lo introduce nel computer (come faceva prima coi pesanti cassetti di schede).
Il primo algoritmo è mostrato in figura 5, cominciamo dall'interfaccia CLE. CLE sta per Compiler Loader Execute e riassume le tre fasi comprese dall'ingresso di un programma nel computer fino all'uscita dei suoi risultati (escluso). Infatti un programma scritto in qualsiasi linguaggio al alto livello viene prima compilato poi caricato in memoria (load) e poi mandato in esecuzione. Interfaccia CLE sta per algoritmo che preleva i vari programmi dal lettore schede, esegue la fase CLE e stampa i risultati. L'interfaccia CLE (fig. 5a) è composta da 6 passi. Il primo passo (gosub Controllo lettore) corrisponde a leggere una scheda tramite il lettore. Col passo 2 (posto che ogni programma termina con un asterisco come carattere di controllo) si cicla continuamente finché non è stato letto un intero programma. Il passo 3 è la fase CLE e quindi dopo tale passo il programma "ha girato". Col passo 4 se non ci sono linee da stampare si continua col programma successivo (vai a 1) altrimenti (passo 5) si salta a Controllo stampante per stampare una linea e (passo 6) si torna la passo 4 per vedere se ci sono altre linee da stampare. Per completezza vediamo come funzionano anche le altre routine di figura 5. La 5b come detto legge una scheda tramite il lettore, e per fare ciò utilizza i meccanismi di sincronizzazione CR-busy e CR-ready (CR sta per Card-Reader, lettore di schede). L'algoritmo è assai semplice: definisce una area buffer (trova un posto libero in memoria dove parcheggiare ciò che leggerà); attende che il lettore sia libero (wait CR-ready); dà il comando al lettore e segnala CR-busy (dà ordine al lettore schede di partire e leggere una scheda). Basta. Parallelamente (fig. 5c) il lettore schede attende il segnale di partenza (Wait CR-busy); preleva il comando dal Bus principale e l'esegue (legge la scheda e pone in memoria il suo contenuto); segnala che è pronto per una nuova lettura (signal CR-ready) e torna al primo passo in attesa di un nuovo comando. Per la stampa, 5d e 5e, funzionano praticamente nello stesso modo: unica differenza il prefisso LP che sta per Line Printer e [linee da stampare] decrementato ogni volta che viene chiamata Controllo stampante, in modo da fermarsi (passo 4 di 5a) quando non vi sono più linee da stampare.
Spieghiamoci meglio. Il primo programma è caricato in memoria azionando il lettore di schede. Dato che in tale istante non vi sono altre cose da fare, è ovvio che il primo programma sarà caricato come abbiamo sempre fatto ossia aspettando che sia terminata l'operazione. Avendo ora un programma in memoria, possiamo contemporaneamente leggere le schede del secondo programma e nelle pause tra l'arrivo di una scheda e l'altra, compilare (per essere più precisi eseguire la fase CLE de) il primo. Terminata la fase CLE, se anche il secondo programma è stato letto, possiamo iniziare a leggere il terzo, stampare i risultati del primo e compilare il secondo. Se non vi è sfuggito nulla, capirete bene che eccetto il primo "giro" il nostro sistema operativo Batch in ogni momento stamperà i risultati del programma i, compilerà caricherà ed eseguirà il programma i+1 leggerà le schede del programma i+2.
Passiamo ora all'interfaccia CLE di fig 7a. Avevamo detto che il primo programma doveva essere letto per intero. Ciò è realizzato dai primi 4 passi: Si definisce un'area buffer (come prima) e si pone sul bus principale il comando per il lettore. Con signal CR-busy (passo 3) il lettore legge una scheda. Mentre fa questo il controllo è fermo sul passo 4: il processo aspetta il segnale di fine lettura (del programma, non di una scheda). Aspetta e basta. Intanto il nostro lettore ha completato la lettura della prima scheda e come promesso manda un interrupt alla CPU (che aspettava, ferma al passo 4). Parte la routine di 7b che gestisce le interruzioni. L'interruzione proviene dal lettore e viene invocata la subroutine Controllo lettore. Lì (7c) se l'ultimo carattere letto è "*" vuol dire che il primo programma è finito e occorre mandare il segnale di fine lettura. Altrimenti (passi 2 e seguenti di 7c) si dà il comando di leggere un'altra scheda. Return di 7c ci fa ritornare a 7b dove return ci fa ritornare al passo 4 di 7a dove era avvenuta l'interruzione. E' ovvio che tutto ciò continua (il processo aspetta sempre al passo 4 salvo quando è interrotto dal lettore) fino a quando non viene letto 'sto benedetto asterisco. Dopo tutto questo abbiamo soltanto letto il primo programma; continuiamo. Passo 5 (sempre di 7a): [linee da stampare] è posto uguale a 0: è ovvio, in quest'istante abbiamo solo il primo programma in memoria e dobbiamo ancora eseguirlo: cosa vorremmo stampare? Passi 6,7 e 8: si dà ordine al lettore di leggere una nuova scheda. Passo 9: se [linee da stampare] è 0 vai a 12: è il caso nostro. Compilazione caricamento ed esecuzione. Mentre ciò avviene, il lettore terminerà la lettura della prima scheda del secondo programma e manderà un interrupt: ciò in parole provere equivale a dare un nuovo ordine di lettura e a continuare dal punto (del passo 12 di 7a) dove era avvenuta l'interruzione. Finita la fase CLE (passo 12), se era stato letto l'asterisco del secondo programma allora era stato dato anche il signal di fine lettura e al passo 14 non ci si ferma, altrimenti aspettiamo. A questo "giro" anche 15 ci dà via libera dato che nessuna linea doveva essere stampata. Da questo momento siamo nel pieno del funzionamento dato che con "vai a 6" inizia un ciclo dove stamperemo i risultati del programma 1, eseguiremo la fase CLE del programma 2 (che è finito in memoria mentre eseguivamo la fase CLE del programma 1) e leggeremo il programma 3. Della serie: ...e così via! Impaginato originale...
Articolo pubblicato su www.digiTANTO.it - per ulteriori informazioni clicca qui |