|
Appunti informatica |
|
Visite: 1472 | Gradito: | [ Medio appunti ] |
Leggi anche appunti:Il microprocessore :storia ed evoluzioneIl microprocessore :storia ed evoluzione Storia Il microprocessore nasce SchedulingScheduling Con Scheduler o Schedulatore si intende generalmente il Il dispositivo Bus/MemoriaIl dispositivo Bus/Memoria I campi relativi alla configurazione di un bus sono |
Device Driver
Un Driver è un'unità di programma (nello specifico un insieme di procedure) che consente al SO di pilotare un dispositivo hardware semplicemente dialogando con una interfaccia come se fosse un dispositivo logico (il SO può utilizzare il dispositivo senza conoscerne la specifica implementazione hardware), è da ritenersi specifico non solo dal punto di vista dell'hardware che pilota ma anche del SO per cui è scritto (SO differenti assumono interfacce differenti) e per la sua implementazione è richiesta una conoscenza approfondita del dispositivo da pilotare (viene infatti rilasciato generalmente dal produttore del dispositivo, oppure da terze parti sulla base comunque della documentazione tecnica rilasciata dal produttore
In fase di boot, una opportuna routine di inizializzazione si occupa di riconoscere i dispositivi collegati al sistema, di predisporre i driver opportuni e di impartire dei comandi alle varie interfacce dei diversi dispositivi pilotati, specificando le modalità operative secondo le quali le diverse periferiche dovranno funzionare. Il driver interviene solo a valle di questa inizializzazione, operando a regime sui cosiddetti registri del controllore della periferica, che sono in particolare di 3 tipi: nel Registro di Controllo (sola scrittura) il driver inserisce i dati relativi all'operazione richiesta, settando poi a 1 il Bit di Attivazione una volta che il caricamento dei registri è completato e dando quindi il "via" alla periferica; al termine delle operazioni, il Bit di Attivazione viene azzerato dalla periferica, e dal Registro di Stato (sola lettura) il driver preleva eventuali condizioni di errore restituite dal dispositivo; sul Registro Dati (più spesso un buffer o batteria di registri) il driver effettua operazione di I/O, caricando in esso dati e parametri utili all'operazione richiesta e prelevando da esso i risultati dell'operazione una volta conclusa.
Nei SO a kernel monolitico (come UNIX) il driver è un modulo compilato insieme al kernel (i driver disponibili sono supervisionati e testati per garantire la massima stabilità ed efficienza, ma se viene collegata una periferica non supportata dal SO non è possibile utilizzarla, a meno di non rigenerare e ricompilare il SO), strettamente correlato ad esso in quanto sua parte integrante, ed eseguito nel contesto del kernel: un processo che necessita di un'operazione di I/O effettua una SVC, la routine del kernel invocata costituisce la testa del driver che opera sulla periferica interagendo con la sua interfaccia e poi esegue un'altra routine del kernel che possiamo genericamente definire Wait_For_Interrupt, con la quale si blocca a sua volta in attesa che la periferica restituisca uno stato di "pronto", appena la periferica è "pronta" quest'ultima lancia una interruzione hardware che attiva una ISR e in particolare un suo ramo specifico (la ISR contiene tanti rami quanti sono i driver attivi) contenente la coda del driver, che effettua una o più manipolazioni dell'interfaccia autosospendendosi dopo ognuna di esse, ogni volta in attesa di una interruzione hardware della periferica, fino a completamento dell'operazione desiderata, ultimata la quale il controllo passerà nuovamente prima alla routine del kernel invocata ed infine al processo che ne aveva fatto inizialmente richiesta, sbloccandolo.
Nei SO a microkernel ibrido (come Windows) il driver è un modulo caricato dinamicamente dal kernel in fase di boot (può essere quindi non perfettamente stabile o ottimizzato, ma chiunque può sviluppare o reperire un driver per una periferica non supportata in modo nativo dal SO, senza ricompilare il SO stesso), eseguito tipicamente nel contesto di un processo driver, normalmente visibile dai processi utenti che ne richiedono i servizi ed accessibile in modo utente, con il quale i processi utente suddetti stanno in relazione clienti-servente o anche produttori-consumatore: un processo utente che necessita di un'operazione di I/O invia un messaggio di richiesta del servizio ad uno specifico processo driver, per cui - ricevuta una richiesta - il processo driver interagisce con l'interfaccia della periferica al fine di predisporre l'esecuzione dell'operazione desiderata (testa del driver), resta in attesa su un semaforo privato che la periferica sia "pronta" (la Wait_For_Interrupt qui diventa una semplice Wait) e appena la periferica è "pronta" questa lancia una interruzione hardware che attiva una ISR (ancora contenente tanti rami quanti sono i driver attivi) di cui ciascun ramo contiene stavolta semplicemente una Signal che sblocca lo specifico semaforo privato dello specifico processo driver, e restituisce a questo il controllo in modo che esso possa (entrato nella coda del driver) prelevare lo stato/risultato dell'operazione dall'interfaccia della periferica oppure, secondo necessità, interagire nuovamente con la periferica seguendo lo stesso schema (manipolazione dell'interfaccia attesa su semaforo privato interrupt ISR signal nuova manipolazione dell'interfaccia) fino a completamento dell'operazione desiderata, per spedire finalmente un messaggio di risposta al processo cliente che inizialmente aveva fatto richiesta del servizio (per esempio, una notifica che l'operazione richiesta è stata eseguita con successo). Si osservi che la sequenza testa del driver Wait_For_Interrupt coda del driver può essere normalmente contenuta in un ciclo, sul quale il processo driver rimane eventualmente in attesa passiva quando non ci sono richieste, e come questa attesa viene realizzata dipende dal modo in cui "processi clienti produttori di richieste" e "processo driver servente consumatore di richieste" comunicano: in una macchina a scambio di messaggi, il servente può semplicemente rimanere bloccato su una receive bloccante; in una macchina a memoria comune, invece, il cliente deposita le sue richieste in un'area di memoria condivisa da dove il servente le raccoglie, per cui il processo driver può rimanere in attesa su un opportuno semaforo (che conta le richieste depositate) sul quale i clienti eseguono Signal. In particolare, con riferimento ad un modello a memoria comune, è interessante l'utilizzo di un monitor come gestore di risorsa, dove la risorsa da gestire in mutua esclusione è proprio il buffer comune in cui processi clienti e processo driver servente depositano e prelevano rispettivamente le richieste di I/O: i processi clienti si comportano da scrittori del buffer del monitor operando con una opportuna entry procedure "produci", il processo servente si comporta invece da lettore del buffer del monitor operando con una opportuna entry procedure "consuma", la quale trasferisce il contenuto del buffer comune all'interno di un proprio buffer interno al processo driver (organizzato magari come una coda), da cui quest'ultimo potrà trasdurre le richieste I/O direttamente al dispositivo pilotato, per esempio effettuando un ulteriore trasferimento degli stessi dati nel buffer dati dell'interfaccia del dispositivo; il duplice trasferimento buffer monitor buffer interno del processo driver buffer dati dell'interfaccia costituisce di fatto una duplice ridondante copia delle stesse informazioni che può sembrare inutile, ma che in realtà è fondamentale per aumentare il grado di parallelismo: se il buffer fosse unico una sola operazione per volta (a causa dei vincoli di accesso imposti dal monitor) potrebbe essere eseguita tra "invio richiesta" e "smaltimento richiesta", viceversa in questo caso mentre un cliente invia una richiesta di I/O al buffer comune, il processo driver può in parallelo dare il via ad un'altra operazione I/O trasferendo dati dal suo buffer interno all'interfaccia del dispositivo.
Che il driver sia eseguito nel contesto del kernel oppure nel contesto di uno specifico processo driver, il SO deve mettere a disposizione un insieme minimo di routine che consentano di scriverlo, ovvero per avviare l'operazione di I/O, per arrestarla, per scrivere in un registro di input dell'interfaccia, per leggere da un registro di output dell'interfaccia, per acquisire il valore di un registro di stato dell'interfaccia
procedure Start_IO(p : device);
begin
<istruzioni assembly per settare i bit di attivazione nel registro
di controllo del dispositivo e abilitazione dell'interfaccia ad
operare ad interruzione>
end;
procedure Halt_IO(p : device);
begin
<istruzioni assembly per resettare i bit di attivazione nel registro
di controllo del dispositivo>
end;
procedure Put(p : device, d : dato);
begin
<registro buffer dell'interfaccia>:= d;
end;
procedure Get(p : device, var d : dato);
begin
d :=<registro buffer dell'interfaccia>;
end;
procedure Get_Status(p : device, var s : stato);
begin
s :=<registro di stato dell'interfaccia>;
end;
Come tali routine vengano scritte realmente dipende dalla struttura della periferica e della sua interfaccia: certamente per poterle scrivere in assembly occorre conoscere precisamente gli indirizzi associati ai registri di interfaccia, così come la funzione svolta da ciascuno di essi e le esatte sequenze di byte necessarie a pilotare il funzionamento del dispositivo. Inoltre prima e dopo l'esecuzione di ciascuna di esse, è necessario eseguire una apposita routine per acquisire il controllo della periferica (solo se essa è libera si può procedere, altrimenti se è occupata bisogna attendere) e una per rilasciarla: se l'utente attiva il driver invocando una SVC sarà la stessa routine kernel a verificare l'accessibilità del dispositivo e poi a rilasciarlo, altrimenti le routine di acquisizione e rilascio faranno parte del processo driver, rispettivamente precedendo la testa del driver e seguendo la coda del driver.
Infine, che il driver sia eseguito nel contesto del kernel oppure nel contesto di uno specifico processo driver, il SO deve prevedere un meccanismo per cui, quando la periferica genera una interruzione di fine I/O e si entra nella ISR, quest'ultima deve far ripartire il processo nel cui contesto il driver è stato eseguito, rispettivamente il processo utente che ha lanciato la SVC oppure il processo driver: il modo più ovvio di realizzare questo meccanismo è utilizzare nel processo a cui fare ritorno una routine di Wait_For_Interrupt che implementi una Wait su un semaforo privato, e di inserire nel ramo della ISR che si occupa di effettuare il ritorno una Signal su quel semaforo. In realtà, una Signal ordinaria nella ISR ha solo l'effetto di inserire il processo che ha eseguito la Wait nella coda dei processi Ready, mentre invece l'esecuzione della coda del driver dovrebbe avere la massima priorità, soprattutto perchè se essa non termina neanche la periferica su cui sta operando verrà liberata; usare inoltre una Signal con prerilascio non è formalmente scorretto ma non risponde allo scopo del caso: non ha senso infatti preoccuparsi di confrontare la priorità del processo sbloccante e quella del processo che si vuole sbloccare, dal momento che occorre assumere che il processo da sbloccare abbia sempre la priorità assoluta; infine, affinchè al termine della ISR riparta il driver, è necessario che il processo normalmente candidato a diventare Running (quello in testa alla coda dei processi Ready) a sua volta venga bloccato, ma nel contempo è bene che conservi la sua priorità sicchè, non appena il driver avrà terminato la sua esecuzione, esso potrà ripartire. Tutto questo meccanismo viene realizzato ponendo a valle della ISR una opportuna routine di risposta alla interruzione
procedure Risposta_Interruzione(sp : indice_semaforo); "sp" è l'indice del semaforo privato
var i : indice_processo; su cui il driver del dispositivo si è
messo in attesa
procedure Inserimento_In_Testa;
begin
<linka il PCB del "processo attualmente Running" in testa alla "coda dei processi ready">
end;
begin
Salvataggio_Stato;
Inserimento_In_Testa;
if <semafori[sp].coda NON vuota> then begin Coda associata al semaforo "sp"
<Poni il processo il processo in testa alla coda come "processo attualmente Running">
Ripristina_Stato;
end;
else semafori[sp].contatore:=semafori[sp].contatore + 1;
end;
il cui ramo else in generale non viene mai percorso (tranne nel solo caso particolare in cui non ci sia effettivamente alcun processo in attesa, il che può accadere se si assume che l'interruzione di cui stiamo parlando non sia necessariamente hardware e che sia stata generata da un processo temporizzatore sul cui semaforo non c'è appunto alcun processo in attesa in questo caso la routine è equivalente ad una Signal), oppure la sua versione semplificata
procedure Risposta_Interruzione(p : device);
procedure Inserimento_In_Testa;
begin
<linka il PCB del "processo attualmente Running" in testa alla "coda dei processi ready">
end;
begin
Salvataggio_Stato;
Inserimento_In_Testa;
<Poni il processo "driver[p]" come "driver" è il vettore contenente gli
"processo attualmente Running"> indici dei processi driver dei vari
Ripristina_Stato; dispositivi
end;
che rinuncia a qualsiasi analisi, ripristinando direttamente uno specifico driver, con evidente ottimizzazione dei tempi.
Appunti su: |
|