Introduzione
Gestione di un disco
Algoritmi di schedulazione lettura/scrittura
Gestione del clock
Schema generale dell'I/O su Minix
Clock driver in Minix
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:
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:
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.