OS Writing [3]: Per non essere fuori dal mondo

Un computer nasce, sostanzialmente, per fare conti. Quali conti e a cosa servano è una questione relativa, che al processore non interessa minimamente. Passategli dei dati e farà ciò che deve, senza fare domande. E qui sorge il problema: come glieli passiamo questi dati? Ovviamente, certi dati potrebbero essere salvati nella memoria centrale, l’unico dispositivo a cui, ora come ora, sappiamo accedere. Ma spesso e volentieri i dati provengono da altri dispositivi: per esempio, le letture acquisite da qualche sensore, o punti da e verso una scheda video, o bit ricevuti su una seriale, insomma quello che vi pare. Una CPU di per sé stessa non serve a niente, se non le fornite delle periferiche tramite le quali ricevere i dati da elaborare. E le periferiche possiamo anche dargliele, non è un grosso problema. Il problema è gestirle, specialmente se i dati da elaborare li vuole più di un processo e, peggio ancora, se questi dati li vogliono contemporaneamente. Per evitare pasticci, l’unica soluzione è che i dati i processi non se li prendano da soli, ma che glieli passi il sistema operativo.

DUE MONDI DA UNIRE

Per chi ha programmato un microcontrollore ad un livello abbastanza basso (ossia senza un sistema operativo di alcun tipo tra i piedi), ricevere un dato da una seriale o da una tastiera si può fare in due modi: sincrono e asincrono. In modo “sincrono” significa che, da qualche parte del codice avrete messo un loop che aspetta che il micro setti il magico flag che viene messo a uno quando il buffer dati della seriale è pieno. Quando il buffer è pieno, il flag va a uno, si esce dal ciclo di attesa, si cancella il flag, si copia il contenuto del buffer da qualche parte e si passa avanti. In modo “asincrono” significa, invece, che la ricezione dei dati dalla seriale l’avrete fatta all’interno della routine di gestione dell’interrupt che viene lanciata in automatico quando il buffer è pieno, contemporaneamente al settaggio di questo flag che dicevamo. Parte la ISR, si cancella il flag, si copiano i dati da qualche parte e si passa avanti. Quale delle due strategie sia migliore (ovviamente) dipende da cosa state facendo. Se quel dato vi serve proprio prima di poter procedere, e non potete fare assolutamente nulla nel frattempo, allora l’input sincrono è l’unica soluzione; altrimenti, meglio una ISR, che vi lascia libera la CPU per fare altro.

Per chi ha scritto software per PC, o per microcontrollore ad un livello più alto (quindi con un sistema operativo di qualche tipo tra i piedi), l’input è sempre (o quasi) sincrono. Mettete una scanf() nel codice e l’esecuzione si bloccherà fintanto che il buffer dati non sarà stato riempito. A meno che il vostro programma non sia multithread (supponiamo di no), il vostro processo resterà bloccato nell’attesa, e non c’è verso che possiate fare altro nel frattempo. I più esperti sapranno che il discorso non è del tutto esatto. Molti sistemi operativi permettono di registrare delle callback per aggirare questo problema dell’input bloccante. Una callback è semplicemente una funzione che verrà chiamata in automatico alla ricezione dell’input, quindi il sistema operativo dà per scontato che i dati li gestiate all’interno della callback, e non blocca più il processo in corrispondenza della scanf(). Dunque, in quel caso la scanf() serve semplicemente a dire al sistema “guarda, mi servono dei dati, quando li hai chiamami questa funzione qua”, e l’esecuzione del processo continua come se nulla fosse.

Le callback sembrano qualcosa di fantascientifico ai nuovi adepti, ma in realtà non sono niente di particolare. Ci torneremo tra un attimo; per adesso, restiamo nell’ipotesi dell’input bloccante. Il fatto che la richiesta di input blocchi il processo non significa che il sistema operativo si blocchi anche lui (per solidarietà) nell’attesa di quell’input. Sarebbe troppo inefficiente. Internamente, il sistema operativo aspetta un interrupt dalla seriale (o chi per lei), e nel frattempo fa altro.

Figura 1: Architettura software per la gestione dei dispositivi

