Gestione dell/I/O

Introduzione
Gestione di un disco
Algoritmi di schedulazione lettura/scrittura
Gestione del clock
Schema generale dell'I/O su Minix
Clock driver in Minix
 
 

Introduzione (Indice )

Combinazione della gestione sincrona e asincrona dell'I/O: dal punto di vista dell'utente la gestione dell'OI/O e' un'attivita' sincrona, dal punto di vista del sistema operativo si tratta di una attivita' asincrona (interrupt driven).

Principi generali di hardware per I/O

Abbiamo due livelli:

  1. device driver (parte software)
  2. device controller (parte elettronica di controllo e parte meccanica o elettromeccanica)

I dispositivi (quasi tutti) possono essere classificati in block device e character device.

Block device: dischi, nastri, floppy, CDROM
E' possibile leggere ciascun blocco in modo indipendente dagli altri. E' caratterizzato dalla dimensione del blocco (128, ..., 1024 byte).

Character device: terminali, stampanti, network interface...
gestiscono l/I/O come una stringa di caratteri.

Esistono anche altri device non classificabili (es. clock)

Struttura di un device

Parte meccanica/  --->   interfaccia        ----->   controller
elettromeccanica         controller device

Controller: e' la parte elettronica che consente di guidare il device fisico vero e proprio, ed e' anche l'interfaccia tra il device ed il software. Si tratta di un componente molte volte assimilabile ad un elaboratore dedicato.

Interfaccia tra il controller e il device

E' di basso livello

ESEMPIO: disco
Il diver produce una sequenza di bit introdotta da un preambolo, seguito dai bit trasmessi, e quindi da un valore di checksum o codice di correzione dell'errore (ECC).
La sequenza di bit (lunga ad esempio 4096 bit per un disco formattato con 8 settori da 512 byte) e' convertita in un blocco dal controller che esegue le eventuali azioni di correzione dell'errore.

Architettura tipo minicomputer a single bus:


Alcuni controller possono essere interfacciati con piu' device dello stesso tipo.

Comunicazione controller-CPU

I/O controller

   I/O address 

 Interrupt vector

clock

040 -043

   8 

 keyboard

 060 - 063

  9

 secondary RS232

 2F8 - 2FF

11

 hard disk

 320 - 32F

13

 printer

 378 - 37F

15

 monocrome display 

 380 - 3BF

-

 color display

 3D0 - 3DF

-

 floppy disk

 3F0 - 3F7

 14

 primary RS232 

 3F8 - 3FF

12

Gestione di un disco (Indice )


Hardware per la gestione del disco

Organizzazione fisica:

Le operazioni di trasferimento partono sempre dall'indirizzo di un settore.
Operazione di seek: posiziona la testina sul cilindro desiderato (seek time circa 5-100 millisec.)

Come Minix vede un floppy per PC-IBM (360K):

Numero di cilindri:               40
Tracce per cilindro:               2
Settori per traccia:               9
Settori per dischetto:           720
Bytes per settore:               512
Byte per dischetto:           368640
Seek time (cilindro adiacente):    6 millisec.
Seek time (caso medio):           77 millisec.
Rotational time:                 200 millisec.
Motor start/stop time:           250 millisec.
Time to transfer 1 sector:        22 millisec.


Il trasferimento avviene quando il primo settore passa sotto la testina (rotational delay <= 20 millisec.).
Il software di Minix usa blocchi da 1K quindi legge/scrive 2 settori consecutivi per volta.

Software per la gestione del disco

Il software per effettuare un'operazione di trasferimento dati da/a disco deve fornire al controller:

Considerazione: il tempo di lettura/scrittura e' determinato da seek time, rotational delay, actual transfer time.

Algoritmi di schedulazione per le richieste di lettura/scrittura (Indice )

Il software per lagestione della lettura/scrittura disco puo' accettare anche piu' richieste, che vengono memorizzate su di una tabella, per poi schedularne l'esecuzione, in modo da ottimizzare i tempi.
Il task di Minix per la gestione del disco, invece, accetta una sola richiesta alla volta.

Questi algoritmi considerano le richieste di trasferimento dati facendo riferimento al numero di cilindro indirizzato. (tentativo di minimizzare il seek time medio).
Utilizzano una struttura dati costituita da una tabella indiciata in base al numero del cilindro. Per ogni entry abbiamo una lista di richieste pendenti per quel cilindro.

Esempio

Vediamo 3 algoritmi:

  1. FCFS: nessuna ottimizzazione
  2. SSF: puo' essere unfair
  3. Elevator: ha un upper bound di 2 * (numero di cilindri)

FCFS First Come First Served
Soddisfa le richieste nell'ordine in cui arrivano. Nell'esempio abbiamo 111 spostamenti unitari. SSF Shortest Seek time First
La prossima richiesta soddisfatta e' quella relativa al cilindro piu' vicino. In genere questo algoritmo tende a mantenere la testina posizionata nel centro del disco  quando ci sono dischi molto carichi e molte richieste, e potrebbe esserci starvation. Nell'esempio 61 spostamenti. Elevator

Questo algoritmo ha la proprieta' che per qualunque insieme di richieste il numero massimo degli spostamenti e' 2*(numero di cilindri). Nell'esempio 60 spostamenti.
Una variante dell'algoritmo prevede uno scanning circolare della tabella, mantenendo quindi una sola direzione per il bit (che a questo punto non serve piu'). Si evita cosi' che alcune richieste agli estremi del disco siano penalizzate.
 


Gestione del clock (Indice )

Si tratta di un dispositivo atipico perche' non corrisponde ad uno special file di tipo block o carattere. Distinguiamo subito fra clock hardware e clock sftware:

Clock hardware

Clock semplici: sono collegati alla rete (110-220 Volt) e generano interrupt ad ogni ciclo (50-60 Hz).

Clock programmabili (livello base): sono formati da 3 componenti:

Il cristallo genera un segnale periodico molto preciso (1-100MHz), che e' inviato al contatore, provocandone un decremento.
Quando il contatore arriva a zero, genera un interrupt diretto alla CPU.

Modi di programmazione

Vantaggi dei clock programmabili
Si puo' controllare via software la frequenza degli interrupt. Con un cristallo a 1 MHz ad esempio, si ha 1 impulso ogni microsecondo. Usando un contatore a 16 bit si possono programmare interrupt ad intervalli da 1 microsecondi a 65536 millisecondi.

Clock software

Mantenimento della data.

E' importante mantenere la data  in modo che il suo aggiornamento, che deve essere fatto ad ogni tick, sia il piu' veloce possibile. Vediamo il sistema usato da Unix.
Si sceglie una data iniziale di riferimento, ad esempio 01/01/1970 12:00:00.
Quando viene impostata la data odierna, questa viene trasformata nell'equivalente numero di tick a partire dalla data di riferimento. Ad ogni tick il numero e' incrementato.
Il problema e' il possibile overflow nel mantenimento di tale numero. Ad esempio con avendo un clock a 60 Hz e un contatore a 32 bit, c'e' un overflow ogni 2 anni circa.
Si possono trovare diverse soluzioni:

Realizzazione del time-slice

Ad ogni processo e' associato un contatore in cui e' memorizzato il "quanto" di tempo assegnato al processo stesso in numero di tick. Ad ogni clock interrupt il contatore e' decrementato dal clock driver. Quando il contatore diventa zero, il processo ha esaurito il suo quanto.

Gestione delle system call di tipo "alarm"

Queste system call sono utilizzabili dai programmi utente per ricervere un "warning" (in Unix ha la forma di un signal) dopo un certo intervallo di tempo. In presenza di piu' richieste di questo tipo, il problema che si pone e' quello di simulare piu' clock virtuali con un unico clock fisico, nel modo piu' "veloce" possibile.
Possibile soluzione (usata da Minix): si utilizza una lista di elementi, uno per ogni processo che ha un alarm pendente, ordinata in base ai tempi succesivi di scadenza dell'allarme. Ogni elemento contiene un numero che indica dopo quanti tick di tempo va emesso un signal (a quel processo) dopo che quell'elemento e' diventato il primo della lista. Il numero dell'elemento al top viene decrementato ad ogni tick, il signal e' emesso quando il numero si annulla, e l'elemento e' cancellato dalla lista.

Esempio:

La lista corrisponde ad un elenco di processi che richiedono rispettivamente un signal agli istanti di tempo:  4203  4207  4213  4215  4216.

Realizzazione del watchdog

Il meccanismo utilizzato e' simile a quello per gli allarmi. In questo caso il clock software, anziche' inviare un signal al processo richiedente, attiva una procedura che gli e' stata fornita dal processo chiamante. Un esempio e' l'arresto del motore per il floppy.

Accounting della CPU

Per avere una accurata misurazione e' meglio usare un supporto hardware. Solitamente nei clock chip ci sono piu' di un timer. Si utilizza allora un secondo timer che si fa partire quando si avvia il processo e si ferma quando il processo termina.
Metodo meno accurato: Si associa un contatore software al processo.
 
 

Schema generale dell'I/O su Minix (Indice )

Il Device independent software trasforma nomi simbolici di device in riferimenti al driver corrispondente.

Esempio

/dev/fd0
Nell'entry fd0 del file directory /dev viene identificato l'i-node di un file speciale che contiene il major e minor device number.
Il major DN identifica il driver, il minor DN e' un parametro passato al driver, che serve ad identificare eventuali diverse unita' dello stesso tipo.
Usi possibili:
mknod   /dev/fd0   b   2   0
mount   /dev/fd0   user
mkfs   /dev/fd0   360

I device drivers in Minix

Per ogni classe di device di I/O esiste uno specifico task di I/O (device driver).
Tale driver e' eseguito come processo.

I task di Minix comunicano con gli altri processi esclusivamente attraverso scambio di messaggi. I processi utente non possono comunicare direttamente con i task, ma possono utilizzarli tramite il server File System (FS).

1. Richiesta di servizio
3. Messaggio richiesta servizio al task per I/O
2. Risposta del task
4. Esito operazione

Esempio di struttura di un messaggio di richiesta di servizio per un device a blocchi (disco)

  m.m_type (INT)        --> tipo di operazione
  m.m_device *(INT)     --> minor device number
  m.position (LONG)     --> posizione all'interno del device
  m.PROC_NR (INT)       --> identificatore processo utente chiamante
                            (indice nella proc. table)
  m.m_source            --> identificatore server chiamante
  m.address (CHAR)      --> indirizzo all'interno del task
  m.count (INT)         --> dimensione di trasferimento

Messaggio di Risposta:

  m.m_type (INT)        --> sempre "TASK_REPLY"
  m.REP_PROC_NR (INT)   --> come proc_nr in chianata
  m.REP_STATUS (INT)    --> bytes trasferiti o codice di errore.

Struttura tipica di un task di I/O

message msg       /* buffer per il messaggio */
io_task ()
{
......
int  r, caller, proc_nr
initialize ();      /* eseguito solo allo startup */
while (TRUE)  
   {
   receive (ANY, &msg);   /* attende una richiesta */
   caller = msg.m_source  /* server dal quale arriva la richiesta */
   proc_nr = msg.PROC_NR  /* proc. utente richiedente */
   ......
   switch (msg.m_type)
      {
      case READ:  r = do_read(); break;
      case WRITE: r = do_write(); break;
      case OTHER: r = do_other(); break;
      default:    r = ERROR
      }
   msg.m_type = TASK_REPLY;
   msg.REP_STATUS = r;    /* return code */
   send (caller, &msg);    /* send reply back */
   }
}

Nota: i task hanno una struttura strettamente sequenziale, come i server. Possono cioe' accogliere una sola richesta alla volta. Un task di I/O, dopo aver effettuato una richiesta di I/O all'hardware, si sospendeattendendone la risposta (sotto forma di interrupt), ad esempio in do_read(). Ogni altra richiesta e' sospesa finche' il servizio attuale non e' completato (principio del rendez-vous). I server hanno una struttura analoga.

Ciascuna procedura do_xxx soddisfa una richiesta specifica e restituisce uno status code che dice cosa e' accaduto durante lo svolgimento dell'operazione. Questo codice e' restituito al server chiamante.

Maggiori informazioni sulla struttura del device per disco fisso di Minix si trovano sul file minix_device.html
 


Clock driver in Minix (Indice )

Il driver del clock di Minixe' contenuto nel file clock.c nel la directory kernel. La struttura generale e' quella del task descritto precedentemente.
Il clock task inizializza il clock (i8253) utilizzando la procedura init_clock.
Esegue quindi un loop senza fine nel quale provvede a ricevere i messaggi ed eseguire i servizi richiesti.

Possibili richieste:

Variabili utilizzate

realtime: contatore incrementato ad ogni tick
boot_time: con realtime consente di avere il tempo attuale
next_alarm: tempo al quale attivare il prossimo allarme.
sched_ticks: numero di tick alla prossima chiamata dello scheduler.
watch_dog: array che memorizza la procedura indicata dai task
lost_ticks: numero di tick persi (aggiornata ad ogni interrupt)

E' permesso avere un solo allarme pendente per processo. Viene infatti riservata una sola word nella tavola dei processi per memorizzare l'allarme.

Procedure importanti

set_alarm e' gestita dalla procedura do_setalarm. I messaggi arrivano dal MM su richiesta di un processo utente, oppure dagli I/O task per settare dei time-out. In quest'ultimo caso il task chiamante specifica anche la procedura da eseguire allo scadere del tempo. La procedura setta i parametri di allarme e poi scandisce la lista dei processi per individuare il primo allarme in scadenza. Il valore piu' basso trovato e' caricato in "next alarm" (non e' cioe' mantenuta una lista ordinata).

get_time: procedura do_get_time. Restituisce il tempo calcolato da "boot time" + realtime.

set_time: procedura do_set_time. Assegna il tempo di boot come new_time - realtime. Nota: i tempi vanno intesi come colpi di clock da 01/01/1970.

clock_tick: e' gestito da do_clock_tick. Il messaggio in questo caso e' generato dall'interrupt di click (proviene dal finto processo HARDWARE).

Negli aa. 1996/97 e 1998/99 sono state svolte due esercitazioni relative all'implementazione di driver per scheda grafica e mouse. E' possibile consultare la documentazione relativa, con la descrizione di questo tipo di driver.

Prima Esercitazione 1996/97.

Prima Esercitazione 1998/99.