La situazione è grossomodo quella della Figura 1. Internamente, il sistema operativo contiene una ISR per dispositivo. Questa ISR si attiva nel momento in cui il dispositivo in questione ha dei dati pronti, e il suo compito è semplicemente quello di salvarli in un buffer da qualche parte. Il sistema operativo contiene però anche una service call (SVC), una ISR che risponde ad un evento software che il processo utente può usare per chiedere al sistema operativo di passargli un pò di dati da quel buffer. Dunque, per gestire un dispositivo bisogna fare due cose: uno, gestire l’interrupt dal dispositivo e di volta in volta salvare i dati; due, gestire l’interrupt dal processo, così da restituirgli i dati ricevuti dal dispositivo.

INTERROMPIMI, SE SERVE

Figura 2: ISR per l'interrupt hardware

Delle due, la ISR per l’interrupt hardware è la più facile; l’altra richiede qualche accortezza in più. Come si vede dalla Figura 2, la ISR deve semplicemente copiare i dati ricevuti in un buffer, che avrà una certa dimensione (BUFSIZE) e sarà salvato da qualche parte in memoria, non ci interessa dove. La cosa interessante, qui, è da dove vengano questi dati. Osservate il codice: nel buffer stiamo copiando il contenuto di un membro di una struttura dati. Qualcuno potrebbe chiedersi come ci sono finiti i dati in quel membro della struttura dati. E’ una bella domanda. Il mistero si infittisce se notate che, se traduceste in assembly quell’istruzione, avremmo poco più di una load seguita da una store. La load preleva i dati dal membro dati e li mette in un registro, e la store li copia dal registro al membro dati. Questo significa che i dati che salviamo nel buffer già vengono dalla memoria, perché è da lì che la load li va a prendere, e questo significa che la domanda a cui rispondere è un’altra: come ci sono finiti i dati in memoria?

Per quanto strana, questa cosa non solo ha senso, ma è anche l’unica possibile. Come abbiamo detto all’inizio, la memoria è l’unico dispositivo di cui la CPU abbia la minima cognizione. La CPU non può accedere direttamente ad altri dispositivi; può solo accedere alla memoria, con le load e le store. Quindi, il problema di come i dati della tastiera siano finiti in memoria non è banale; anzi, potremmo quasi dire che si tratta di un problema necessario. Se i dati non finiscono in qualche modo in memoria, la CPU non può accedervi. Punto.

Figura 3: Memory-mapped IO

La soluzione a questo problema è il cosiddetto memory-mapped I/O. Osservate l’architettura nella Figura 3. È una comune architettura a bus, giusto? Abbiamo un master (la CPU) e un pò di slave (la memoria centrale e le altre periferiche) tutti collegati al bus comune. Quando il master vuole accedere a uno degli slave, mette il suo indirizzo sul bus indirizzi e lo slave il cui indirizzo coincide con quello messo sul bus risponderà alla richiesta. Se facciamo corrispondere l’indirizzo dello slave ad un indirizzo di memoria (ovvero uno che possa essere utilizzato con una load o una store), il gioco è fatto. Quando la load pone un certo indirizzo sul bus, lo slave che conosce quell’indirizzo risponderà con i dati richiesti. Dal punto di vista della CPU, accedere ad un dispositivo è lo stesso che accedere in memoria; bisogna solo dare l’indirizzo giusto. Questo è il memory-mapped I/O: si riservano un pò di indirizzi e li si assegnano ai vari dispositivi. Quando la CPU vuole accedere ad un registro contenuto in un dispositivo, le basta mettere sul bus l’indirizzo giusto o (è lo stesso) eseguire una load o una store verso l’indirizzo giusto, e al resto penserà il dispositivo. Quando sfogliate il datasheet di un microcontrollore e incappate nella mappa di memoria, e notate che certi indirizzi sono “riservati” per i dispositivi, questo è quello che sta accadendo. [...]

ATTENZIONE: quello che hai appena letto è solo un estratto, l'Articolo Tecnico completo è composto da ben 3014 parole ed è riservato agli ABBONATI. Con l'Abbonamento avrai anche accesso a tutti gli altri Articoli Tecnici che potrai leggere in formato PDF per un anno. ABBONATI ORA, è semplice e sicuro.

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend