Rilevazione di malfunzionamenti nei device driver del disco nel kernel Linux

Dimensione: px
Iniziare la visualizzazioe della pagina:

Download "Rilevazione di malfunzionamenti nei device driver del disco nel kernel Linux"

Transcript

1 Facoltà di Ingegneria Corso di Studi in Ingegneria Informatica tesi di laurea magistrale Rilevazione di malfunzionamenti nei device driver del disco nel kernel Linux Anno Accademico Relatore Ch.mo Prof. Domenico Cotroneo Relatore Ing. Roberto Natella Correlatore Ing. Francesco Fucci Candidato Luigi De Simone matr. M

2 A Ilaria e alla mia Famiglia per avermi fatto diventare quello che sono

3 For me, the first challenge for computing science is to discover how to maintain order in a finite, but very large, discrete universe that is intricately intertwined. And a second, but not less important challenge is how to mould what you have achieved in solving the first problem, into a teachable discipline: it does not suffice to hone your own intellect (that will join you in your grave), you must teach others how to hone theirs. The more you concentrate on these two challenges, the clearer you will see that they are only two sides of the same coin: teaching yourself is discovering what is teachable. EDSGER WYBE DIJKSTRA (1979), My hopes of computing science (EWD 709)

4 Indice Introduzione 1 1 Motivazioni Analisi dei difetti nei sistemi operativi Tassonomia dei difetti nei device driver Violazioni di protocollo del Sistema Operativo Violazione di protocollo del dispositivo Difetti di concorrenza Errori generici di programmazione Fault isolation nei device driver Problemi aperti Contributo tesi Device Driver in Linux Architettura di Input/Output I/O Port-mapped e I/O Memory-mapped Il Linux Device Driver Model Il filesystem sysfs Kobject Ksets Operazioni di basso livello sul filesystem sysfs Rappresentazione di Bus, Device e Device driver Bus: la struttura bus_type I

5 Dispositivo: la struttura device Driver: la struttura device_driver Accesso ai dispositivi: i device file Device Driver Strutture dati per l interfacciamento con il filesystem La struct inode La struct file La struct file_operations Database dei dispositivi in Linux I dispositivi a caratteri I dispositivi a blocchi Il Generic Block Layer La struttura di un richiesta di I/O La struct block_device La struct gendisk e hd_struct Relazione tra block_device, gendisk e hd_struct Operazioni sui dispositivi a blocchi Registrazione e inizializzazione del device driver Apertura di un dispositivo a blocchi Sottomettere una richiesta al dispositivo a blocchi Comunicazione con il BUS L architettura PCI Device Driver PCI La struttura pci_bus La struttura pci_dev La struttura pci_driver Registrazione di un driver PCI generico Accesso allo spazio di memoria e allo spazio di I/O Accesso ad una regione di I/O port-mapped Accesso ad una regione di I/O memory-mapped.. 94 II

6 2.5 Direct Access Memory (DMA) Cenni sulla gestione della memoria in Linux Virtual Memory Area (VMA) Utilizzo del DMA Il DMA Layer Protocolli per la gestione dei dischi Fondamenti sugli Hard Disk Lo standard SATA Architettura I Registri Il Command Block Register e Status/Control Register Frame Information Strucuture (FIS) Protocolli di comunicazione in SATA Non-data Command Protocol DMA Command protocol Lettura DMA da parte dell host sul dispositivo Scrittura DMA write da parte dell host sul dispositivo PIO Data-In e Data-Out protocol Lettura PIO dal dispositivo Scrittura PIO verso il dispositivo FPDMA QUEUED command protocol Stato del dispositivo Stato dell host Esempio di FIS utilizzati durante l invio di comandi FPDMA Esempio della comunicazione tra host e dispositivo durante l esecuzione di comandi FPDMA Advanced Host Controller Interface (AHCI) Registri HBA Configuration Register III

7 HBA Memory Register Strutture utilizzate in memoria Received FIS Structure Command List Structure Operazioni per un trasferimento dati Il driver ahci SCSI Layer libata I moduli ahci e libahci I comandi Ricostruzione del comportamento di un driver Trace Collection Phase: generazione delle tracce di esecuzione State Abstraction Phase: astrazione dello stato Astrazione degli elementi dello stato concreto Transizioni tra gli stati FSM Model Generator Phase Implementazione Monitor Generator Phase Implementazione Esempio di estrazione di un modello Creazione di un monitor Configurazione utilizzata Descrizione del workload Modello generato Valutazione del monitor Valutazione dell overhead introdotto dal monitor Valutazione del corretto funzionamento Conclusione e Sviluppi Futuri 208 IV

8 A Registri ATA e FIS 209 A.1 ATA Command Block Register A.1.1 Status Register A.2 Frame Information Structure (FIS) A Register FIS Host to Device A Register FIS Device to Host A Set Device Bits Device to Host A DMA Activate FIS Device to Host A DMA Setup FIS Bidirectional A PIO Setup FIS Host to Device A DATA FIS Bidirectional Bibliografia 229 V

9 Elenco delle figure 2.1 Architettura di I/O tipica Northbridge e Southbridge Classica architettura I/O di un PC Porti di I/O e registri Esempio della struttura del Device Model associato ad un mouse USB Esempio di gerarchia di Kset Estratto dal Linux Device List Esempio di major e minor number del disco Database dei dispositivi nel kernel Relazione tra le strutture cdev, file, file_operations e inode Dispositivi di memoria nel kernel file_operations selezionato in base al minor number Componenti del kernel Linux coinvolti durante un operazione su disco Layout tipico di una pagina che racchiude dati su disco Block Device Layer Relazione tra le strutture bio e bio_vec Collegamento tra le strutture nel block layer Il disco visto nel device model Esempio di layout tipico di un sistema PCI Configuration Space PCI Esempio di Configuration Space di un controller AHCI Flusso di chiamate all interno del generico driver PCI Tipi di indirizzi utilizzati in Linux VI

10 2.24 Esempio di pagina di memoria Mappa della memoria di un processo Mappa di memoria del processo /sbin/init Esempio della struttura di un hard disk Distinzione tra settore, blocco, traccia e cilindro Lettura di un settore del disco Dispositivi PATA connessi al sistema Dispositivi SATA connessi al sistema Strati dell architettura SATA Frame SATA nel Link Layer Command Block Register e SATA Status/Control Register Definizione SATA Status/Control Register Shadow Register Block semplificato Tipi di FIS Lettura DMA Scrittura DMA Dispositivo riceve un comando READ FPDMA o WRITE FPDMA Dispositivo pronto ad inviare dati in riferimento ad un comando di lettura (READ FPDMA) Dispositivo pronto ricevere dati in riferimento ad un comando di scrittura (WRITE FPDMA) Scenario 2: Host State Command Issue Scenario 3: Host State Received Interrupt DMA Setup FIS durante il protocollo FPDMA Command Block Register associato ad un comando READ FPDMA Command Block Register associato ad un comando READ FPDMA Set Device Bits FIS ricevuto dopo il completamento di comandi FPDMA da parte del dispositivo PCI Header Registro ABAR VII

11 3.25 Registri dell HBA AHCI Generic Host Control Port Control Register Spazio di memoria utilizzato da AHCI Command List Structure e Received FIS Structure Received FIS Structure Command List Structure Command Table Architettura di un driver per un disco SATA/AHCI Il Sottosistema SCSI nel kernel Linux Sottosistema SCSI Driver stack utilizzato nella gestione di un disco SATA Relazione tra le strutture libata Relazione tra le strutture utilizzate in ahci Aree di memoria mappate dalla funzione ahci_port_start Fasi per la creazione di un componente di monitoraggio Parte del log generato con SystemTap Class Diagram del metamodello di un FSM FSM associata al modello comportamentale del driver per i comandi FPDMA Controller SATA utilizzato FSM del modello comportamentale del driver Zoom della FSM del modello comportamentale del driver Overhead introdotto dal monitoraggio Kernel Log parte Kernel Log parte Kernel Log parte A.1 Command Block Register (Shadow Register Block) VIII

12 A.2 Device Register A.3 Device Control Register A.4 Error Register A.5 Status Register A.6 Register FIS - Host to Device A.7 Register FIS - Device to Host A.8 Set Device Bits - Device to Host A.9 DMA Activate FIS A.10 DMA Setup FIS - Bidirectional A.11 PIO Setup FIS - Host to Device A.12 Data FIS - Bidirectional IX

13 Ringraziamenti Dopo tante virgole, punti e virgola, doppi punti durante il mio cammino di scrittura, sono arrivato finalmente a mettere un punto e a capo! Arrivato a questo punto della mia vita come non potrei ringraziare tutte le persone che mi hanno accompagnato direttamente e indirettamente verso questa nuova linea. Prima di tutto vorrei ringraziare la mia famiglia partendo dai miei genitori che hanno sempre creduto nelle mie potenzialità, anche quando forse gli ho dato l impressione di valere ben poco e che questo traguardo fosse inarrivabile: spero siate lo stesso orgogliosi di me; ai miei fratelli che sono stati in una maniera o nell altra sempre dei punti di riferimento, e infine a l enorme quantità di parenti che non ti fanno sentire mai solo, alla famiglia della mia fidanzata e soprattutto a miei nonni che mi guardano da lassù...grazie a tutti di vero cuore! Sono passati davvero tanti anni da quando giovane imberbe mi apprestavo a vivere questo lungo viaggio. Ringrazio tutti gli amici e ormai colleghi che durante questo tempo sono stati compagni di studio, di stronzate e di caffè (ormai ne sono assuefatto!); in particolare vorrei ringraziare Mauro, Sergio ma soprattutto Davide degno compagno di follie visionarie; un ringraziamento và anche a Dario. Ringrazio ancora Lorenzo (dalla profonda Avellino), Mario, Francesco, Carmine, Fiorella e tutti quelli che ho dimenticato di ringraziare. Inoltre, vorrei ringraziare in particolare coloro che mi hanno seguito in questo durissimo lavoro di tesi: Francesco con cui ho perso ore, giorni e mesi accompagnati a mal di testa per entrare nell oscuro mondo dei driver rimanendo sempre disponibile a qualsiasi assurdità che mi veniva per la testa; un ringraziamento a Roberto che è sempre stato di una disponibilità disarmante soprattutto per l aiuto X

14 datomi nell ultimo periodo di tesi; ringrazio il Prof. Domenico Cotroneo per la sua professionalità, disponibilità e simpatia e per avermi sempre spronato con questo lavoro. Vorrei anche ringraziare tutti i ragazzi conosciuti al CINI: Anna, Francesca, Luca, Raffaele, Francesco, Roberto, Domenico. E stata davvero una bellissima esperienza! Vorrei ringraziare anche tutti i miei fratelli non di sangue che hanno costituito una parte importante della mia vita fino ad oggi. Infine vorrei ringraziare Ilaria che ormai da più di 6 anni sopporta e soprattutto supporta un ingegnere folle come me, conoscendo a memoria ogni mio esame fatto e spronandomi a dare il massimo soprattutto nei miei momenti di lato oscuro dicendomi sempre di mettere quatt cos accussì comm ven n ven n :D...sei stupenda e non avrei potuto chiedere di meglio dalla vita. Che dire, ora mi aspetta, se Dio vuole, una nuova avventura che non so e non voglio sapere nemmeno dove mi porterà; potrei finire citando il film Io, speriamo che me la cavo, e penso che finirò proprio così. Napoli, lì 03/07/2013 XI

15 Introduzione Computer science research is different from these more traditional disciplines. Philosophically it differs from the physical sciences because it seeks not to discover, explain, or exploit the natural world, but instead to study the properties of machines of human creation. In this it is analogous to mathematics, and indeed the "science" part of computer science is, for the most part mathematical in spirit. But an inevitable aspect of computer science is the creation of computer programs: objects that, though intangible, are subject to commercial exchange. DENNIS RITCHIE (1984), Reflections on Software Research Negli ultimi 10 anni la reliability nei sistemi critici software e hardware è diventata uno dei fattori più importanti, in particolare in quei sistemi critici che dipendono da un sistema operativo. Basti pensare che oggi, in quasi tutto quello che si utilizza, dagli smartphone, ai sistemi embedded, ai personal computer, a sistemi di gestione del traffico, nelle telecomunicazioni, server farm, ecc., esiste visibilmente o invisibilimente un sistema operativo. I sistemi operativi sono diventati il software di base più importante e allo stesso tempo più critico in un sistema di elaborazione. Il miglioramento della reliability è una delle più grandi sfide ancora aperte oggi [GKM02], ma col tempo sta diventando una necessità stringente. Infatti l occorenza di un fallimento all interno di un così vasto componente software può avere enormi ripercussioni all interno di tutto il sistema in qualsiasi dominio di applicazione che sia di business o di safety. Basti pensare alle possibili perdite economiche che potrebbero avere colossi informatici come Google, Amazon, Microsoft per un malfunzionamento di un server, o di un insieme di server, causato da un fallimento del 1

16 sistema operativo. Infatti, casi di fallimento abbastanza clamorosi sono avvenuti proprio nei servizi cloud di Amazon [Ama11] (a causa di una race condition nel codice dei server che gestivano la memorizzazione dei dati), di Microsoft [Gig12] (a causa di un errata configurazione dei dispositivi di rete) e di Google [War11] (a causa di un difetto nella gestione della memoria). Il problema è che la maggior parte dei sistemi operativi commodity, come Windows e Linux, sono utilizzati per applicazioni che richiedono un alta disponibilità (high availability), ma essi sono progettati in modo tale da essere best-effort, ovvero senza fornire garanzie di affidabilità. Questo mio lavoro di tesi è focalizzato nel contesto del miglioramento della reliability dei device driver nei sistemi operativi commodity. Infatti, come vedremo nel cap. 1, i device driver sono tra i componenti software con più difetti, e quindi sono la causa principale di fallimenti all interno del sistema operativo e di perdita dei dati. Questo lavoro di tesi propone un approccio per la creazione di un componente di monitoraggio in grado rilevare malfunzionamenti dovuti ai device driver in maniera tale da attivare tempestivamente meccanismi di recovery. L approccio è stato sviluppato e sperimentato nel contesto del kernel Linux, e in particolare rispetto ai device driver del disco. 2

17 Roadmap Nel Cap. 1 di questo lavoro di tesi si introduce il lettore sulle motivazioni che spingono ad analizzare approfonditamente i device driver; si descriverà una tassonomia dei difetti di quest ultimi, proseguendo con i contributi dati dalla comunità scientifica descrivendo alcuni dei lavori effettuati nell ambito della fault isolation nei driver; infine, si concluderà il capitolo con i contributi dati in questo lavoro di tesi. Successivamente cominceremo a descrivere nel dettaglio i device driver, soffermandoci nel Cap. 2 sull architettura Linux e in particolare su come vengono gestiti i device driver e la loro comunicazione con i componenti dell architettura di I/O. Nel Cap. 3 sarà effettuata una panoramica sugli attuali standard utilizzati nel contesto dei dispositivi di memorizzazione di massa, focalizzando l attenzione sullo standard SATA e sul meccanismo AHCI ormai utilizzato come standard de facto. Successivamente, nel Cap. 4 si porrà l attenzione sullo studio fatto riguardante il driver ahci utilizzato nel kernel Linux per gestire i dischi SATA e su come tale driver si interfacci con il resto del kernel. Dopo aver studiato il driver ahci, nel Cap. 5 si porrà l attenzione sull approccio utilizzato per creare un infrastruttura di monitoraggio che permetta di rilevare eventuali malfunzionamenti dovuti al driver. Infine, nel Cap. 6 si discuterà dell utilizzo dell infrastruttura creata per generare il monitor, mostrando come possa rilevare effettivamente anomalie durante l esecuzione del driver. 3

18 Capitolo 1 Motivazioni There are two ways to write error-free programs; only the third one works. ALAN PERLIS 1.1 Analisi dei difetti nei sistemi operativi Recenti studi [GGP06, Mur04] hanno dimostrato che la causa dominante di fallimenti nei sistemi operativi sono difetti all interno dei device driver; per esempio, in Windows XP l 85% dei fallimenti di tipo crash del sistema sono dovuti ai device driver. Uno dei principali fattori che spiega questo, è che nelle architetture monolitiche un device driver è parte integrante del kernel. Questo è necessario perchè un driver deve avere accesso completo all hardware sottostante affinchè possa interfacciarsi con i registri che controllano il dispositivo; inoltre, ci sono anche motivi legati alle prestazioni. Questo significa che un driver è un software che viene eseguito con i medesimi privilegi del kernel, e offrendo servizi per svariati componenti all interno del sistema operativo, un suo malfunzionamento può propagarsi in maniera considerevole, sia all interno delle strutture del kernel stesso, che alle applicazioni, fino ad arrivare ai dispositivi fisici. Quindi un device driver, potenzialmente, può provocare gravi perdite e/o corruzione di dati o addirittura il crash dell intero sistema 4

19 operativo. Un secondo fattore che aumenta il grado di difetti nei device driver è il loro sviluppo. Infatti la maggior parte dei driver developer lavorano per aziende produttrici di hardware ed hanno, da un lato, una conoscenza elevata del dispositivo per cui scrivono il driver, e dall altra una conoscenza minore dell organizzazione del kernel e della sua programmazione rispetto agli sviluppatori del kernel stesso. I difetti nei driver possono essere introdotti a causa della complessità dei protocolli di comunicazione con il kernel: i driver developer possono implementare o utilizzare in maniera non corretta le interfacce con il kernel; inoltre, a causa della complessità dei protocolli del dispositivo, i driver developer possono non capire appieno i dettagli delle interfacce software del dispositivo e quindi sbagliano ad implementarle nel driver. A dare conferma di tutto ciò è uno studio nel quale si dimostra che in Linux la densità di difetti nel codice dei device driver è dalle tre alle sette volte maggiore che nel resto dei componenti del kernel [CYC + 01]. Un terzo fattore è quello che i device driver sono difficili da testare rispetto ad altri componenti del kernel ed inoltre la loro verifica è un attività molto onerosa. Esistono infatti due motivi principali per cui il testing è difficoltoso [BBC + 06]: il primo motivo è che è molto difficile determinare esattamente quando ci sono degli errori nelle interazioni tra driver e kernel: una chiamata errata ad un API del kernel potrebbe infatti non portare immediatamente al fallimento del sistema, lasciandolo in uno stato inconsistente; il secondo motivo, forse più critico, è che i device driver possono avere dei fault latenti che sono risvegliati solo in rare circostanze, le quali sono difficilmente riproducibili deterministicamente. Uno studio empirico sul codice del kernel di Linux [KS12] mostra che in media il 28% dei device driver di Linux supporta più di un chipset (dispositivo) e quindi diventa poco fattibile testare tutte le combinazioni driver-dispositivo. Anche se sono stati fatti enormi passi in avanti nell ambito del driver testing [RKS12, KCC10], tutti i motivi elencati in precedenza portano alla conclusione che i device driver possono avere dei difetti che, non essendo rilevati durante la fase di testing, causano comportamenti anomali (scritture errate e/o letture inconsistenti) durante la fase operazionale. 5

20 Considerando, infine, che il codice dei device driver, in Linux, occupa oltre il 70% del codice totale del kernel, e che negli anni la crescita del kernel (in termini di dimensione del codice) è stata determinata principalmente dai device driver [CYC + 01], si capisce perfettamente il perchè tutte le statistiche che riguardano il failure rate nei sistemi operativi sono dominate proprio dai device driver. Per i motivi su citati, la ricerca negli ultimi anni è stata condotta verso lo sviluppo di tecniche di fault tolerance nei driver, di tecniche per migliorarne la qualità del codice, e verso la creazione di nuove architetture che ne migliorino in generale la reliability. Gli approcci esistenti di Fault Tolerance per device driver sono basati principalmente sullo sviluppo di tecniche di isolation e di recovery. L idea alla base è quella di confinare il driver in un dominio di esecuzione privilegiato in modo però da non permettergli accessi in aree di memoria utilizzate da altre componenti del kernel; all occorrenza di un eventuale fallimento del driver verrà effettuato un ripristino dello stato dello stesso a prima che fallisse, in modo tale da continuare l esecuzione in maniera trasparente. Come vedremo nella sez. 1.3, entrambi gli approcci sono stati utilizzati per sviluppare sottosistemi e strumenti che da un lato migliorano notevolmente la reliability nei driver, ma dall altro soffrono di alcune importanti limitazioni. 1.2 Tassonomia dei difetti nei device driver In questa sezione andremo ad evidenziare una tassonomia dei difetti nei device driver. E stato fatto uno studio empirico [RCKH09] in cui sono stati analizzati circa 500 difetti in diversi device driver in modo da identificarne delle classi di appartenenza. In base a quest analisi sono state identificate quattro categorie principali di difetti, in particolare dovuti a: 1. Violazioni di protocollo del Sistema Operativo: questo tipo di violazioni avvengono quando non viene rispettato il protocollo di comunicazione tra Sistema operativo e device driver. In questi casi rientrano tutte le viola- 6

21 zioni delle interazioni che riguardano il formato, l ordine e la tempificazione delle interazioni tra driver e sistema operativo; esempi sono quando il driver non gestisce correttamente richieste di hotplugging e di power management; 2. Violazioni di protocollo del dispositivo: questo tipo di violazioni avvengono quando non viene rispettato il protocollo di comunicazione tra driver e dispositivo. In questi casi rientrano quando il driver porta il dispositivo in uno stato inconsistente, interpreta in modo errato lo stato del dispositivo, oppure, invia una sequenza di comandi che non è contemplata nel protocollo, imposta dei timeout per il completamento delle operazioni del dispositivo in maniera errata, oppure non tiene conto dell ordine di memorizzazione dei byte in memoria (endianness); 3. Errori di gestione della concorrenza: questo tipo di fallimenti avviene per difetti nella gestione della concorrenza, ovvero errori di programmazione all interno dei driver che introducono race condition e in generale deadlock; 4. Errori generici di programmazione: questi fallimenti sono dovuti a difetti che non riguardano in particolare il device driver ma sono riferiti ad errori di programmazione nell utilizzo del linguaggio. Un esempio classico è quando il driver vuole accedere ad un area di memoria che però è stata deallocata Violazioni di protocollo del Sistema Operativo Il Sistema Operativo implicitamente definisce regole di comunicazione con i device driver. Tali regole riguardano in particolare l ordinamento, la tempificazione e il contenuto delle interazioni associate alle interfacce tra driver e kernel. Quando il device driver non rispetta queste regole si hanno violazioni di protocollo del Sistema Operativo. Difetti che portano a questo tipo di violazioni vengono introdotti quando si implementa in maniera errata la logica di comunicazione tra driver e kernel. I difetti correlati riguardano, per esempio, l invocazione di funzioni da parte del driver in un ordine non corretto. In generale, diverse classi di driver implementano interfacce simili con il Sistema Operativo, e possiamo generalizzare dicendo che un 7

22 driver implementa sempre funzioni per l inizializzazione, lo shutdown, il power management e il trasferimento dati. Per ognuna di queste operazioni, lo sviluppatore può introdurre difetti che portano il driver ad un comportamento scorretto. Per le operazioni di inzializzazione, shutdown e configurazione del device driver, possiamo avere un ordinamento errato della chiamata alle routine che stabiliscono la connessione del driver verso i livelli del kernel che implementano la logica di bus e del sottosistema di I/O. Ci possono essere anche difetti che violano il protocollo di power management, per esempio quando il driver cerca di effettuare il resume di un dispositivo precedentemente sospeso (suspended) senza ripristinare lo stato di power del dispositivo. Difetti di questa tipologia sono anche introdotti a causa dell utilizzo sbagliato delle strutture dati del kernel da parte del device driver; infatti un device driver può allocare in maniera scorretta alcuni campi di una struttura oppure gestire in maniera errata i dati ricevuti dalle funzioni del kernel Violazione di protocollo del dispositivo Questo tipo di violazioni avvengono quando il device driver si comporta in maniera diversa da quanto stabilito dal protocollo hardware necessario per comunicare correttamente con il dispositivo. Questo è dovuto all elevata complessità di questi protocolli per cui si sbaglia a capire appieno tutti i dettagli implementativi. Di solito questo tipo di difetti porta a fallimenti che possono propagarsi verso il dispositivo fisico causandone il malfunzionamento. Possiamo avere: Difetti di valore: tali difetti sono relativi alla gestione errata dei dati scambiati tra driver e dispositivo; tali dati includono in particolare tutti i comandi di I/O e di configurazione che vegono scambiati tramite i registri del dispositivo. Quindi, questi difetti includono l uso scorretto dei registri, l invio di dati non validi e l interpretazione scorretta dei dati ricevuti dal dispositivo; Difetti di ordinamento: questi difetti sono relativi al corretto controllo del dispositivo da parte del driver; infatti il driver deve tener conto dello stato in- 8

23 terno della macchina a stati che descrive il dispositivo: l invio di una sequenza di comandi verso il dispositivo deve essere tale da raggiungere effettivamente ciò che ci si aspetta. Difetti di temporizzazione: tali difetti sono quelli che portano il driver ad usare in modo scorretto timeout che sono utilizzati dal driver stesso per simulare per esempio il completamento di un comando da parte del dispositivo; Difetti di concorrenza La maggior parte dei Sistemi Operativi utilizza un modello multithread in cui ci sono thread concorrenti che accedono ad aree di memoria condivise. Difetti di questo tipo sono introdotti quando non si implementa correttamente la gestione della concorrenza all interno del driver. I device driver sono esposti a questo tipo di difetti perchè sono intrinsecamente concorrenti (e.g. le routine di gestione dell interrupt sono eseguite in maniera concorrente al codice del driver che gestisce l I/O). Si possono avere dei difetti che causano una sincronizzazione sbagliata tra le funzioni responsabili dell invio dei dati da e verso il dispositivo: per esempio, le funzioni che gestiscono la creazione di un comando e la gestione dell interrupt accedono alle stesse strutture dati che potrebbero portare in uno stato inconsistente. Ci possono essere errori di sincronizzazione introdotti nelle funzioni che gestiscono l inizializzazione, lo shutdown e la configurazione del controller del dispositivo: per esempio, il driver può eseguire una richiesta di shutdown mentre è in corso un trasferimento dati richiesto precedentemente. Difetti di questo tipo possono essere introdotti anche quando il driver non inizializza correttamente le primitive di sincronizzazione che utilizza per evitare race condition (e.g., in Linux, esistono primitive come mutex, semafori, ecc.): per esempio, lo sviluppatore può dimenticare il rilascio di un lock dopo la sua acquisizione causando un deadlock. 9

24 1.2.4 Errori generici di programmazione In questo categoria rientrano tutti quei difetti introdotti nei device driver che non riguardano in particolare i protocolli di comunicazione con il sistema Sistema Operativo e i protocolli di comunicazione con il dispositivo. Per esempio, tali difetti sono errori di allocazione di memoria, errori aritmetici e il non controllo dei valori di ritorno delle funzioni. 1.3 Fault isolation nei device driver In questo paragrafo andremo ad analizzare una panoramica dei lavori fatti sulla reliability nei driver interessandoci in particolare sulle architetture che utilizzano tecniche di fault isolation, incentrando la discussione sulle potenzialità offerte e sulle limitazioni che hanno. In generale, le tecniche per migliorare l affidabilità del software si dividono principalmente in tre gruppi: Fault Prevention: tecniche di questo tipo mirano a prevenire che difetti nei driver siano introdotti nel sistema. Un primo approccio è quello di sviluppare i driver utilizzando linguaggi di alto livello che non consentano certi tipi di errori; un esempio è quello di evitare al programmatore di gestire l aritmetica dei puntatori in modo da ridurre questo tipo di errori. Un secondo approccio è quello di generare il codice dei device driver da una specifica formale di comportamento, evitando che gli errori nei driver possano impattare sulla reliability. Fault Removal: tecniche di questo tipo mirano a rilevare ed eliminare i difetti nei driver sia durante la fase di sviluppo che nella fase operazionale. Come già accennato nel paragrafo precedente, la detection dei difetti può essere fatta, ad esempio, già a tempo di compilazione utilizzando linguaggi con tipi avanzati come Haskell e C#, dove molti fallimenti possono essere espressi come violazioni di tipi safe. Invece, altri tipi di difetti possono essere rileva- 10

25 ti con l aiuto di strumenti di analisi statica del codice e di model checking: queste tecniche analizzano il codice sorgente o il binario del driver e lo confrontano ad una specifica formale dove sono definite proprietà ben precise; successivamente si identificano i comportamenti che violano queste proprietà. Infine, il testing classico rimane la tecnica più comune utilizzata per la fault removal nei device driver. Fault Tolerance: queste tecniche mirano a far funzionare il sistema operativo anche in presenta di guasti nel driver. In generale questo tipo di tecniche includono approcci di fault isolation e di recovery. Come già abbiamo detto in precedenza, la fault isolation permette di evitare il propagarsi del fallimento al resto del sistema isolando il driver. Per quanto riguarda la recovery, invece, si utilizzano tecniche avanzate per eseguire azioni che mirano a ricreare lo stato del driver a prima del fallimento in modo da poter servire nuovamente gli utilizzatori del driver stesso. Le tecniche di Fault Isolation possono essere classificate in hardware enforced e software enforced. Le tecniche hardware enforced si basano su strumenti di protezione forniti dall hardware sottostante come l IO-MMU, un dispositivo hardware che viene utilizzato per fornire una separazione dello spazio di indirizzamento garantendo la protezione della memoria nelle operazioni tra dispositivo e memoria. Un esempio di tool basato su tecniche hardware enforce è Nooks [SMLE02] che permette di isolare l azione driver incapsulandolo in un dominio di protezione chiamato nook: in questo dominio il driver ha l accesso a tutti lo spazio di indirizzamento del kernel ma può scrivere soltanto nel proprio spazio di indirizzamento; ovviamente il kernel possiede i permessi di lettura e scrittura sia nel proprio dominio che nel dominio del driver. Un altro approccio per isolare un driver è quello basato sulla filosofia microkernel. Infatti, è possibile sviluppare device driver che sono eseguiti in user-space [Tan07] in modo da isolarli dal resto del kernel: un possibile errore non si propagherebbe all interno del kernel anche se l errore potrebbe lo stesso impattare su altri componenti del sistema. Un esempio di approccio basato su micro-kernel è 11

26 sviluppato da Herder et al.[hbg + 06]. Tale filosofia è anche utilizzata in SUD che la estende nelle architetture monolotiche come ad esempio Linux; SUD cerca di evitare che driver maliziosi possano corrompere le strutture dati del kernel (citare SUD). Invece, le teniche software enforced forniscono l isolamento attraverso i seguenti approcci: Instrumentazione del codice oggetto di un driver; Meccanismi type-safe. Una soluzione basata sul primo approccio è stata proposta da Wahbe et al. [WLAG93]; tale approccio, chiamato Software enforced Fault Isolation (SFI), consiste nel raggiungere l isolamento del driver inserendo dei controlli prima di ogni istruzione non sicura, come ad esempio, istruzioni di scrittura in memoria, in modo da forzare il driver a non effettuare accessi in scrittura su aree di memoria al di fuori del dominio del driver. Il secondo approccio, invece, è quello di sviluppare device driver con linguaggi di programmazione cosiddetti type-safe, in modo da sfruttare la proprietà di memory safety 1 ; infatti la maggior parte dei device driver attuali è scritta in linguaggi di programmazione, come C o C++, che in genere non sono considerati molto type-safe: esempi di utilizzo di quest approccio sono in SafeDrive [ZCA + 06] e in Singularity [HL07]. Una delle limitazioni principali di tutte le tecniche viste è che gli strumenti sviluppati introducono un overhead non trascurabile sulle performance del sistema. Inoltre esse contemplano modi di fallimenti che non includono, per esempio, violazioni di protocollo driver-kernel e driver-dispositivo (vedi sez. 1.2). 1 nel software developing, la proprietà di memory safety aiuta ad evitare di introdurre difetti nel codice che possono causare errori nella memoria (RAM) come ad esempio buffer overflow e puntatori pendenti (dangling pointer). in 12

27 1.4 Problemi aperti La ricerca in questi anni si è orientata maggiormente verso lo sviluppo di tecniche di isolation che cercano di evitare la propagazione del fallimento di un device driver al resto del kernel. Questo tipo di tecniche sono però limitate a problemi di memory safety, cioè evitano che il driver possa accedere in scrittura in aree di memoria che appartengono ad altre componenti del kernel. Infatti, anche se sono stati fatti molti studi che sviluppano strumenti che si basano su tecniche di fault isolation, esse non impediscono al device driver di: Eseguire operazioni scorrette verso il dispositivo (vedi sez ); Eseguire operazioni scorrette verso il sistema operativo (vedi sez ); Operare in maniera scorretta su quelle strutture dati in memoria che rappresentano lo stato del dispositivo; Evitare problemi di gestione della concorrenza (race condition, vedi sez ); Portare il dispositivo fisico in uno stato inconsistente. 1.5 Contributo tesi Il lavoro di tesi svolto si colloca nel contesto dei problemi discussi nelle sezioni precedenti e fornisce i seguenti contributi: E stata effettuata un analisi approfondita degli standard utilizzati nel panorama dei dispositivi di memorizzazione di massa, ponendo l attenzione sugli hard disk; in riferimento a ciò sono stati studiati gli standard ATA e SATA ed infine AHCI. Inoltre sono stati approfonditi i componenti del kernel Linux che permettono l interfacciamento con i dischi e in particolar modo è stato studiato nel dettaglio un driver reale per un disco SATA/AHCI. Tali studi si sono rilevati molto complessi a causa sia della complessità oggettiva del funzionamento di un dispositivo così complesso, sia della complessità intrinseca 13

28 del kernel e in particolar modo dei driver. Questo studio è stato svolto per definire strategie di monitoraggio di device driver per la rilevazione di eventuali malfunzionamenti dovuti a difetti nei driver stessi. Come abbiamo detto nel punto precedente, la tesi è incentrata sullo sviluppo di una strategia per la costruzione di un monitor per i driver del disco. Quindi, il contributo dato è stato quello di definire un approccio per la creazione di un monitor; tale approccio è basato su eventi chiave che avvengono all interno dell esecuzione di un qualunque driver del disco come la traduzione dei comandi, che provengono dai livelli superiori, e l invio vero e proprio di tali comandi tradotti verso il dispositivo fisico; inoltre gli eventi analizzati appartengono anche a tutti quelli che permettono la gestione del completamento dei comandi da parte del dispositivo fisico verso il sistema operativo. Infine, è stata fatta un implementazione prototipale dell approccio descritto precedentemente, e una validazione preliminare del prototipo implementato. 14

29 Capitolo 2 Device Driver in Linux When done well, software is invisible. BJARNE STROUSTRUP In questo capitolo andremo ad approfondire lo sviluppo dei device driver nel kernel Linux. In particolar modo ci soffermeremo su come siano gestiti nel kernel Linux, approfondendo i componenti del kernel che riguardano la gestione di dispositivi a blocchi come il disco. Inoltre andremo a studiare le strutture e le interfacce utilizzate per la comunicazione con il bus PCI e la gestione del DMA. 2.1 Architettura di Input/Output L architettura di un PC può essere vista essenzialmente come un sistema i cui elementi cooperano per fornire i servizi richiesti, e schematicamente questo sistema è composto da un microprocessore (CPU), una memoria RAM e svariati dispositivi di I/O. Affinchè queste tre componenti possano comunicare in modo giusto tra loro c è bisogno di un percorso (o più di uno) che connetta ognuno di questi elementi: tali percorsi sono chiamati bus. Ogni computer ha un cosiddetto bus di sistema che connette la maggior parte dei dispositivi fisici alla CPU; nelle architetture recenti, quello più utilizzato è il bus PCI (Peripheral Component Interconnect), anche se in realtà ne esistono una grande 15

30 varietà come ad esempio ISA, EISA, MCA, SCSI, e USB ecc., che sono collegati tra loro attraverso dispositivi chiamati bridge. Inoltre, esistono due tipologie di bus ad alta velocità che sono dedicati al trasferimento dati: il frontside bus che collega la CPU e il RAM controller, e il backside bus 1 che collega la CPU direttamente alla cache hardware esterna. Infine, il componente che collega il bus di sistema al frontside bus, e quindi alla CPU, è l host bridge. Nella Figura 2.1 è mostrata schematicamente l architettura di I/O tipica, dove il bus di sistema utilizzato è il bus PCI: Figura 2.1: Architettura di I/O tipica In realtà nelle architetture moderne l host bridge è diviso internamente in due dispositivi chiamati northbridge e southbridge (Figura 2.2): il northbridge è il dispositivo delegato alle comunicazioni più veloci, cioè alle comunicazioni tra la CPU, la memoria RAM e il controller video (AGP o PCI Express); inoltre, il northbridge si interfaccia con il southbridge, il dispositivo che gestisce le comunicazioni più lente, ovvero le comunicazioni tra CPU e i dispositivi di I/O, come ad esempio dispositivi USB, dischi IDE o SATA, schede di rete ecc. 1 Non è presente in tutte le architetture. 16

31 Figura 2.2: Northbridge e Southbridge Il percorso che seguono i dati che partono dalla CPU e giungono ai dispositivi di I/O, e viceversa, in genere è chiamato I/O bus. Essenzialmente, questo bus è connesso ad un dispositivo di I/O attraverso una gerarchia di componenti hardware: i porti di I/O, le interfacce di I/O e il device controller (Figura 2.3). Ogni dispositivo connesso al bus di I/O ha un insieme di indirizzi di I/O chiamati porti di I/O. Speciali istruzioni assembler permettono alla CPU di accedere a questi particolari indirizzi in modo da poter colloquiare con il dispositivo: questo si traduce in letture e scritture verso questi porti. I porti di I/O sono strutturati in un insieme di registri specializzati come mostrato nella Figura 2.4. La CPU invia i comandi verso il dispositivo scrivendo nel registro di controllo (Control Register) e può conoscere lo stato del dispositivo leggendo il registro di stato (Status Register); inoltre la CPU può anche inviare e ricevere dati da e verso il dispositivo leggendo e 17

32 Figura 2.3: Classica architettura I/O di un PC scrivendo dati rispettivamente nel registro di input (Input Register) e nel registro di output (Output Register). In realtà lo stesso porto di I/O può essere utilizzato per più scopi: alcuni bit funzioneranno, per esempio, come registro di stato, altri come registro di controllo; lo stesso vale per i registri di input e output. Figura 2.4: Porti di I/O e registri L interfaccia di I/O è un dispositivo hardware che è il tramite fra i porti di I/O e il controller del dispositivo. Lo scopo principale è quello di interpretare i valori contenuti nei registri del dispositivo, ovvero nei porti di I/O, e tradurli in dati e comandi verso il dispositivo stesso; inoltre, rileva i cambiamenti di stato del dispositivo e imposta in modo opportuno i relativi porti di I/O. 18

33 Infine, il device controller è il dispositivo hardware alla base della gerarchia, ed è in grado di interpretare i comandi ricevuti dall interfaccia di I/O e istruire correttamente il dispositivo ad eseguirli inviando opportuni segnali elettrici; inoltre, converte i segnali elettrici ricevuti dal dispositivo e modifica, tramite l interfaccia di I/O, i valori nei registri. Dispositivi complessi come i dischi hanno il relativo controller, il quale riceve comandi di alto livello dalla CPU, come lettura o scrittura di blocchi, e li converte in comandi di basso livello verso il disco I/O Port-mapped e I/O Memory-mapped Abbiamo visto nel paragrafo precedente che la CPU ha bisogno di un meccanismo che gli permetta di accedere al dispositivo. Concettualmente, a livello hardware, non c è nessuna differenza tra aree di memoria e regioni di I/O: entrambe sono accedute attraverso l asserzione di segnali elettrici sul bus di controllo e sul bus indirizzi e leggendo o scrivendo i dati sul bus dati. I costruttori di CPU hanno due principali filosofie nell implementare lo spazio dedicato ai dispositivi di I/O: la prima è che esiste un unico spazio indirizzabile sia per la memoria che per i dispositivi fisici; la seconda, invece, è che i dispositivi di I/O sono concettualmente diversi dalla memoria e quindi gli si riserva dello spazio di indirizzamento completamente separato dalla memoria principale. In base alle due filosofie descritte, i metodi per comunicare con i dispositivi hardware sono essenzialmente due: I/O Port-Mapped; I/O Memory-Mapped; Il primo meccanismo si basa sulla seconda filosofia discussa, ed è alla base delle architetture x86. In queste architetture esiste uno spazio di memoria virtuale separato dalla memoria principale che è utilizzato dalla CPU per gestire l I/O. Anche se questo spazio è completamente separato da quello della memoria principale, i porti possono essere mappati lo stesso in memoria. Questa separazione è ottenuta 19

34 utilizzando pin della CPU dedicati esclusivamente per l I/O oppure utilizzando un intero bus dedicato all I/O. Il dispositivo con cui si vuole comunicare è identificato da un numero di porto, a cui si inviano i dati che poi saranno inoltrati al dispositivo stesso tramite il controller. Ovviamente esistono tipi diversi di porti: alcuni saranno di sola lettura, altri di sola scrittura, ma in generale un porto può funzionare in maniera bidirezionale. Ogni architettura implementa l accesso ai porti in maniera differente e quindi il kernel deve fornire uno strato di astrazione. In Linux, per esempio, abbiamo le seguenti funzioni: unsigned inb(unsigned port); void outb(unsigned char byte, unsigned port); Queste funzioni permettono di leggere o scrivere un byte (8-bit). Il tipo di ritorno di inb ovviamente dipende dall architettura in uso; unsigned inw(unsigned port); void outw(unsigned short word, unsigned port). Queste funzioni accedono a porti a 16-bit (word); unsigned inl(unsigned port); void outl(unsigned longword, unsigned port). Queste funzioni accedono a porti a 32-bit (long word) Il secondo meccanismo, I/O memory-mapped, prevede che la comunicazione tra CPU e dispositivo avviene utilizzando gli stessi meccanismi utilizzati per indirizzare la memoria principale. Nonostante la diffusione dei meccanismi I/O port-mapped utilizzati nel mondo x86, il meccanismo più utilizzato oggi rimane quello memorymapped. Per questo motivo le moderne architetture di CPU mettono a disposizione meccanismi che consentono di mappare i porti di I/O in indirizzi di memoria RAM. Per esempio, bus di sistema come PCI sono spesso indirizzati attraverso un meccanismo memory mapped. Le funzioni utilizzate per accedere a questi tipo di aree di memoria sono le stesse utilizzate per accedere ad aree di memoria RAM: readb(addr), readw(addr), readl(addr), utilizzate per leggere da un indirizzo di memoria; writeb(val, addr), writew(val, addr), writel(val, addr), utilizzate per scrivere un valore in un area di memoria; 20

35 Prima di utilizzare aree di memoria di I/O memory-mapped, a seconda dell architettura o del bus in utilizzo, potrebbe essere necessario effettuare il mapping dello spazio di indirizzo del dispositivo in indirizzi di memoria di sistema del kernel; questo meccanismo è chiamato software I/O mapping che nel kernel Linux è effettuato impostando correttamente la tabella della pagine (page table) tramite l utilizzo delle funzioni ioremap e iounmap. 2.2 Il Linux Device Driver Model Nelle versioni iniziali del kernel Linux venivano offerte funzionalità di base per la gestione e lo sviluppo dei device driver, tra cui meccanismi per l allocazione dinamica delle risorse, meccanismi per riservare aree di memoria dedicate all I/O o per riservare particolare linee di interrupt, e funzioni di utilità per l attivazione dell ISR (Interrupt Service Routine) che gestisce l interrupt sollevata dal dispositivo. Il kernel offriva solo queste funzionalità perchè i vecchi dispositivi erano difficili da programmare ed erano completamente diversi uno dall altro, anche se utilizzavano lo stesso bus di comunicazione. L introduzione della tecnologia PCI come tipologia di bus di sistema, però, ha guidato molto la progettazione e lo sviluppo dei dispositivi hardware recenti, e conseguentemente la maggior parte dei dispositivi, anche appartenendo a classi differenti, hanno al loro interno funzionalità simili. I device driver per dispositivi recenti dovrebbero considerare tre problemi: Power management, ovvero gestire i segnali elettrici dei dispositivi in modo da spegnerli o da metterli in uno stato a basso voltaggio (low-power state). Un esempio classico è quando il computer entra in uno stato di standby e quindi ogni dispositivo hardware che compone il sistema (hard disk, scheda video, scheda di rete ecc.) deve essere messo in uno stato low-power; questo implica che ogni dispositivo deve entrare nello stato di low-power nell ordine corretto, per esempio, non posso metter in standby prima il controller di un dispositivo e poi il dispositivo stesso perchè non arriverebbe mai il segnale di standby al dispositivo; 21

36 Plug-and-play, ovvero l allocazione trasparente di risorse necessarie alla configurazione del dispositivo; Hot-plugging, ovvero il supporto all inserimento e alla rimozione di dispositivi durante l esecuzione del sistema; Per questi motivi, dalla versione 2.6 in poi, è stato introdotto il Device Driver Model, un modello che fornisce l astrazione necessaria a rappresentare i dispositivi e la topologia che hanno nel sistema, considerando anche i bus utilizzati e, per ogni dispositivo, il driver utilizzato. Questo modello offre le seguenti funzionalità: Power management e system shutdown: come detto prima, il device model permette di spegnere i dispositivi nel corretto ordine quando si effettua lo shutdown del sistema, e offre in generale funzionalità per il power management; Comunicazione con lo spazio utente: l implementazione del filesystem virtuale sysfs (vedi sez ) permette di esporre a livello utente la struttura dei device nel sistema; Dispositivi Hotpluggable: il device model gestisce i dispositivi che utilizzano il meccanismo di hotplug ed in particolare gestisce la comunicazione con lo spazio utente per quanto riguarda l inserimento e la rimozione dei dispositivi; Classe del dispositivo: il device model mette a disposizione meccanismi per l assegnazione di una classe ad un particolare dispositivo. La classe descrive il tipo di dispositivi con cui si ha a che fare; Ciclo di vita di un oggetto: tutte le funzionalità viste in precedenza richiedono meccanismi che riguardano il ciclo di vita degli oggetti, le relazioni che esistono tra di loro e la loro rappresentazione nell user space: tutto questo è implementato nel device model; Per ottenere tutte queste funzionalità, la struttura del device model è molto complessa. Una piccola parte della struttura del device model associata, per esempio, ad un mouse USB è mostrata nella Figura

37 Figura 2.5: Esempio della struttura del Device Model associato ad un mouse USB Dalla figura possiamo notare 3 strutture albero: quello Devices che mostra essenzialmente come il dispositivo è connesso al sistema, l albero Buses, che tiene traccia dei dispositivi connessi ad ogni bus nel sistema e l albero Classes i cui nodi figli specificano le funzionalità che mette a disposizione il particolare dispositivo. Nei successivi paragrafi andremo ad analizzare le strutture che troviamo nei livelli più alti del device model per poi scendere a quelle più vicine ai dispositivi Il filesystem sysfs Il filesystem sysfs è un filesystem speciale, simile a /proc, che è montato di solito nella directory /sys. Il filesystem /proc è stato il primo filesystem che permetteva alle applicazioni user mode di accedere alle strutture interne del kernel. Il filesystem sysfs ha fondamentalmente lo stesso obiettivo, ma fornisce informazioni aggiuntive sulle strutture del kernel. Inoltre sysfs è organizzato in maniera molto più strutturata rispetto a /proc. L obiettivo del filesystem /sysfs è quello di esporre le relazioni gerarchiche tra i componenti che compongono il device driver model, 23

38 quindi è effettivamente la rappresentazione dell albero dei device del sistema. Le directory top-level di questo filesystem sono le seguenti: block: contiene una directory per ogni dispositivo a blocchi registrato, indipendentemente dal bus a cui è connesso. Ognuna di queste directory, a loro volta, contengono le partizioni del dispositivo a blocchi; devices: contiene tutti i dispositivi hardware rilevati dal kernel, i quali sono organizzati in base al bus a cui sono connessi. Praticamente la directory devices esporta il device model all interno sistema. La struttura di questa directory è la reale topologia dei device del sistema; bus: contiene i bus esistenti nel sistema ai quali sono collegati i dispositivi; drivers: contiene i device driver registrati nel kernel; class: contiene la tipologia dei dispositivi all interno del sistema (scheda audio, di rete, video, ecc.). La stessa classe può includere dispositivi connessi a bus differenti e gestiti da driver diversi; power: contiene i file per gestire gli stati di power di alcuni dispositivi hardware; firmware: contiene i file per gestire il firmware di alcuni dispositivi hardware; Le relazioni tra i componenti del device driver model sono espresse nel filesystem sysfs tramite link simbolici tra le directory e i file. Per esempio il file /sys/block/- sda/device può essere un link simbolico alla sottodirectory innestata in /sys/devices/pci0000:00 che rappresenta il controller SCSI connesso al bus PCI. Lo scopo principale che hanno i file regolari all interno del filesystem sysfs è quello di rappresentare gli attributi che hanno driver e dispositivi. Per esempio il file dev all interno della directory /sys/block/hda contiene i numeri major e il minor number (vedi sez ) del disco master nella prima catena IDE Kobject La struttura principale del device model è kobject (Kernel Object), definita in <linux/kobject.h>. Inizialmente questa struttura è stata pensata per implementare semplicemente un contatore di riferimenti ad un oggetto nel kernel, ma con il tempo ha incluso molte altre funzionalità tra cui: 24

39 tiene traccia dei riferimenti ad un oggetto del kernel, e quando non ce ne sono l oggetto può essere cancellato; ogni oggetto mostrato nel sysfs ha un kobject che interagisce con il kernel per creare una sua rappresentazione visibile; un kobject fà da collante tra tutte le strutture utilizzate nel device model; gestisce gli eventi di hotplug, ovvero informa i processi in spazio utente che un device è stato inserito o rimosso dal sistema; La struttura di un kobject è definita in questo modo: struct kobject { c o n s t char name ; struct list_head entry ; struct kobject parent ; struct kset kset ; struct kobj_type ktype ; struct sysfs_dirent sd ; struct kref kref ; unsigned i n t state_initialized : 1 ; unsigned i n t state_in_sysfs : 1 ; unsigned i n t state_add_uevent_sent : 1 ; unsigned i n t state_remove_uevent_sent : 1 ; unsigned i n t uevent_suppress : 1 ; } ; Di solito un kobject non esiste da solo (standalone) ma, siccome tutti i servizi che mette a disposizione sono fatti a nome di altri oggetti, esso è inserito (embedded) all interno di altre strutture. Descriviamo i campi: name è il nome associato al kobject; entry è l elemento di una lista standard utilizzato per raggruppare i vari kobject; parent è il puntatore al kobject padre, e permette quindi di stabilire una relazione gerarchica tra kobject; 25

40 kset è il puntatore alla struttura kset (vedi sez ) che raggruppa i vari kobject in un set; ktype fornisce informazioni sulla struttura che incorpora il kobject (vedi dopo per i dettagli); kref è utilizzato per semplificare la gestione dei riferimenti; Per inizializzare un kobject si effettuano i seguenti passi: Si effettua uno zeroing della struttura dove è contenuto. Quest operazione, ovviamente, setta a zero anche il kobject stesso. Se questo non avviene nell inizializzazione della struttura contenente basta fare: memset ( kobj, 0, sizeof ( kobj ) ) ; Si settano i campi interni del kobject con la chiamata a kobject_init. Questa funzione tra le tante cose setta il reference count a 1; Si imposta il nome del kobject tramite la funzione kobject_set_name; In genere si utilizza la funzione kobject_create per effettuare il primo e il secondo passo. Abbiamo detto che una delle principali funzionalità di un kobject è quello di reference counting. Questo significa che se esiste almeno un riferimento ad un certo oggetto nel kernel allora tutto il codice che è in relazione a quell oggetto deve continuare ad esistere. Le due funzioni che gestiscono il reference count sono: kobject_get che incrementa il reference count e ritorna un puntatore al kobject; kobject_put che decrementa il reference count e possibilmente libera la memoria associata all oggetto; In generale non si conosce il preciso momento in cui il reference count di un kobject va a zero. C è bisogno quindi di una funzione che effettua il rilascio dell oggetto. A tale scopo interviene una struttura che definisce il tipo del kobject (ktype) con cui abbiamo a che fare; questa struttura si chiama kobj_type ed è fatta così: 26

41 struct kobj_type { void ( release ) ( struct kobject ) ; c o n s t struct sysfs_ops sysfs_ops ; struct attribute default_attrs ; } ; Si può notare l esistenza del puntatore alla funzione di release: tale funzione funge da distruttore, ed è invocata dal kernel quando il conteggio dei riferimenti al kobject diventa zero. La variabile sysfs_ops punta ad una struttura sysfs_ops (vedi sez ) che fornisce i metodi per implementare gli attributi dell oggetto. Gli attributi sono riferiti dalla variabile default_attrs che punta ad un array di puntatori di strutture attribute (vedere 2.2.4); queste strutture definiscono gli attributi di default associati al kobject. Gli attributi, essenzialmente, rappresentano delle proprietà relative all oggetto dato; se il kobject è esportato al sysfs, i suoi attributi sono esportati come file Ksets La struttura kobject è spesso utilizzata per collegare tra di loro oggetti in una struttura gerarchica che rappresenta la struttura del sottosistema che si sta modellando. Ci sono 2 modi per fare questo collegamento: il puntantore parent e il concetto di Kset. Il campo parent nella struttura kobject (vedi sez ) è un puntatore ad un altro kobject che rappresenta l oggetto di livello superiore nella gerarchia. Per esempio, se un kobject rappresenta un dispositivo USB, il suo parent (genitore) potrebbe essere un oggetto che rappresenta l HUB in cui è inserito il dispositivo. L uso principale del puntatore parent è quello di posizionare l oggetto nella gerarchia sysfs. Ksets Un kset è una collezione di kobject che sono embedded all interno di strutture dello stesso tipo. Un kset è utilizzato per raggruppare, ad esempio, le entry del sysfs relative allo stesso tipo di dispositivo o di bus. Un kset inoltre 27

42 è sempre rappresentato nel sysfs, infatti, appena viene inizializzato e aggiunto al sistema, ci sarà una directory sysfs per esso. La struttura è definita in questo modo: struct kset { struct list_head list ; spinlock_t list_lock ; struct kobject kobj ; struct kset_uevent_ops uevent_ops ; } ; Descriviamo i campi: list è una lista concatenata di tutti i kobject nel kset; list_lock è uno spinlock; kobj è il kobject che rappresenta la classe base di questo insieme; uevent_ops punta alla struttura che descrive il comportamento di hotplug di un kobject nel kset. Uevent sta per user event, ed è un meccanismo per la comunicazione all user-space di informazioni riguardo l hotplugging e la rimozione di dispositivi dal sistema. L aggiunta di un kobject ad un kset è fatta di solito quando l oggetto è creato e consiste in 2 passi: 1. il campo kset della struttura kobject deve puntare al kset in questione; 2. si può a questo punto passare il kobject alla funzione kobject_add: i n t kobject_add ( struct kobject kobj, struct kobject parent, c o n s t char fmt,... ) Esiste una funzione che effettua sia la kobject_create (vedi sez ) che la kobject_add ed è: struct kobject kobject_create_and_add ( c o n s t char name, struct kobject parent ) ; Per cancellare un kobject da un kset esiste la funzione: void kobject_del ( struct kobject kobj ) ; 28

43 Un kset mantiene le informazioni dei suoi nodi figli attraverso una lista concatenata standard che è fornita dal kernel. Nella maggior parte dei casi i kobject contenuti nel kset hanno un puntatore al kset stesso nel proprio campo parent. Un kset, quindi, appare come nella Figura 2.6: Figura 2.6: Esempio di gerarchia di Kset Operazioni sui Kset un kset sono simili a quelle per gestire un kobject: La maggior parte delle funzioni disponibili per gestire void kset_init ( struct kset kset ) ; i n t kset_add ( struct kset kset ) ; i n t kset_register ( struct kset kset ) ; void kset_unregister ( struct kset kset ) ; struct kset kset_get ( struct kset kset ) ; void kset_put ( struct kset kset ) ; Anche un kset ha un nome, che è mantenuto nell oggetto kobject embedded nella struttura. Quindi se per esempio vogliamo settare il nome nel nostro kset possiamo fare in questo modo. Supponiamo di creare un kset di nome My_Kset), avremo: kobject_set_name (&my_set >kobj, My_Kset ) ; Operazioni di basso livello sul filesystem sysfs I kobject descritti in precedenza sono il meccanismo che c è dietro il filesystem sysfs. Dietro ogni directory presente all interno di questo filesystem c è nascosto un kobject. Abbiamo detto anche che un kobject può esportare uno o più attributi che 29

44 appaiono nella directory sysfs associata come dei file che contengono informazioni generate dal kernel. La chiamata alla funzione kobject_add, come abbiamo visto in precedenza, aggiunge un kobject ad un kset, e in realtà crea anche una directory nel sysfs. Quindi il nome utilizzato per il kobject sarà il nome della directory creata. L entry della directory sysfs è localizzata nella directory corrispondente al puntatore parent del kobject, e possiamo avere tre casi: se parent non è nullo il kobject sarà mappato in una subdirectory del genitore; se parent è NULL, la entry è impostata al kobject del kset, ovvero una subdirectory dentro kset->kobj; se, sia il puntatore parent che il campo del kset sono NULL, allora si assume che il kobject non abbia genitori e quindi si mappa come directory di livello radice nel sysfs. Adesso vediamo come si specificano gli attributi di un kobject. Quando si crea un kobject si assegnano automaticamente degli attributi di default. Gli attributi, come abbiamo visto, vengono specificati nella struttura che definisce il tipo di kobject: kobj_type (vedi sez ). Abbiamo detto, inoltre, che default_attrs punta ad un array di strutture di tipo attribute definita come segue: struct attribute { char name ; struct module owner ; mode_t mode ; } ; In questa struttura vediamo il campo name che indica ovviamente il nome dell attributo, il campo owner che è un puntatore al modulo (se c è) che è responsabile dell implementazione di questo attributo, e il campo mode che indica alcuni bit di protezione da applicare all attributo stesso. Di solito il campo mode è settato a S_IRUGO per avere un attributo di sola lettura; se invece vogliamo avere un attributo con permessi di accesso in scrittura possiamo settare mode a S_IWUSR. L array default_attrs in realtà dice solo quali sono gli attributi di un particolare kobject 30

45 ma non dà informazioni al filesystem sysfs su come realmente sono implementati: questo è compito della struttura sysfs_ops definita come segue: struct sysfs_ops { ssize_t ( show ) ( struct kobject kobj, struct attribute attr, char buffer ) ; ssize_t ( store ) ( struct kobject kobj, struct attribute attr, c o n s t char buffer, size_t size ) ; } ; Il metodo show è utilizzato quando un attributo è letto da parte dello user space. Il metodo store, invece, è utilizzato per scrivere un valore nell attributo, sempre che si hanno permessi di scrittura attivi. Infine, per aggiungere e/o rimuovere attributi non di default al kobject sono disponibili due funzioni: i n t sysfs_create_file ( struct kobject kobj, struct attribute attr ) ; i n t sysfs_remove_file ( struct kobject kobj, struct attribute attr ) ; Rappresentazione di Bus, Device e Device driver Nei paragrafi precedenti abbiamo visto tutte le strutture di alto livello utilizzate dal device model. In questo paragrafo scenderemo nel dettaglio sulle strutture utilizzate per rappresentare un bus, un dispositivo e un driver Bus: la struttura bus_type Il device model di Linux rappresenta qualunque tipo di bus attraverso la struttura bus_type definita come segue: struct bus_type { c o n s t char name ;... struct kset subsys ; struct kset drivers ; struct kset devices ; struct klist klist_devices ; struct klist klist_drivers ;... i n t ( match ) ( struct device dev, struct device_driver drv ) ; i n t ( uevent ) ( struct device dev, struct kobj_uevent_env env ) ; 31

46 } ; i n t ( probe ) ( struct device dev ) ; i n t ( remove ) ( struct device dev ) ; void ( shutdown ) ( struct device dev ) ; i n t ( suspend ) ( struct device dev, pm_message_t state ) ; i n t ( resume ) ( struct device dev ) ; Descriviamo alcuni dei campi più importanti: name è il nome del bus; è utilizzato per identificarlo nel filesystem sysfs; tutti i dispositivi e i driver associati al bus sono gestiti utilizzando, rispettivamente, gli elementi drivers e devices; essendo degli oggetti kset, sono integrati automaticamente nel sysfs filesystem; klist_devices e klist_drivers, sono due liste che mantengono informazioni sui dispositivi e i driver disponibili in modo da essere ricercati in maniera molto veloce; subsys permette la connessione con il sottosistema bus; match è il puntatore alla funzione che tenta di trovare una corrispondenza tra il driver e un dispositivo dato; uevent è il puntatore alla funzione che è utilizzata per informare il bus che un nuovo dispositivo è stato aggiunto al sistema; probe è il puntatore alla funzione che viene richiamata quando è necessario collegare un driver ad un dispositivo; la funzione controlla se il dispositivo fisico è realmente presente nel sistema; remove è il puntatore alla funzione che rimuove il collegamento tra driver e dispositivo, per esempio, quando un dispositivo hotpluggable è rimosso dal sistema; i campi shutdown, suspend, e resume sono puntatori a funzioni che gestiscono il power management dispositivo; 32

47 Per effetuare la registrazione di un bus nel kernel si effettua la chiamata alla funzione bus_register. Inoltre, siccome un bus ha bisogno di conoscere tutti i dispositivi che sono connessi a lui e tutti i rispettivi driver, gli oggetti devices e drivers hanno settato come genitore (parent) l oggetto bus Dispositivo: la struttura device Il device model definisce la seguente struttura per descrivere un dispositivo: struct device { struct klist klist_children ; struct klist_node knode_parent ; / node i n s i b l i n g l i s t / struct klist_node knode_driver ; struct klist_node knode_bus ; struct device parent ; struct kobject kobj ; char bus_id [ BUS_ID_SIZE ] ; / p o s i t i o n on parent bus /... struct bus_type bus ; / type o f bus d e v i c e i s on / struct device_driver driver ; / which d r i v e r has a l l o c a t e d t h i s d e v i c e / void driver_data ; / data p r i v a t e to the d r i v e r / void platform_data ; / Platform s p e c i f i c data, d e v i c e c o r e does not touch i t /... void ( release ) ( struct device dev ) ; } ; Prima di descrivere i campi, diciamo che klist e klist_node sono strutture dati migliorate rispetto alla struttura dati list_head, infatti effettuano la gestione dei lock e dei reference sugli elementi che sono aggiunti alla lista. klist è la testa della lista e klist_node è l elemento della lista. Detto questo andiamo a descrivere i campi della struttura device: kobj è il kobject embedded nella struttura (vedi sez ); klist_children è la testa della lista concatenata dove sono specificati i dispositivi di basso livello; knode_parent è utilizzato come elemento se il dispositivo stesso è incluso in questa lista; 33

48 parent punta all istanza di device genitore del device in considerazione; knode_driver è l elemento della lista che mantiene una lista di istanze di device che gestisce il driver; driver punta alla particolare struttura device_driver (vedi sez ) che controlla il dispositivo; bus_id specifica univocamente la posizione del dispositivo sul bus a cui è connesso. La posizione di un dispositivo sul bus PCI è identificata da una stringa come questa: <bus_numer>:<slot_number>:<function_number>; bus è il puntatore all oggetto istanza della struct bus_type, sul quale il dispositivo è connesso; driver_data, platform_data e firmware_data sono elementi privati rispettivamente al driver, alla platform e al firmware che sono utilizzati per lavorare correttamente con il dispositivo; release è la funzione che libera le risorse allocate nel kernel e viene utilizzata quando il dispositivo non è più utilizzato; Il kernel fornisce, come per il bus, la funzione per aggiungere un nuovo dispositivo nel kernel: questo è fatto tramite la chiamata a device_register che effettua la registrazione in due passi, invocando: 1. device_initialize: aggiunge un nuovo dispositivo al sottosistema device attraverso la chiamata a funzione kobj_set_kset_s (dev, devices_subsys); 2. device_add: questa funzione è leggermente più complicata ed effettua i seguenti passi: (a) memorizza la relazione genitore/figlio nella gerarchia dei kernel object; (b) si registra il dispositivo nel device subsystem tramite kobject_add; 34

49 (c) si aggiunge un collegamento al sysfs, uno nella directory del bus che punta al dispositivo, e una nella directory del dispositivo, che punta al bus subsystem tramite la funzione bus_add_device; (d) si prova l autoprobe del dispositivo in modo da aggiungere il dispositivo alla lista dei dispositivi connessi al particolare bus, sempre che si trovi un driver adatto (funzione bus_attach_device); (e) si aggiunge il dispositivo alla lista dei figli del device genitore; Inoltre la coppia di funzioni device_get e device_put sono utilizzate per gestire il conteggio dei riferimenti al device Driver: la struttura device_driver Il device model mette a disposizione l astrazione per un generico driver del dispositivo tramite la definizione della seguente struttura: struct device_driver { c o n s t char name ; struct bus_type bus ; struct kobject kobj ; struct klist klist_devices ; struct klist_node knode_bus ;... i n t ( probe ) ( struct device dev ) ; i n t ( remove ) ( struct device dev ) ; void ( shutdown ) ( struct device dev ) ; i n t ( suspend ) ( struct device dev, pm_message_t state ) ; i n t ( resume ) ( struct device dev ) ; } ; Il significato dei campi è il seguente: name è il nome che identifica il driver; bus punta all oggetto istanza della struttura bus_type; klist_devices è la testa della lista che rappresenta gli oggetti device gestiti da questo driver; 35

50 knode_bus è utilizzato per collegare tutti i dispositivi sul bus comune; probe è il puntatore alla funzione che controlla se il dispositivo (passato come parametro) può essere gestito dal driver presente nel sistema; remove è il puntatore alla funzione invocata quando si vuole rimuovere un dispositivo dal distema; shutdown, suspend e resume sono puntatori a funzioni che gestiscono il power management del dispositivo; Come per il bus e il dispositivo, il kernel fornisce una funzione per la registrazione di un driver che è driver_register. Questa funzione esegue le seguenti operazioni: 1. aggiunge il driver al bus tramite la funzione bus_add_driver; 2. viene effettuato l autoprobing se supportato tramite la funzione driver_attach; questo viene effettuato fino a che non si trovano dispositivi per cui il driver è responsabile (funzione match del driver); 3. il driver viene aggiunto alla lista che contiene tutti i driver registrati sul bus tramite klist_add_tail; Accesso ai dispositivi: i device file In Linux ogni cosa è vista come un file e anche i dispositivi fisici non fanno eccezione. In particolare i dispositivi di I/O sono trattati come file speciali chiamati device file per i quali non è associato nessun blocco sull hard disk, ma sono utilizzati per collegare il dispositivo fisico al driver che lo gestisce. Il fatto che un dispositivo fisico sia visto come file significa che le stesse system call utilizzate su un file classico potranno essere utilizzate anche su un device file, ovviamente con risultati diversi: per esempio, la system call write() utilizzata su di un file classico effettuerà una vera e proprio scrittura nel file (come ci si aspetta), mentre se utilizziamo la stessa system call su un device file che rappresenta una stampante (per esempio /dev/lp0 ), in realtà stiamo inviando dei dati verso la stampante. Inoltre rispetto ad un file 36

51 classico, un device file ha delle differenze che riguardano soprattutto i comandi che sono forniti dal kernel e che, ovviamente, non possono essere utilizzati sui file classici. I dispositivi fisici, essenzialmente si classificano per il modo che hanno di comunicare con il sistema: Dispositivi a caratteri (char device): sono dispositivi che effettuano una comunicazione carattere per carattere (e.g. seriale), e hanno quindi un basso volume di dati da trasportare per unità di tempo: sono dispositivi a caratteri la tastiera, le interfacce seriali; Dispositivi a blocchi (block device): sono dispositivi che effettuano una comunicazione a blocchi di dati (byte, word, ecc.); i dispositivi a blocchi per eccellenza sono gli Hard Disk, lettori CD-ROM e DVD. Altri tipi di dispositivi, come per esempio le interfacce di rete (net device) sono gestiti in maniera particolare nel kernel; infatti, non sono visti come device file, ma le applicazioni devono utilizzare il meccanismo delle socket che forniscono un astrazione per utilizzare questi dispositivi. Un device file, come abbiamo detto, è un vero e proprio file memorizzato nel filesystem a cui è associato un inode (vedi sez ) che a differenza dei file normali non memorizza puntatori a blocchi che sono sul disco (ovvero i dati effettivi contenuti nel file) ma contiene un identificativo del dispositivo fisico associato. In genere questo identificativo è formato dal tipo di device file (char o block) e da una coppia di numeri: il major e il minor number. Il major number di solito indentifica una classe di dispositivi, quindi dispositivi come, per esempio, i dischi SCSI avranno tutti lo stesso major number. Quello che distinguerà un dispositivo della stessa classe dall altro è il minor number. La lista completa di tutti i major e i minor number assegnati ad un particolare dispositivo è disponibile nella Linux Device List [Lis]: ogni riga del registro è composta da major number, tipo di device file e minor number assegnato (vedi Figura 2.7). Nel kernel il tipo di dato utilizzato per rappresentare insieme il major e il minor number è dev_t. Una volta assegnati 37

52 questi 2 numeri, il kernel li utilizzerà per identificare il driver che deve gestire il dispositivo. Figura 2.7: Estratto dal Linux Device List Nella Figura 2.8 è mostrato, per esempio, il list della directory /dev/sda la quale rappresenta il disco: Figura 2.8: Esempio di major e minor number del disco Si noti la b all inizio dell identificativo che sta per block; questo ci indica che abbiamo a che fare con un dispositivo a blocchi. Inoltre notiamo come il major number pari ad 8 (disco scsi) è lo stesso per tutti i dispositivi elencati; i dispositivi specificati dal minor number sono tutte le partizioni presenti sul disco, dove ognuna di esse è vista dal kernel ancora come un dispositivo. Nei kernel Linux più recenti la gestione dei device file è fatta dinamicamente: esiste un demone, udevd che permette la creazione dinamica di un device file appena il dispositivo è riconosciuto dal kernel; infatti, non appena rileva il dispositivo (durante l avvio o a runtime) viene creato un kobject che è esportato allo spazio utente tramite il filesystem sysfs. A questo punto il kernel manda un messaggio 38

53 di hotplug allo spazio utente che è catturato dal demone udevd; questo messaggio include informazioni come il major e il minor number che il driver ha assegnato al dispositivo. Quando il dispositivo è registrato, sarà creata una entry nella directory /dev in modo da consentire l accesso agli utenti. 2.3 Device Driver Abbiamo visto nel paragrafo 2.2 come il kernel Linux mette a disposizione un modello che unifica la gestione di bus, dispositivi e driver. Abbiamo visto come un device driver è la parte essenziale di un sistema complesso che è in grado di far comunicare il dispositivo con lo spazio utente tramite l invocazione di system call. In questo paragrafo ci concrentremo sulle strutture del kernel utilizzate da un device driver e distingueremo i driver per dispositivi a caratteri e i driver per dispositivi a blocchi, concentrandoci maggiormente su quest ultimi Strutture dati per l interfacciamento con il filesystem In questo paragrafo andremo a vedere nel dettaglio tutte le strutture dati utilizzate da un device driver per interfacciarsi con il filesystem La struct inode Abbiamo visto nel paragrafo che ad un dispositivo fisico è associato un file speciale chiamato device file; adesso ci chiedamo come tale file venga gestito nel filesystem. A questa domanda risponde il fatto che ad ogni file nel virtual file system (VFS) (cap.12 [BC05]) è associato un inode, una struttura dati che gestisce tutte le proprietà di un file, e ovviamente anche di un device file, e permette al kernel di effettuare operazioni di gestione e di manipolazione dei file stessi. Un oggetto inode è l istanza della struttura inode definita in <linux/fs.h>. Riportiamo, di seguito, solo la parte della struttura che riguarda i dispositivi e i relativi driver. struct inode {... 39

54 } ; dev_t i_rdev ;... umode_t i_mode ;... struct file_operations i_fop ;... union {... struct block_device i_bdev ; struct cdev i_cdev ; } ;... Possiamo notare questi elementi: i_rdev contiene il major e il minor number associato al dispositivo; i_mode contiene il tipo di dispositivo associato (a blocchi o a caratteri); i_fop punta ad una struttura fondamentale, file_operations (vedi sez ), che definisce un insieme di funzioni operanti su file come ad esempio la open, read, e la write che sarrano poi utilizzate dal VFS per lavorare sui dispositivi; i_bdev e i_cdev sono i puntatori alle strutture specifiche del dispositivo che l inode sta rappresentando: un dispositivo a caratteri è rappresentato dalla struttura cdev (vedi sez ), mentre un dispositivo a blocchi è rappresentato dalla struttura block_device (vedi sez ); Descriveremo l utilizzo di questa struttura all atto dell apertura di un device file che rappresenta un dispositivo a blocchi (vedi sez ) La struct file La struttura file è definita in <linux/fs.h> ed è insieme alla struttura inode una delle più importanti nel kernel. Questa struttura non ha niente a che vedere con i puntatori a FILE utilizzati nel linguaggio C, che tra l altro non appaiono mai nel codice del kernel. La struttura rappresenta essenzialmente un file aperto nel sistema e quindi non un device file in particolare. Un oggetto di tipo file è creato dal kernel 40

55 all apertura di un file (quando viene invocata la open) ed è utilizzato da tutte le funzioni che vogliono operare su tale file; l oggetto di tipo file rimane in memoria fino a che tutte le istanze del file non sono chiuse (system call close). Vediamo la struttura nel dettaglio: struct file { struct list_head fu_list ; struct path f_path ; #define f_dentry f_path. dentry #define f_vfsmnt f_path. mnt c o n s t struct file_operations f_op ; atomic_t f_count ; unsigned i n t f_flags ; mode_t f_mode ; loff_t f_pos ; struct fown_struct f_owner ; unsigned i n t f_uid, f_gid ; struct file_ra_state f_ra ; unsigned long f_version ;... struct address_space f_mapping ;... } ; Non andremo nel dettaglio dei campi, ma possiamo notare che la struttura possiede molti campi per la gestione del file, e uno dei campi più importanti è f_op, puntatore al file_operations (vedi sez ) struttura che specifica tutte le funzioni invocabili per quel particolare file La struct file_operations Abbiamo visto nei paragrafi precedenti come ogni istanza della struttura file e ogni istanza della struttura inode ha un puntatore alla struttura file_operations. Questa struttura mantiene puntatori a funzione per tutte le possibili operazioni che si possono effettuare su un file ed in particolare su un device file. La struttura è definita in <linux/fs.h>. Vediamola nel dettaglio: struct file_operations { struct module owner ; loff_t ( llseek ) ( struct file, loff_t, i n t ) ; 41

56 } ; ssize_t ( read ) ( struct file, char user, size_t, loff_t ) ; ssize_t ( write ) ( struct file, c o n s t char user, size_t, loff_t ) ; ssize_t ( aio_read ) ( struct kiocb, c o n s t struct iovec, unsigned long, loff_t ) ; ssize_t ( aio_write ) ( struct kiocb, c o n s t struct iovec, unsigned long, loff_t ) ; i n t ( readdir ) ( struct file, void, filldir_t ) ; unsigned i n t ( poll ) ( struct file, struct poll_table_struct ) ; i n t ( ioctl ) ( struct inode, struct file, unsigned int, unsigned long ) ; long ( unlocked_ioctl ) ( struct file, unsigned int, unsigned long ) ; long ( compat_ioctl ) ( struct file, unsigned int, unsigned long ) ; i n t ( mmap ) ( struct file, struct vm_area_struct ) ; i n t ( open ) ( struct inode, struct file ) ; i n t ( flush ) ( struct file, fl_owner_t id ) ; i n t ( release ) ( struct inode, struct file ) ; i n t ( fsync ) ( struct file, struct dentry, i n t datasync ) ; i n t ( aio_fsync ) ( struct kiocb, i n t datasync ) ; i n t ( fasync ) ( int, struct file, i n t ) ; i n t ( lock ) ( struct file, int, struct file_lock ) ; ssize_t ( sendpage ) ( struct file, struct page, int, size_t, loff_t, i n t ) ; unsigned long ( get_unmapped_area ) ( struct file, unsigned long, unsigned long, unsigned long, unsigned long ) ; i n t ( check_flags ) ( i n t ) ; i n t ( dir_notify ) ( struct file filp, unsigned long arg ) ; i n t ( flock ) ( struct file, int, struct file_lock ) ; ssize_t ( splice_write ) ( struct pipe_inode_info, struct file, loff_t, size_t, unsigned i n t ) ; ssize_t ( splice_read ) ( struct file, loff_t, struct pipe_inode_info, size_t, unsigned i n t ) ; Descriviamo i campi più importanti: owner è il puntatore al modulo che possiede la struttura. Questo puntatore è utilizzato per evitare che sia rimosso il modulo dal sistema mentre le operazioni che offre sono ancora in uso; read è il puntatore alla funzione che effettua la lettura di dati dal dispositivo; write è il puntatore alla funzione che invia i dati verso il dispositivo (scrive sul dispositivo); 42

57 open è il puntatore alla funzione che viene richiamata per prima all apertura del device file. Questo puntatore può essere anche NULL; se succede questo il driver non sarà avvertito del fatto che il device file del dispositivo che gestisce è stato aperto; release è il puntatore alla funzione che è invocata quando la struttura file sta per essere rilasciata. Come per la open anche questo puntatore può essere NULL; ioctl è il puntatore alla funzione che offre un modo per mandare comandi particolari al dispositivo (per esempio il comando di formattare un floppy disk che non è né una scrittura né una lettura); flush è il puntatore alla funzione che esegue l operazione di flush. Questa operazione è invocata quando un processo chiude la sua copia del descrittore di un file per il dispositivo; llseek è il puntatore alla funzione che è utilizzata per cambiare la posizione corrente di lettura/scrittura in un file. Default file_operations Per ognuna delle classi di dispositivo il kernel fornisce un file_operations di default. Per i dispositivi a caratteri, il kernel può fornire di default solo l operazione di open perchè tali dispositivi implementano un insieme di operazioni che dipendono dal dispositivo stesso. Quindi di default è definito il seguente insieme di operazioni per un dispositivo a caratteri: <fs/ devices. c> struct file_operations def_chr_fops = {. open = chrdev_open } ; Il compito principale della funzione chrdev_open sarà quello di riempire la struttura con i puntatori delle altre funzioni che offre il dispositivo. Invece, per i dispositivi a blocchi, la storia è diversa perchè tutti i dispositivi di questa tipologia aderiscono ad uno schema ben preciso. Questo consente al kernel di fornire, di default, un insieme più ampio di funzioni: 43

58 <fs/ block_dev. c> c o n s t struct file_operations def_blk_fops = {. open = blkdev_open,. release = blkdev_close,. llseek = block_llseek,. read = do_sync_read,. write = do_sync_write,. aio_read = generic_file_aio_read,. aio_write = generic_file_aio_write_nolock,. mmap = generic_file_mmap,. fsync = block_fsync,. unlocked_ioctl = block_ioctl,. splice_read = generic_file_splice_read,. splice_write = generic_file_splice_write, } ; In realtà, per i dispositivi a blocchi, non viene utilizzata solo questa struttura ma verrà utilizzata anche la struttura block_device_operations (vedi sez ) che è simile a quella su citata: il codice delle operazioni per interfacciarsi con lo specifico dispositivo è referenziato in tale struttura. La struttura block_device_operations, quindi, deve essere implementata specificatamente per il particolare dispositivo a blocchi, mentre la stessa struttura file_operations è utilizzata per qualunque file che rappresenta un dispositivo a blocchi. A differenza dei dispositivi a caratteri, un dispositivo a blocchi non è completamente descritto dalla struttura file_operations perchè le richieste sul dispositivo sono gestite con un sistema complesso di cache e code di richieste che vedremo nel paragrafo Database dei dispositivi in Linux Il kernel ha la necessità di conoscere quali dispositivi a blocchi o a caratteri sono disponibili in un certo istante nel sistema, e quindi c è la necessità di avere una sorta di database per mantenere quest informazione. Sia per i dispositivi a caratteri che per quelli a blocchi, il database utilizzato è simile, ma si particolarizza in alcune cose. Vedremo nel paragrafo che ogni dispositivo a caratteri è rappresentato da un istanza della struttura cdev, a differenza dei dispositivi a blocchi che 44

59 sono rappresentati, invece, da un istanza della struttura gendisk (vedere paragrafo ); quest ultima struttura gestisce le partizioni dei dispositivi a blocchi, anche se il dispositivo non ha partizioni (il dispositivo viene visto come un dispositivo avente un unica grande partizione). Il kernel utilizza un array globale che implementa una tabella hash utilizzando il major number come chiave. Per i dispositivi a caratteri l array si chiama cdev_map, mentre quello per i dispositivi a blocchi ha il nome di bdev_map. Ognuno di questi due array è istanza della stessa struttura kobj_map. Il metodo di hashing utilizzato è il seguente: hashv alue = major%255. Questo è sufficiente perchè soltanto un numero limitato di dispositivi ha un major number più grande di 255. Il database appare come nella Figura 2.9. Figura 2.9: Database dei dispositivi nel kernel Vediamo nello specifico i campi della struttura kobj_map che è definita in <linux/drivers/base/map.c>: struct kobj_map { struct probe { struct probe next ; dev_t dev ; unsigned long range ; struct module owner ; kobj_probe_t get ;... void data ; } probes [ ] ; struct mutex lock ; } ; 45

60 Il mutex lock permette di serializzare gli accessi alla tabella. Gli elementi della struttura probe sono i seguenti: next collega tutti gli elementi della tabella hash in una lista concatenata; dev identifica il device number. Il tipo dev_t include sia il major che il minor number; range rappresenta il numero di minor number consecutivi che il dispositivo utilizza; owner punta al modulo (se esiste) che fornisce il device driver; get punta ad una funzione che restituisce l istanza del kobject associato al dispositivo; data è l elemento che permette di distinguere se sto considerando un dispositivo a caratteri o a blocchi; per i dispositivi a caratteri, data punterà ad un istanza della struttura cdev, mentre per i dispositivi a blocchi punterà ad un istanza della struttura gendisk; Per quanto riguarda i dispositivi a caratteri esiste un ulteriore database, struct char_device_struct; tale struttura è utilizzata per gestire l allocazione del range di device number per i vari driver. Infatti un driver può richiedere un device number dinamicamente, oppure può specificare un range che suppone disponibile ad essere acquisito. Nel primo caso, il kernel ha bisogno di trovare un range di device number liberi, mentre nel secondo caso, deve assicurarsi che il range da acquisire non vada ad interferire con quelli esistenti. Registrazione dei dispositivi a caratteri e a blocchi dispositivo a caratteri bisogna effettuare due passi: Per registrare un 1. Registrare oppure allocare un range di device number (major e minor); 2. Attivare il dispositivo aggiungendolo nel database; 46

61 Per effettuare il primo passo, il kernel mette a disposizione due funzioni: se il driver vuole utilizzare uno specifico range di numeri verrà utilizzata la funzione register_chrdev_region, mentre se il driver vuole allocare un range dinamicamente verrà utilizzata la funzione alloc_chrdev_region. Il prototipo di queste due funzioni è il seguente: <fs. h> i n t register_chrdev_region ( dev_t from, unsigned count, c o n s t char name ) i n t alloc_chrdev_region ( dev_t dev, unsigned baseminor, unsigned count, c o n s t char name ) ; Per quanto riguarda invece il secondo passo, una volta che il range di device number è stato ottenuto, il dispositivo deve essere attivato e aggiunto al database. Questo prevede l inizializzazione della struttura cdev tramite la funzione cdev_init e successivamente l aggiunta al database è effettuata dalla chiamata a cdev_add. I prototipi delle funzioni sono i seguenti: <cdev. h> void cdev_init ( struct cdev cdev, c o n s t struct file_operations fops ) ; i n t cdev_add ( struct cdev p, dev_t dev, unsigned count ) ; L elemento fops in cdev_init contiene il puntatore alle operazioni che gestiscono la reale comunicazione con il dispositivo, mentre l elemento count in cdev_add specifica quanti minor number il dispositivo fornisce. Per quanto riguarda i dispositivi a blocchi, la registrazione è un poco più complessa e consiste essenzialmente nell utilizzo di alloc_disk che allocherà la struttura gendisk e di add_disk che registrerà la struttura gendisk nel kernel; gendisk è la struttura che descrive un dispositivo a blocchi. Tutto questo sarà discusso approfonditamente nel Paragrafo I dispositivi a caratteri Abbiamo detto in precedenza che ogni dispositivo a caratteri è rappresentato da un istanza della struttura cdev. Vediamo adesso nello specifico i campi che compongono questa struttura: 47

62 <cdev. h> struct cdev { struct kobject kobj ; struct module owner ; c o n s t struct file_operations ops ; struct list_head list ; dev_t dev ; unsigned i n t count ; } ; kobj è l oggetto kobject embedded nella struttura (vedi sez ). E utilizzato per la gestione di varie informazioni riguardante la struttura in cui è inserito; owner punta al modulo (se esiste) che fornisce il driver; ops è il puntatore alla struttura file_operations dove sono definiti i puntatori alle funzioni che saranno implementate dal driver per la comunicazione con il dispositivo; list permette di implementare una lista di tutti gli oggetti inode che definiscono i device file che rappresentano il dispositivo; dev specifica il major e il minor number associato al dispositivo; count specifica il numero di minor number utilizzati dal dispositivo; La prima azione eseguita quando si utilizza un driver è la open del device file associato. Nei dispositivi a caratteri, la generica funzione per l apertura è chrdev_open definita in <fs/char_dev.c>, la cui firma è la seguente: s t a t i c i n t chrdev_open ( struct inode inode, struct file filp ) Assumiamo che l elemento inode rappresenti il device file che ancora deve essere aperto. Dato il device number, la funzione kobject_lookup, richiamata all interno della funzione chrdev_open, effettua una query al database dei dispositivi a caratteri (vedi sez ) e ritorna il kobject associato al driver. Questo permette di ottenere 48

63 l istanza di cdev 2. Avendo l istanza di cdev il kernel può accedere alle specifiche file_operations del dispositivo referenziate dall elemento ops. La Figura 2.10 mostra le relazioni fra le varie strutture dopo l apertura del device file: Figura 2.10: Relazione tra le strutture cdev, file, file_operations e inode Notiamo che inode->i_cdev punta all istanza di cdev e quindi la prossima volta che l inode sarà aperto non c è più bisogno di fare la query al database. Inoltre l inode è aggiunto alla lista utilizzata dall oggetto cdev e infine sia l elemento file- >f_op che cdev->ops puntano al file_operations specificato nell oggetto istanza di cdev e quindi potrà essere invocata la open specifica per il dispositivo che effettuerà tutte le inizializzazioni necessarie al dispositivo; per alcuni dispositivi la funzione di open effettuerà anche delle operazioni di handshaking con il dispositivo stesso se usato per la prima volta. Consideriamo, per esempio, il dispositivo con major number pari ad 1: tali dispositivi sono quelli di memoria in Linux e per ogni minor number viene identificato un particolare dispositivo, come mostrato nella Figura Inizialmente sarà utilizzata la funzione generica per l apertura di un dispositivo a caratteri che avrà major number pari ad 1; grazie al particolare minor number sarà poi selezionato il giusto file_operations (vedi Figura 2.12). Per terminare il discorso sulle operazioni sui dispositivi a caratteri, c è da dire che le funzioni che permettono la scrittura e la lettura dipendono ovviamente dal particolare dispositivo e quindi non si possono generalizzare. 2 Si utilizza la macro container_of(pointer, type, field) che permette di ottenere il puntatore ad una struttura dal puntatore a qualche altra struttura contenuta all interno della prima 49

64 Figura 2.11: Dispositivi di memoria nel kernel Figura 2.12: file_operations selezionato in base al minor number I dispositivi a blocchi I dispositivi a blocchi differiscono profondamente dai dispositivi a caratteri, principalmente per 3 motivi: 1. L accesso ai dati può essere eseguito in qualunque punto. Questo può anche essere il caso dei dispositivi a caratteri ma non necessariamente; 2. I dati sono sempre trasferiti in blocchi di dimensione fissata; infatti, anche se è richiesto un singolo byte, il driver del dispositivo deve recuperare un blocco completo dal dispositivo. Tutto questo differisce dai dispositivi a caratteri i quali restituiscono un singolo byte per volta; 3. Per l accesso ai dispositivi a blocchi si utilizzano pesantemente tecniche di caching; questo significa che le operazioni di lettura e scrittura sono ottimizzate, ovvero sono effettuate in modo tale da accedere il meno possibile al disco fisico considerando (il disco mantiene nella propria i cache i dati acceduti più di rencente). Tutto questo non ha senso se consideriamo i dispositivi a caratteri. 50

65 Consideriamo adesso l insieme degli strati del kernel attraversati durante un operazione su disco. Supponiamo che un processo effettui una system call read su un file memorizzato su disco. Tipicamente il kernel effettua nell ordine le seguenti operazioni per servire la richiesta: 1. La system call read attiva la giusta funzione del VFS, tramite la struturra file_operations, alla quale passa il descrittore del file e un offset che specifica la posizione da leggere; 2. La funzione del VFS determina se i dati richiesti sono già presenti in memoria RAM, oppure se si deve effettuare un operazione di lettura su disco. Il kernel, tramite il la cache del disco in memoria primaria (Page Cache), tiene traccia dei dati che sono stati recentemente letti o scritti su disco in modo tale da fornirli ad un eventuale richiesta. 3. Assumendo che i dati richiesti non siano in memoria, essi devono essere letti necessariamente dal disco, quindi bisogna determinarne la posizione fisica. Per fare questo il kernel si affida al livello Mapping Layer, il quale effettua di solito i seguenti due passi: (a) Determina la lunghezza del blocco (da non confondere con l unità di accesso di un file) del filesystem che contiene il file e calcola l estensione del file in termini di file block number: infatti un file è visto come un insieme di blocchi, e quindi il kernel deve determinare l id numerico di questi blocchi; (b) Una volta che i blocchi sono stati identificati, il mapping layer invoca una funzione specifica del filesystem in uso che accede all inode del file su disco e determina la posizione dei dati richiesti in termini di logical block number. Infatti il disco è visto come un insieme di blocchi, e quindi il kernel deve determinare l id dei blocchi che effettivamente contengono i dati. A causa del fatto che un file può essere memorizzato in blocchi non adiacenti del disco, l inode del file contiene anche informazioni sul mapping tra file block number e logical block number. 51

66 4. A questo punto il kernel conosce esattamente dove è posizionato il file all interno del disco, quindi può inviare il comando di lettura verso il disco. Questo viene fatto attraverso il Generic Block Layer, il quale inizia una serie di operazioni di I/O che trasferiscono i dati richiesti. In generale sono necessarie più operazioni di I/O perchè il file potrebbe essere memorizzato su blocchi non adiacenti. Ogni operazione di I/O è rappresentata dalla struttura bio che mantiene tutte le informazioni necessarie per i componenti di più basso livello (I/O Scheduler e Block Device Driver) per effettuare il trasferimento vero e proprio. 5. Il Generic Block Layer, creata la richiesta, la invia all I/O Scheduler che riordina le richieste di trasferimento dati pendenti in base alle politiche interne del kernel. Il compito principale dell I/O Scheduler è di raggruppare tutte le richieste di I/O che riguardano blocchi fisici vicini. 6. Infine, il block device driver riceve la richiesta schedulata dall I/O Scheduler e la serve, effettuando il trasferimento vero e proprio dei dati costruendo e inviando i comandi all interfaccia del controller del disco fisico. La seguente Figura 2.13 mostra i vari strati del kernel che si sono percorsi durante una read su un file: Nei dispositivi a blocchi si distinguono due unità di memorizzazione principali: settori e blocchi. Un settore è un unità fissata dipendente dal dispositivo fisico e specifica la più piccola quantità di dati (byte) che può essere trasferita da e verso il dispositivo stesso. Nella maggior parte dei dischi la dimensione utilizzata per un settore è di 512 byte, anche se ci sono dispositivi che utilizzano dimensioni di settore pari a 1024 o 2048 byte. In Linux la dimensione utilizzata per un settore è convenzionalmente fissata a 512 byte; se il dispositivo utilizza una dimensione diversa, il kernel ha il compito di effettuare le giuste conversioni. Detto questo, in Linux, un insieme di dati memorizzati su un disco è identificato dalla sua posizione espressa come indice del primo settore e dal numero di settori che compongono 52

67 Figura 2.13: Componenti del kernel Linux coinvolti durante un operazione su disco l insieme; l indice relativo ad un settore è memorizzato in variabili di 32 o 64 bit di tipo sector_t. Un blocco, invece è l unità base utilizzata nei trasferimenti dati per il filesystem e il VFS. Un blocco corrisponde ad uno o più settori adiacenti sul disco fisico e quindi la sua dimensione è multiplo intero di quella del settore. Ogni blocco ha associato un proprio buffer di memoria RAM (block buffer) che permette al kernel di memorizzare il contenuto del blocco stesso sia per operazioni di lettura, dove il buffer è riempito con i dati letti dal disco fisico, che per operazioni di scrittura dove il valore contenuto nel buffer associato al blocco verrà scritto nei settori fisici. In realtà il kernel, e in particolare un driver per dispositivi a blocchi, deve considerare anche un altra unità di memorizzazione: il segmento. Abbiamo detto che ogni operazione di I/O su disco consiste essenzialmente nel trasferimento di dati da 53

68 settori fisici adiacenti su disco in aree di memoria RAM e viceversa. Nella maggior parte dei casi questo trasferimento dati avviene direttamente dal controller del disco verso la memoria, e viceversa, attraverso operazioni DMA (Direct Memory Access) (vedi sez. 2.5) che non coinvolgono la CPU; appena il controller del disco ha terminato il trasferimento, solleverà un interrupt che sarà intercettato dal driver che lo gestirà in modo opportuno. Questo trasferimento DMA dovrebbe essere effettuato su settori adiacenti del disco per non inficiare sulle prestazioni stesse del disco a causa del movimento della testina di lettura/scrittura sui piatti (vedi sez.3.1). I vecchi controller del disco supportavano operazioni DMA semplici, cioè operazioni in cui i dati da trasferire si dovevano trovare in posizioni contigue di memoria; nei recenti controller, invece, si supportano operazioni DMA dette di scatter-gather nelle quali i dati trasferiti possono appartenere anche a celle di memoria non contigue. Per ogni operazione DMA scatter-gather il driver del dispositivo deve inviare al controller due informazioni: la prima è quella dell indice del primo settore su disco e il numero totale di settori da trasferire; la seconda è una lista che descrive le aree di memoria coinvolte nel trasferimento, ognuna delle quali è identificata da un indirizzo e dalla lunghezza. Tutto questo richiede al driver di conoscere questa ulteriore unità di memorizzazione chiamata segmento: un segmento, quindi non è nient altro che una pagina di memoria o una parte di essa la quale contiene dati di settori adiacenti su disco. (spiegare physical segment e hardware segment e il mapping fatto dall IOMMU). Infine, a livello più alto, il kernel gestisce le pagine di memoria che sono composte da un insieme di segmenti. In Figura 2.14 è mostrato il layout di una pagina di 4096 byte che esplicita tutto quello appena detto. Quindi, possiamo concludere che il kernel, a seconda dello strato in cui si trova, vede una lunghezza dei dati su disco diversa, infatti: Il controller del disco ha a che fare con i settori del disco, quindi l I/O Scheduler e i block device driver devono gestire settori di dati; Il VFS, il mapping layer e il filesystem vedono i dati organizzati a blocchi di dati; 54

69 Figura 2.14: Layout tipico di una pagina che racchiude dati su disco I block device driver, intervenendo in trasferimenti di tipo DMA, hanno a che fare con i segmenti di dati; il Page Cache lavora sulle pagine (page) riferite ai dati su disco, ognuna delle quali riferisce ad un page frame; Il Generic Block Layer, quindi è il componente fondamentale nella gestione dei dischi, facendo da collante tra gli strati superiori ed inferiori del kernel Il Generic Block Layer Nel paragrafo precedente abbiamo accennato alle funzionalità che offre il Block Layer, questo strato fondamentale del kernel di Linux. Esso gestisce tutte le richieste provenienti dagli strati superiori, ovvero dal VFS e dal Mapping Layer, per poi creare delle richieste che andranno ad effettuare operazioni su disco. Come abbiamo detto, i dispositivi a blocchi sono molto diversi da quelli a caratteri, soprattutto nella gestione delle letture e scritture. Il kernel utilizza un supporto conosciuto come request queue management che gestisce il fatto che letture e scritture di dati 55

70 sono buffered e riordinate per ottimizzare il throughput generale. A prova di ciò, il block layer fornisce funzionalità di accodamento delle richieste: ad ogni dispositivo a blocchi è associata una request queue. La Figura 2.15 mostra una panoramica sul block device layer. Figura 2.15: Block Device Layer Le cose importanti da notare sono: Esiste una coda di attesa che gestisce sia le richieste di lettura che di scrittura da e verso dispositivo; Ci sono puntatori a funzione per l I/O scheduler che riordina le richieste inviate dal Block Layer; E disponibile l astrazione gendisk che astrae un generico hard disk e include sia i dati per il partizionamento del disco stesso, che puntatori alle operazioni di più basso livello; Andiamo adesso nel dettaglio delle strutture utilizzate nel block layer che permettono di gestire un dispositivo a blocchi. 56

71 La struttura di un richiesta di I/O Il Block Layer essenzialmente utilizza tre strutture principali per la gestione delle richieste di I/O da inviare al dispositivo: la prima è request_queue che rappresenta una coda di richieste di tipo request (la seconda struttura utilizzata); ogni request avrà al suo interno la descrizione dei segmenti di memoria coinvolti nell operazione di I/O: questa descrizione è fatta tramite la terza struttura utilizzata che è chiamata bio. Nei paragrafi successivi si andrà nel dettaglio di ognuna di queste strutture. La struct request_queue Abbiamo detto che per i dispositivi a blocchi, le richieste di scrittura e lettura sono inserite in una coda gestita dal dispositivo stesso. Questa coda è rappresenta dalla struttura dati request_queue; vediamola nel dettaglio: <blkdev. h> struct request_queue { / Together with queue_head f o r c a c h e l i n e s h a r i n g / struct list_head queue_head ; struct list_head last_merge ; elevator_t elevator ; struct request_list rq ; / Queue r e q u e s t f r e e l i s t s / request_fn_proc request_fn ; make_request_fn make_request_fn ; prep_rq_fn prep_rq_fn ; unplug_fn unplug_fn ; merge_bvec_fn merge_bvec_fn ; prepare_flush_fn prepare_flush_fn ; softirq_done_fn softirq_done_fn ;... / Auto unplugging s t a t e / struct timer_list unplug_timer ; i n t unplug_thresh ; / A f t e r t h i s many r e q u e s t s / unsigned long unplug_delay ; / A f t e r t h i s many j i f f i e s / struct work_struct unplug_work ; struct backing_dev_info backing_dev_info ;... / queue needs bounce pages f o r pages above t h i s l i m i t / 57

72 } ; unsigned long bounce_pfn ; i n t bounce_gfp ; unsigned long queue_flags ; / queue s e t t i n g s / unsigned long nr_requests ; / Max # o f r e q u e s t s / unsigned i n t nr_congestion_on ; unsigned i n t nr_congestion_off ; unsigned i n t nr_batching ; unsigned s h o r t max_sectors ; unsigned s h o r t max_hw_sectors ; unsigned s h o r t max_phys_segments ; unsigned s h o r t max_hw_segments ; unsigned s h o r t hardsect_size ; unsigned i n t max_segment_size ; L elemento queue_head è la testa della lista principale utilizzata in questa struttura, ed è usato per costruire una lista doppiamente concatenata di richieste, i cui elementi sono di tipo request (vedi par ), ognuno dei quali rappresenta la richiesta di lettura o scrittura inviata al dispositivo a blocchi. Il kernel riordina successivamente questa lista in modo da ottenere prestazioni di I/O migliori. Il modo con cui le richieste sono riorganizzate dipende dal valore contenuto nell elemento elevator. L elemento rq, di tipo request_list, è utilizzato come cache per le richieste. Dopo quest ultimo elemento, incontriamo nella struttura un insieme di puntatori a funzioni che rappresentano il cuore della gestione delle richieste. Il tipo di ritorno di ognuna di esse è definito tramite un typedef: <blkdev. h> typedef void ( request_fn_proc ) ( struct request_queue q ) ; typedef i n t ( make_request_fn ) ( struct request_queue q, struct bio bio ) ; typedef i n t ( prep_rq_fn ) ( struct request_queue, struct request ) ; typedef void ( unplug_fn ) ( struct request_queue ) ; typedef i n t ( merge_bvec_fn ) ( struct request_queue, struct bio, struct bio_vec ) ; typedef void ( prepare_flush_fn ) ( struct request_queue, struct request ) ; typedef void ( softirq_done_fn ) ( struct request ) ; Le prime 4 funzioni sono responsabili della gestione delle richieste, e in particolare: request_fn è l interfaccia standard per aggiungere nuove richieste alla coda del- 58

73 le richieste. Questa funzione è automaticamente richiamata dal kernel quando il driver deve eseguire delle scritture o letture sul dispositivo. In gergo questa funzione è chiamata anche strategy routine (vedi par ); make_request_fn è la funzione che crea nuove richieste. Nell implementazione standard del kernel questa funzione aggiunge semplicemente la richiesta alla lista delle richieste e quando ce ne sono abbastanza, sarà richiamata la request_fn specifica del driver; prep_rq_fn è la funzione che prepara una richiesta. In generale questa funzione non è implementata, ma se lo è, genera dei comandi hardware necessari per la preparazione della richiesta prima che la reale richiesta sia inviata al dispositivo. Per i dispositivi a blocchi esiste la funzione di utilità blk_queue_prep_rq che setta l elemento prep_rq_fn nella coda data; unplug_fn è la funzione utilizzata per fare l unplug del dispositivo a blocchi (la tecnica è spiegata in seguito). Le rimanenti tre funzioni sono un po più specifiche rispetto alla precedenti, infatti: merge_bvec_fn è la funzione che permette di aumentare la dimensione della richiesta con più dati. Infatti molti driver hanno la necessità di variare i limiti della dimensione della coda delle richieste e quindi devono implemetare questa funzione. In particolare il kernel fornisce la funzione blk_queue_merge_bvec per impostare merge_bvec_fn per una determinata coda di richieste; prepare_flush_fn è utilizzata quando si deve effettuare il flush della coda delle richieste; nel contesto di questa funzione, si possono effettuare tutte le operazioni di pulizia necessarie; softirq_done_fn è utilizzato come funzione di callback per notificare al driver che una determinata richiesta è stata completata; il completamento asincrono di una richiesta può essere effettuato tramite la chiamata a blk_complete_request. Vediamo i restanti campi: 59

74 queue_flags è utilizzato per controllare lo stato interno della coda; nr_request indica il massimo numero di richieste che possono essere associate ad una singola coda; Gli ultimi campi della struttura rispecchiano impostazioni del particolare dispositivo hardware preso in considerazione: max_sectors specifica il massimo numero di settori che un dispositivo può elaborare in una singola richiesta. La dimensione del settore è utilizzata come unità; max_hw_sectors è simile a max_sectors ma tiene conto di un vincolo hardware; max_phys_segments specifica il massimo numero di segmenti non adiacenti per richieste di scatter-gather usate per trasportare dati non contigui; max_hw_segments è simile a max_phys_segments ma tiene conto di qualsiasi rimappaggio che è stato fatto dall IOMMU; hardsect_size specifica la dimensione fisica del settore con il quale il dispositivo deve operare. Questo valore, come già detto, è quasi sempre 512; max_segment_size è la massima dimensione del segmento (espresso in byte) per una singola richiesta; La struct request La struttura request descrive una singola richiesta, come abbiamo visto nel paragrafo precedente. Andiamo adesso nei dettagli della struttura: <blkdev. h> struct request { struct list_head queuelist ; struct list_head donelist ; struct request_queue q ; unsigned i n t cmd_flags ; enum rq_cmd_type_bits cmd_type ;... sector_t sector ; / next s e c t o r to submit / 60

75 sector_t hard_sector ; / next s e c t o r to complete / unsigned long nr_sectors ; / no. o f s e c t o r s l e f t to submit / unsigned long hard_nr_sectors ; / no. o f s e c t o r s l e f t to complete / / no. o f s e c t o r s l e f t to submit i n the c u r r e n t segment / unsigned i n t current_nr_sectors ; / no. o f s e c t o r s l e f t to complete i n the c u r r e n t segment / unsigned i n t hard_cur_sectors ; } ; struct bio bio ; struct bio biotail ;... void elevator_private ; void elevator_private2 ; struct gendisk rq_disk ; unsigned long start_time ; unsigned s h o r t nr_phys_segments ; unsigned s h o r t nr_hw_segments ;... unsigned i n t cmd_len ;... Vediamo i campi più importanti: queuelist è utilizzato per puntare all elemento request, ancora da completare, in una request_queue; q è il puntatore alla coda di richieste request_queue; donelist è utilizzato per ountare alla lista di richieste completate; sector specifica il primo settore da cui parte il trasferimento; current_nr_sectors specifica il numero di settori da trasferire; nr_sectors specifica il numero di settori ancora da trasferire; hard_sector, hard_cur_sectors, e hard_nr_sectors hanno lo stesso significato dei tre campi precedenti ma sono riferiti al dispositivo reale e non ad un dispositivo virtuale. Di solito questi 6 valori appena descritti hanno lo stesso 61

76 valore a meno che non si sta utilizzando qualche forma di RAID o dei volumi di tipo logico (LVM); nr_phys_segments e nr_hw_segments specificano, durante le operazioni di scatter-gather, il numero di segmenti nella richiesta e il numero di segmenti utilizzati dopo un remapping possibile effettuato dall IOMMU; elevator_private e elevator_private2 sono utilizzati per gestire informazioni private all I/O Scheduler, spesso chiamato elevator; bio punta all oggetto bio (vedi par ) corrente il cui trasferimento deve essere ancora completato; biotail punta all ultimo bio nella lista di richieste; cmd_type identifica il tipo di richiesta che si sta inviando; alcuni tipi di richiesta possibili sono definiti nella seguente enumerazione: enum rq_cmd_type_bits { REQ_TYPE_FS = 1, / f s r e q u e s t / REQ_TYPE_BLOCK_PC, / s c s i command / REQ_TYPE_SENSE, / s e n s e r e q u e s t / REQ_TYPE_PM_SUSPEND, / suspend r e q u e s t / REQ_TYPE_PM_RESUME, / resume r e q u e s t / REQ_TYPE_PM_SHUTDOWN, / shutdown r e q u e s t / REQ_TYPE_FLUSH, / f l u s h r e q u e s t / REQ_TYPE_SPECIAL, / d r i v e r d e f i n e d type / REQ_TYPE_LINUX_BLOCK, / g e n e r i c block l a y e r message /... } ; cmd_flags specifica dei flag per il tipo di richiesta utilizzato; alcuni flag sono specificati nella seguente enumerazione: enum rq_flag_bits { REQ_RW, / not set, read. set, w r i t e / REQ_FAILFAST, / no low l e v e l d r i v e r r e t r i e s / REQ_SORTED, / e l e v a t o r knows about t h i s r e q u e s t / REQ_SOFTBARRIER, / may not be passed by i o s c h e d u l e r / 62

77 } ; REQ_HARDBARRIER, / may not be passed by d r i v e e i t h e r / REQ_FUA, / f o r c e d u n i t a c c e s s / REQ_NOMERGE, / dont touch t h i s f o r merging / REQ_STARTED, / d r i v e a l r e a d y may have s t a r t e d t h i s one / REQ_DONTPREP, / dont c a l l prep f o r t h i s one / REQ_QUEUED, / u s e s queueing / REQ_ELVPRIV, / e l e v a t o r p r i v a t e data attached / REQ_FAILED, / s e t i f the r e q u e s t f a i l e d / REQ_QUIET, / dont worry about e r r o r s / REQ_PREEMPT, / s e t f o r " ide_preempt " r e q u e s t s / REQ_ORDERED_COLOR, / i s b e f o r e or a f t e r b a r r i e r / REQ_RW_SYNC, / r e q u e s t i s sync (O_DIRECT) / REQ_ALLOCED, / r e q u e s t came from our a l l o c pool / REQ_RW_META, / metadata i o r e q u e s t / REQ_NR_BITS, / s t o p s here / La struttura bio La struttura bio descrive un operazione di I/O pendente su un dispositivo a blocchi focalizzandosi sulle informazioni che riguardano l area su disco coinvolta nell operazione di I/O. Le informazioni contenute in questa struttura saranno poi utilizzate dal device driver del dispositivo che effettuerà le operazioni richieste. Essenzialmente, la struttura bio contiene le seguenti informazioni: l indice del primo settore e il numero di settori utilizzati nel trasferimento; uno o più segmenti che descrivono le aree di memoria RAM utilizzate nel trasferimento; La struttura bio è definita in <linux/bio.h>. Vediamo i campi principali: struct bio { sector_t bi_sector ; struct bio bi_next ; / r e q u e s t queue l i n k / struct block_device bi_bdev ;... unsigned s h o r t bi_vcnt ; / how many bio_ vecs / unsigned s h o r t bi_idx ; / c u r r e n t index i n t o bvl_vec / unsigned s h o r t bi_phys_segments ; unsigned s h o r t bi_hw_segments ; unsigned i n t bi_size ; / r e s i d u a l I /O count /... struct bio_vec bi_io_vec ; / the a c t u a l vec l i s t / 63

78 } ; bio_end_io_t bi_end_io ; void bi_private ;... bi_sector ci dà informazione sullo specifico settore da cui parte il trasferimento dati; bi_next collega tra loro, in una lista concatenata, più bio associati ad una richiesta; bi_bdev è il puntatore all istanza block_device (vedi sez ) che descrive il dispositivo a cui è inviata la richiesta; bi_phys_segments e bi_hw_segments specificano il numero di segmenti coinvolti in un trasferimento prima e dopo il remapping effettuato dall IOMMU; bi_size indicates the total size of the request in bytes. bi_io_vec è il puntatore al primo elemento dell array di oggetti di tipo bio_vec (vedere la struttura più avanti); bi_vcnt specifica il numero di elementi nel bi_io_vec; bi_idx indica l elemento corrente che si sta processando; bi_private è utilizzato per memorizzare informazioni specifiche del dispositivo; bi_destructor è il puntatore alla funzione che rimuove l oggetto bio dalla memoria; bi_end_io è il puntatore alla funzione che deve essere invocata dal driver quando il trasferimento dati è terminato. Al termine della funzion il block layer è in grado di risvegliare i processi che sono in attesa per altre richieste; La struttura bio_vec, citata precedentemente, è utilizzata per descrivere ogni segmento di memoria. La struttura è mostrata di seguito: 64

79 struct bio_vec { struct page bv_page ; unsigned i n t bv_len ; unsigned i n t bv_offset ; } ; Descriviamo i campi: bv_page è il puntatore al descrittore di una pagina che nel kernel è descritta dalla struttura page; bv_len è la lunghezza del segmento espresso in byte; bv_offset è l offset dei dati all interno del segmento; La struttura bio e bio_vec sono collegate tra loro come mostrato nella Figura Figura 2.16: Relazione tra le strutture bio e bio_vec Quindi, grazie a queste due strutture, bio e bio_vec, il kernel è in grado di tenere traccia di tutta l operazione di I/O, dalla sua richiesta al suo completamento. Per creare una nuova operazione di I/O, il block layer alloca una nuova struttura bio con la chiamata a funzione bio_alloc La struct block_device La struttura principale che descrive un dispositivo a blocchi è la struttura block_device, già citata nei paragrafi precedenti. Una parte è riportata di seguito: 65

80 <fs. h> struct block_device { dev_t bd_dev ; struct inode bd_inode ; i n t bd_openers ;... struct list_head bd_inodes ; void bd_holder ;... struct block_device bd_contains ; unsigned bd_block_size ; struct hd_struct bd_part ; unsigned bd_part_count ; i n t bd_invalidated ; struct gendisk bd_disk ; struct list_head bd_list ;... unsigned long bd_private ; } ; Descriviamo i campi più importanti: bd_dev contiene il device number; bd_inode è un puntatore all inode che rappresenta il dispositivo a blocchi nel pseudo-filesystem bdev; bd_openers contiene il numero delle aperture del dispositivo a blocchi fatte con la do_open; bd_inodes è la testa della lista di tutti gli inode che rappresentano i speciali device file per il dispositivo a blocchi; questa è da non confondere con l inode di bdev, che rappresenta il dispositivo; bd_holder è il puntatore all holder (vedi dopo) del descrittore del dispositivo a blocchi; bd_contains è il puntatore a questo block_device se il disco non contiene partizioni; se block_device rappresenta una partizione, punterà all istanza di block_device che rappresenta l intero disco; 66

81 bd_part punta ad una struttura speciale, hd_struct (vedi sez ), che rappresenta la partizione contenuta nel dispositivo a blocchi; bd_part_count conta quante partizioni del dispositivo sono riferite nel kernel; bd_invalidated è impostato ad 1 se le informazioni della partizione contenute nel kernel non sono valide; bd_disk punta alla struttura gendisk (vedi sez ) che fornisce un ulteriore livello di astrazione utilizzato anche per il partizionamente del disco; bd_list è l elemento lista che consente di tener traccia di tutte le istanze di block_device disponibili nel sistema; bd_private può essere usato per memorizzare alcune informazioni specifiche da chi mantiene l istanza del block_device; Chi mantiene (holder) l istanza di block_device può essere, per esempio, il filesystem Ext3, dove il superblock è registrato come holder, oppure, se la partizione è uno spazio di swap, il codice di swapping è registrato come holder La struct gendisk e hd_struct Mentre la struttura block_device (vedi sez ) rappresenta un dispositivo a blocchi a livello di device driver, un altra astrazione è utilizzata per la connessione con le generiche strutture del kernel. Quando un hard disk viene aggiunto nel sistema, il kernel andrà a leggere e analizzare le informazioni di partizionamento del dispositivo ma non andrà a creare le istanze di block_device per le singole partizioni perchè sono indipendenti dalla struttura che le rappresenta. Per questo motivo il kernel utilizza una nuova struttura, gendisk per fornire una rappresentazione di un generico hard disk partizionato. Vediamo una parte di questa struttura: <genhd. h> struct gendisk { i n t major ; / major number o f d r i v e r / 67

82 } ; i n t first_minor ; i n t minors ; / maximum number o f minors, =1 f o r d i s k s that cant be p a r t i t i o n e d. / char disk_name [ 3 2 ] ; / name o f major d r i v e r / struct hd_struct part ; / [ indexed by minor ] / i n t part_uevent_suppress ; struct block_device_operations fops ; struct request_queue queue ; void private_data ; sector_t capacity ; i n t flags ; struct device driverfs_dev ; struct kobject kobj ;... Descriviamo i campi: major specifica il major number del dispositivo; first_minor e minor specificano il range con il quale i minor number sono allocati (ogni partizione ha il proprio minor number); disk_name è il nome del disco, utilizzato per rappresentarlo in sysfs e in /proc/partitions; part è un array di puntatori di tipo hd_struct (vedi dopo); esiste una entry per ogni partizione sul disco; part_uevent_suppress se impostato ad un valore positivo nessun evento di hotplug viene inviato allo spazio utente se si verificano cambiamenti di partizioni oppure è stato rilevato un disco; fops è il puntatore alle funzioni specifiche del dispositivo le quali implementano varie funzionalità di basso livello; queue è necessario per gestire la coda delle richieste; private_data è un puntatore ai dati privati del driver, e non è modificabile dalle funzioni generiche del block layer; 68

83 capacity specifica la capacità del disco in settori; driverfs_dev identifica il dispositivo hardware al quale il disco appartiene; kobj è il kobject embededd; Come abbiamo accennato prima, la struttura che gestisce le partizioni è hd_struct; vediamo i campi fondamentali: <genhd. h> struct hd_struct { sector_t start_sect ; sector_t nr_sects ; struct kobject kobj ;... } ; start_sect definisce il settore iniziale; nr_sects definisce la dimensione della partizione sul dispositivo; kobj è il kobject associato alla partizione; Da notare è che anche se la struttura gendisk rappresenta un disco partizionato non esclude il fatto che possa rappresentare anche un disco senza nessuna partizione. Inoltre, la struttura gendisk può solo essere allocata da una funzione ausiliaria alloc_disk e non direttamente nel driver. La firma della funzione è la seguente: <genhd. h> struct gendisk alloc_disk ( i n t minors ) ; Dato il numero di minor che ha il dispositivo, la chiamata a questa funzione alloca soltanto un istanza della struttura gendisk con lo spazio necessario ai puntatori di oggetti di tipo hd_struct, che sono le partizioni del disco. Le istanze saranno poi allocate solo quando la reale partizione del disco sarà rilevata e aggiunta al dispositivo con la funzione add_partition. Istanze di gendisk saranno distrutte tramite la funzione del_gendisk. 69

84 Relazione tra block_device, gendisk e hd_struct Le strutture introdotte fino ad ora sono in relazione tra loro, come mostra la Figura Figura 2.17: Collegamento tra le strutture nel block layer Per ogni partizione del dispositivo a blocchi che è già stata aperta, esiste un istanza della struttura block_device. Gli oggetti che rappresentano la partizione sono collegati all oggetto che rappresenta l intero disco tramite l elemento bd_contains. Tutte le istanze di block_device, tramite il puntatore bd_disk, sono collegate alla generica struttura che rappresenta il disco: gendisk. Per rappresentare un disco è necessaria solo un istanza di gendisk. Si noti poi come l istanza di gendisk, tramite il puntatore part, punti all array di puntatori verso le istanze di hd_struct, che come abbiamo già detto rappresentano le partizioni del disco. Se l istanza di block_device rappresenta una partizione, allora avrà l elemento bd_part che punterà all oggetto istanza di hd_struct. La struttura gendisk ha al suo interno un kobject e quindi abbiamo un ulteriore rappresentazione del disco a livello di device model (Figura 2.18): Dalla Figura 2.18 si evince che il sottosistema block è rappresentato da un kset istanza di block_subsystem. Il kset contiene una lista concatenata nella quale i 70

85 Figura 2.18: Il disco visto nel device model kobject di ogni gendisk sono collegati tra loro. Si può notare anche come le partizioni rappresentate dalla struttura hd_struct hanno al loro interno un kobject il cui elemento parent punta al kobject che rappresenta il generico disco gendisk Operazioni sui dispositivi a blocchi Abbiamo già detto che i dispositivi a blocchi utilizzano una particolare struttura oltre alla struttura file_operations che è block_device_operations. Vediamo una parte della struttura: <fs. h> struct block_device_operations { i n t ( open ) ( struct inode, struct file ) ; i n t ( release ) ( struct inode, struct file ) ; i n t ( ioctl ) ( struct inode, struct file, unsigned, unsigned long ) ;... i n t ( media_changed ) ( struct gendisk ) ; i n t ( revalidate_disk ) ( struct gendisk ) ;... struct module owner ; } ; Le funzioni open, release, e ioctl hanno lo stesso significato di quelle contenute nella struttura file_operations, ovvero permettono di aprire e chiudere i file, e mandare comandi speciali al dispositivo a blocchi. Come abbiamo già detto, queste funzioni non sono invocate direttamente dal VFS ma indirettamente dalle operazioni contenute nella struttura file_operations per quanto riguarda il dispositivo a blocco, definite nella struttura def_blk_fops (vedi sez ). 71

86 Gli elementi rimanenti di block_device_operations sono: media_changed è il puntatore alla funzione che controlla se il dispositivo rimovibile è stato cambiato; revalidate_disk è il puntatore alla funzione che controlla se il dispositivo a blocchi mantiene dati validi; Registrazione e inizializzazione del device driver In questo paragrafo andremo a vedere nel dettaglio tutte le operazioni necessarie per la registrazione del driver di un disco e la registrazione del dispositivo nel kernel. E da precisare che il controller di un dispositivo a blocchi è connesso di solito ad un architettura a bus standard come PCI o SCSI, e tali architetture mettono a disposizione funzioni di utilità per la registrazione del dispositivo e del relativo driver. Vediamo di seguito i passi necessari che il driver deve effettuare, omettendo di citare le funzioni di inzializzazione nel device model. 1. Il driver prima di tutto deve creare il proprio dispositivo, e inserire all interno della struttura un riferimento alla struttura gendisk. Un esempio è il seguente: struct my_dev_t {... spinlock_t lock ; // per l a mutua e s c l u s i o n e struct gendisk disk ;... } my_dev ; L elemento lock è necessario per proteggere i campi della struttura dagli accessi concorrenti. Invece, l elemento disk è il puntatore alla struttura gendisk, che come abbiamo visto nei paragrafi precedenti rappresenta l intero disco che verrà gestito dal driver. 2. La seconda cosa da fare è riservare il major number per il dispositivo che il driver sta gestendo. Di solito questo è effettuato con la funzione register_blkdev 72

87 che aggiungerà un nuovo elemento nella lista dei major number registrati nel file speciale /proc/devices. Il codice è il seguente: rc = register_blkdev ( MY_MAJOR, " my_dev" ) ; i f ( rc ) goto error_major_number_busy ; 3. Una volta assegnato il major number al dispositivo, il driver deve allocare la struttura gendisk utilizzando la funzione alloc_disk a cui viene passato il numero di minor number: questo numero identifica il numero di partizioni, ovvero il numero di elementi di tipo hd_struct, che il disco conterrà. Da precisare è che la partizione numero 0 non sarà utilizzata. Un esempio di codice è il seguente: spin_lock_init(&my_dev. lock ) ; my_dev. disk = alloc_disk ( 1 6 ) ; i f (! my_dev. disk ) goto error_no_gendisk_available ; 4. Dopo aver allocato la struttura che descrive il disco, il driver ha bisogno di inizializzare alcuni campi della struttura gendisk come mostrato di seguito: my_dev. disk >private_data = &my_dev ; my_dev. disk >major = MY_MAJOR ; my_dev. disk >first_minor = 0 ; my_dev. disk >minors = 1 6 ; set_capacity ( my_dev. disk, CAPACITY_OF_MY_DISK ) ; strcpy ( foo. disk >disk_name, " my_disk " ) ; my_dev. disk >fops = &my_dev_ops ; In particolare notiamo che l elemento private_data è inizializzato con l indirizzo della mia struttura. Inoltre, la chiamata alla funzione set_capacity imposta la capacità totale del disco che sto gestendo. Altra cosa importante è settare il riferimento alle operazioni che il dispositivo implementa, ovvero l insieme delle operazioni specifiche che il dispositivo espone. 73

88 5. A questo punto il driver dovrà creare per ogni gendisk la struttura di code di richieste che conterrà le richieste in attesa di essere servite dal disco. Un esempio del codice relativo è il seguente: my_dev. disk >queue = blk_init_queue ( my_strategy_routine, &my_dev. lock ) ; i f (! my_dev. disk >queue ) goto error_request_queue_not_available ; blk_queue_hardsect_size ( my_dev. disk >queue, my_dev_hard_sector_size ) ; blk_queue_max_sectors ( my_dev. disk >queue, my_dev_max_sectors ) ; blk_queue_max_hw_segments ( my_dev. disk >queue, my_dev_max_hw_segments ) ; blk_queue_max_phys_segments ( my_dev. disk >queue, my_dev_max_phys_segments ) ; Il kernel mette a disposizione la funzione standard blk_init_queue per allocare una coda di richieste associata a gendisk e inizializzare molti dei suoi campi con i valori di default forniti dal kernel (in realtà verrà richiamata a sua volta la funzione blk_init_queue_node); in questo caso il driver deve soltanto implementare la funzione di request_fn, perchè le rimanenti funzionalità di gestione sono implementate da funzioni standard. La funzione my_strategy_routine è la strategy routine che è invocata attraverso la funzione request_fn associata alla struttura request_queue (vedi par ) 6. Il driver prima di registrare il disco nel kernel, deve registrare una linea di interrupt (IRQ) per il dispositivo e la relativa funzione per gestire l interrupt; se la funzione che gestisce l interrupt è my_dev_interrupt, allora verrà effettuata la seguente chiamata a funzione: request_irq ( MY_IRQ, my_dev_interrupt, SA_INTERRUPT SA_SHIRQ, " my_dev", NULL ) ; per i dettagli vedere cap. 13[BC05]; 7. Dopo avere allocato la coda delle richieste e impostato il gestore dell interrupt, il driver finalmente può registrare e attivare il disco nel kernel. Questo è effettuato attraverso la chiamata a add_disk ( my_dev. disk ) ; 74

89 La funzione add_disk essenzialmente invoca la funzione kobj_map, già accennata in 2.9, che stabilisce il collegamento tra il driver e il major number con i relativi minor number associati al dispositivo, utilizzando il database bdev_map; inoltre registra il kobject associato alla struttura gendisk e i kobject associati alla partizioni che compongono il disco e il kobject associato alla coda di richieste. La strategy routine La strategy routine è una funzione, o un insieme di funzioni, che viene utilizzata dal driver del dispositivo a blocchi per servire le richieste accodate nella coda di richieste da servire. Come abbiamo già accennato, la strategy routine è invocata attraverso la funzione request_fn specificata nella struttura request_queue (vedi par ) il cui riferimento è passato alla strategy routine attraverso lo strato I/O Scheduler. Di solito la strategy routine viene eseguita appena viene inserita una nuova richiesta all interno di un coda di richieste vuota. La maggior parte dei device driver implementano la strategy routine seguendo questo ragionamento: La strategy routine inizia il trasferimento dati per la prima richiesta della coda e inizializza il controller del dispositivo in modo tale che possa sollevare un interrupt appena finisce il trasferimento dei dati, poi la strategy routine termina; Quando il controller del disco solleva un interrupt, il gestore dell interrupt invoca di nuovo la strategy routine che farà iniziare un altro traferimento dati per la richiesta corrente, oppure se tutti i dati associati alla richiesta sono stati traferiti, rimuove dalla coda delle richiesta la richiesta corrente e inizia a processare la prossima richiesta nella coda. Per migliorare le prestazioni, sappiamo che i dati sono trasferiti utilizzando operazioni DMA. Un driver può inizializzare più trasferimenti DMA per ogni segmento contenuto nella struttura bio (vedi par ), oppure effettua un unico trasferimento scatter-gather per trasferire tutti i segmenti che compongono un bio. Tutto 75

90 questo dipende soprattutto dal dispositivo a blocchi in particolare e quindi dalle caratterestiche del controller; quindi la scrittura della strategy routine dipende da come il controller del dispositivo gestisce il trasferimento dei blocchi fisici in memoria e viceversa Apertura di un dispositivo a blocchi Supponiamo che un processo effettui una open su un device file che rappresenta un dispositivo a blocchi. Il VFS, nel momento in cui è aperto un device file, cambia il file operations di default, in modo tale che ogni system call invocata sul dispositivo viene tradotta in un invocazione alle funzioni relative al dispositivo stesso. Essenzialmente la open risolve il pathname del device file e inizializza gli oggetti inode, dentry e file. L oggetto inode è inizializzato leggendo il corrispondente inode sul disco attraverso le funzioni specifiche al tipo di filesystem in uso; per esempio se il filesystem è ext2 o ext3, si utilizzeranno rispettivamente le funzioni ext2_read_inode e ext3_read_inode, che in realtà sono sostituite nelle versioni più recenti del kernel da ext2_iget e ext3_iget. Quando la funzione si accorge che l inode è relativo ad un device file essa invoca la funzione init_special_inode che: inizializza il campo i_rdev dell oggetto inode con il major e minor number del device file; inizializza il campo i_fop dell oggetto inode all indirizzo o del file operations di default di un dispositivo a caratteri (def_chr_fops) oppure del file operations di default di un dispositivo a blocchi (def_blk_fops), in accordo al tipo di dispositivo in esame; Di seguito è mostrata la funzione: void init_special_inode ( struct inode inode, umode_t mode, dev_t rdev ) { inode >i_mode = mode ; i f ( S_ISCHR ( mode ) ) { inode >i_fop = &def_chr_fops ; inode >i_rdev = rdev ; } e l s e i f ( S_ISBLK ( mode ) ) { 76

91 } inode >i_fop = &def_blk_fops ; inode >i_rdev = rdev ; } e l s e printk ( KERN_DEBUG " i n i t _ s p e c i a l _ i n o d e : bogus i_mode (%o ) \n ", mode ) ; Si può notare come, in base al tipo di dispositivo (S_ISCHR e S_ISBLK sono macro per testare il tipo del dispositivo), sono inizializzati i campi dell oggetto inode in maniera corretta, assegnando i default file_operations (vedi par ) specifici per la tipologia di dispositivo. La system call open, inoltre, invoca la funzione dentry_open che alloca un nuovo oggetto file, inizializzando il campo f_op all indirizzo memorizzato nel campo i_fop dell oggetto inode, ovvero a def_chr_fops oppure def_blk_fops. Quindi grazie alle due tabelle def_chr_fops e def_blk_fops, ogni system call invocata sul device file attiverà la relativa funzione scritta nel driver del dispositivo anziché la funzione specifica del filesystem sottostante. Quando il kernel riceve un richiesta di apertura di un device file associato ad un dispositivo a blocchi, deve prima di tutto controllare se il device file è già aperto: se tale device file è già aperto non deve creare ed inizializzare un nuovo descrittore del dispositivo, ma solo aggiornarlo. La relazione che c è tra il major e il minor number e il corrispondente dispositivo a blocchi è mantenuta tramite uno pseudo file system chiamato bdev, che non è visibile allo spazio utente; ad ogni descrittore del dispositivo sarà associato un file speciale in bdev. Dopo l invocazione init_special_inode, la system call open invocherà la funzione dentry_open che a sua volta invocherà la funzione blkdev_open che è la funzione responsabile dell apertura del dispositivo a blocchi. Tale funzione prende in ingresso il riferimento all oggetto inode e il riferimento all oggetto file ed esegue essenzialmente la funzione bd_acquire per determinare l indirizzo bdev dell oggetto block_device associato al dispositivo a blocchi. Per fare questo la funzione riceve in ingresso il riferimento all oggetto inode e controlla il campo inode->i_bdev e: 77

92 se è non nullo significa che il dispositivo è già stato aperto e quindi si aggiorna il campo che gestisce il conteggio di utilizzatori della struttura e restituisce inode->i_bdev; se è nullo, invece, significa che il dispositivo ancora deve essere aperto per la prima volta, quindi si invoca la funzione bdget, che riceve in ingresso il major e il minor number del dispositivo, e cercherà, attraverso lo pseudo-filesystem bdev, se il corrispondente inode esiste, ritornando il puntatore ad esso; se l inode non esiste, allora la funzione allocherà un nuovo inode e un nuovo descrittore del dispositivo a blocchi e ritornerà sempre il puntatore all inode appena creato. Tutto questo è realizzato grazie alla struttura bdev_inode: <fs/ block_dev. c> struct bdev_inode { struct block_device bdev ; struct inode vfs_inode ; } ; Dopo che la funzione bd_acquire ha restituito l indirizzo dell oggetto block_device, la funzione blkdev_open invocherà la funzione blkdev_get che invocando la funzione get_gendisk otterrà l indirizzo della struttura gendisk che rappresenta il dispositivo a blocchi su cui si potrà operare. Una volta che la funzione blkdev_open è terminata, la system call open procederà come al solito. Da questo momento in poi ogni system call effettuata sul file aperto che rappresenta il dispositivo, invocherà una della funzioni che sono nel file_operations di default del dispositivo Sottomettere una richiesta al dispositivo a blocchi Il kernel sottomette una richiesta al dispositivo in 2 passi: 1. Crea un istanza di bio che descrive la richiesta e la incorpora in un oggetto request accodato in una request_queue; 2. Elabora le richieste accodate eseguendo le azioni necessarie descritte nel bio; Per creare un istanza di bio è utilizzata la fuzione bio_alloc alla quale si passerà il numero di segmenti che compongono un bio in modo tale che la struttura bio_vec, 78

93 che rappresenta ogni segmento, sia allocata in maniera corretta. Una volta che l oggetto bio è stato allocato si invocherà la funzione submit_bio che creerà una nuova richiesta con il bio appena creato, e inserirà tale richiesta nella coda delle richieste del driver invocando la funzione generic_make_request che a sua volta invocherà la funzione riferita dal campo make_request_fn (vedi par ). Quindi la funzione submit_bio farà essenzialmente l inoltro delle richieste, utilizzando la funzione funzione generic_make_request; in particolare, quest ultima: 1. invocherà la funzione generic_make_request_checks che effettuarà dei sanity check, per esempio se la richiesta eccede i limiti fisici del dispositivo, e inoltre: (a) invoca la funzione bdev_get_queue per determinare il riferimento alla coda delle richieste (request_queue) associata al dispositivo data la richiesta; (b) se il dispositivo è partizionato, utilizza la funzione blk_partition_remap per assicurare che le operazioni di lettura/scrittura vengano fatte su un area del disco corretta: infatti se una partizione p parte dal settore start(p) e la richiesta di I/O deve essere fatta sul settore n nella partizione allora bisogna effettuare il remap del blocco del disco a start(p) + n. 2. invocherà la funzione riferita da queue->make_request_fn che generalmente sarà la funzione blk_queue_bio; tale funzione creerà una request, in riferimento al bio allocato (tralasciando tutto quello che accade nel I/O Scheduler che ottimizzerà la coda delle richieste), che arriverà al driver per essere servita; Le code di richieste sono gestite dal kernel con un meccanismo chiamato Queue Plugging, meccanismo che permette di riorganizzare le richieste in coda in modo da ottimizzare le prestazioni. Una coda di richiesta può essere in due stati: plugged quando il sistema è sovraccarico e quindi le nuove richieste che arriveranno non saranno processate fino a che la coda non passa nello stato di unplugged, ovvero quando tutte le richieste nella coda possono essere servite. Adesso si capisce l utilità dell elemento unplug_fn (vedi par ): sarà utilizzato per puntare alla funzione 79

94 che implementa l unplug di una coda tramite un meccanismo di timeout. Quindi una volta che la richiesta è stata creata e messa nella coda delle richieste, se tale coda è nello stato di unplugged sarà subito processata, altrimenti, se è nello stato di plugged sarà aggiunta alla coda senza essere processata; successivamente sarà effettuato prima o poi l unplugged della coda dal kernel in modo da invocare successivamente la funzione riferita dal puntatore q->request_fn (vedi par ): tale funzione sarà necessaria per servire la richiesta; per esempio, se consideriamo un disco SCSI, tale funzione è scsi_request_fn che è la strategy_routine utilizzata in un driver di un disco SCSI. 2.4 Comunicazione con il BUS Abbiamo visto nel Paragrafo 2.2 come il kernel gestisce un generico bus utilizzando la struttura bus_type. In questo paragrafo andremo a descrivere nel dettaglio l utilizzo delle strutture e delle funzioni che permettono al kernel di interfacciarsi con un bus di tipo PCI che oggi è il tipo di bus più largamente utilizzato. Per avere dettagli sull implementazione hardware consultare lo standard (mettere riferimento) L architettura PCI L architettura PCI (Peripheral Component Interface) è una delle tante architetture che permette la comunicazione tra i dispositivi che compongono un personal computer e la CPU, ed ha principalmente 3 punti di forza: 1. Supporta elevate larghezze di banda per il trasferimento di dati tra i dispositivi e il sistema. Infatti è stata pensata per soppiantare l obsoleta architettura ISA (Industry Standard Architecture) aumentando la velocità del clock a 25 o 33 MHz e nelle recenti architetture anche a 66 o 133 MHz; 2. Semplifica l aggiunta o la rimozione di periferiche al sistema; 3. E indipendente dalla piattaforma utilizzata, quindi indipendente dall architettura CPU utilizzata; 80

95 4. Lo standard PCI è stato recentemente esteso per aumentare ancora le velocità di trasmissioni su bus (PCIe); Un dispositivo connesso ad un bus PCI è identificato da 3 numeri: Bus Number (8 bit): è il numero del bus a cui il dispositivo è stato assegnato. Lo standard PCI permette fino a 256 bus per sistema; Device Number (5 bit): è l identificatore univoco del dispositivo connesso al bus identificato dal bus number; dispositivi su diversi bus possono avere device number uguali. Si possono avere fino a 32 dispositivi assegnati ad un singolo bus; Function Number (3 bit): è utilizzato per gestire dispositivi che implementano più dispositivi al loro interno; l esempio classico è quello dei southbridge (e.g. Intel ICH8), che gestisce vari dispositivi come il controller IDE, USB controller, modem, scheda di rete ecc; La maggior parte dei PC hanno almeno 2 bus PCI che sono collegati tra loro attraverso altri dispositivi PCI chiamati bridge. In generale l intera struttura di un sistema a bus PCI è fatta ad albero, dove ogni bus è connesso a quello del livello superiore fino ad arrivare alla radice dell albero che è il bus numero 0 (Figura 2.19). Un dispositivo PCI in genere ha 3 spazi di memoria indirizzabili per la comunicazione con il sistema: Spazio di Input/Output che utilizza indirizzi a 32 bit, fornendo quindi un massimo di 4GB di porti di I/O utilizzabili per comunicare con i dispositivi; Spazio di Memoria (Dati) che a seconda del processore può utilizzare indirizzi a 32 o 64 bit; Spazio di Configurazione (Configuration Space) in cui sono contenute informazioni di configurazione particolari del dispositivo; Il Configuration Space è composto da 256 byte ma soltanto i primi 64 sono standardizzati e tipicamente i rimanenti byte sono utilizzati per uno scambio addizionale 81

96 Figura 2.19: Esempio di layout tipico di un sistema PCI di dati tra dispositivo e driver; la struttura di queste informazioni è definita nella documentazione hardware del dispositivo. Il layout dei primi 64 byte dello spazio di configurazione è il seguente (Figura 2.20). Figura 2.20: Configuration Space PCI I registri vendorid, deviceid e class code sono sempre utilizzati. Ogni produttore di dispositivi PCI assegna dei valori precisi a questi registri di sola lettura, e il driver a sua volta può usarli per cercare il dispositivo nel sistema. Descriviamo brevemente questi 3 registri: 82

97 vendorid: è un registro di 16 bit e identifica il costruttore del device (per esempio per i dispositivi Intel il vendorid è 0x8086); deviceid: è un registro di 16 bit sempre impostato dal costruttore, ed insieme al vendorid crea la cosiddetta firma del dispositivo usata da un device driver per identificare i propri device; class code: è un registro di 24 bit che individua la classe di un dispositivo. I primi 8 bit identificano la base class (o gruppo) a cui appartiene il dispositivo (per esempio sono base class network, communication ecc), i rimanenti 16 bit identificano la subclass, ovvero il particolare dispositivo appartenente a quel gruppo. Esempi di base class e subclass sono i seguenti: Mass storage (PCI_BASE_CLASS_STORAGE) SCSI controller (PCI_CLASS_STORAGE_SCSI ) IDE controller (PCI_CLASS_STORAGE_IDE) RAID controller (PCI_CLASS_STORAGE_RAID) Network (PCI_BASE_CLASS_NETWORK) Ethernet (PCI_BASE_NETWORK_ETHERNET) FDDI (PCI_BASE_NETWORK_FDDI ) System components (PCI_BASE_CLASS_SYSTEM ) DMA controller (PCI_CLASS_SYSTEM_DMA) Real-time clock (PCI_CLASS_SYSTEM_RTC ) Opzionali sono invece i registri subsystem vendorid e subsystem deviceid che sono utilizzati per differenziare ancora di più i dispositivi tra di loro. I 6 registri Base Address Register (BAR0...BAR5 ), ognuno a 32 bit, definiscono gli indirizzi per la comunicazione tra il dispositivo PCI e il resto del sistema: 83

98 il driver, oppure il sistema operativo stesso, programmerà questi registri tramite comandi di configurazione al controller PCI per informare il dispositivo su quali indirizzi è mappato. Un altro registro importante è IRQ Line utilizzato per specificare la la linea (numero) di interrupt che usa il dispositivo. Sono possibili fino a 255 linee di interrupt (se IRQ Line vale zero significa che il dispositivo non utilizza le interrupt). In realtà esistono dei meccanismi in modo tale che una stessa linea di interrupt può essere condivisa da più dispositivi e quindi il numero di linee di interrupt utilizzate è molto minore di 255. In generale, utilizzando quest insieme di registri, un driver PCI può dire al kernel che genere di dispositivo supporta. In Figura 2.21 è mostrato il Configuration Space associato ad un controller AHCI di un disco SATA. Figura 2.21: Esempio di Configuration Space di un controller AHCI Device Driver PCI Il kernel Linux mette a disposizione molte strutture dati che permettono di gestire tutta l infrastruttura PCI e sono tutte definite in <linux/pci.h>. Le 3 strutture principali, che insieme costituiscono il cosiddetto PCI layer, sono: struct pci_bus: i vari bus PCI nel sistema sono rappresentati da un istanza di questa struttura; struct pci_dev: questa struttura descrive un particolare dispositivo PCI; struct pci_driver: ogni driver PCI è descritto da questa struttura; Inoltre il kernel utilizza una lista globale, pci_root_buses, che tiene traccia di tutti i bus PCI del sistema. 84

99 La struttura pci_bus Ogni bus PCI è rappresentato nel kernel da un istanza della struttura pci_bus, definita come segue: <pci. h> #define PCI_BUS_NUM_RESOURCES 8 struct pci_bus { struct list_head node ; / node i n l i s t o f buses / struct pci_bus parent ; / parent bus t h i s b r i d g e i s on / struct list_head children ; / l i s t o f c h i l d buses / struct list_head devices ; / l i s t o f d e v i c e s on t h i s bus / struct pci_dev self ; / b r i d g e d e v i c e as seen by parent / struct resource resource [ PCI_BUS_NUM_RESOURCES ] ; / a d d r e s s space routed to t h i s bus / struct pci_ops ops ; / c o n f i g u r a t i o n a c c e s s f u n c t i o n s / void sysdata ; / hook f o r sys s p e c i f i c e x t e n s i o n / struct proc_dir_entry procdir ; / d i r e c t o r y entry i n / proc / bus / p c i / unsigned char number ; / bus number / unsigned char primary ; / number o f primary b r i d g e / unsigned char secondary ; / number o f secondary b r i d g e / unsigned char subordinate ; / max number o f s u b o r d i n a t e buses / char name [ 4 8 ] ;... } ; Descriviamo i campi: node è l elemento nella lista pci_root_buses; parent è il puntatore al bus genitore, che rappresenta il bus di più alto livello; ci può essere solo un bus genitore; children è utilizzato per gestire la lista dei figli di un bus genitore; devices è l elemento utilizzato per gestire la lista di tutti i dispositivi attaccati al bus; self rappresenta il dispositivo bridge che deve essere necessariamente utilizzato da tutti i bus di sistema (ad eccezione del bus numero 0) per essere indirizzati; resource è un array utilizzato per mantenere informazioni sulle aree di memoria occupate dal bus nello spazio di memoria virtuale; 85

100 ops è un puntatore ad un insieme di funzioni utilizzate per accedere allo spazio di configurazione; sysdata è l elemento che associa la struttura del bus alle funzioni specifiche del driver (usato raramente); procdir fornisce un interfaccia per il filesystem proc; number identifica univocamente il bus nel sistema; subordinate specifica il massimo numero di bus subordinati che il bus stesso può avere; name è una stringa che specifica il nome del bus (per esempio PCI Bus 0000:01 ); può anche essere vuoto; La struttura pci_dev La struttura pci_dev è utilizzata per descrive un dispositivo PCI nel kernel. Tale struttura è definita come segue: <pci. h> struct pci_dev { struct list_head global_list ; / node i n l i s t o f a l l PCI d e v i c e s / struct list_head bus_list ; / node i n per bus l i s t / struct pci_bus bus ; / bus t h i s d e v i c e i s on / struct pci_bus subordinate ; / bus t h i s d e v i c e b r i d g e s to / void sysdata ; / hook f o r sys s p e c i f i c e x t e n s i o n / struct proc_dir_entry procent ; / d e v i c e entry i n / proc / bus / p c i / unsigned i n t devfn ; / encoded d e v i c e & f u n c t i o n index / unsigned s h o r t vendor ; unsigned s h o r t device ; unsigned s h o r t subsystem_vendor ; unsigned s h o r t subsystem_device ; unsigned i n t c l a s s ; / 3 bytes : ( base, sub, prog i f ) / u8 revision ; / PCI r e v i s i o n, low byte o f c l a s s word / u8 hdr_type ; / PCI header type ( multi f l a g masked out ) / u8 pcie_type ; / PCI E d e v i c e / port type / u8 rom_base_reg ; / which c o n f i g r e g i s t e r c o n t r o l s the ROM / u8 pin ; / which i n t e r r u p t pin t h i s d e v i c e u s e s / struct pci_driver driver ; / which d r i v e r has a l l o c a t e d t h i s d e v i c e /... 86

101 struct device dev ; / Generic d e v i c e i n t e r f a c e / / d e v i c e i s compatible with t h e s e IDs / unsigned s h o r t vendor_compatible [ DEVICE_COUNT_COMPATIBLE ] ; unsigned s h o r t device_compatible [ DEVICE_COUNT_COMPATIBLE ] ; i n t cfg_size ; / S i z e o f c o n f i g u r a t i o n space / } ; / I n s t e a d o f touching i n t e r r u p t l i n e and base a d d r e s s r e g i s t e r s d i r e c t l y, use the v a l u e s s t o r e d here. They might be d i f f e r e n t! / unsigned i n t irq ; struct resource resource [ DEVICE_COUNT_RESOURCE ] ; / I /O and memory r e g i o n s + expansion ROMs /... Questo è solo una porzione della struttura e possiamo notare i riferimenti al bus a cui è connesso il dispositivo (l elemento bus) e al driver che ha allocato questo device (l elemento driver). Notiamo anche l irq number (elemento irq) utilizzato dal dispositivo per gestire le interrupt e l elemento resource che è l array che mantiene gli oggetti di tipo resource riservati dal driver per le operazioni di I/O. Inoltre possiamo notare tutti gli elementi da devfn a rom_base_reg che contegono i valori dei registri del configuration space descritto nel Paragrafo La struttura pci_driver La terza struttura, pci_driver, è utilizzata per implementare e descrivere un driver PCI. Praticamente rappresenta l interfaccia tra il kernel e il driver che controlla il dispositivo. Quindi ogni driver PCI deve specificare le sue funzionalità in modo tale che il kernel possa controllare e gestire correttamente tutti i driver disponibili. Una parte della struttura è specificata di seguito: <pci. h> struct pci_driver {... char name ; c o n s t struct pci_device_id id_table ; i n t ( probe ) ( struct pci_dev dev, c o n s t struct pci_device_id id ) ; 87

102 } ; void ( remove ) ( struct pci_dev dev ) ;... struct device_driver driver ;... Descriviamo gli elementi della struttura: name descrive semplicemente il nome del driver; di solito è il nome del modulo nel quale il driver è implementato; driver è l elemento che collega il driver in questione con l astrazione generica di un driver che mette a disposizione il device model (vedi sez ); id_table è un puntatore alla struttura pci_device_id (descritta in seguito) che è utilizzata per definire una lista di ID di dispositivi PCI che il driver supporta; probe è un puntatore alla funzione chiamata dal PCI core quando un nuovo dispositivo è inserito nel sistema; controlla se il dispositivo PCI è supportato dal driver in questione (questa funzionalità è chiamata probing); remove è un puntatore alla funzione che il PCI core chiama quando il dispositivo sta per essere rimosso dal sistema, o quando il driver PCI sta per essere rimosso dal sistema; ovviamente la rimozione di un dispositivo PCI ha senso solo se il sistema supporta il meccanismo di hotplugging; Registrazione di un driver PCI generico In questo paragrafo andremo nel dettaglio dell implementazione di un driver PCI per un dispositivo generico. Mostremo dapprima il codice sorgente di un semplice driver PCI e ne discuteremo poi le funzioni e le strutture incontrate. Il codice è il seguente: #include <linux / config. h> #include <linux / kernel. h> #include <linux / module. h> #include <linux / pci. h> 88

103 #include <linux / init. h> s t a t i c struct pci_device_id ids [ ] = { { PCI_DEVICE ( PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_3 ), }, { 0, } } ; MODULE_DEVICE_TABLE ( pci, ids ) ; s t a t i c unsigned char skel_get_revision ( struct pci_dev dev ) { u8 revision ; pci_read_config_byte ( dev, PCI_REVISION_ID, &revision ) ; r e t u r n revision ; } s t a t i c i n t probe ( struct pci_dev dev, c o n s t struct pci_device_id id ) { / Do probing type s t u f f here. Like c a l l i n g r e q u e s t _ r e g i o n ( ) ; / pci_enable_device ( dev ) ; i f ( skel_get_revision ( dev ) == 0 x42 ) r e t u r n ENODEV ; r e t u r n 0 ; } s t a t i c void remove ( struct pci_dev dev ) { / c l e a n up any a l l o c a t e d r e s o u r c e s and s t u f f here. l i k e c a l l r e l e a s e _ r e g i o n ( ) ; / } s t a t i c struct pci_driver pci_driver = {. name = " p c i _ s k e l ",. id_table = ids,. probe = probe,. remove = remove, } ; s t a t i c i n t init pci_skel_init ( void ) { r e t u r n pci_register_driver(&pci_driver ) ; } s t a t i c void exit pci_skel_exit ( void ) { pci_unregister_driver(& pci_driver ) ; } MODULE_LICENSE ( "GPL" ) ; module_init ( pci_skel_init ) ; module_exit ( pci_skel_exit ) ; 89

104 Nella filosofia di Linux un device driver è un modulo caricato all interno del kernel; quando si effettua il caricamento di questo modulo viene richiamata la funzione module_init che chiama a sua volta la funzione atta all inizializzazione del modulo. In questo semplice scheletro di driver, la funzione pci_skel_init inizializza il modulo registrando il driver PCI nel kernel: questo è fatto con la chiamata alla funzione pci_register_driver passandogli come parametro il riferimento all oggetto pci_driver; questa funzione è fondamentale poiché permette di registrare effettivamente il nostro driver nel kernel descrivendo quello che sa fare il driver stesso e quali dispositivi riuscirà a gestire. Possiamo notare nella struttura pci_driver i riferimenti al nome utilizzato dal driver, alle funzioni di probe e di remove e il riferimento alla struttura pci_device_id. La struttura pci_device_id, citata prima, è fondamentale per specificare quali dispositivi supporta il driver. Vediamola nel dettaglio: struct pci_device_id { u32 vendor ; u32 device ; u32 subvendor ; u32 subdevice ; u32 c l a s s ; u32 class_mask ; kernel_ulong_t driver_data ; } I primi 5 campi sono essenzialmente i registri di configurazione che abbiamo descritto in precedenza. Il campo class_mask come il campo class permette al driver di specificare la classe di dispositivi PCI a cui appartiene il dispositivo stesso. Il campo driver_data serve al driver per mantenere informazioni da utilizzare. Ci sono due funzioni di utilità per inizializzare la struttura pci_device_id: PCI_DEVICE (vendorid, deviceid) che crea una struttura pci_device_id con i campi vendor e device specificati nei parametri di ingresso; i campi subvendor e subdevice sono impostati a PCI_ANY_ID; PCI_DEVICE_CLASS (device_class, device_class_mask) che crea una struttura pci_device_id con la PCI class specificata; 90

105 Nell esempio di driver mostrato all inizio, ids è un array di oggetti di tipo pci_device_id, dove ogni elemento specifica un particolare dispositivo che il driver riesce a gestire. Una volta definito e registrato il driver, nella funzione di probe, prima che il driver PCI possa accedere alle risorse (regioni di I/O oppure gestione delle interrupt) del dispositivo PCI, bisogna effettuare la chiamata alla funzione pci_enable_device che attiva realmente il dispositivo, ed è qui che si assegnano le linee di interrupt e le regioni di I/O. Dopo che il driver ha rilevato il dispositivo da guidare, è necessario accedere allo spazio di configurazione perchè è l unico modo per sapere dove il dispositivo PCI è stato mappato in memoria e nello spazio di I/O. Questo è consentito grazie ad un insieme di funzioni che permettono di leggere e scrivere nei registri di configurazione del dispositivo PCI: i n t pci_read_config_byte ( struct pci_dev dev, i n t where, u8 val ) ; i n t pci_read_config_word ( struct pci_dev dev, i n t where, u16 val ) ; i n t pci_read_config_dword ( struct pci_dev dev, i n t where, u32 val ) ; i n t pci_write_config_byte ( struct pci_dev dev, i n t where, u8 val ) ; i n t pci_write_config_word ( struct pci_dev dev, i n t where, u16 val ) ; i n t pci_write_config_dword ( struct pci_dev dev, i n t where, u32 val ) ; In entrambi i casi il parametro where indica l offset all interno dello spazio di configurazione. Considerando il nostro driver di esempio, dopo aver attivato il driver, nella funzione probe si richiama la funzione skel_get_revision che effettua una semplice lettura dal registro revisionid dello spazio di configurazione per poi salvarne il contenuto. Quando il driver PCI deve essere rimosso dal sistema, la struttura pci_driver deve essere de-registrata dal kernel. Questo è effettuato al module_exit che invoca la funzione pci_unregister_driver: tale funzione rimuove tutte le strutture dati relative ai dispositivi PCI collegati al driver e prima che termini viene invocata la funzione remove per effettuare tutte le operazioni di pulizia. Per concludere, in Figura 2.22 è mostrato il flusso di chiamate all interno del device driver appena mostrato. 91

106 Figura 2.22: Flusso di chiamate all interno del generico driver PCI Accesso allo spazio di memoria e allo spazio di I/O Un dispositivo PCI, come abbiamo visto dalla definizione dello spazio di configurazione (vedi sez ), mette a disposizione fino a 6 regioni di indirizzi di I/O. Ognuna di queste regioni è composta da locazioni di memoria oppure locazioni di I/O. I dispositivi PCI che implementano i propri registri di I/O come aree di memoria etichettano queste aree come nonprefetchable: questo significa che la CPU non effettuerà nessuna operazione di caching e nessuna operazione di ottimizzazione su queste aree per evitare effetti collaterali. Un dispositivo PCI esporta la dimensione e la posizione delle proprie regioni di I/O (memory-mapped oppure port-mapped) tramite i registri di configurazione BAR i cui nomi simbolici sono PCI_BASE_ADDRESS_0 fino a PCI_BASE_ADDRESS_5. Esistono delle funzioni del kernel per accedere alle informazioni riguardo le regioni di I/O di un 92

107 dispositivo PCI e sono: unsigned long pci_resource_start (struct pci_dev *dev, int bar): questa funzione ritorna il primo indirizzo (indirizzo di memoria oppure numero di porto I/O) associato ad una delle sei regioni di I/O di un dispositivo PCI. La regione è selezionata dal paramentro in ingresso bar (il base address register) che può contenere un valore da 0 a 5. unsigned long pci_resource_end (struct pci_dev *dev, int bar): questa funzione ritorna l ultimo indirizzo relativo alla regione di I/O specificata in bar; l indirizzo sarà quindi l ultimo utilizzabile; unsigned long pci_resource_flags (struc pci_dev *dev, int bar): questa funzione ritorna semplicemente i flag associati alla regione di I/O specificata in bar. I flag sono definiti in <linux/ioport.h>, e i più importanti sono: IORESOURCE_IO e IORESOURCE_MEM specificano che la regione è di I/O oppure memoria; IORESOURCE_PREFETCH e IORESOURCE_READONLY specificano che l area di memoria è prefetchable oppure protetta da scrittura; Accesso ad una regione di I/O port-mapped Per operare su una regione di I/O port-mapped, come per esempio i registri di controllo del device, il driver dovrà effettuare nell ordine: 1. Prendere I/O base address dal giusto base address register (bar) nel configuration space: unsigned long io_base = pci_resource_start ( pdev, bar ) ; Si assume che i registri di controllo sono mappati nella regione di memoria associata alla variabile bar. 2. Marcare l indirizzo di memoria io_base con la chiamata a funzione request_region (meccanismo obbligatorio da usare): 93

108 request_region ( io_base, length, " my_driver " ) ; length è la dimensione dello spazio del registro di controllo e my_driver identifica il proprietario di questa regione. Si può usare anche la funzione wrapper pci_request_region, definita in <drivers/pci/pci.c>. 3. Addizionare all indirizzo di base di I/O (io_base) l offset relativo alle specifiche hardware del dispositivo; tale informazione si può ottenere dal datasheet del dispositivo. Con l indirizzo ottenuto si può operare utilizzando le funzioni inb and outb accennate nel Paragrafo 2.1.1: / Read / register_data = inb ( io_base + REGISTER_OFFSET ) ; / Use / /... / / Write / outb ( register_data, iobase + REGISTER_OFFSET ) ; Accesso ad una regione di I/O memory-mapped Per operare invece in una regione di memoria, come ad esempio il frame buffer di una scheda video PCI, si effettuano i seguenti passi: 1. Si prendono il base address, la lunghezza (length) e i flag associati alla regione di memoria: unsigned long mmio_base = pci_resource_start ( pdev, bar ) ; unsigned long mmio_length = pci_resource_length ( pdev, bar ) ; unsigned long mmio_flags = pci_resource_flags ( pdev, bar ) Si assume che questa regione di memoria sia mappata utilizzando il valore del base address register scritto in bar. 2. Si setta poi il proprietario della regione di memoria appena inizializzata con la funzione offerta dal kernel request_mem_region: request_mem_region ( mmio_base, mmio_length, " my_driver " ) ; 94

109 Si potrebbe anche usare la function wrapper pci_request_region, citata in precedenza. 3. Come detto in precedenza le regioni di memoria, come quelle associate a dei registri del dispositivo, devono essere preservate da effetti collaterali, e quindi devono essere marcate come non-prefetchable (oppure non-cacheable) dalla CPU. Invece altre regioni possono essere cacheable. In base quindi a questa differenziazione si utilizzano i flag di accesso appropriati e le chiamate a funzione appropriate: void iomem buffer ; i f ( flags & IORESOURCE_CACHEABLE ) { buffer = ioremap ( mmio_base, mmio_length ) ; } e l s e { buffer = ioremap_nocache ( mmio_base, mmio_length ) ; } Per essere sicuri, ed evitare i controlli precedenti si può usare la funzione pci_iomap definita in <lib/pci_iomap.c>: buffer = pci_iomap ( pdev, bar, mmio_length ) ; 2.5 Direct Access Memory (DMA) Abbiamo accennato nei paragrafi precedenti come le operazioni di I/O sui dispositivi a blocchi sfruttano pesantemente operazioni di DMA. In questo paragrafo daremo dei cenni sulla gestione della memoria in Linux e successivamente andremo nel dettaglio dell implementazione dell infrastruttura DMA utilizzata nei driver Cenni sulla gestione della memoria in Linux Linux è un sistema operativo che ha un sistema di memoria virtuale: questo significa che gli indirizzi visti dai programmi utenti non corrispondono direttamente agli indirizzi fisici utilizzati dall hardware sottostante. Questa virtualizzazione consente 95

110 ai programmi utente di allocare più memoria di quanto ne è disponibile fisicamente; infatti un singolo processo può avere un spazio di indirizzi virtuale più grande dello spazio di memoria fisico. Ovviamente è possibile effettuare il mapping tra la memoria virtuale e la memoria di un dispositivo. In Linux abbiamo a che fare con varie tipologie di indirizzi diversi: Indirizzi virtuali utente: questi sono gli indirizzi visti dallo spazio utente del programma. Ogni processo ha il proprio spazio di indirizzi virtuali; Indirizzi fisici: questi sono gli indirizzi utilizzati dalla CPU e dalla memoria di sistema (RAM); Indirizzi di Bus: sono indirizzi utilizzati tra i bus periferici e la memoria centrale. Spesso questa tipologia di indirizzi è la stessa di quella degli indirizzi fisici usati dal processore. In alcune architetture viene fornita un unità, la IOMMU (Input/Output Memory Management Unit), che rimappa gli indirizzi tra il bus e la memoria principale. L IOMMU è fondamentale quando, per esempio, vogliamo che un buffer, che è sparpagliato (in gergo scattered) in memoria, viene visto dal dispositivo come contiguo; Indirizzi logici kernel: questi indirizzi costituiscono il normale spazio di indirizzi del kernel; questi indirizzi mappano una porzione (se non tutta) della memoria principale e spesso sono trattati come se fossero degli indirizzi fisici; infatti in molte architetture, l unica differenza che c è tra indirizzi logici kernel e indirizzi fisici è un offset costante. L area di memoria restituita da una chiamata a kmalloc, per esempio, è costituita da indirizzi logici kernel; Indirizzi virtuali kernel: sono indirizzi simili a quelli logici kernel, nel senso che sono indirizzi mappati dal kernel-space ad indirizzi fisici; il fatto è che diversamente dagli indirizzi logici kernel, gli indirizzi virtuali kernel non hanno una corrispondenza uno a uno verso gli indirizzi fisici. Tutti gli indirizzi logici kernel sono indirizzi virtuali kernel, ma non è vero il viceversa. Per esempio, la memoria allocata con la chiamata a vmalloc restituisce un area di memoria 96

111 costrituita da indirizzi virtuali kernel che non sono mappati direttamente in indirizzi fisici; La Figura 2.23 mostra le tipologie di indirizzi in Linux: Figura 2.23: Tipi di indirizzi utilizzati in Linux Sappiamo che la memoria fisica è divisa in unità chiamate pagine. La dimensione di queste pagine è fissata, e nei sistemi attuali è 4096 byte. Se consideriamo un indirizzo, fisico o virtuale, esso può essere diviso in una parte chiamata page number e una parte che è di offset nella pagina, come mostrato nella Figura Nei sistemi che hanno pagine di 4096 byte, utilizzando un indirizzo a 32 bit, i 12 bit meno significativi sono utilizzati per l offset e il resto dei bit più significativi sono utilizzati per il page number. Nelle architetture x86 e nelle configurazioni di default del kernel, lo spazio di indirizzi virtuali di 4GB è diviso in due: lo user-space e il kernel-space. Una divisione utilizzata nella maggioranza dei casi è quella in cui si assegna allo user-space 3GB e al kernel-space 1GB. Nel kernel-space è mappato tutto il codice del kernel e le strutture dati utilizzate; la maggior parte dello spazio però è utilizzato dal mapping 97

112 Figura 2.24: Esempio di pagina di memoria tra gli indirizzi virtuali kernel e quelli fisici, perchè il kernel non può manipolare indirizzi che non sono mappati nel suo spazio. Quindi in Linux c è una distinzione tra high memory (user-space) e low memory (kernel-space) e, per quello che abbiamo detto precedentemente, nella low memory è riservato uno spazio per accedere ad indirizzi nella high memory. Storicamente, il kernel utilizza gli indirizzi logici kernel che sono riferiti a pagine della memoria fisica e, siccome questi indirizzi logici kernel non sono disposibili per l high memory, le funzioni del kernel hanno a che fare con moltissimi puntatori a pagine in quest area di memoria. La struttura dati che rappresenta una pagina fisica è definita in <linux/mm_types.h> è si chiama page. Tale struttura è molto complessa da come si può intuire e di seguito è mostrata sola una parte con alcuni campi di interesse: struct page {... atomic_t count ;... void virtual ;... unsigned long flags ;... } 98

113 count è il numero di riferimenti che ci sono alla pagina; virtual è l indirizzo virtuale kernel della pagina, se è mappata; da specificare che le pagine nella low memory sono sempre mappate, ma quelle nella high memory no; flags è un insieme di bit flag che descrivono lo stato della pagina; Ovviamente il kernel mantiene uno o più array di pagine che tengono traccia di tutte le pagine fisiche nel sistema. Il meccanismo che traduce un indirizzo virtuale nella suo indirizzo fisico fà uso della cosiddetta page table, un albero multilivello di array che contiene il mapping tra pagine virtuali e fisiche e i relativi flag associati alle pagine Virtual Memory Area (VMA) Nel kernel la struttura che è utilizzata per gestire le diverse regioni dello spazio di indirizzi di un processo è chiamata Virtual Memorya Area (VMA). La mappa della memoria di un processo è fatta, generalmente come mostrato nella Figura 2.25: Possiamo notare le seguenti aree (escludendo il kernel-space): Un area TEXT dove risiede il codice eseguibile del programma; Due aree dati che includono le variabili non inizializzate (BSS) e le variabili inizializzate dal programmatore (DATA); Un area per ogni memory mapping attivo; Un area di HEAP e di STACK; Le aree di memoria di un processo possono essere viste in /proc/pid/maps dove pid indica il PID del processo. Per esempio possiamo vedere nella Figura 2.26 la mappa di memoria del processo /sbin/init. Ogni riga mostrata ha il seguente formato: start-end perm offset major:minor inode image 99

114 Figura 2.25: Mappa della memoria di un processo Ogni campo visto in precedenza (tranne che per image) corrisponde ad un campo nella struttura che rappresenta una VMA, ovvero la struttura vm_area_struct. I campi mostrati in precedenza hanno il seguente significato: start-end individuano l inizio e la fine dell area di memoria composta da indirizzi virtuali; perm indica i permessi, di lettura, scrittura o di esecuzione, che il processo ha su quest area di memoria; offset indica dove inizia la memoria per cui è stato mappato il file; major e minor indica il dispositivo che mantiene il file che è stato mappato; inode indica l oggetto inode del file che è stato mappato; 100

115 Figura 2.26: Mappa di memoria del processo /sbin/init image indica il nome del file che è stato mappato; Il kernel offre una funzione che permette di effettuare il mapping della memoria del dispositivo allo spazio di indirizzi nell user-space: tale funzione è mmap; quando il kernel invoca tale funzione crea una nuova VMA per rappresentare questo mapping. In generale, mappare un dispositivo significa associare un range di indirizzi dello user-space a quelli della memoria del dispositivo fisico, e quindi leggere o scrivere su questi indirizzi significa leggere o scrivere sul dispositivo. La funzione mmap è utilizzata per i dispositivi PCI, la maggior parte dei quali mappa i propri registri di controllo in indirizzi di memoria, e quindi un applicazione ad alte prestazioni, invece di accedere ai registri di controllo tramite la funzione ioctl (vedi sez ) utilizza semplici accessi in memoria. Il puntatore alla funzione mmap, specifica del dispositivo, è contenuto nella struttura file_operations (vedi sez ), e sarà richiamata quando la syscall mmap sarà invocata Utilizzo del DMA Agli inizi dello sviluppo di architetture PC, la CPU era l unico componente del sistema che guidava gli indirizzi e i dati sul bus per prelevare o memorizzare valori dalla memoria principale (RAM): in questo caso la CPU è detta bus master. Con lo sviluppo di nuove architetture di bus, come ad esempio PCI, ogni dispositivo periferico può agire da bus master se al suo interno c è una qualche circuiteria hardware che lo permetta. Il dispositivo hardware che permette il trasferimento 101

116 di dati tra la RAM e un dispositivo di I/O, senza coinvolgere la CPU, è chiamato DMA. Una volta che il DMA è stato attivato dal processore, è capace di trasferire i dati per conto suo, senza coinvolgere la CPU durante questo trasferimento; quando poi il trasferimento è completato, il DMA manderà al processore un interrupt per avvertirlo del completamento. Ovviamente CPU e DMA saranno in conflitto quando tenteranno di accedere alle medesime locazioni di memoria; per ovviare a questo problema esiste un altra circuiteria hardware, chiamata memory arbitrer, che fa da arbitro degli accessi. Un driver di un dispositivo può utilizzare il DMA in 2 modi differenti: in modo sincrono (DMA sincrono) e in modo asincrono (DMA asincrono). Il primo caso, DMA sincrono, si verifica quando un processo utente richiede un trasferimento dati verso il dispositivo. Un esempio è quando abbiamo una scheda audio che sta riproducendo un file audio: l applicazione utente scrive i dati che rappresentano il suono sul device file associato al dispositivo; il driver accumulerà questi dati nel buffer del kernel e nello stesso momento istruisce la scheda audio di copiare i dati dal buffer del kernel verso il digital signal processor che elaborerà queste informazioni in modo da riprodurre l audio; quando la scheda audio ha terminato il trasferimento, solleva un eccezione verso la CPU che sarà catturata dal driver il quale controllerà se ci sono altri dati nel buffer del kernel da riprodurre: se ci sono il driver attiva un nuovo trasferimento DMA. Per generalizzare, considerando un operazione di read, il trasferimento DMA sincrono avviene nei seguenti passi: 1. Il processo utente invoca una read; 2. Il driver relativo al dispositivo invoca la funzione relativa e alloca un buffer DMA istruendo il dispositivo fisico a trasferire i suoi dati in questo buffer; 3. Il processo si mette in attesa (sleep); 4. Il dispositivo hardware scrive i dati nel buffer DMA allocato dal driver; 5. Solleva un interrupt quando ha completato il trasferimento; 102

117 6. Il gestore delle interrupt effettua l ack dell interrupt verso il dispositivo e sveglia il processo utente che sarà in grado di leggere i dati; Il secondo caso, DMA asincrono, si verifica quando il dispositivo hardware, in maniera appunto asincrona, invia dati al sistema. Un esempio è quando abbiamo una scheda di rete che riceve un pacchetto dalla rete. Il dispositivo, prima di tutto, memorizza tale pacchetto nella sua area di memoria di I/O e successivamente solleva un interrupt alla CPU. Il driver della scheda di rete risponde a questo interrupt con un ack e istruisce il dispositivo di copiare questo pacchetto dalla sua memoria di I/O nel buffer del kernel; quando il trasferimento è completato, il dispositivo solleva un ulteriore interrupt e quindi il driver avvisa gli strati superiori del kernel che un nuovo pacchetto è arrivato. Come in precedenza, generalizziamo i passi che sono effettuati durante un trasferimento DMA asincrono: 1. Il dispositivo fisico solleva un interrupt per avvisare al sistema che ha dei dati da inviare; 2. Il gestore delle interruzioni del driver alloca un buffer DMA e istruisce il dispositivo fisico su dove deve inviare questi dati; 3. Il dispositivo fisico scrive i suoi dati nel buffer DMA è solleva un interrupt quando finito; 4. Il gestore delle interruzioni del driver, ancora una volta, intercetta l interrupt e sveglia eventuali processi che sono coinvolti in questo trasferimento; Gli indirizzi di bus Sappiamo che in generale, nelle architetture x86, ci sono tre tipi di indirizzi che sono utilizzati internamente al processore e sono: Indirizzi logici: sono indirizzi inclusi nelle istruzioni in linguaggio macchina e specificano in particolare l indirizzo di un operando o di un istruzione; ogni indirizzo logico è formato da una parte segment e una di offset; 103

118 Indirizzi lineari: sono indirizzi di 32 bit che possono essere utilizzati per indirizzare fino a 4GB di celle di memoria; il range di questi indirizzi va da 0x a 0xffffffff; Indirizzi fisici: sono gli indirizzi delle celle di memoria fisiche; corrispondono quindi ai segnali elettrici mandati sui pin della CPU che gestiscono gli indirizzi verso il bus di memoria; sono rappresentati con 32 bit o 36 bit; Il problema è che quando si ha a che fare con un trasferimento DMA, quest ultimo coinvolge uno o più buffer di memoria, e prima che il trasferimento venga attivato, il driver del dispositivo deve essere sicuro che la circuiteria DMA possa accedere direttamente alle locazione di memoria RAM. Abbiamo detto che durante questo trasferimento di dati la CPU non interviene, questo implica che il bus dati viene direttamente pilotato dal dispositivo di I/O di interesse e dal DMA stesso. Tutto questo significa che i dispositivi di I/O vedono i buffer DMA attraverso la lente del bus controller e dell IOMMU (se c è), e quindi lavorano con una quarta tipologia di indirizzi, gli indirizzi di bus, che corrisponde ad indirizzi di memoria utilizzati da tutti i dispositivi hardware del sistema (tranne la CPU) per guidare il bus dati. Detto questo, quando il kernel sta per effettuare un operazione DMA, si deve avere conoscenza dell indirizzo di bus del buffer DMA coinvolto. Le funzioni DMA messe a disposizione dal kernel permettono di mappare l indirizzo virtuale kernel di un buffer DMA nel suo indirizzo di bus, in modo tale che il buffer stesso possa essere acceduto sia dalla CPU che dal dispositivo fisico. Gli indirizzi di bus (anche detti DMA address), nel kernel Linux, sono descritti dal tipo dma_addr_t che non è nient altro che un unsigned a 32 o a 64 bit come definito in <linux/types.h>: #ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT typedef u64 dma_addr_t ; #e l s e typedef u32 dma_addr_t ; #endif / dma_addr_t / Nelle architetture x86, gli indirizzi di bus coincidono con gli indirizzi fisici, mentre in altre architetture questo può essere non vero. Per esempio nelle architetture 104

119 come SPARC (Sun) e Alpha (Hewlett-Packard) questo non avviene: esiste, infatti l IOMMU che quale mappa gli indirizzi fisici in indirizzi di bus. Tutti i driver che utilizzano il DMA devono inizializzare correttamente l IOMMU prima di iniziare il trasferimento dati Il DMA Layer In Linux lo strato che si preoccupa di allocare un buffer DMA e gestire una generica operazione DMA è il DMA Layer. Questo strato fornito dal kernel permette di essere indipendente sia dal tipo di bus (ISA, PCI, PCI-X, ecc.), sia dall architettura del processore utilizzato (x86, x64, SPARC, Alpha, ecc.). La prima cosa da capire è se il dispositivo fisico è in grado di effettuare operazioni DMA con il sistema. Di default, il kernel assume che un dispositivo possa eseguire operazioni DMA con indirizzi di bus a 32 bit; se non è questa la lunghezza degli indirizzi da utilizzare, allora bisogna impostarla tramite la chiamata a funzione dma_set_mask oppure pci_set_dma_mask; queste funzioni controllano se la dimensione dell indirizzo di bus (detta maschera), data come parametro in ingresso, è accettata dal bus: se tale dimensione è accettata viene notificato allo strato bus che il dispositivo utilizzerà questa dimensione degli indirizzi per le operazioni DMA. Coerenza della cache e DMA Mapping Il DMA Layer si preoccupa anche di un problema critico: la coerenza della cache della CPU. Infatti, l architettura di sistema non necessariamente offre un protocollo di coerenza tra la cache del processore e il DMA a livello hardware. Il controllo della coerenza della cache deve essere effettuato per evitare problemi di inconsistenza dei dati. Per esempio, supponiamo che un driver istruisca il dispositivo a leggere dei dati utilizzando un trasferimento DMA; se il DMA accede ad una particolare locazione della memoria fisica, ma il corrispondente valore nella cache hardware non è stato ancora scritto in RAM, il dispositivo fisico andrà a leggere un dato vecchio, violando la proprietà di consistenza dei dati. 105

120 Gli sviluppatori di driver possono gestire i buffer DMA in due modi diversi scegliendo, secondo il gergo Linux, due tipi di DMA Mapping diversi: Coherent DMA Mapping; Streaming DMA Mapping; Coherent DMA Mapping Quando si utilizza questo tipo mapping, il kernel assicura che non ci siano problemi di coerenza della cache tra la memoria e il dispositivo fisico; infatti, ogni operazione di scrittura della CPU sulla RAM è immediatamente visibile al dispositivo fisico e viceversa; per questo motivo, questo tipo di mapping è chiamato anche sincrono o consistente. Di solito, il driver del dispositivo alloca un buffer di memoria e stabilisce il mapping DMA coerente nella fase di inizializzazione stessa del driver; il mapping durerà fino a che il driver sarà rilasciato, portando ad un impiego di risorse elevato. A causa di ciò, l utilizzo di questo tipo di mapping è utile solo se sia la CPU che il dispositivo di I/O utilizzano frequentemente un buffer DMA: questo è il caso, per esempio, di una scheda video. Per stabilire il mapping DMA coerente il kernel fornisce le funzioni pci_alloc_consistent e dma_alloc_coherent. Per esempio, per ottenere un buffer DMA consistente possiamo utilizzare il seguente codice: void pci_alloc_consistent ( struct pci_dev pdev, size_t size, dma_addr_t dma_handle ) Questa funzione, come abbiamo già detto, alloca un buffer DMA, genera il suo indirizzo di bus (dma_handle) e restituisce l indirizzo virtuale kernel associato. I primi due parametri di ingresso, pdev e size, riguardano rispettivamente il dispositivo PCI e la dimensione del buffer DMA richiesto. Il terzo parametro, dma_handle, è l indirizzo di bus che la chiamata a questa funzione genererà. Per eliminare il mapping appena creato il kernel fornisce le funzioni pci_free_consistent e dma_free_coherent. Un esempio di allocazione e deallocazione di un buffer DMA coerente è mostrato nel codice seguente: 106

121 / A l l o c a t e / void vaddr = pci_alloc_consistent ( pdev, size, &dma_handle ) ; / Use / /... / / Free / pci_free_consistent ( pdev, size, vaddr, dma_handle ) ; Il mapping fatto con dma_alloc_coherent può gestire buffer DMA di dimensione minima di una pagina; se abbiamo bisogno di una dimensione più piccola dobbiamo utilizzare il concetto di DMA pool, non trattato in questa tesi (per approfondire vedi cap. 15 [CRKH05]). Streaming DMA Mapping A differenza di ciò che abbiamo visto nel mapping coerente, quando si utilizza Streaming DMA Mapping, il driver stesso deve preoccuparsi della coerenza della cache utilizzando opportune funzioni di sincronizzazione. Quindi, a dispetto del mapping coerente, questo di solito è chiamato anche mapping asincrono o non-coerente. I buffer di memoria sono di solito mappati soltanto per una singola operazione DMA, ovvero mappati prima del trasferimento DMA e de-mappati al completamento. Questo tipo di mapping è utile quando non ci sono accessi frequenti tra la CPU e i dispositivi di I/O, e quest ultimi possiedono il buffer per lunghi periodi di tempo. Il mapping di tipo streaming è utilizzato spesso quando si effettuano operazioni DMA asincrono e ogni operazione di DMA è fatta su buffer differenti. Un esempio classico è il driver di una scheda di rete, dove i buffer che mantengono i pacchetti trasmessi sono mappati e demappati al volo. Per inizializzare un trasferimento DMA, il driver deve prima di tutto allocare dinamicamente il buffer di memoria attraverso il page frame allocator oppure il generic memory allocator (approfondire su [BC05] cap.8). Si deve anche informare il kernel sulla direzione dei dati che si vuole utilizzare durante il trasferimento DMA; alcuni valori utilizzati sono: DMA_TO_DEVICE: impostato quando per esempio viene invocata una system call write per cui i dati vanno verso il dispositivo; 107

122 DMA_FROM_DEVICE: impostato quando i dati stanno andando verso la CPU; DMA_BIDIRECTIONAL: impostato quando i dati si muovono in tutte e due le direzioni; DMA_NONE: è un simbolo fornito solo per il debugging; utilizzare il buffer con questa direzione causa un kernel panic; Successivamente il driver stabilisce il mapping di tipo streaming invocando le funzioni messe a disposizione dal kernel: pci_map_single e dma_map_single. Andiamo a considerare la prima: dma_addr_t pci_map_single ( struct pci_dev pdev, void ptr, size_t size, i n t direction ) ; I primi tre argomenti della funzione, pdev, ptr e size, contengono informazioni rispettivamente sul dispositivo PCI, sull indirizzo virtuale kernel del buffer DMA, e sulla dimensione stessa del buffer DMA. Il quarto argomento esplicita la direzione del trasferimento DMA. La funzione restituisce l indirizzo di bus del buffer DMA allocato. Per eliminare il mapping creato si utilizzano le funzioni pci_unmap_single o dma_unmap_single. Per evitare i problemi di coerenza della cache, appena prima di iniziare il trasferimento DMA dalla RAM al dispositivo, il driver dovrebbe invocare le funzioni pci_dma_sync_single_for_device oppure dma_sync_single_for_device, le quali effettuano, se necessario, il flush dell elemento della cache hardware corrispondente all elemento nel buffer DMA. Inoltre il driver non dovrebbe accedere al buffer di memoria appena dopo la fine del trasferimento DMA dal dispositivo alla RAM: infatti, dovrebbe prima leggere il buffer e poi invocare le funzioni pci_dma_sync_single_for_cpu o dma_sync_single_for_cpu, le quali invalidano, se necessario, l elemento della cache hardware della CPU corrispondente all elemento del buffer di memoria appena letto. In realtà, nelle architetture x86, queste due 108

123 funzioni non fanno quasi niente perchè la coerenza tra la cache della CPU e il DMA è fatta in hardware. Una tipologia particolare di streaming mapping è lo Scatter/Gather Mapping. Questo tipo di mapping è utile quando i buffer coinvolti in un trasferimento DMA sono sparsi in regioni di memoria non contigue. Grazie a questo tipo di mapping, infatti, si accederà a questi buffer in un unica operazione DMA, ovvero tutti i buffer DMA saranno mappati come se fosse un unico grande buffer. Molti dispositivi possono accettare una cosiddetta scatterlist che rappresenta l insieme di buffer DMA da mappare. Le funzioni per effettuare il mapping di una lista scatter/gather di buffer DMA sono dma_map_sg e pci_map_sg. Consideriamo prima il prototipo di dma_map_sg: i n t dma_map_sg ( struct device dev, struct scatterlist sglist, i n t nents, enum dma_data_direction direction ) ; La scatterlist è specificata utilizzando il secondo argomento, sglist che è un puntatore alla struttura scatterlist (vedi dopo); l elemento nents specifica il numero di elementi scatterlist (di solito abbiamo un array di oggetti di tipo scatterlist); il parametro direction specifica la direzione (vedere prima) che ha il trasferimento DMA in questione; il parametro dev indica il dispositivo per cui il driver effettua operazioni DMA; tale dispositivo è descritto dalla struttura device (vedi sez ); la funzione restituisce il numero di elementi mappati. La funzione pci_map_sg è implementata in <asm-generic/pci-dma-compat.h>: s t a t i c inline i n t pci_map_sg ( struct pci_dev hwdev, struct scatterlist sg, i n t nents, i n t direction ) { r e t u r n dma_map_sg ( hwdev == NULL? NULL : &hwdev >dev, sg, nents, ( enum dma_data_direction ) direction ) ; } I parametri di ingresso hanno lo stesso significato della funzione precedente, e ovviamente il dispositivo è rappresentato da un istanza della struttura pci_dev; da come si può notare tale funzione è un wrapper di dma_map_sg. La struttura scatterlist, come abbiamo detto, descrive un buffer DMA. Tale struttura è definita in <asm-generic/scatterlist.h>: 109

124 struct scatterlist { #ifdef CONFIG_DEBUG_SG unsigned long sg_magic ; #endif unsigned long page_link ; unsigned i n t offset ; unsigned i n t length ; dma_addr_t dma_address ; #ifdef CONFIG_NEED_SG_DMA_LENGTH unsigned i n t dma_length ; #endif } ; Possiamo notare i seguenti campi: page_link elemento utilizzato per il riferimento all oggetto page che rappresenta una pagina fisica in memoria; offset è appunto l offset del buffer all interno della pagina; length è la lunghezza del buffer; dma_address è l indirizzo di bus relativo al buffer; Per gestire l insieme di elementi che compongono un array di scatterlist il kernel mette a disposizione due macro sempre definie in <asm-generic/scatterlist.h>: sg_dma_address e sg_dma_len. Vediamo le loro definizioni: #define sg_dma_address ( sg ) ( ( sg ) >dma_address ) #ifdef CONFIG_NEED_SG_DMA_LENGTH #define sg_dma_len ( sg ) ( ( sg ) >dma_length ) #e l s e #define sg_dma_len ( sg ) ( ( sg ) >length ) #endif Come possiamo vedere, la macro sg_dma_address restituisce l indirizzo di bus relativo alla prima entry nell array di scatterlist. Invece la macro sg_dma_len restituisce la lunghezza del buffer. Detto questo, per utilizzare correttamente questo tipo di mapping gli sviluppatori del kernel suggeriscono di lavorare solo sul numero di elementi di una scatterlist 110

125 restituito da pci_map_sg, o alternativamente fermarsi nello scorrere la lista finchè sg_dma_address(sg) non restituisce un indirizzo pari a 0. Il kernel, oltre alle due macro viste in precedenza, offre un ulteriore macro per scorrere una lista di elementi scatterlist, che è definita come seuge: #define for_each_sg ( sglist, sg, nr, i ) \ f o r ( i = 0, sg = ( sglist ) ; i < ( nr ) ; i++, sg = sg_next ( sg ) ) si nota l utilizzo della funzione sg_next che restituisce semplicemente il prossimo elemento scatterlist nella lista. Una volta che il trasferimento DMA è completato per effettuare l unmap dei buffer coinvolti nel trasferimento, si utilizzeranno le funzioni dma_unmap_sg o pci_unmap_sg. La funzione dma_unmap_sg ha il seguente prototipo: void dma_unmap_sg ( struct device dev, struct scatterlist sglist, i n t nents, enum dma_data_direction direction ) ; Come possiamo notare i parametri sono gli stessi della funzione dma_map_sg. La funzione pci_unamp_sg, invece ha un implementazione simile a pci_map_sg: s t a t i c inline void pci_unmap_sg ( struct pci_dev hwdev, struct scatterlist sg, i n t nents, i n t direction ) { dma_unmap_sg ( hwdev == NULL? NULL : & hwdev >dev, sg, nents, ( enum dma_data_direction ) direction ) ; } Per ricapitolare un pò tutto quello mostrato fin ora mostriamo un esempio di utilizzo del mapping scatter-gather: i n t i, count = dma_map_sg ( dev, sglist, nents, direction ) ; struct scatterlist sg ; for_each_sg ( sglist, sg, count, i ) { hw_address [ i ] = sg_dma_address ( sg ) ; hw_len [ i ] = sg_dma_len ( sg ) ; } / Dopo che i l t r a s f e r i m e n t o è completato / dma_unmap_sg ( dev, sglist, nents, direction ) ; Cosa molto importante da notare è che le variabili count e nents possono avere due valori diversi: questo è spiegato dal fatto che alcuni elementi della lista scatterlist possono rappresentare aree di memoria contigue e quindi tali elementi vengono 111

126 fusi in un solo elemento nella scatterlist e quindi la chiamata a dma_map_sg restituisce il numero reale di elementi che mappa. Ovviamente lo scorrimento della lista è fatto su count, ma l unmap deve essere effettuato sullo stesso valore nents passato alla funzione dma_map_sg. Conclusioni Per concludere questo paragrafo, possiamo dire che in generale nelle architetture x86 non ci sono mai problemi di coerenza della cache quando si utilizza il DMA, in quanto i dispositivi hardware si preoccupano da loro stessi di conoscere tutti gli accessi alla cache hardware in modo da sapere se la cache è coerente o meno. Quindi, un driver in architetture x86 può scegliere di implementare entrambi i modi di mapping appena descritti. Il kernel, inoltre permette di mappare anche buffer DMA allocati nello userspace (high memory). Infatti, gli sviluppatori di driver possono utilizzare le funzioni pci_map_page e dma_map_page per mappare questo tipo di buffer; per eliminare questo mapping basterà richiamare le funzioni pci_unmap_page o dma_unmap_page. 112

127 Capitolo 3 Protocolli per la gestione dei dischi Knowledge is of two kinds. We know a subject ourselves, or we know where we can find information upon it. SAMUEL JOHNSON In questo capitolo andremo a descrivere inizialmente il funzionamento fisico di un generico disco, andando successivamente a studiare i maggiori standard utilizzati per la gestione dei dischi e in particolar modo dello standard SATA. Continueremo il capitolo mostrando i protocolli di comunicazione utilizzati nei dischi SATA, e infine descriveremo lo standard AHCI, un meccanismo hardware utilizzato per i dischi SATA, ormai divenuto oggi uno standard de facto. 3.1 Fondamenti sugli Hard Disk Un hard disk è essenzialmente composto dalle seguenti unità fisiche: Piatto (platter): ogni hard disk è composto da uno o più piatti paralleli identificati da un numero univoco; un piatto è destinato alla memorizzazione dei dati; 113

128 Traccia (track): ogni piatto è composto da numerosi anelli concentrici numerati, detti tracce (tracks), ognuna delle quali è identificata da un numero univoco; Cilindro (cylinder): l insieme di tutte le tracce alla stessa distanza dal centro e presenti su tutti i piatti è detto cilindro. Quindi un cilindro è l insieme di tutte le tracce che hanno lo stesso numero identificativo, ma che sono su piatti diversi; Settore (sector): ogni piatto è diviso in settore circolari, cioè spicchi radiali uguali, ognuno dei quali identificato da un numero univoco; Blocco (block): è l insieme di settori posti nello stessa posizione in tutti i piatti; Testina (head): è presente su ogni piatto ed è utilizzata per accedere in scrittura o lettura ai dati memorizzati sul piatto stesso: la particolarità delle testine è che sono tutte solidali tra loro, nel senso che se una testina è posizionata sopra una determinata traccia, allora tutte le altre testine degli altri piatti sono posizionate al cilindro a cui la traccia appartiene; Cluster: è un insieme di frammenti di tracce contigue. Le Figure 3.1 e 3.2 esplicitano i concetti appena descritti. Per leggere un dato scritto in un determinato settore, le testine di posizionano sulla traccia a cui appartiene il settore e iniziano a leggere il settore individuato (vedi Figura 3.3). Per identificare un particolare settore si sono utilizzati nel tempo varie tecniche. La più vecchia utilizzava la cosiddetta coordinata CHS (Cylinder Head Sector), dove ogni settore veniva individuato da una tupla contenente 3 coordinate: (cilindro, testina, settore). La tecnica CHS rispecchiava praticamente le proprietà fisiche del disco. Nei vecchi BIOS si utilizzavano 20 bit per individuare un settore, e nello specifico si utilizzavano 10 bit per il cilindro, 6 bit per il settore e 4 per la testina 114

129 Figura 3.1: Esempio della struttura di un hard disk avendo quindi la possibilità di indirizzare 1024 cilindri, 63 settori 1 e 16 testine al massimo. Con tale schema, mantenuto fino all introduzione dello standard ATA, si potevano indirizzare massimo = settori, ovvero 504 Mb, dato che la dimensione di un settore è tipicamente di 512 byte. Ovviamente, da come si può intuire, il meccanismo CHS limitava di molto la capacità utilizzabile dei hard disk di allora. Nel tempo furono introdotte nuove tecniche per l indirizzamento di un settore. Una delle più recenti e più utilizzate oggi è la tecnica LBA (Logical Block Addressing), introdotta nello standard ATA. Tale schema astrae il disco fisico e permette di utilizzare un indirizzo a 28 bit, 16 per il cilindro, 8 per il settore e 4 per la testina; con tale indirizzo si può indirizzare al massimo 128Gb, aumentando quindi di gran lunga la capacità utilizzabile degli hard disk. Nello schema LBA i settori non sono più individuati da una terna ma solo da un numero univoco: sarà poi compito del controller del disco effettuare la conversione da LBA a CHS. Quindi, mentre il controller del disco utilizza sempre indirizzi CHS, tali indirizzi, in 1 non 64, forse perchè dovuto ad errori di programmazione che facevano partire il conteggio dei settori da 1 e non da

130 Figura 3.2: Distinzione tra settore, blocco, traccia e cilindro generale, non sono utilizzati a livello di sistema operativo, il quale utilizza indirizzi LBA che astraggono la geometrica fisica del disco. In seguito, fu poi introdotto nello standard ATA6 lo schema LBA48, che prevede un indirizzo di 48 bit per indirizzare un settore; questo comporta un ulteriore aumento della capacità massima gestibile pari a 128Pb. 3.2 Lo standard SATA Nel mondo dell informatica con ATA (Advanced Technology Attachment) si identifica l interfaccia standard per la connessione di dispositivi di memorizzazione di massa (dischi rigidi, CD-ROM, DVD-ROM, floppy, ecc.) all interno di un computer. Tale standard è mantenuto dal comitato X3/INCITS. Lo standard ATA inizialmente era previsto per connettere soltanto dei dischi rigidi, ma con il tempo è stata sviluppata un estensione detta ATAPI (ATA Packet Interface) che ha permesso l interfacciamento con altri dispositivi di immagazzinamento dati (lettori CD-ROM, lettori DVD-ROM, ecc.) utilizzando sempre l interfaccia ATA. Con il tempo, il termine ATA, per ragioni di mercato, è stato sostituito dapprima con IDE (Integrated Drive Electronics), poi EIDE, fino ad arrivare oggi al termine PATA (Parallel ATA), largamento utilizzato per indicare tutte le tecnlogie 116

131 Figura 3.3: Lettura di un settore del disco utilizzate nei dispositivi di memorizzazione di massa che utilizzano una trasmissione dei dati parallela. Con l avvento dello standard SATA (Serial ATA), i dispositivi paralleli sono stati pian piano sostituiti da quelli a trasmissione seriale. La tecnologia SATA fa riferimento essenzialmente a due standard gestiti rispettivamente da due organi: T13 e SATA-IO. The Serial ATA International Organization (SATA-IO) [SI] si focalizza principalmente sulla trasmissione seriale (SATA) di dati utilizzando lo standard ATA, mentre T13 anche sulla trasmissione parallela (PATA) di dati utilizzando sempre lo standard ATA. In questa tesi faremo riferimento allo standard mantenuto da SATA-IO [Org09]. Lo standard SATA utilizza una parte del lavoro svolto negli anni dallo standard ATA introducendo dei miglioramenti sia sull utilizzo dei cavi che connettono le periferiche, ma soprattutto un aumento delle prestazioni in generale. Oltre allo standard SATA, che sarà oggetto di studio di questo capitolo, andremo a studiare nel dettaglio lo standard AHCI (Advanced Host Controller Interface), un meccanismo hardware che permette la comunicazione con i dispositivi SATA introducendo funzionalità avanzate per migliorare ancora le prestazioni, come per esempio l implementazione del meccanismo Native Command Queueing (NCQ) (vedi [CT03]). 117

132 3.2.1 Architettura Abbiamo detto che lo standard SATA è per certi versi l evoluzione dello standard PATA, introducendo collegamenti seriali ad alta velocità; infatti i link utilizzano un metodo di trasmissione detto Digital Differential signaling che in parole povere permette di trasmettere informazioni con due segnali complentari inviati su una coppia di fili chiamati differential pair; questi cavi inoltre utilizzano la tecnlogia Gigabit e le tecniche di codifica 8b/10b [WF83]. Nel gergo ATA si identificano due attori principali nella comunicazione: L host che identifica il computer stesso, o il BIOS, oppure il controller del disco; Il dispositivo (device) che identifica il dispositivo fisico, ovvero il disco, oppure qualsiasi dispositivo che si interfaccia con il protocollo ATA/SATA; Detto questo, consideriamo come due dispositivi sono connessi utilizzando la tecnlogia PATA attraverso un adattatore PATA (Parallel ATA Adapter). La Figura 3.4 mostra come lo standard PATA permetta di collegare fino a due dispositivi al bus PATA utilizzando la tecnica di comunicazione Master/Slave. Ogni dispositivo sarà collegato all adattatore (controller) attraverso il classico ribbon cable a 40 o 80 pin, in modo tale che i dispositivi siano nella cosiddetta daisy chain ovvero in serie. Con lo standard SATA le cose cambiano un poco perchè viene introdotto il concetto di Serial ATA HBA (Host Bus Adapter): un HBA è un componente che connette al bus di espansione del sistema host un dispositivo fisico in modo da consentire la comunicazione; un HBA è anche conosciuto con il termine di controller. Gli stessi due dispositivi di prima sono connessi al sistema come mostrato in Figura 3.5. La parte in grigio scuro del Serial ATA HBA è funzionalmente uguale a quella del PATA Adapter; infatti un driver che conosce soltanto PATA accederà al sottosistema SATA come se fosse un sottosistema PATA. In questo caso il software vedrà i due dispositivi come se fossero 2 dischi Master connessi su due porti diversi. La parte destra del SATA HBA (parte grigio chiara) sarà responsabile di convertire 118

133 Figura 3.4: Dispositivi PATA connessi al sistema le operazioni software in un flusso seriale di dati e controlli. Si può notare che lo standard SATA prevede che ogni dispositivo sia connesso con il proprio connettore al proprio porto di comunicazione. Livelli dell architettura l architettura: In SATA esistono quattro strati per descrivere tutta Application Layer: questo strato è responsabile dell esecuzione dei comandi ATA, il che significa controllare gli accessi ai registri nel Command Block Register (vedi sez ). In questo strato sono definiti anche tutti i protocolli di comunicazione utilizzati dall host e dal dispositivo; Transport Layer: questo strato è responsabile della creazione dei comandi, ovvero dell allocazione di informazioni di controllo e dati da inserire in un pacchetto o frame conosciuto come FIS (Frame Information Structure) (vedi sez ) inviato tra l host e il dispositivo; Link Layer: questo strato è responsabile di prendere il pacchetto costruito nello strato di trasporto e di codificarlo o decodificarlo con la tecnica 8b/10b inserendo dei bit di controllo; 119

134 Figura 3.5: Dispositivi SATA connessi al sistema Physical Layer: questo strato è responsabile della comunicazione fisica, ovvero della trasmissione e ricezione delle informazioni codificato come un flusso di dati seriale sul cavo fisico; La Figura 3.6 mostra l interconnessione tra i vari strati nello standard SATA. L host può utilizzare l Application Layer attraverso l interfaccia dei registri che è la stessa utilizzata dallo standard PATA. Vedremo quest interfaccia nel paragrafo Come abbiamo visto all inizio di questo paragrafo, SATA introduce dei miglioramenti a livello fisici. In realtà un ulteriore miglioramento che introduce SATA rispetto PATA è che incorpora meccanismi di Error Detection sui pacchetti inviati tra host e dispositivo e viceversa. Infatti viene introdotto il concetto di Frame Information Structure (FIS) (vedi sez ), un pacchetto che ha al suo interno informazioni sui comandi e dati; tale pacchetto viene racchiuso in frame più grande nel Link Layer, che oltre ad aggiungere informazioni sull inizio (SOF Primitive) e sulla fine (EOF Primitive) del frame, e informazioni di controllo sul flusso dei dati (HOLD e HOLDA Primitive) aggiunge un controllo di ridondanza ciclica (CRC) per rilevare eventuali errori di trasmissione (vedi Figura 3.7). Per i dettagli del Link Layer vedere lo standard [Org09]. 120

135 Figura 3.6: Strati dell architettura SATA I Registri Figura 3.7: Frame SATA nel Link Layer In questo paragrafo descriviamo l insieme dei registri utilizzati in SATA e in particolare descriveremo il concetto di Command Block Register e di Frame Information Structure (FIS) Il Command Block Register e Status/Control Register Il Command Block Register è utilizzato nello standard ATA e definisce un insieme di registri utilizzati per l invio di comandi al dispositivo o per leggere lo stato del dispositivo. Tali registri sono inoltre utilizzati per il controllo del dispositivo e per l invio di uno stato alternativo (alternate state). In appendice (vedi Appendice 121

136 A) sono descritti nel dettaglio tali registri. In SATA gli host adapter includono dei registri addizionali mappati separatamente e indipendentemente dai registri contenuti nel Command Block Register; questi nuovi registri sono utilizzati per riportare informazioni addizionali sullo stato e sugli errori in un dispositivo SATA, e inoltre sono utilizzati per consentire la gestione vera e proprio di un dispositivo SATA. Questi registri sono chiamati Serial ATA Status and Control Register (SCR) e sono organizzati come 16 registri contigui di 32 bit ciascuno. Nella Figura 3.8 è mostrato sia il Command Block Register ATA, che in SATA prende il nome di Shadow Register Block, sia i SATA Status/Control Register. Figura 3.8: Command Block Register e SATA Status/Control Register I SATA Status/Control Register sono definiti come in Figura 3.9. Figura 3.9: Definizione SATA Status/Control Register 122

137 In SATA sono state introdotte molte caratteristica e una delle più importanti è il cosiddetto First Party DMA (FPDMA). Questa tecnica trasferisce alcuni controlli dal DMA engine al dispositivo in modo da consentire al dispositivo stesso di avere una sorta di buffer per effettuare il riordino dei comandi allo scopo di ottimizzare le performance: questo meccanismo è chiamato Native Command Queueing (NCQ). Per sfruttare questa tecnica sono stati introdotti comandi di tipo FPDMA che utilizzano un protocollo leggermente diverso dai comandi READ DMA e WRITE DMA. Per questo motivo sono stati introdotti i registri SCR, tra cui uno dei più importanti è il SActive register. Questo registro di 32 bit è bit-significant, ovvero l i-esimo bit rappresenta il comando i-esimo accodato. Quindi, il valore del registro rappresenta l insieme dei comandi accodati in sospeso, i quali ancora devono essere completati con successo; di questo ne discuteremo nel paragrafo dei protocolli di comunicazione (vedi sez. 3.3) Frame Information Strucuture (FIS) Come abbiamo già detto, la più grande differenza tra SATA e PATA è che tecnologicamente si parla di due trasmissioni fisiche agli antipodi. In SATA però c è l introduzione del cosiddetto pacchetto o frame FIS, acronimo di Frame Information Structure, per il trasporto dei dati tra host e device e viceversa. Questo pacchetto incapsula comandi ATA, quindi la tecnologia SATA utilizza lo stesso insieme di comandi ATA utilizzati nei dispositivi PATA. Per essere più specifici, un pacchetto FIS è un insieme di double word (Dword: 32 bit) che permettono la trasmissione di informazioni relative allo Shadow Register Block (vedi sez ) tra un host e un device. Riportiamo in Figura 3.10 il layout del Shadow Register Block semplificato. 123

138 Figura 3.10: Shadow Register Block semplificato Un pacchetto FIS può essere di 3 tipi diversi: Register, Setup, e Data. Attualmente in [Org09] sono specificati 8 diversi tipi di pacchetti FIS, come mostrato nella Figura Figura 3.11: Tipi di FIS Ognuno di questi FIS è necessario per la particolare comunicazione che avviene tra un host e il dispositivo SATA. Descriviamo brevemente il loro significato: 124

139 Register FIS - Host to Device: questo FIS è utilizzato per trasferire il contenuto dello Shadow Register Block dall host al dispositivo; questo FIS, quindi sarà utilizzato per l invio di comandi ATA verso il dispositivo; Register FIS - Device to Host: questo FIS è utilizzato per aggiornare il contenuto dello Shadow Register Block dell host; quindi, tale meccanismo è utilizzato dal dispositivo per notificare all host che un comando è stato completato oppure che in generale ci sono stati dei cambiamenti di stato nel dispositivo; DMA Activate FIS - Device to Host: questo FIS è utilizzato dal dispositivo per segnalare all host che si sta procedendo con un trasferimento dati DMA. L host avrà bisogno di ricevere tale FIS prima di inviare uno o più Data FIS per completare il trasferimento dati richiesto; DMA Setup FIS - Device to Host: questo FIS è il meccanismo utilizzato tra host e device per iniziare un accesso first-party DMA alla memoria dell host (RAM). Infatti tale FIS è utilizzato per programmare correttamente il controller DMA dell host o del device prima del trasferimento vero e proprio dei dati. Grazie ad un DMA Setup FIS la memoria fisica dell host viene astratta in modo tale da referenziare le regioni di memoria attraverso un descrittore; tale descrittore consente all host di comunicare al device a quali regioni di memoria ha un accesso DMA. L implementazione specifica dell astrazione del descrittore della memoria non è definita nello standard; Data FIS - Bi-directional: questo FIS è utilizzato per il traporto di dati effettivi da leggere o scrivere da/sul disco. Quindi, questo tipo di FIS può essere inviato sia dall host verso il device che viceversa. Di solito un trasferimento dati include uno o più Data FIS; BIST Activate FIS - Bi-directional: questo FIS deve essere utilizzato per mettere il ricevitore in uno o più modalità di loopback (vedi [Org09]); 125

140 PIO Setup FIS - Device to Host: questo FIS è utilizzato dal dispositivo per fornire all host adapter informazioni riguardo la data-phase di un trasferimento PIO per consentirgli di gestire il trasferimento nella maniera più efficiente possibile. Per trasferimenti PIO, il dispositivo deve inviare all host questo FIS appena prima di ogni trasferimento di un Data FIS necessario per il trasferimento dati; questo deve essere fatto sia per trasferimenti dall host al device che viceversa. A causa degli stringenti vincoli temporali in ATA, il PIO Setup FIS contiene sia il valore iniziale dello stato che quello finale: tali valori sono utilizzati dall host adapter, primo per segnalare al driver che è pronto per scrivere dei dati con PIO (il bit BSY è messo a 0 e il bit DRQ è messo ad 1) e secondo, durante un burst di scritture PIO per segnalare correttamente al driver di azzerare il bit DRQ e possibilmente settare il bit BSY ad 1; Set Device Bits - Device to Host: questo FIS è utilizzato dal dispositivo per caricare i bit del Shadow Register Block per i quali il dispositivo ha un accesso di scrittura esclusivo. Questi bit sono gli 8 bit dell Error Register e i 6 bit dello Status Register (i 2 bit rimanenti sono BSY e DRQ che non devono essere modificati da tale FIS). Questo FIS include un bit per segnalare all host adapter di generare un interrupt se i bit BSY e DRQ sono entrambi a 0 quando il FIS è ricevuto; 3.3 Protocolli di comunicazione in SATA In questo paragrafo andremo ad analizzare la maggior parte dei protocolli utilizzati per il trasferimento dati. Nello standard SATA sono definite 2 tipologie di protocolli: Power-on e COMRESET protocol, Device Idle protocol Software Reset protocol; Command protocol; Nella seconda tipologia sono inclusi i seguenti protocolli: 126

141 Non-data Command protocol; PIO Data-In Command protocol; PIO Data-Out Command protocol; DMA Command protocol; DMA Queued Command protocol; PACKET Command protocol; FPDMA Command protocol; In questo paragrafo andremo a vedere soltanto nel dettaglio i protocolli di tipo command, che poi sono quelli maggiormente utilizzati durante la normale esecuzione di comandi su disco; non entreremo nel dettaglio dei protocolli DMA Queued Command e PACKET (per approfondire vedi [Org09]) Non-data Command Protocol Comandi che utilizzano tale protocolo non richiedono nessun trasferimento dati tra host e il device. Un esempio di comando che utilizza questo tipo di protocollo è il comando NOP, ma anche il comando FLUSH. Quando il driver scrive nel registro Command Register, l host adapter setta il bit BSY a 1 nello Status Register e trasmette il pacchetto relativo al comando al dispositivo. Quando il comando è completato, il dispositivo trasmette verso l host un FIS Register per settare il valore finale dei registri DMA Command protocol Lettura DMA da parte dell host sul dispositivo Prima che il comando di lettura DMA sia inviato, il driver deve preparare e abilitare il controller DMA dell host adapter facendogli conoscere il puntatore (o i puntatori) all indirizzo di memoria coinvolto nel trasferimento, ed inoltre la direzione del trasferimento; 127

142 Il driver a questo punto invia il comando al dispositivo scrivendo il tipo di comando da inviare nel Command Register che è all interno del Shadow Register Block; In risposta al fatto che sono stati scritti dei bit nel Shadow Register Block, l host adapter setta il bit BSY a 1 nello Status Register e invierà al dispositivo un Register Host to Device (H2D) FIS costruito con le informazioni contenute nel Shadow Register Block; Quando il dispositivo ha elaborato il comando ed è pronto (ovvero non ha nessun altro comando da elaborare), esso invia i dati che l host ha richiesto in uno o più Data FIS; L host adapter riceve i vari Data FIS dal dispositivo ed essendo il controller del DMA attivo, invia tali dati verso il controller DMA dell host adapter il quale inoltra tali dati nelle locazioni di memoria giuste; Appena il trasferimento è stato completato, il dispositivo invia un Register Device to Host D2H FIS all host, in modo da indicargli che il comando è completato, azzerando il bit BSY nello Status Register, e se il flag di Interrupt è settato, sarà sollevato un interrupt verso l host. Ci possono essere delle condizioni di errore in cui non ci sono Data FIS trasmessi dal dispositivo all host prima che il Register FIS da parte del dispositivo verso l host venga trasmesso con le relative informazioni sullo stato del dispositivo; se accade ciò, il driver deve interrompere il setup del controller DMA. In Figura 3.12 è mostrato un esempio di scambio di messaggi tra l host e dispositivo Scrittura DMA write da parte dell host sul dispositivo Prima che il comando di DMA WRITE sia inviato, il driver deve far conoscere al controller DMA dell host adapter il puntatore (o i puntatori) all indirizzo di memoria coinvolto nel trasferimento ed inoltre la direzione del trasferimento, 128

143 Figura 3.12: Lettura DMA preparando il controller DMA abilitandolo. Il controller DMA è pronto ma deve comunque rimanere in attesa di un segnale da parte del dispositivo a procedere con il trasferimento; Il driver a questo punto invia il comando al dispositivo scrivendo il comando da inviare nel Command Register che è all interno del Shadow Register Block; In risposta al fatto che sono stati scritti dei bit nel Shadow Register Block, l host adapter setta il bit BSY a 1 nello Status Register e invierà al dispositivo un Register H2D FIS costruito con le informazioni contenuto nello Shadow Register Block; Quando il dispositivo è pronto a ricevere i dati dall host, trasmetterà un DMA Activate FIS verso l host, il quale attiverà il controller DMA che precedentemente era stato inizializzato. Il controller DMA invierà, a questo punto, i dati da scrivere verso il dispositivo nella forma di uno o più Data FIS. Se sono necessari più Data FIS per effettuare il trasferimento, il dispositivo deve mandare un DMA Activate FIS per ogni Data FIS inviato dall host. La quantità di dati da trasferire è conosciuta dal driver durante la fase di setup del comando. 129

144 Appena il trasferimento è stato completato, il dispositivo invia un Register D2H FIS all host, in modo da indicargli che il comando è stato completato, azzerando il bit BSY nello Status Register, e se il flag di Interrupt è settato, sarà sollevato un interrupt verso l host. Anche in questo caso, in alcune condizioni di errore, il dispositivo può segnalare che il comando è stato completato trasmettendo un Register FIS all host senza aver trasmesso un DMA Activate FIS; in tale caso il driver dovrebbe effettuare delle operazioni di pulizia del controller DMA e abortire il trasferimento. In Figura 3.13 è mostrato un esempio di scambio di messaggi tra l host e dispositivo. Figura 3.13: Scrittura DMA PIO Data-In e Data-Out protocol Lettura PIO dal dispositivo Il driver invia un comando PIO read al dispositivo scrivendo il tipo di comando nel Command Register del Shadow Register Block; In risposta al fatto che il Command Register è stato scritto, l host adapter setta il bit BSY a 1 nello Status Register e invia un Register H2D FIS al dispositivo costruito secondo il contenuto dell intero Shadow Register Block; 130

145 Quando il dispositivo ha elaborato il comando ed è pronto al trasferimento dati verso l host, invia prima un PIO Setup FIS all host; appena l host riceve tale FIS mantiene le informazioni contenute in esso in un buffer di memoria temporaneo; Dopo che il dispositivo ha inviato il PIO Setup FIS, può inviare un Data FIS all host. Appena l host adapter riceve il DATA FIS, e avendo le informazioni necessarie nel PIO Setup FIS precedentemente ricevuto, trasferisce il contenuto dei registri contenuti nel PIO Setup FIS nei registri del Shadow Register Block compreso il valore dello stato iniziale, comportando il set ad 1 del bit DRQ e il set a 0 del bit BSY. Infine, se il bit di Interrupt è settato ad 1, sarà sollevato un interrupt verso l host. A questo punto l host adapter riceve i dati in arrivo, che sono inclusi nei Data FIS, in FIFO speed-matching 2, connesso ovviamente al Data Register nel Shadow Register Block; A fronte dell interrupt sollevato e del set del bit DRQ ad 1 dello Status Register, il driver esegue l istruzione REP INSW (istruzione assembler che permette il traferimento di molti dati utilizzando un unica istruzione) sul Data Register e preleva i dati dalla testa della coda speed-matching mentre il cavo seriale sta aggiungendo dati alla coda. Ovviamente c è un controllo di throttling (regolazione) sul flusso dei dati che previene eventuali errori di buffer underflow/overflow. Quando il numero di word lette dall host dal Data Register del Shadow Register Block è arrivato al numero specificato nel PIO Setup FIS ricevuto dal dispositivo, l host traferisce il valore di stato dal PIO Setup FIS dentro lo Status Register del Shadow Register Block, causando l azzeramento del bit DRQ. 2 Un buffer che consente la comunicazione tra dispositivi che hanno velocità diverse 131

146 Se ci sono altri dati da trasferire, lo stato finale segnala che il BSY è settato ad 1, e il processo si ripete partendo dall invio, da parte del dispositivo, del PIO Setup FIS verso l host Scrittura PIO verso il dispositivo Il driver invia un comando PIO write al dispositivo scrivendo il tipo di comando nel Command Register del Shadow Register Block; In risposta al fatto che il Command Register è stato scritto, l host adapter setta il bit BSY a 1 nello Status Register e invia un Register H2D FIS al dispositivo costruito secondo il contenuto dell intero Shadow Register Block; Quando il dispositivo è pronto a ricevere i dati da scrivere con PIO, invia verso l host un PIO Setup FIS indicandogli che è pronto a ricevere dati da scrivere in termini di numero di word. In risposta alla ricezione di un PIO Setup FIS, con il bit D settato ad 0 che indica una scrittura verso il dispositivo, l host inizia a traferire il contenuto del registro Status all interno del PIO Setup FIS all interno dello Status Register nel Shadow Register Block, causando il settaggio del bit DRQ a 1 e del bit BSY a 0; se l interrupt bit all interno del FIS ricevuto è settato ad 1, sarà generato un interrupt verso l host. A causa del fatto che il bit DRQ è stato settato ad 1, il driver effettua un istruzione di REP OUTSW (simile alla REP INSW) verso il Data Register; I dati scritti nel Data Register sono messi in un outbound speed-matching FIFO (vedi nota precedente) e sono trasmessi al dispositivo all interno di un Data FIS. L istruzione REP OUTSW mette i dati alla fine della coda FIFO mentre il cavo seriale preleva questi dati dalla testa della coda. Ovviamente c è un controllo di throttling (regolazione) sul flusso dei dati che previene eventuali errori di buffer underflow/overflow. 132

147 Quando il numero di word specificate nel PIO Setup FIS è stato scritto nella coda FIFO di trasmissione, l host controller copia lo stato finale specificato nel PIO Setup FIS nello Status Register del Shadow Register Block causando l azzeramento del bit DRQ e la terminazione del frame con un CRC e un EOF p (vedi formato di un frame nello standard [Org09]). Se ci sono ulteriori settori di dati che devono essere traferiti, il valore dello stato finale trasferito nello Status Register del Shadow Block Register ha il bit BSY settato ad 1; Se ci sono altri blocchi di dati da trasferire, lo stato finale segnala che il bit BSY è settato ad 1, quindi il processo si ripete dal punto in cui il dispositivo invia il PIO Setup FIS all host; Quando il numero di settori specificati nel registro Sector Count sono stati trasferiti, il dispositivo deve inviare un Register D2H FIS sollevando un interrupt per notificare all host che il comando è stato completato, e inoltre deve settare il bit BSY a zero. In caso di errore di scrittura, il dispositivo può, inserire nel PIO Setup FIS stato di errore e l interrupt riferito al completamento del comando, senza inviare il Register D2H FIS FPDMA QUEUED command protocol Prima di descrivere tale protocollo, definiamo come TAG di un comando un identificativo univoco del comando. Per descrivere questo protocollo consideriamo lo stato in cui può stare il dispositivo e lo stato in cui può stare l host Stato del dispositivo Supponiamo che il dispositivo sia in uno stato di IDLE e che non si siano verificati errori; in tale stato il dispositivo potrà: Ricevere un comando di lettura (FPDMA READ) o di scrittura (FPDMA WRITE); 133

148 Essere pronto ad inviare dati in riferimento ad un comando di lettura (FPD- MA READ); Essere pronto a ricevere dati in riferimento ad un comando di scrittura (FPD- MA WRITE); Essere pronto a completare con successo uno o più comandi di lettura o di scrittura; A seconda dello scenario, il dispositivo si comporterà in maniera diversa. Descriamo questi 4 scenari. Dispositivo riceve un comando READ FPDMA o WRITE FPDMA 1. Dallo stato di IDLE, il dispositivo riceve un FIS e va nello stato di CHECK_FIS; 2. Nello stato di CHECK_FIS il dispositivo controlla se il FIS è di tipo Register e il bit C è 1 (scrittura nel Command Register -> comando inviato); il dispositivo va nello stato di CHECK_CMD; 3. Nello stato di CHECK_CMD il dispositivo controlla se il comando inviato è di tipo WRITE FPDMA o READ FPDMA e in entrambi i casi li accoda andando nello stato di AddCommand_ToQueue; 4. Nello stato di AddCommand_ToQueue, se il dispositivo accoda correttamente il comando va nello stato di ClearInterfaceBusy 5. Nello stato di ClearInterfaceBusy il dispositivo invia un Register FIS all host con il bit BSY a 0 e il bit DRQ a 0 e il bit Interrupt a 0 per segnalare che l interfaccia del dispositivo è pronta a ricevere un ulteriore comando; se il Register FIS è stato trasmesso correttamente il dispositivo ritorna nello stato di IDLE; In Figura 3.14 è mostrato tale scenario. 134

149 Figura 3.14: Dispositivo riceve un comando READ FPDMA o WRITE FPDMA Dispositivo pronto ad inviare dati in riferimento ad un comando di lettura (READ FPDMA) 1. Dallo stato di IDLE, se il dispositivo è pronto a trasmettere dati verso l host in riferimento ad un comando di READ FPDMA, il dispositivo passa nello stato di DataPhase_ReadSetup. 2. Nello stato di DataPhase_ReadSetup il dispositivo invia all host un DMA Setup FIS con DMA Buffer ID = TAG, il bit D ad 1 e il bit Interrupt ad 1; se la trasmissione del DMA Setup FIS è andata a buon fine, il dispositivo passa nello stato di DataXmitRead. 3. Nello stato di DataXmitRead, il dispositivo invia all host i Data FIS necessaria al trasferimento dati; (a) se il DMA Transfer Count del DMA Setup FIS non è terminato e non ci sono errori, il dispositivo rimane nello stato di DataXmitRead; (b) altrimenti, passa nello stato di IDLE: 135

150 i. se il DMA Transfer Count del precedente DMA Setup FIS è terminato ma il trasferimento dati per il comando non è completo, e non c è nessun errore; ii. se il trasferimento dati è terminato per il comando, e nessun errore si è verificato; iii. se si è verificato qualche errore unrecoverable; In Figura 3.15 è mostrato tale scenario. Figura 3.15: Dispositivo pronto ad inviare dati in riferimento ad un comando di lettura (READ FPDMA) Dispositivo pronto ricevere dati in riferimento ad un comando di scrittura (WRITE FPDMA) 1. Dallo stato di IDLE, se il dispositivo è pronto a ricevere dati dall host in riferimento ad un comando di WRITE FPDMA; il dispositivo passa nello stato di DataPhase_PreWriteSetup. 2. Nello stato di DataPhase_PreWriteSetup: (a) se l opzione Auto-Activate, nel DMA Setup FIS, è supportata e abilitatata, allora si andrà nello stato di DataPhase_WriteSetup; i. Nello stato di DataPhase_WriteSetup il dispositivo invia all host un DMA Setup FIS con: DMA Buffer ID = TAG, il bit D a 0, il bit Auto-Activate (A) ad 1 e il bit Interrupt ad 1; se la trasmissione del DMA Setup FIS è completata, il dispositivo passa nello stato di DataXmitWrite; 136

151 (b) altrimenti, se l opzione Auto-Activate, nel DMA Setup FIS, non è supportata o non è abilitata, allora si andrà nello stato di DataPhase_OldWriteSetup; i. Nello stato DataPhase_OldWriteSetup il dispositivo invia all host un DMA Setup FIS con: DMA Buffer ID = TAG, il bit D a 0, il bit Auto-Activate (A) a 0 e il bit Interrupt ad 1; se la trasmissione del DMA Setup FIS è completata, il dispositivo passa nello stato di DataPhase_XmitActivate; A. nello stato di DataPhase_XmitActivate il dispositivo invia un DMA Activate FIS all host; se la trasmissione del DMA Activate FIS è completata, il dispositivo passa nello stato di DataXmitWrite; 3. Nello stato di DataXmitWrite il dispositivo: (a) ritorna nello stato di DataPhase_XmitActivate se il DMA Transfer Count, relativo al precedente DMA Setup FIS, non è esaurito e nessun errore si è verificato; (b) passa nello stato di IDLE: i. se il DMA Transfer Count, relativo al precedente DMA Setup FIS, è terminato ma il trasferimento dati relativo al comando non è terminato e non ci sono errori; ii. se il trasferimento dati relativo al comando è terminato e non ci sono stati errori; iii. se si sono verificati errori unrecoverable; In Figura 3.16 è mostrato tale scenario. 137

152 Figura 3.16: Dispositivo pronto ricevere dati in riferimento ad un comando di scrittura (WRITE FPDMA) 138

153 Dispositivo pronto a completare con successo uno o più comandi di lettura o di scrittura Prima di descrivere questo scenario, bisogna precisare che un dispositivo può essere pronto a completare con successo uno o più comandi FPDMA WRITE o READ e contemporaneamente può anche essere pronto a ricevere o inviare dati per nuovi comandi FPDMA. Alcuni dispositivi gestiscono questa situazione con l implementazione del cosiddetto status aggregation dove si può scegliere indiscriminatamente se completare comandi precedenti o servire nuovi comandi; se il dispositivo non supporta lo status aggregation il dispositivo deve avere priorità sul completamento di un comando. Detto questo descriviamo lo scenario: 1. Dallo stato di IDLE, se il dispositivo è pronto a completare con successo un comando FPDMA, allora passa nello stato di SendStatus; 2. Nello stato di SendStatus il dispositivo trasmette un Set Device Bits FIS con il bit ERR a 0, l Interrupt bit a 1 e il bit n-esimo del campo ACT settato ad 1, dove n è uguale al TAG dei comandi che sono stati completati dall ultima restituzione di stato; se la trasmissione del Set Device Bits FIS è completata, allora l host ritorna nello stato di IDLE; Stato dell host Supponiamo che il dispositivo sia in uno stato di IDLE (ovviamente questo stato è diverso dallo stato di IDLE del dispositivo) e che non si siano verificati errori; in tale stato l host potrà avere 3 scenari: Scenario 1: E disponibile un TAG libero per un comando che è in attesa dell assegnazione di un TAG; Scenario 2: Un comando per cui è stato già assegnato un TAG è in attesa di essere inviato al dispositivo, e il bit BSY è azzerato, e non si è nella First-Party DMA Data Phase 3 ; 3 La First-Party DMA Phase è definita come il periodo che intercorre dalla ricezione di un 139

154 Scenario 3: L host ha ricevuto un interrupt dal dispositivo; A seconda dello scenario, l host si comporterà in maniera diversa. questi 3 scenari. Descriviamo Scenario 1 1. Dallo stato di IDLE, se è disponibile un TAG libero per un comando, e tale comando è in attesa dell assegnazione di un TAG, l host passa nello stato di PresectACT; 2. Nello stato di PresectACT si assegna il TAG libero al comando e si scrive il registro SActive con il valore 1 nella posizione TAG-esima; se il TAG è stato assegnato al comando e il registro SActive è scritto allora si ritorna nello stato di IDLE; Scenario 2 1. Dallo stato di IDLE, se un comando a cui è stato già assegnato un TAG è in attesa di essere inviato al dispositivo, e il bit BSY è 0, e non si è nella First- Party DMA Data Phase, allora l host passa nello stato di IssueCommand; 2. Nello stato di IssueCommand, sempre se non si è nella First-Party DMA Data Phase, l host invia un Register FIS al dispositivo con il nuovo comando e il TAG relativo; l host ritorna nello stato di IDLE: (a) se la trasmissione del Register FIS è completata (Command Issued); (b) altrimenti, se la trasmissione del Register FIS viene rinviata (deferred, Command not Issued), attendendo che BSY ritorni a 0; In Figura 3.17è mostrato tale scenario. DMA Setup FIS fino a che il Transfer Count associato termini, oppure il bit ERR nel Shadow Block Register è settato. In tale intervallo di tempo l host non può nè inviare nuovi comandi al dispositivo, nè il dispositivo può segnalare all host il completamento di un comando. 140

155 Figura 3.17: Scenario 2: Host State Command Issue Scenario 3 1. Dallo stato di IDLE, se l host ha ricevuto un interrupt dal dispositivo, passa nello stato di DeviceINT; 2. Nello stato di DeviceINT l host legge lo Status Register per azzerare i flag interrupt pendenti e salvare tale valore come SavedStatus; incondizionatamente l host passa nello stato di CompleteRequest1 ; 3. Nello stato di CompleteRequest1, l host compara il valore del registro SActive con il valore dello stesso registrato memorizzato dall ultimo interrupt ricevuto, per determinare se sono stati completati dei comandi; (a) se sono stati rilevati dei comandi completati allora l host passa nello stato di CompleteRequest2 ; i. nello stato di CompleteRequest2, l host rende nuovamente disposibili i TAG relativi a i bit azzerati nel registro SActive e aggiorna di conseguenza tale registro; da questo stato, incondizionatamente, l host passa nello stato di CompleteRequest3 ; (b) se non sono stati rilevati dei comandi completati allora l host passa nello stato di CompleteRequest3 ; 4. Nello stato di CompleteRequest3 l host testa l ERR bit nel SavedStatus: (a) se questo bit è zero, l host ritorna nello stato di IDLE; 141

156 (b) se questo bit è uno, l host va in uno stato di gestione dell errore; In Figura 3.18 è mostrato tale scenario. Figura 3.18: Scenario 3: Host State Received Interrupt Esempio di FIS utilizzati durante l invio di comandi FPDMA In questo paragrafo andremo a vedere il formato dei principali FIS inviati tra host e dispositivo durante il protocollo FPDMA. Durante l invio di comandi FPDMA di lettura o di scrittura, secondo il protocollo descritto in 3.3.4, il dispositivo invia verso l host un DMA Setup FIS come mostrato in Figura Possiamo notare come nel campo Buffer Identifier (per i dettagli del paccheto vedi sez. A.2.0.5) è inserito il TAG del comando in modo tale da informare il DMA 142

157 Figura 3.19: DMA Setup FIS durante il protocollo FPDMA engine dell host (e quindi il driver) quali sono le aree di memoria DMA associate al comando. Nelle Figure 3.20 e 3.21 sono mostrate invece i Command Block Register associati rispettivamente ad un comando READ FPDMA e WRITE FPDMA. Tali Command Block Register saranno poi tradotti dal driver in FIS in modo tale da inviarli verso il dispositivo. Figura 3.20: Command Block Register associato ad un comando READ FPDMA Dopo il completamento dei comandi da parte del dispositivo, quest ultimo invia un FIS per notificare all host questo evento. Tale FIS, come abbiamo descritto in 143

158 Figura 3.21: Command Block Register associato ad un comando READ FPDMA , è del tipo Set Device Bits (per i dettagli vedi sez. A.2.0.3), ed è nel formato mostrato in Figura Figura 3.22: Set Device Bits FIS ricevuto dopo il completamento di comandi FPDMA da parte del dispositivo Possiamo notare come il riferimento al comando completato è messo nel campo Protocol Specific (vedi sez. A.2.0.3) dove è inserito il valore del registro SActive che come abbiamo visto nella sez rappresenta i comandi accodati che sono stati completati o meno Esempio della comunicazione tra host e dispositivo durante l esecuzione di comandi FPDMA Supponiamo che l host debba mandare due comandi di lettura (READ FPDMA)al dispositivo. Il protocollo seguito, a grandi linee, è il seguente: 1. L host invia un comando di lettura, per esempio, con Tag=0 settando il bit 0 nel registro SActive scrivendo il valore h e inviando un Register FIS H2D al dispositivo. 144

159 2. Il dispositivo dopo aver ricevuto il Register H2D FIS con Tag=0 dall host, accoderà il comando e risponderà all host con un Register D2H FIS per azzerare il bit BSY nello Status Register. 3. Se il bit BSY non è zero l host deve attendere per l invio di un ulteriore comando; se BSY è 0 allora l host può inviare un ulteriore comando di lettura, per esempio, con Tag=5 attraverso il settaggio del registro SActive scrivendo il valore h (ovvero in binario ) e inviando al dispositivo un nuovo Register FIS H2D al dispositivo. In questo momento il registro SActive conterrà il valore 21h. 4. Il dispositivo dopo aver ricevuto il Register FIS H2D con Tag=5, setterà il bit BSY a 0 e invierà all host un DMA Setup FIS con Tag=5 in modo da servire la seconda richiesta arrivata. 5. L host, ricevuto il DMA Setup FIS, caricherà l indirizzo che punta alla tabella che mi descrive la regione fisica dove è mappato il buffer numero 5 (tale tabella è chiamata Physical Region Fescriptor Tables, PRDT) nel DMA Engine; 6. Il dispositivo invia i dati richiesti dall host corrispondenti al comando con Tag=5; 7. Il DMA Engine dell host redirige i dati che provengono dal dispositivo verso il buffer numero 5; 8. Il dispositivo, per indicare che il comando con Tag=5 è completato, invia un Set Device Bits FIS con il bit Interrupt alto e il valore del registro SActive con il valore h; 9. L host riceve il Set Device Bits FIS con il campo SActive pari a 20h e il bit di Interrupt alto e quindi azzererà il bit relativo al Tag=5 nel registro SActive (il registro varrà 01h) e l Interrupt sarà attivo. 10. Il dispositivo invia all host un DMA Setup FIS con Tag=0; 145

160 11. L host, ricevuto il DMA Setup FIS, caricherà il puntatore PRDT nel DMA Engine corrispondente al buffer numero 0. Nel frattempo l host elabora l interrupt ricevuto precendentemente. Leggendo il valore del registro SActive determina che il bit numero 5 non è asserito e quindi può ritenere libero il tag relativo. 12. Il dispositivo invia i dati richiesti dall host corrispondenti al comando con Tag=0; 13. Il DMA Engine dell host redirige i dati che provengono dal dispositivo verso il buffer numero 5; 14. Il dispositivo, per indicare che il comando con Tag=0 è completato, invia un Set Device Bits FIS con il bit Interrupt alto e il valore del registro SActive con il valore h; 15. L host riceve il Set Device Bits FIS con il campo SActive pari a 01h e il bit di Interrupt alto e quindi azzererà il bit relativo al Tag=5 nel registro SActive (il registro varrà 00h) e l Interrupt sarà attivo. 16. Il dispositivo è nello stato di IDLE; 17. L host elabora l interrupt ricevuto precendentemente. Leggendo il valore del registro SActive determina che il bit numero 0 non è asserito e quindi può ritenere libero il tag relativo. 18. L host è nello stato di IDLE 3.4 Advanced Host Controller Interface (AHCI) AHCI (Advanced Host Controller Interface) è un meccanismo hardware inventato e utilizzato da INTEL, ormai diventato uno standard de-facto. La specifica AHCI pone l attenzione sul fatto che un controller AHCI (spesso chiamato HBA Host Bus Adapter ) è stato progettato per essere un sistema che permetta il 146

161 trasferimento di dati tra la memoria di sistema (RAM) e il disco fisico SATA. Praticamente, AHCI incapsula i dispositivi SATA e fornisce un interfaccia PCI standard verso gli host. Una delle innovazioni introdotte da SATA, è che gli sviluppatori possono facilmente accedere ai dispositivi SATA utilizzando la memoria di sistema e i registri mappati in memoria. Un controller AHCI può gestire fino a 32 porti ai quali possono essere connessi altrettanti dispositivi SATA come ad esempio hard disk, port multiplier, o bridge SATA. Inoltre, AHCI supporta tutte le caratteristiche native di SATA come ad esempio il command queueing, l hotplugging, power management ecc. Dal punto di vista degli sviluppatori di driver, un controller AHCI non è nient altro che un dispositivo PCI con capacità di bus mastering Registri Come detto nell introduzione, gli host possono comunicare con un controller AHCI attraverso la memoria di sistema e i registri memory-mapped. Il controller AHCI è un dispositivo che comunica su bus PCI, in cui l ultimo PCI Base Address Register, cioè BAR5 che ha offset pari a 0x24, punta ad un indirizzo in memoria che è il cosiddetto ABAR (AHCI Base Memory Register); grazie al ABAR si possono localizzare in memoria tutti i registri e le aree di memoria AHCI che si possono utilizzare. Gli altri BAR sono utilizzati in maniera tradizionale, come se fosse un controller IDE HBA Configuration Register Un controller AHCI, essendo un dispositivo PCI ha un insieme di registri che sono quelli che appartengono al Configuration Space (vedi sez ); questi registri in particolare sono specificati nel cosiddetto PCI Header. Nella Figura 3.23 sono illustrati i registri PCI. Andiamo a vedere nel dettaglio il registro ABAR (vedi Figura 3.24). Il registro ABAR deve puntare ad un area di memoria abbastanza grande da poter contenere i registri globali di AHCI, i registri specifici per ogni porto e i registri definiti dal costruttore del controller. 147

162 Figura 3.23: PCI Header Figura 3.24: Registro ABAR HBA Memory Register I registri di un controller AHCI sono essenzialmente divisi in due parti: Generic Host Control: è un insieme di registri globali che controllano il comportamento dell intero controller; Port Control Register: ognuno dei 32 porti supportati dal controller AHCI possiede un insieme di questi registri che sono necessari per implementare un porto; In Figura 3.25 possiamo vedere questa distinzione. I registri di un HBA devono necessariamente essere mappati in aree di memoria non-cacheable, ovvero locazioni di memoria su cui il processore non effettua nessuna 148

163 Figura 3.25: Registri dell HBA AHCI operazione di ottimizzazione. In Figura 3.26 e 3.27 sono mostrati rispettivamente i registri Generic Host Control e Port Control Register. Generic Host Control Control. In Figura 3.26 sono mostrati i registri Generic Host Figura 3.26: Generic Host Control Tra i registri più importanti possiamo sottolineare il registro Host Capabilities (CAP) che descrive tutto quello che supporta l host controller, e il registro Interrupt Status che identifica quale porto ha sollevato l interrupt e richiede di essere gestito. Port Control Register In Figura 3.27 sono definiti i registri Port Control Register. Due tra i registri più importanti sono PxSACT e PxCI, utilizzati durante l invio di comandi verso il dispositivo. Il registro PxSACT è bit-significant, ovvero ogni bit corrisponde al TAG del comando (bit 0 -> comando TAG 0) in attesa di essere 149

164 Figura 3.27: Port Control Register inviato che utilizza NCQ. Il registro PxCI allora stesso modo di PxSACT è bitsignificant e indica che il comando con TAG relativo al bit alto nel registro è stato costruito in memoria e può essere inviato al dispositivo. Vedremo in l utilizzo di questi due registri. Un altro registro degno di nota è il registro PxIS che descrivo lo stato dell interrupt relativamente ad un porto Strutture utilizzate in memoria La maggior parte della comunicazione tra il software (driver) e un dispositivo SATA è fatta attraverso un HBA per mezzo dei descrittori nella memoria di sistema, i quali indicano lo stato di tutti i FIS inviati e ricevuti. Nella Figura 3.28 possiamo vedere la separazione concettuale che abbiamo dello spazio di memoria di un HBA AHCI. Si può notare dalla Figura 3.28 che un host comunica con il dispositivo attraverso la struttura Command List che contiene una lista da 1 a 32 comandi disponibili per l esecuzione per ogni porto, e un dispositivo invia informazioni all host tramite la struttura Received FIS Structure che contiene tutti i FIS ricevuti dal dispositivo. I puntatori a queste due strutture, contenuti rispettivamente nei registri Px- 150

165 Figura 3.28: Spazio di memoria utilizzato da AHCI CLB e PxFB, sono indirizzi a 64 bit oppure a 32 bit se l HBA non supporta l indirizzamento a 64 bit. La Figura 3.29 esplicita quello appena detto Received FIS Structure Un HBA utilizza un area di memoria di sistema per comunicare informazioni sui FIS ricevuti. La Received FIS Structure è riferita dai registri PxFBU e PxFB. Nella Figura 3.30 è mostrata questa struttura. Quando arriva un determinato tipo di FIS l HBA lo copia in una specifica area; vediamo i vari casi: quando arriva un DMA Setup FIS da parte del dispositivo, l HBA lo copia nell area DSFIS di questa struttura; 151

166 Figura 3.29: Command List Structure e Received FIS Structure quando arriva un PIO Setup FIS da parte del dispositivo, l HBA lo copia nell area PSFIS di questa struttura; quando arriva un D2H Register FIS da parte del dispositivo, l HBA lo copia nell area RFIS di questa struttura; quando arriva un Set Device Bits FIS da parte del dispositivo, l HBA lo copia nell area SDBFIS di questa struttura; quando arriva un FIS sconosciuto, l HBA lo copia nell area UFIS di questa struttura e setta il bit PxSERR.DIAF.F che si riflette sul settaggio del bit PxIS.UFS a 1, il quale indica che il FIS è appunto sconosciuto; Command List Structure La Command List Structure è riferita dai registri PxCLBU e PxCLB. Un host comunica con un dispositivo attraverso questa struttura. La Figura 3.31 mostra da 152

167 Figura 3.30: Received FIS Structure cosa è composta questa struttura. Da come possiamo notare dalla Figura 3.31, la Command List Structure è composta da 1 fino a 32 Command Header, chiamati slot. Ogni Command Header è una struttura formata da una serie di Dword che danno informazioni sul particolare comando ATA o ATAPI e include dettagli sulla direzione, tipo, puntatore scatter/gather. Per inviare un comando, l host costruisce un Command Header, e setta il bit nel registro Port Command Issue (PxCI). Il controller AHCI automaticamente invierà il comando al dispositivo e attenderà una risposta. Se si è verificato qualche errore, saranno settati i bit di errore nel registro Port Interrupt Status (PxIS) e informazioni aggiuntive potranno essere recuperate dai registri Port Task File Data 153

168 Figura 3.31: Command List Structure (PxTFD), Port Serial ATA Status (PxSSTS) e Port Serial ATA Error (PxSERR). Se il comando è stato inviato correttamente, il bit del registro Command Issue sarà azzerato, e il data payload ricevuto, se c è, sarà copiato dal device alla memoria dell host dal controller AHCI. Siccome SATA, a differenza di PATA, supporta l accodamento dei comandi per incrementare le prestazioni, il numero massimo di comandi che si possono accodare sarà proprio il numero di Command Header disponibili, ovvero 32. Ogni Command Header, tramite i campi CTBA0 e CTBA_U0, punta ad una struttura chiamata Command Table (vedi Figura 3.32). Descriviamo i campi nel dettaglio: Command FIS (CFIS): in questo campo è indicato il FIS costruito lato software. Per le operazioni di trasferimento dati, questo campo ha il formato di un FIS H2D Register. L HBA setta il bit PxTFD.STS.BSY, e invia questa struttura alla porto connesso. Se è connesso un Port Multiplier, questo campo deve contenere il numero di porto del Port Multiplier all interno del FIS stesso 154

169 Figura 3.32: Command Table e non deve essere specificato dall HBA. Lunghezze valide di un CFIS vanno dalle 2 alle 16 Dword; ATAPI Command (ACMD): questa è una regione costruita lato software di 12 o 16 byte che contiene il comando ATAPI da trasmettere se nel Command Header il bit A è settato. Il comando ATAPI deve essere lungo o 12 o 16 byte. La lunghezza trasmessa dall HBA è determinata dal FIS PIO Setup che è stato inviato attraverso la richiesta del device di un comando ATAPI; Physical Region Descriptor Table (PRDT): questa tabella contiene la lista scatter/gather per il trasferimento dati. Ha un numero di entry da 0 (se così significa che non c è nessuno dato da trasferire) a La Figura 3.32 precedente mostra i campi che compongono ogni entry della tabella: Data Base Address (DBA): indica l indirizzo fisico a 32 bit del blocco 155

170 dati. Il blocco deve essere allineato a word, indicato dal fatto che il bit 0 è riservato; Data Base Address Upper 32-bits (DBAU): questi sono i 32 bit alti dell indirizzo fisico del blocco dati. Questo campo è valido solo se l HBA ha specificato che supporta l indirizzamento a 64 bit tramite il bit S64A nel Capabilities Register, altrimenti il campo sarà ignorato; Reserved: bit riservati; Interrupt on Completion (I): questo bit se settato, indica che l hardware dovrebbe sollevare un interrupt quando il blocco dati per questa particolare entry è stato trasferito, il che implica che non ci sono dati nell hardware HBA. I dati possono ancora essere in volo verso la memoria di sistema (caso di lettura da disco), oppure lato device (caso in cui un R_OK o R_ERR devono ancora essere ricevuti). L HBA deve settare il bit PxIS.DPS dopo il completamento del trasferimento dati, e se abilitato, sollevare un interrupt; Data Byte Count (DBC): è un valore 0 based e indica la lunghezza espressa in byte del blocco dati. Ogni entry della PRDT può specificare un massimo di 4MB di lunghezza di un blocco dati. Il bit 0 di questo campo deve essere sempre pari a 1 per indicare un byte count pari. Il valore 1 indica 2 byte, 3 indica 4 byte e così via Operazioni per un trasferimento dati Le struttura dati di AHCI sono costruite assumendo che ogni porto del controller ha al suo interno il proprio DMA engine, ovvero che ogni porto può effettuare il trasferimento dati indipendentemente dall altro. Il driver (software) presenta al porto di un controller (HBA) una lista di comandi che saranno elaborati; l HBA può supportare un singolo comando per porto oppure una lista di comandi per porto. Quando il sistema operativo ha pronto un comando da inviare al disco, il driver intercetta questo comando che traduce opportunamente verso il disco, 156

171 ovvero inserisce il comando nella lista degli slot vuoti, e setterà il bit dello slot corrispondente nel registro PxCI (PortX Command Issue). L HBA controllerà ogni volta il registro PxCI per determinare se ci sono comandi da inviare al dispositivo. I passi seguiti per costruire un comando da inviare al dispositivo fisico sono i seguenti: 1. Il driver costruisce un command FIS che metterà nella memoria riservata al controller del disco specificando il tipo di command FIS; 2. Se il comando è ATAPI, il campo ACMD deve essere riempito con il codice specifico del comando ATAPI; 3. Il driver imposterà il Command Header come segue: (a) il campo PRDTL contenente il numero di entry nella tabella PRD; (b) il campo CFL contenente la lunghezza del comando specificato nel campo CFIS; (c) il bit A settato ad 1 se il comando è ATAPI; (d) il bit W settato ad 1 se il comando è verso il dispositivo (scrittura); (e) il bit P (opzionale) settato ad 1; (f) se il disco è connesso ad un Port Multiplier, il campo PMP sarà settato in modo opportuno; 4. Se il comando è un queued command, il driver deve prima settare il corrispondente bit, relativo al tag del comando, nel registro PxSACT. 5. Infine il driver deve settare il corrispondente bit, relativo al tag del comando, nel registro PxCI, per indicare al controller del disco che uno specifico comando è attivo. Una volta che il comando è stato inviato, il driver attenderà il completamento di quest ultimo; questo significa che attenderà l interruzione generata dal dispositivo all atto del completamento di un comando. In particolare la ISR controllerà il 157

172 registro di Interrupt Status (registro IS del controller) per determinare quale porto ha generato quest interrupt. Per ogni porto che ha un interrupt pendente, il driver: 1. determina la causa dell interrupt leggendo il registro PxIS (è possibile che siano settati ad 1 più bit); 2. azzera i bit nel registro PxIS che corrispondono alla causa dell interrupt; 3. azzera il bit di interrupt nel registro del controller IS corrispondente al porto che ha sollevato l interrupt; 4. se si stanno eseguendo comandi non-queued, legge il registro PxCI, e compara il valore corrente alla lista di comandi precedentemente inviati dal driver che sono ancora in sospeso; se invece si stanno eseguendo comandi NCQ, il driver legge il registro PxSACT e compara il valore corrente con la lista dei comandi precedentemente inviati dal driver. Il driver completa con successo quei comandi in sospeso per i quali il bit nel registro appropriato (PxCI o PxSACT) è stato azzerato. 5. Se si è verificato qualche errore il driver deve eseguire le azioni necessarie al recovery; 158

173 Capitolo 4 Il driver ahci If you think it s simple, then you have misunderstood the problem. BJARNE STROUSTRUP Linux gestisce gli hard disk riutilizzando il sottosistema SCSI (Small Computer System Interface), infatti, ogni disco è visto come se fosse proprio un disco SCSI. In questo paragrafo andremo a dare dei cenni su questo sottosistema che ci serviranno successivamente per introdurre i driver di più basso livello che si interfacciano con il disco SATA. Prima di andare nel dettaglio di SCSI, libata e del driver ahci, in Figura 4.1 è mostrata l architettura generica per un driver di un disco SATA evidenziando tutte le componenti descritte nei capitoli precedenti 1. 1 Il blocco Block Layer include anche il sottosistema SCSI 159

174 Figura 4.1: Architettura di un driver per un disco SATA/AHCI 160

175 4.1 SCSI Layer Abbiamo visto nella sezione tutti i componenti del kernel Linux che sono coinvolti in un operazione su disco; inoltre abbiamo visto in particolare come il Block Device Layer sia un componente fondamentale per l elaborazione di richieste di accesso verso il disco e in particolare il block device driver è quello che effettivamente realizza il trasferimento verso il disco. Per i dischi SATA in particolare viene utilizzato il sottosistema SCSI che è uno dei tanti block device driver per i dischi (vedi Figura 4.2). Questo sottosistema è composto da 3 strati principali come mostrato in Figura 4.3. Figura 4.2: Il Sottosistema SCSI nel kernel Linux L Upper Level SCSI, rappresenta la parte più alta dell interfaccia tra il sottosistema SCSI e il kernel. Tale livello consiste in un insieme di driver che accettano le richieste dai livelli superiori (per esempio il VFS) e traduce opportunamente quest ultime in richieste SCSI; inoltre, tale livello è responsabile anche del completamento dei comandi SCSI e la notifica di ciò ai livelli superiori. In questo livello abbiamo considerato solo il Disk driver (sd) che è implementato in <linux/drivers/scsi/sd.c>. Questo driver è il collante tra il Block Layer e il SCSI subsystem. 161

176 Figura 4.3: Sottosistema SCSI Il Mid Level SCSI implementa dei servizi comuni sia per l Upper Level che per il Lower Level; alcuni di questi servizi sono implementati in <linux/drivers/- scsi/scsi.c>. Questo livello è molto importante perchè astrae l implementazione effettiva dei driver di basso livello, e quindi qualsiasi controller di un dispositivo può essere utilizzato allo stesso modo; inoltre, in questo livello vengono anche gestiti la registrazione del driver di basso livello e la relativa gestione di eventuali errori. Essenzialmente, in questo livello viene effettuato l accodamento delle richieste che provengono dal livello superiore per poi inviarle al livello inferiore; le funzioni che permettono questo sono definite in <linux/drivers/scsi/scsi_lib.c>. Quando un comando viene completato, questo livello è responsabile di prendere lo stato della richiesta ricevuto dal livello inferiore e notificare tale stato di completamento della richiesta al livello superiore; ovviamente, se le richieste non vengono eseguite in un certo tempo, oppure si è verificato un errore, tale livello cerca di gestire queste situazioni, per esempio, re-inviando il comando, oppure notificando al livello superiore l errore. Infine, il Low Level SCSI, comprende un insieme di driver di basso livello, cosiddetti SCSI low-level driver (SCSI LLD). Questi driver sono specifici per il particolare controller del dispositivo. Ogni LLD fornisce le specifiche interfacce per la comunicazione con il dispositivo sottostante, ma utilizza allo stesso tempo l insieme di interfacce standard verso il Mid Level. In particolare, in questo lavoro di tesi, il LLD trattato è quello che permette la comunicazione con un disco SATA 162

177 generico, che è il driver ahci. Vedremo nella sezione 4.2 un framework generico per la gestione dei dischi ATA e SATA che è libata. Ciò che lega effettivamente il LLD al sottosistema SCSI è la struttura scsi_host_template definita in <include/scsi/scsi_host.h>: tale struttura verrà utilizzata dal driver di basso livello per specificare, tramite puntatori a funzione, tutte le funzioni di interfaccia verso il sottosistema SCSI. Una delle funzioni più importanti è riferita dall elemento queuecommand che è utilizzata per accodare uno SCSI command block (descrittore del comando SCSI), verso il driver di basso livello. Nel prossimo paragrafo vedremo una panoramica sull implementazione del driver ahci e la relazione che ha con il sottosistema SCSI appena descritto. 4.2 libata In questa sezione andremo nel dettaglio di libata una libreria utilizzata all interno di Linux per supportare tutta la famiglia di dispositivi basati su ATA e in particolare basati su SATA/AHCI. Infatti, libata fornisce una serie di API per lo sviluppo di driver di basso livello per dischi ATA/SATA. libata può essere pensato come un ulteriore livello a stretto contatto con il sottosistema SCSI. Alcuni dei componenti principali di questo framework sono: libata-core che è il cuore della libreria, dove sono definite tutte le funzioni generiche ATA; libata-scsi dove sono definite le funzioni di interfacciamento con il sottosistema SCSI; libata.h dove sono definite strutture e macro per l implementazione di tutto quello specificato nello standard ATA/SATA; libata-eh dove sono specificate tutte le funzioni di gestione degli errori; Il sottosistema SCSI utilizzerà libata per l interfacciamento con i dispositivi SATA, e libata a sua volta utilizzerà il driver specifico del disco. In Figura 4.4 è mostrato 163

178 l insieme dei driver utilizzati nel contesto di gestione di un disco. Nel prossimo paragrafo verranno descritti i moduli ahci e libahci che sono utilizzati nel kernel per gestire un disco SATA/AHCI generico. Per ricapitolare l architettura del driver Figura 4.4: Driver stack utilizzato nella gestione di un disco SATA I moduli ahci e libahci Per un disco SATA generico, in Linux, viene utilizzato il driver ahci che utilizza a sua volta il modulo libahci. Nel primo modulo sono scritte tutte le funzioni che riguardano la registrazione e l inizializzazione del driver del controller AHCI; tali operazioni vengono effettuate all avvio del sistema operativo. Nel secondo modulo sono scritte tutte le routine di basso livello per gestire effettivamente il disco, che possiamo essenzialmente dividire in due funzioni che effettuano la preparazione e l invio dei comandi, e di una funzione che gestisce gli interrupt. I moduli ahci e libahci implementano effettivamente tutto quello che abbiamo discusso nella sez In questo paragrafo mostrerò dapprima le strutture fondamentali utilizzate da libata e poi una panoramica sulle funzioni che permettono il collegamento con le strutture del kernel relative al device model, al bus PCI, e al sottosistema SCSI. 164

179 Le strutture fondamentali In libata un comando è rappresentato dalla struttura ata_queued_cmd o in maniera abbreviata qc; tale struttura è allocata durante l inizializzazione del porto del disco SATA ed è utilizzata ogni volta per l esecuzione dei comandi. Vediamo la struttura: struct ata_queued_cmd { struct ata_port ap ; struct ata_device dev ; struct scsi_cmnd scsicmd ; void ( scsidone ) ( struct scsi_cmnd ) ; struct ata_taskfile tf ; u8 cdb [ ATAPI_CDB_LEN ] ; unsigned long flags ; / ATA_QCFLAG_xxx / unsigned i n t tag ; unsigned i n t n_elem ; unsigned i n t orig_n_elem ; i n t dma_dir ; unsigned i n t sect_size ; unsigned i n t nbytes ; unsigned i n t extrabytes ; unsigned i n t curbytes ; struct scatterlist sgent ; struct scatterlist sg ; struct scatterlist cursg ; unsigned i n t cursg_ofs ; unsigned i n t err_mask ; struct ata_taskfile result_tf ; ata_qc_cb_t complete_fn ; void private_data ; void lldd_task ; } ; Descriviamo alcuni dei campi più importanti: ap è il puntatore all istanza della struttura ata_port, la quale descrive il porto che ha inviato tale comando; la struttura ata_port sarà descritta nel dettaglio più avanti; dev è il puntatore all istanza della struttura ata_device, la quale descrivere il disco nel contesto libata; 165

180 scsicmd è il riferimento all oggetto della struttura che descrive un comando SCSI da cui è stato tradotto il comando in questione; tf è l istanza della struttura taskfile che descrive completamente il Shadow Register Block; flags sono i flag utilizzati dal comando in questione all interno del contesto di libata; tag è il TAG associato al comando; gli elementi n_elem, orig_n_elem, dma_dir, sgent, sg, cursg riguarda la gestione delle operazioni DMA (vedi sez. 2.5); complete_fn è la callback associata alla funzione che completa il comando; private_data permette di memorizzare informazioni specifiche; Come abbiamo visto precedentemente, all interno della struttura ata_queued_cmd c è il riferimento alla struttura principale in libata che è ata_port; tale struttura, come si può intuire, è molto complessa perchè ha al suo interno tutto il necessario per poter rappresentare un porto ATA e in particolare il porto di un controller AHCI; vediamo un pezzo della struttura: struct ata_port {... struct ata_host host ; void private_data ;... } Gli elementi fondamentali sono: host che è il riferimento all oggetto istanza di ata_host che, tramite l elemento private_data permette di accedere alla struttura ahci_host_priv che descrive essenzialmente il Generic Host Control (vedi par ); private_data permette invece di accedere a dati privati del porto, e in particolare nel driver ahci viene utilizzato per accedere alla struttura ahci_port_priv 166

181 in cui ci sono i riferimenti alle aree di memoria che contengono la Command Table, l area Received FIS e un area per memorizzare un comando descritto come FIS e le relative tabelle PRD che sono associate alla scatterlist; inoltre nella struttura ahci_port_priv c è il riferimento al Command Header del comando in questione; per i dettagli di AHCI vedere la Sez La Figura 4.5 mostra tutte le relazioni appena descritte. Figura 4.5: Relazione tra le strutture libata La struttura ahci_port_priv sarà ripresa in maniera dettagliata successivamente. Inizializzazione e abilitazione del controller Il modulo ahci, come tutti i moduli, ha bisogno di essere caricato nel kernel. La funzione che effettua questo è ahci_init: <linux / drivers / ata / ahci. c> s t a t i c i n t init ahci_init ( void ) { 167

182 } r e t u r n pci_register_driver(& ahci_pci_driver ) ; Come abbiamo visto in 2.4.3, si effettua la registrazione del driver nel sottosistema PCI, passando alla funzione adibita il riferimento alla struttura di tipo pci_driver, ovvero ahci_pci_driver; vediamola brevemente: <linux / drivers / ata / ahci. c> s t a t i c struct pci_driver ahci_pci_driver = {. name = DRV_NAME,. id_table = ahci_pci_tbl,. probe = ahci_init_one,. remove = ata_pci_remove_one, #ifdef CONFIG_PM. suspend = ahci_pci_device_suspend,. resume = ahci_pci_device_resume, #endif } ; Da notare sono il riferimento alla id_table che è la struttura ahci_pci_tbl: tale struttura specifica tutti i chipset AHCI supportati dal driver, ed è formata nel seguente modo: s t a t i c c o n s t struct pci_device_id ahci_pci_tbl [ ] = { / I n t e l /... { PCI_VDEVICE ( INTEL, 0 x3b29 ), board_ahci }, / PCH AHCI /... { } / t e r m i n a t e l i s t / } ; possiamo notare la macro PCI_VDEVICE che è simile a PCI_DEVICE (vedi sez ), ma consente di specificare anche dei dati privati al driver nel successivo campo della struttura pci_device_id; infatti subito dopo c è l elemento board_ahci che è utilizzato come riferimento nella lista ahci_port_info[] di tipo ata_port_info, struttura che ha al suo interno ha la struttura ata_port_operations: tale struttura è utilizzata per specificare le funzioni che gestiscono tutte le operazioni di basso livello del driver. Relativamente all elemento board_ahci abbiamo: s t a t i c c o n s t struct ata_port_info ahci_port_info [ ] = { / by f e a t u r e s / 168

183 [ board_ahci ] = {. flags = AHCI_FLAG_COMMON,. pio_mask = ATA_PIO4,. udma_mask = ATA_UDMA6,. port_ops = &ahci_ops, },... si può notare il riferimento alla struttura ata_port_operations, che nel caso specifico ha il nome di ahci_ops; una parte di quest ultima struttura è la seguente: struct ata_port_operations ahci_ops = {.... qc_defer = ahci_pmp_qc_defer,. qc_prep = ahci_qc_prep,.... qc_issue = ahci_qc_issue,. qc_fill_rtf = ahci_qc_fill_rtf,.... scr_read = ahci_scr_read,. scr_write = ahci_scr_write,.... port_start = ahci_port_start,. port_stop = ahci_port_stop,... } ; EXPORT_SYMBOL_GPL ( ahci_ops ) ; Si possono notare le principali funzioni utilizzate dal driver di basso livello ahci per un controller di tipo board_ahci: ahci_pmp_qc_defer è la funzione che effettua il defer del comando, ovvero controlla se il comando può essere inviato o meno; ahci_qc_prep è la funzione che prepara il comando da inviare; ahci_qc_issue è la funzione che invia il comando al dispositivo; ahci_qc_fill_rtf è la funzione che crea un taskfile di risposta ad un comando, se necessario; ahci_scr_read è la funzione che permette di leggere i registri Status/Control Register; 169

184 ahci_scr_write è la funzione che permette di scrivere i registri Status/- Controlo Register; ahci_port_start è la funzione che effettua una serie di inizializzazioni per rendere il porto del controller attivo (vedere i dettagli avanti); ahci_port_stop è la funzione che de-inizializza ogni porto in uso nel controller; In Figura 4.6 è mostrata la relazione tra le strutture citate fin ora in questo paragrafo. Figura 4.6: Relazione tra le strutture utilizzate in ahci Riprendendo la struttura ahci_pci_driver, oltre a tutto quello appena detto, è da notare il riferimento alla funzione di probe che è la funzione ahci_init_one; quest ultima funzione è quella richiamata all accensione del PC, quando viene rilevato il dispositivo connesso al bus PCI, oppure quando viene inserito un nuovo controller nel sistema. Tale funzione essenzialmente effettua le seguenti operazioni: 170

185 acquisisce tutte le regioni di memoria necessarie al controller mappando tutte le strutture descritte nel cap su AHCI; inizializza il controller DMA; inizializza tutti i porti del controller AHCI; setta il dispositivo come abilitato ad agire come bus master; infine attiva l host, ovvero l oggetto istanza di ata_host, che come abbiamo detto in precedenza rappresenta l host bus adapter (HBA, vedi sez. 3.4). E degna di attenzione l ultima istruzione che esegue che esegue ahci_init_one, ovvero l attivazione dell host; vediamola nel dettaglio: <linux / drivers / ata / ahci. c> s t a t i c i n t ahci_init_one ( struct pci_dev pdev, c o n s t struct pci_device_id ent ) {... r e t u r n ata_host_activate ( host, pdev >irq, ahci_interrupt, IRQF_SHARED, & ahci_sht ) ; } La funzione ata_host_activate rende effettivamente attivo il controller AHCI, e in particolare: informa l infrastruttura del kernel che gestisce gli interrupt, che il controller richiede il particolare IRQ number (condiviso) specificato in pdev->irq e che ogni interrupt generato dal dispositivo deve essere gestito dall interrupt handler riferito da ahci_interrupt; tramite ahci_sht il driver indica al sottosistema SCSI il particolare SCSI Host Template utilizzato da questo driver; vediamo in dettaglio la struttura: <linux / drivers / ata / ahci. c>... s t a t i c struct scsi_host_template ahci_sht = { AHCI_SHT ( " a h c i " ) } ;... <linux / ahci. h> #define AHCI_SHT ( drv_name ) \ 171

186 ATA_NCQ_SHT ( drv_name ), \. can_queue = AHCI_MAX_CMDS 1, \. sg_tablesize = AHCI_MAX_SG, \. dma_boundary = AHCI_DMA_BOUNDARY, \. shost_attrs = ahci_shost_attrs, \. sdev_attrs = ahci_sdev_attrs... <linux / libata. h> #define ATA_NCQ_SHT ( drv_name ) \ ATA_BASE_SHT ( drv_name ), \. change_queue_depth = ata_scsi_change_queue_depth... #define ATA_BASE_SHT ( drv_name ) \. module = THIS_MODULE, \. name = drv_name, \. ioctl = ata_scsi_ioctl, \. queuecommand = ata_scsi_queuecmd, \. can_queue = ATA_DEF_QUEUE, \. this_id = ATA_SHT_THIS_ID, \. cmd_per_lun = ATA_SHT_CMD_PER_LUN, \. emulated = ATA_SHT_EMULATED, \. use_clustering = ATA_SHT_USE_CLUSTERING, \. proc_name = drv_name, \. slave_configure = ata_scsi_slave_config, \. slave_destroy = ata_scsi_slave_destroy, \. bios_param = ata_std_bios_param, \. unlock_native_capacity = ata_scsi_unlock_native_capacity, \. sdev_attrs = ata_common_sdev_attrs... come possiamo vedere si specifica il riferimento alla funzione che farà da queuecommand che è la funzione ata_scsi_queuecmd, l anello di congiunzione tra il sottosistema SCSI e libata. grazie alla struttura ata_port_operations, la funzione inizializza ogni porto richiamando la funzione che fa lo start del porto. Per il disco preso in considerazione, la funzione richiamata per fare lo start è ahci_port_start; tale funzione essenzialmente esegue: un mapping DMA coerente (vedi par ) di 3 aree di memoria utilizzate da AHCI: 172

187 1. La prima area è destinata a contenere i Command Header (vedi sez Command Header); 2. La seconda area è destinata a contenere la Received FIS area (vedi sez ); 3. La terza ed ultima area è destinata a contenere i comandi (in termini di FIS) e le relative scatter-gather list (vedi sez Command Table); La Figura 4.7 mostra le aree di memoria appena descritte (per i dettagli su ahci_sg vedi sez ). Figura 4.7: Aree di memoria mappate dalla funzione ahci_port_start infine, rende effettivamente pronto il porto del controller a ricevere richieste per inoltrarle al disco; 173

188 4.2.2 I comandi I comandi in libata possono essere originati da 2 fonti: nel Mid Level SCSI, oppure in libata stesso. I comandi generati all interno di libata sono utilizzati per gestire l inizializzazione e gli errori. In genere, tutte le richieste generate nel Block Layer sono passate al sottosistema SCSI attraverso la funzione callback queuecommand riferita nello SCSI Host Template (SHT) (vedi par ). Abbiamo visto che in libata un comando è rappresentato dalla struttura ata_queued_cmd. I comandi in libata possono essere originati da 2 fonti: nel Mid Level SCSI, oppure in libata stesso. I comandi generati all interno di libata sono utilizzati per gestire l inizializzazione e gli errori. Andremo a descrivere solo i comando generati dal sottosistema SCSI. Invio di comandi SCSI Tralasciando come il driver gestisce i comandi interni a libata, vediamo come si inviano i comandi SCSI verso il driver di basso livello. Tutti i driver che utilizzano libata fanno affidamento alla funzione ata_scsi_queuecmd, già citata in precedenza. Tale funzione permette di accodare un comando SCSI verso il driver di basso livello. Nel caso di ahci il comando SCSI, dopo essere stato accodato, verrà tradotto in un comando ATA/SATA grazie alla funzione ata_scsi_translate; tale funzione effettua nell ordine i seguenti passi: 1. inizializza e alloca un qc con il comando SCSI tradotto opportunamente in ATA; 2. inizializza qc con le informazioni relative al mapping DMA della scatterlist associata al comando SCSI (si suppone che la generazione della scatterlist associata alle operazioni da svolgere è fatta nel sottosistema SCSI); 3. inizializza la callback function per il completamento del comando, permettendo al driver di basso livello di notificare il completamento del comando al sottosistema SCSI; la funzione nel caso di studio è ata_scsi_qc_complete per i comandi ATA e atapi_qc_complete per i comandi ATAPI; entrambe le funzioni invocheranno la funzione puntata da qc->scsidone che effettiva- 174

189 mente invocherà la funzione SCSI che completerà il comando nel sottosistema SCSI; 4. infine, dopo la traduzione del comando, il comando sarà effettivamente inviato al dispositivo tramite la funzione generica ata_qc_issue; tale funzione sarà responsabile di mappare la lista scatter-gather associata al comando (funzione ata_sg_setup) ed inoltre grazie ai puntatori specificati nella struttura ata_port_operations (vedi par ), invocherà le funzioni di preparazione (prep) e di invio (issue) del comando del particolare driver utilizzato; nel caso di studio, verranno invocate rispettivamente le funzioni ahci_qc_prep e ahci_qc_issue; in particolare: ahci_qc_prep effettua le seguenti operazioni: popola il FIS con le informazioni contenuto nello Shadow Register Block riferito da qc->tf ; il FIS è memorizzato nella relativa area di memoria (vedere dopo quando si spiega le aree di memoria mappate con ahci_port_start); tutto questo è fatto dalla funzione ata_tf_to_fis; invoca la funzione ahci_fill_sg per costruire la struttura ahci_sg che rappresenta ogni elemento della PRDT (vedi sez. 3.4); invoca la funzione ahci_fill_cmd_slot per costruire il Command Header relativo al comando; ahci_qc_issue effettua le seguenti operazioni: determina l indirizzo di base dove è mappata tutta la struttura AHCI relativa al porto che sta inviando il comando (la funzione che fà questo è ahci_port_base); modifica le informazioni sul link (descritto dalla struttura ata_link) attivo durante la comunicazione con il dispositivo; in base al protocollo utilizzato dal comando inviato scrive i registri PxSACT e PxCI nel modo opportuno (vedi sez ); 175

190 Completamento dei comandi Una volta che i comandi sono stati inviati correttamente verso il dispositivo, essi saranno completati o attraverso la chiamata alla funzione ata_qc_complete, oppure dopo che è stato raggiunto un time-out. Per i comandi che devono essere gestiti tramite interrupt, la funzione ata_qc_complete verrà richiamata dall interrupt handler specificato durante l attivazione dell host (vedi par ); nel nostro caso di studio, tale funzione è ahci_interrupt. Se si possono completare n comandi accodati, allora la funzione ata_qc_complete viene richiamata n volte dalla funzione ata_qc_complete_multiple. La funzione ata_qc_complete a sua volta chiamerà la funzione ata_qc_complete, che effettuerà le seguenti operazioni: 1. Se è stato utilizzato un mapping DMA, la memoria DMA associata al qc viene demappata; la funzione che effettua questo è ata_sg_clean 2. Il qc viene reso inattivo (il flag ATA_QCFLAG_ACTIVE viene azzerato); 3. Si registrano i qc completati in modo da liberare i TAG utilizzati; 4. Infine, si richiama la callback function qc->complete_fn(), ovvero ata_scsi_qc_complete o atapi_qc_complete. 176

191 Capitolo 5 Ricostruzione del comportamento di un driver An abstraction is one thing that represents several real things equally well. EDSGER WYBE DIJKSTRA In questo capitolo andremo a vedere l approccio utilizzato per ricavare un modello comportamentale di un driver, e in particolare del driver ahci utilizzato come caso di studio. L idea è quella di creare un infrastruttura di monitaraggio dell esecuzione del driver basandosi sulle specifiche dettate nello standard SATA/AHCI (vedi sez. 3.3 e cap. 4). Dapprima mostremo come generare delle tracce di esecuzione reale del driver (Trace Collection Phase) e successivamente creare un astrazione di esso (State Abstracion Phase); dall astrazione ottenuta ricaveremo la macchina a stati finiti che rappresenta il modello comportamentale del driver (FSM Model Generator Phase) e infine, ottenuto il modello, si potrà generare un monitor che possa rilevare malfunzionamenti durante l esecuzione del driver ahci (Monitor Generator Phase). La sequenza di passi per generare il monitor è mostrata in Figura

192 Figura 5.1: Fasi per la creazione di un componente di monitoraggio 5.1 Trace Collection Phase: generazione delle tracce di esecuzione Per generare le tracce di esecuzione del driver, viene utilizzato SystemTap, un infrastruttura che permette di raccogliere informazioni specifiche sull esecuzione di componenti in Linux, soprattutto del kernel; infatti, mette a disposizione costrutti per eseguire il probe di specifiche funzioni, riuscendo ad accedere alle strutture e ai dati di interesse; i probe sono funzioni che vengono invocate da un breakpoint inserito nel codice del kernel, in maniera simile a un debugger. L idea è quella di andare ad effettuare il probe di funzioni fondamentali del driver, in modo tale da costruire istante per istante un log in cui siano inseriti lo stato corrente del driver in termini di valori effettivi delle variabili più importanti dal punto di vista della corretta gestione del dispositivo. Queste variabili appartengono alle strutture discusse 178

193 nel Cap. 4 e in particolare ci siamo riferiti alla struttura ata_queued_command. Andiamo adesso a concretizzare tutto ciò che abbiamo detto. Prima di vedere il codice di probing, andiamo a descrivere il formato utilizzato nel log: [timestamp],[processo],[funzione],[p1;p2;...;pn],[tipo_evento] timestamp: è il timestamp in millisecondi; processo: è il nome del processo; funzione: è il nome della funzione per cui si è fatto il probing; p1;p2;...;pn: sono le variabili di cui è salvato il valore nel log; tipo_evento: questo elemento può valere BEFORE se la riga di log è stata raccolta all inizio dell esecuzione della funzione monitorata, oppure AFTER se la riga di log è stata raccolta al termine della esecuzione della funzione monitorata; Le funzioni fondamentali scelte per il probing sono ahci_qc_issue e ata_scsi_qc_complete (per i dettagli vedi cap. 4). La scelta è ricaduta su queste due funzioni perchè alla chiamata (call) e al ritorno (return) di quest ultime lo stato corrente del driver cambia sempre. Ovviamente per generare un monitor di un driver per un disco che utilizza un altro tipo di controller bisogna andare ad effettuare il probing della specifica funzione di issue. Le variabili, i cui valori costituiscono il contenuto dei parametri p 1,..., p n, appartengono alla struttura dati ata_queued_cmd che come abbiamo visto nel sez è una delle strutture fondamentali del driver. In particolare, le variabili scelte sono: [PxSACT; PxCI; QC_FLAGS; TASKFILE_FLAGS; PROTOCOL; COMMAND_TYPE; TAG; SG_POINTER] PxSACT è il valore del registro omonimo (per i dettagli vedi par ); PxCI è il valore del registro omonimo (per i dettagli vedi par ); 179

194 QC_FLAGS è una bitmask che indica lo stato del qc corrente; TASKFILE_FLAGS è una bitmask che indica lo stato del taskfile; PROTOCOL indica il protocollo utilizzato dal comando corrente; COMMAND_TYPE indica il tipo di comando; TAG indica il tag del comando; SG_POINTER indica il valore dell indirizzo del primo elemento della scatterlist utilizzata dal comando. La generazione del log è stata fatta effettuando il probing delle funzioni su citate durante l esecuzione di test che stressassero in maniera considerevole il disco (vedi sez ). Lo script SystemTap è composto essenzialmente da quattro funzioni di probe che leggono le variabili su descritte stampando un messaggio formattato secondo quello descritto in precedenza. Dopo aver eseguito lo script per il probing delle funzioni, durante i test su citati, è stato ottenuto un file di log. Nella Figura 5.2 è mostrato una parte del log generato con lo script Systemtap. Figura 5.2: Parte del log generato con SystemTap 180

195 5.2 State Abstraction Phase: astrazione dello stato Dopo aver ricavato le tracce di esecuzione del driver, in questo paragrafo descriviamo come è possibile da quest ultime associare un astrazione in modo da estrarre un modello comportamentale del driver. L astrazione è necessaria perchè l insieme dei valori che possono assumere gli elementi che compongono lo stato può essere molto grande; questo implica un numero enorme di stati; tali stati però possono essere raggruppati semanticamente in uno o più stati in numero molto inferiore a quelli che potremmo avere considerando i valori reali degli elementi dello stato. L idea per eseguire l astrazione è per sommi capi quella utilizzata in ADABU [DLWZ06]. Il modello del driver viene costruito a partire dallo stato concreto, ovvero uno stato composto da elementi che hanno il reale valore numerico ricavato dall esecuzione del codice di probing; tale stato verrà astratto dalle cosiddette funzioni di astrazione che consentono di associare essenzialmente una label (etichetta) ad ogni valore concreto dello stato. Inoltre si considera anche la transizione necessaria per passare da uno stato all altro. Nella soluzione proposta, in base allo stato sorgente astratto (abstract source state) e allo stato di destinazione astratto (abstract target state) si ricaverà la transizione che ha portato a questo cambio di stato. Al termine dell esecuzione dello State Abstraction Phase, esso fornità in output un log astratto che sarà poi dato come input al Generatore del modello; il formato di una riga del log astratto è il seguente: [source state],[transition function],[target state] Definiamo adesso, per ogni elemento appartenente allo stato concreto, le relative funzioni di astrazione Astrazione degli elementi dello stato concreto In questo paragrafo andremo a discutere le funzioni che permettono di astrarre ogni elemento dello stato concreto. 181

196 PxSACT, PxCI e TAG Questi due registri, come vistro nel paragrafo 3.4.3, sono necessari per l invio di comandi verso il dispositivo. Vediamo le regole di astrazione utilizzate: 1. se il TAG del comando è definito avremo: (a) se l elemento PxSACT ha il bit TAG-esimo alto, significa che si sta inviando un comando NCQ e quindi lo stato astratto sarà chiamato NCQ COMMAND ISSUED; (b) altrimento se l elemento PxCI ha il bit TAG-esimo alto, significa che si sta inviando un comando non-ncq e quindi lo stato astratto sarà chiamato NON-NCQ COMMAND ISSUED; (c) altrimenti sono in uno stato etichettato - (don t care); 2. altrimenti, se il TAG relativo al comando ha un valore predefinito, pari a ATA_TAG_POISON = 0xfafbfcfd (vedi <linux/libata.h>), significa che il comando non si sta inviando e quindi sono in uno stato etichettato - (don t care); QC_FLAGS Come abbiamo accennato precedentamente, questo elemento dello stato contiene una bitmask che specifica dei flag attivi i quali descrivono lo stato corrente del qc. I flag possibili, associati ad un qc, sono definiti in <linux/libata.h> e sono i seguenti: / s t r u c t ata_queued_cmd f l a g s / ATA_QCFLAG_ACTIVE = ( 1 << 0), / cmd not yet acknowledged to s c s i l y e r / ATA_QCFLAG_DMAMAP = ( 1 << 1), / SG t a b l e i s DMA mapped / ATA_QCFLAG_IO = ( 1 << 3), / standard IO command / ATA_QCFLAG_RESULT_TF = (1 << 4), / r e s u l t TF r e q u e s t e d / ATA_QCFLAG_CLEAR_EXCL = (1 << 5), / c l e a r e x c l _ l i n k on completion / ATA_QCFLAG_QUIET = (1 << 6), / do not r e p o r t d e v i c e e r r o r / ATA_QCFLAG_RETRY = (1 << 7), / r e t r y a f t e r f a i l u r e / ATA_QCFLAG_FAILED = ( 1 << 16), / cmd f a i l e d and i s owned by EH / ATA_QCFLAG_SENSE_VALID = (1 << 17), / s e n s e data v a l i d / ATA_QCFLAG_EH_SCHEDULED = (1 << 18), / EH scheduled ( o b s o l e t e ) / 182

197 Nel caso di studio abbiamo considerato solo il flag ATA_QCFLAG_ACTIVE. Il bit relativo a tale flag, se pari ad 1, specifica che il qc ancora deve essere notificato al sottosistema SCSI e quindi è ancora attivo; se il bit è 0 significa che il qc è considerato completato, ovvero è stato completato nel sottosistema SCSI. Il bit relativo a tale flag diventa 1 appena prima che ata_qc_issue richiami la funzione specifica di preparazione di un comando ahci_qc_prep (vedi par ); invece, il bit relativo al flag diventa 0 appena prima che la funzione ata_qc_complete richiami la funzione ata_scsi_qc_complete (per i dettagli vedi par ). Detto questo vediamo le regole di astrazione utilizzate per questo elemento: 1. se l elemento è pari a 0, significa che il comando è completato ed è stato già notificato al sottosistema SCSI: lo stato astratto è chiamato QC SCSI COMPLETE; 2. altrimenti, se l elemento ha il bit numero 0 pari ad 1 (ovver il flag ATA_QCFLAG_ACTIVE è attivo) significa che il qc è ancora attivo: lo stato astratto è chiamato QC ACTIVE; 3. altrimenti, il qc è considerato non attivo ma non ancora completato nel sottosistema SCSI: lo stato associato è chiamato QC NOT ACTIVE. PROTOCOL Questo elemento dello stato concreto è astratto semplicemente tenendo conto dell enumerazione dei protocolli utilizzata in libata definita in <linux/ata.h>: enum ata_tf_protocols { / ATA t a s k f i l e p r o t o c o l s / ATA_PROT_UNKNOWN, / unknown/ i n v a l i d / ATA_PROT_NODATA, / no data / ATA_PROT_PIO, / PIO data x f e r / ATA_PROT_DMA, / DMA / ATA_PROT_NCQ, / NCQ / ATAPI_PROT_NODATA, / packet command, no data / ATAPI_PROT_PIO, / packet command, PIO data x f e r / ATAPI_PROT_DMA, / packet command with s p e c i a l DMA sauce / } ; 183

198 Se il valore di PROTOCOL è pari a 0 si assegnera il primo elemento dell enumerazione (ATA_PROT_UNKNOWN ), se pari ad 1 il secondo (ATA_PROT_NODATA), e così via. Altrimenti, se il valore non è compreso nell intervallo [0;7], allora l etichetta sarà UNDEF. COMMAND_TYPE Tale elemento sarà astratto con l etichetta che specifica il comando inviato. Per esempio al valore 0x61 è associata FPDMA_WRITE che è il comando di scrittura che utilizza il protocollo NCQ. Le etichette sono basate sulle definizioni date in <linux/ata.h>. Tutti gli altri elementi sono da considerare per sviluppi futuri Transizioni tra gli stati Supponiamo che la riga i-esima del log delle tracce di esecuzione del driver costituisca lo stato sorgente del driver e la riga i+1-esima costituisca invece lo stato di destinazione del driver. Una volta eseguita l astrazione per ogni stato, si deve associare ad una coppia di stati (sorgente, destinazione) la relativa transizione. Nella soluzione proposta, in base all astrazione dello stato di destinazione e ai valori degli elementi astratti, si associano le corrispondenti transizioni secondo le seguenti regole: Elementi PxSACT, PxCI e TAG astratti Elemento QC_FLAGS astratto Transizione associata - QC NOT ACTIVE device_ops_complete - QC SCSI COMPLETE ata_qc_scsi_complete - QC ACTIVE preparation_command NCQ COMMAND ISSUED QC ACTIVE ahci_qc_issue NON-NCQ COMMAND ISSUED QC ACTIVE ahci_qc_issue UNDETERMINATE UNDETERMINATE? Tabella 5.1: Regole di associazione della transizione alla coppia (stato sorgente, stato destinazione) Le transizioni device_ops_complete e preparation_command non hanno la corrispondente funzione del driver associata perchè non corrispondono al return di nessuna delle due funzione di cui si è fatto il probing. La transizione device_ops_complete 184

199 avviene quando il disco ha completato qualche comando e quindi invia un interrupt verso l host che sarà gestito dal driver (sarà richiamata ad un certo punto la funzione ata_qc_scsi_complete). Invece, la transizione preparation_command avviene quando al comando da inviare è stato assegnato il TAG ma esso ancora deve essere inviato. Tutta la State Abstraction Phase è stata implementata sviluppando un parser scritto in Python che prendesse il log concreto prodotto nella Collection Trace Phase e lo astraesse secondo le regole su descritte. 5.3 FSM Model Generator Phase In questo paragrafo descriveremo nel dettaglio come, partendo dall output fornito dallo State Abstraction Layer, si possa generare un modello che rappresenti il comportamento del driver in uso. Il modello di comportamento del driver, nel nostro cado di studio, è descritto da una macchina a stati finiti (Finite State Machine, FSM), anche se è possibile pensare di descrivere il comportamento del driver attraverso altri modelli come per esempio le reti di Petri. La FSM che rappresenta il comportamento del driver è descritta utilizzando il linguaggio XML. Un metamodello che descriva una FSM può essere rappresentato dal class diagram mostrato in Figura

200 Figura 5.3: Class Diagram del metamodello di un FSM In base al metamodello utilizzato, il processo di generazione del modello istanza di quest ultimo, a partire dal log di esecuzione astratto, è descritto attraverso il seguente algoritmo in pseudo codice: create fsm ; add name to fsm ; f o r each line in abstract_log get transition_name from line ; generate key_t f o r transition_name ; i f ( key_t not in hash_table_transition ) then create transition ; add name to transition ; put transition in hast_table_transition [ key_t ] ; end i f get current_transition from hash_table_transition [ key_t ] ; f o r source_state, target_state get state_name from line ; generate key_s f o r state_name ; 186

201 i f ( key_s not in hash_table_state ) create state ; add name to state ; add state in fsm ; get number_of_elements in state from line ; f o r k = 0 to number_of_elements create element ; get value of element from line ; add value to element ; add value to state ; i f ( state is source ) then add state to source of current_transition ; add current_transition to outgoing_transition list of state ; e l s e i f ( state is target ) then add state to target of current_transition ; add current_transition to incoming_transition list of state ; end i f add state to owned_state of fsm ; e l s e get current_state from hash_table_state [ key_s ] ; i f ( current_state is source AND not source in current_transition ) then add current_state to source of current_transition ; add current_transition to outgoing_transition list of current_state ; e l s e i f ( current_state is target AND not target in current_transition ) add current_state to target of current_transition ; add current_transition to incoming_transition list of current_state ; end i f end i f end f o r put current_state in hash_table_state [ key_s ] ; put current_transition in hash_table_state [ key_t ] ; Implementazione Per l implementazione di quanto descritto nel paragrafo precedente, abbiamo ancora utilizzato Python per effettuare il parsing del log astratto, per poi generare due file di testo: 187

202 fsm.txt che descrive la FSM relativa al driver, dove ogni riga specifica lo stato sorgente, la transizione e lo stato destinazione; states.txt che contiene per ogni stato appartenente alla FSM, il valore degli elementi che costituiscono lo stato. Per quanto riguarda il metamodello della FSM e la generazione del modello istanza di quest ultimo, ci siamo serviti di EMF (Eclipse Modeling Framework), un framework che offre strumenti per la modellazione: a partire da una specifica di modello descritta in un formato XML è possibile generare codice JAVA per la gestione del modello. Il metamodello della FSM è stato specificato nel formato standard ecore. Una volta definito il metamodello viene creato un modello istanza che rappresenta il comportamento del driver basandosi sulle informazioni contenute nei due file fms.txt e states.txt. Di seguito è mostrato un esempio di possibile modello considerando solo comandi di FPDMA: 188

203 <?xml version="1.0" encoding="ascii"?> <fsm:fsm xmi:version="2.0" xmlns:xmi=" xmlns:fsm=" name="ahci FSM"> <owned_state name="-/qc SCSI COMPLETE/ATA_PROT_NCQ/FPDMA_WRITE" owning_fsm="/"> <outgoing_transition name="device_ops_complete <device>" <outgoing_transition name="device_ops_complete <device>" <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="preparation_command <libahci>" <incoming_transition name="ata_scsi_qc_complete <libata-scsi>" <values name="value0" value="-"/> <values name="value1" value="qc SCSI COMPLETE"/> <values name="value2" value="ata_prot_ncq"/> <values name="value3" value="fpdma_write"/> </owned_state> <owned_state name="-/qc NOT ACTIVE/ATA_PROT_NCQ/FPDMA_READ" owning_fsm="/"> <outgoing_transition name="ata_scsi_qc_complete <libata-scsi>" <incoming_transition name="device_ops_complete <device>" <incoming_transition name="device_ops_complete <device>" <incoming_transition name="device_ops_complete <device>" <incoming_transition name="device_ops_complete <device>" <values name="value0" value="-"/> <values name="value1" value="qc NOT ACTIVE"/> <values name="value2" value="ata_prot_ncq"/> <values name="value3" value="fpdma_read"/> </owned_state> <owned_state name="ncq COMMAND ISSUED/QC ACTIVE/ATA_PROT_NCQ/FPDMA_WRITE" owning_fsm="/"> <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="device_ops_complete <device>" <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="device_ops_complete <device>" <incoming_transition name="ahci_qc_issue <libahci>" <values name="value0" value="ncq COMMAND ISSUED"/> <values name="value1" value="qc ACTIVE"/> <values name="value2" value="ata_prot_ncq"/> <values name="value3" value="fpdma_write"/> </owned_state> <owned_state name="-/qc ACTIVE/ATA_PROT_NCQ/FPDMA_READ" owning_fsm="/"> <outgoing_transition name="ahci_qc_issue <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <values name="value0" value="-"/> <values name="value1" value="qc ACTIVE"/> <values name="value2" value="ata_prot_ncq"/> <values name="value3" value="fpdma_read"/> </owned_state> 189

204 <owned_state name="-/qc SCSI COMPLETE/ATA_PROT_NCQ/FPDMA_READ" owning_fsm="/"> <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="device_ops_complete <device>" <outgoing_transition name="device_ops_complete <device>" <outgoing_transition name="preparation_command <libahci>" <incoming_transition name="ata_scsi_qc_complete <libata-scsi>" <values name="value0" value="-"/> <values name="value1" value="qc SCSI COMPLETE"/> <values name="value2" value="ata_prot_ncq"/> <values name="value3" value="fpdma_read"/> </owned_state> <owned_state name="-/qc ACTIVE/ATA_PROT_NCQ/FPDMA_WRITE" owning_fsm="/"> <outgoing_transition name="ahci_qc_issue <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <values name="value0" value="-"/> <values name="value1" value="qc ACTIVE"/> <values name="value2" value="ata_prot_ncq"/> <values name="value3" value="fpdma_write"/> </owned_state> <owned_state name="-/qc NOT ACTIVE/ATA_PROT_NCQ/FPDMA_WRITE" owning_fsm="/"> <outgoing_transition name="ata_scsi_qc_complete <libata-scsi>" <incoming_transition name="device_ops_complete <device>" <incoming_transition name="device_ops_complete <device>" <incoming_transition name="device_ops_complete <device>" <incoming_transition name="device_ops_complete <device>" <values name="value0" value="-"/> <values name="value1" value="qc NOT ACTIVE"/> <values name="value2" value="ata_prot_ncq"/> <values name="value3" value="fpdma_write"/> </owned_state> <owned_state name="ncq COMMAND ISSUED/QC ACTIVE/ATA_PROT_NCQ/FPDMA_READ" owning_fsm="/"> <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="device_ops_complete <device>" <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="device_ops_complete <device>" <incoming_transition name="ahci_qc_issue <libahci>" <values name="value0" value="ncq COMMAND ISSUED"/> <values name="value1" value="qc ACTIVE"/> <values name="value2" value="ata_prot_ncq"/> <values name="value3" value="fpdma_read"/> </owned_state> </fsm:fsm> La Figura 5.4 mostra la FSM associato a tale modello. E da sottolineare che la FSM generata è non deterministica. Dalla Figura 5.4 si può notare come alcuni degli stati sono semanticamente gli stessi di quelli descritti nella Sez e in particolare allo stato dell host (vedi sez ); per esempio: lo stato di NCQ COMMAND ISSUED/x/x/x rispecchia lo stato di IssueCommand; 190

205 nello stato di -/QC NOT ACTIVE/x/x si arriva quando l host riceve l interrupt dal dispositivo, ovvero siamo nello stato DeviceInt; nello stato di -/SCSI QC COMPLETE/x/x il driver ha già effettuato tutto quello descritto nella Sez in 3.18; Alcuni stati e transizioni, descritti nella Sez , non sono visibili nella Figura 5.4 perchè sono relativi al livello di trasporto di SATA e quindi si riferiscono alla comunicazione hardware tra il controller e il disco. 5.4 Monitor Generator Phase Una volta generato il modello in un formato standard XML, è possibile generare un monitor che possa rilevare eventuali malfunzionamenti dovuti al driver. Un malfunzionamento del driver si verifica quando la sua esecuzione non aderisce alla macchina a stati finiti generata in precedenza. L idea è quella di basarsi sul modello è generare automaticamente uno script con regole di transizione da una stato all altro; quindi, per ogni transizione, che si traduce in qualche funzione implementata nel driver, bisogna generare delle regole per quanto riguarda gli stati sorgenti (quindi prima di invocare la funzione relativa alla transizione) e per gli stati destinazione (al ritorno della funzione relativa alla transizione). In parole povere, il comportamento del driver è corretto quando: 1. Lo stato sorgente, per una certa transizione, appartiene all insieme di stati sorgenti possibili, ricavati dal modello; 2. Per un dato stato sorgente e una data transizione, essa deve portare ad uno stato destinazione appartenente ai possibili stati di destinazione per quella transizione; Questo si traduce nel fatto che il driver si muove sulla macchina a stati finiti senza percorrere nessun percorso non specificato nel modello comportamentale. Ovviamente, la costruzione del modello non è esaustiva; infatti, come detto nella 191

206 Sez. 5.1, la creazione del modello comportamentale si basa su un esecuzione del driver durante alcuni stress test del disco, e probabilmente alcuni stati ipotetici che potrebbe attraversare il driver non sono stati attraversati durante i test; quindi, il monitor potrebbe dare un numero di falsi positivi che devono essere gestiti Implementazione Il monitor è stato implementato come script SystemTap. Tale script è stato generato automaticamente sfruttando il plugin di Eclipse chiamato Acceleo che è un implementazione di MOF Model to Text Language (MTL); tale plugin permette di generare codice di qualunque tipo a partire da un particolare modello specificato. Il modello utilizzato è quello generato dall FSM Model Generator (vedi sez. 5.3) e grazie alle funzionalità messe a disposizione da Acceleo, è possibile fare il parsing del modello descritto in XML e a seconda degli stati e delle transizioni (si considerano solo transizione in uscita) si generano le regole di transizione precedentemente descritte. Lo script SystemTap generato controllerà istante per istante il comportamento del driver notificando con una scrittura all interno del log del kernel un eventuale malfunzionamento. Il formato del messaggio è il seguente: {SOURCE/TARGET} STATE ERROR transition: nome_funzione [nome_processo] state values: PxSACT = value;pxci = value;qc_flags = value;protocol = value;command = value;tag = value 192

207 Figura 5.4: FSM associata al modello comportamentale del driver per i comandi 193 FPDMA

208 Capitolo 6 Esempio di estrazione di un modello On two occasions I have been asked [by members of Parliament], "Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?"...i am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question. CHARLES BABBAGE In quest ultimo capitolo mostremo un esempio di estrazione di un modello comportamentale di un driver e di come il monitor basato su di esso possa rilevare eventuali malfunzionamenti dovuti a difetti di programmazione all interno del driver. Non viene effettuata una campagna di fault injection esaustiva ma solo modifiche manuali su alcune istruzioni fondamentali per verificare il funzionamento del monitor. Quindi, nelle sezioni successive descriveremo la configurazione di PC utilizzata, il workload utilizzato per estrarre il modello comportamentale del driver e infine una sezione che descrive come abbiamo validato il monitor così ottenuto. 6.1 Creazione di un monitor In questa sezione andremo a descrivere come è possibile creare un monitor. Partiremo descrivendo la configurazione hardware e software utilizzata e successivamente 194

209 mostremo il workload utilizzato per per generare le tracce di esecuzione del driver Configurazione utilizzata Di seguito sono elencate le specifiche tecniche dei componenti utilizzati per l estrazione e la validazione del monitor: Versione del kernel Linux: 3.5.7; Disco fisico utilizzato 1 : ATA device, with non-removable media Model Number: ST AS (vedi [Sea]) Serial Number: 5VH3TBVM Firmware Revision: 0006HPM1 Transport: Serial Modulo del kernel e controller SATA utilizzato 2 (vedi Figura 6.1): Figura 6.1: Controller SATA utilizzato per i dettagli del SATA controller vedere datasheet (vedi [Inta]) SystemTap versione: 2.0 ; Descrizione del workload Il workload per generare il modello comportamentale del driver si basa su due stress test del disco; il motivo per cui è stato fatto questo è perchè le tracce di esecuzione dovevano essere il più esaustive possibili, in modo tale da estrarre un modello del driver significativo. 1 parte prodotta dal comando hdparm -I /dev/sda 2 estratto dal comando lspci -k 195

210 I test utilizzati per stressare il disco sono i seguenti: 1../disktest -Ag -Am -B 16k -C 100 -K 1 -z -ma -pl -P A -S 0: r -w -E 0 -N /afile Tale test appartiene ad una suite di test per Linux chiamata Linux Test Project. Descriviamo alcuni dei parametri utilizzati: -B: identifica la dimensione dei blocchi dati nel trasferimento; nel nostro caso è 16k; -C: identifica il numero di cicli che deve effettuare il test prima di terminare; nel nostr caso il numero di cicli è 100; -K: specifica il numero di thread utilizzati; nel nostro caso è 1; -pl: specifca il pattern utilizzato per il seek che nel nostro caso è Linear writes then reads (vedere manuale disktest); -N : indica il numero di settori disponibili per il trasferimento; Tale test esegue un insieme di read/write con un controllo degli errori per 100 cicli. Se si verifica un errore il test verrà interrotto. 2. iozone -R -l 5 -u 5 -r 4k -s 100m -F f1 f2 f3 f4 f5 tee -a iozone_results.txt Tale test è effettuato utilizzando iozone, un tool di benchmarking per filesystem. Descriviamo il test specificando il significato di ogni opzione: -R: indica ad iozone di generare un output compatibile con excel; -l: specifica il limite inferiore del numero di thread o processi che utilizzerà iozone quando sarà in esecuzione; nel nostro caso sono utilizzati 5 thread; -u: specifica il limite superiore del numero di thread o processi che utilizzerà iozone quando sarà in esecuzione; nel test utilizzato, iozone utilizza al massimo 5 thread. Se si impostassero -l e -u con lo stesso valore n, iozone utilizzerebbe esattamente n thread; quindi, nel nostro test saranno utilizzati esattamente 5 thread; 196

211 -r: specifica la dimensione del record del file che nel nostro test è di 4k; -s: specifica la dimensione del file (o dei file) che verranno utilizzati per il test. Nel nostro caso particolare iozone eseguirà il test su file di 100Mb. -F: specifica il nome dei file temporanei che saranno utilizzati da iozone durante il test. Il numero di file coincide con i valori specificati nei parametri -l e -u. -a: specifica ad iozone di eseguire tutti i 13 tipi di test (lettura, scrittura, rilettura, riscrittura, ecc. per i dettagli vedere manuale di iozone); Modello generato Il modello generato attraverso l utilizzo dell infrastruttura di monitoraggio implementata descritta nel Cap. 5, è quello mostrato in Figura 6.3. La FSM ottenuta ha in totale 80 nodi e 30 transizioni. In Figura 6.3è mostrato uno zoom sulla FSM estratta. 197

212 Figura 6.2: FSM del modello comportamentale del driver 198

213 Figura 6.3: Zoom della FSM del modello comportamentale del driver Di seguito invece è mostrato una parte del modello descritto in XML. <?xml version="1.0" encoding="ascii"?> <fsm:fsm xmi:version="2.0" xmlns:xmi=" xmlns:fsm=" name="ahci FSM"> <owned_state name="-/qc SCSI COMPLETE/ATA_PROT_NODATA/FLUSH_EXT" owning_fsm="/"> <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="preparation_command <libahci>" <incoming_transition name="ata_scsi_qc_complete <libata-scsi>" <values name="value0" value="-"/> <values name="value1" value="qc SCSI COMPLETE"/> <values name="value2" value="ata_prot_nodata"/> <values name="value3" value="flush_ext"/> </owned_state> <owned_state name="-/qc ACTIVE/ATA_PROT_NCQ/FPDMA_READ" owning_fsm="/"> <outgoing_transition name="ahci_qc_issue <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <values name="value0" value="-"/> <values name="value1" value="qc ACTIVE"/> <values name="value2" value="ata_prot_ncq"/> <values name="value3" value="fpdma_read"/> </owned_state> 199

214 <owned_state name="-/qc ACTIVE/ATA_PROT_NCQ/FPDMA_WRITE" owning_fsm="/"> <outgoing_transition name="ahci_qc_issue <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <values name="value0" value="-"/> <values name="value1" value="qc ACTIVE"/> <values name="value2" value="ata_prot_ncq"/> <values name="value3" value="fpdma_write"/> </owned_state>... <owned_state name="-/qc ACTIVE/ATA_PROT_PIO/ID_ATA" owning_fsm="/"> <outgoing_transition name="ahci_qc_issue <libahci>" <incoming_transition name="preparation_command <libahci>" <incoming_transition name="preparation_command <libahci>" <values name="value0" value="-"/> <values name="value1" value="qc ACTIVE"/> <values name="value2" value="ata_prot_pio"/> <values name="value3" value="id_ata"/> </owned_state>... <owned_state name="-/qc SCSI COMPLETE/ATA_PROT_NODATA/SMART" owning_fsm="/"> <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="preparation_command <libahci>" <outgoing_transition name="preparation_command <libahci>" <incoming_transition name="ata_scsi_qc_complete <libata-scsi>" <values name="value0" value="-"/> <values name="value1" value="qc SCSI COMPLETE"/> <values name="value2" value="ata_prot_nodata"/> <values name="value3" value="smart"/> </owned_state>... </fsm:fsm> 6.2 Valutazione del monitor Abbiamo visto nella sez. 6.1 come dal modello comportamentale del driver possiamo generare automaticamente un monitor basato su di esso. In questa sezione valideremo il modello estratto prima di tutto mostrando qual è l overhead introdotto utilizzando il monitor durante l utilizzo del driver; successivamente valideremo il modello introducendo un difetto software all interno del driver per osservare se effettivamente il monitor notifica un comportamento che si discosta da quello atteso. 200

215 6.2.1 Valutazione dell overhead introdotto dal monitor In questa sezione valuteremo l effettivo overhead introdotto dall utilizzo del componente di monitoraggio. La configurazione hardware e software è la stessa utilizzata per creare il monitor (vedi sez ). Nei test effettuati è stato utilizzato mysqlslap uno strumento per emulare l accesso concorrente di client ad un database su un server MySQL misurando i tempi di esecuzione delle query inviate. In relazione al test svolto, sono stati comparati i tempi di esecuzione medi delle query con e senza il monitoraggio. Ogni test è stato ripetuto 100 volte utilizzando i seguenti parametri: auto-generate-sql: genera automaticamente query sql quando non sono specificate; auto-generate-sql-add-autoincrement: aggiunge la colonna AUTO_INCREMENT per generare la tabella; auto-generate-sql-load-type=mixed: genera un carico misto tra inserimenti e letture nella tabella; concurrency: specifica il numero di client concorrenti nell accesso al database. Nel nostro caso vengono utilizzati tre test con 100, 300 e 500 client concorrenti; number-of-queries: specifica il numero di query che saranno distribuite equamente tra i client. Nel nostro caso vengono utilizzati un numero di query pari a 10 n client ; number-char-cols: specifica il numero di colonne con attributi di tipo char. Nel nostro caso il parametro vale 10 per tutti i test; number-int-cols: specifica il numero di colonne con attributi di tipo int. Nel nostro caso il parametro vale 10 per tutti i test; iterations: specifica il numero di iterazione dello stesso test. Nel nostro caso vale 1 per tutti i test. 201

216 Un esempio di test è il seguente for i in {1..100}; do mysqlslap user=root -p password auto-generatesql auto-generate-sql-add-autoincrement auto-generate-sql-load-type=mixed concurrency=100 number-of-queries=10000 number-char-cols=10 number-intcols=10 iterations=1; done I risultati ottenuti sono mostrati nel grafico in Figura 6.4. Figura 6.4: Overhead introdotto dal monitoraggio Si può notare come in tutti i casi il monitoraggio introduce un piccolo overhead nel tempo medio di esecuzione dei test. Tale overhead può essere ritenuto accetabile. Successivamente è stato utilizzato il t-test per evidenziare quanto le distribuzioni dei tempi medi nei vari casi di test siano statisticamente equivalenti. Supponiamo che l ipotesi nulla sia quella per cui la distribuzione differenza, tra la distribuzione in cui è stato condiderato il monitoraggio e quella senza considerare il monitoraggio, provenga da una distribuzione normale con media nulla e varianza non conosciuta; il t-test è stato effettuato considerando un livello di significatività pari a 0.05, riportando i seguenti risultati: 202

217 Numero di client concorrenti h p-value Tabella 6.1: Risultati del t-test Come possiamo notare dalla tabella 6.1 in tutti e tre i casi l ipotesi nulla non viene rigettata e possiamo affermare che le medie delle due distribuzioni considerate possono considerarsi uguali con un livello di confidenza pari al 95% Valutazione del corretto funzionamento In questo paragrafo andremo ad osservare se il monitor effettivamente riesce a rilevare malfunzionamenti del driver. Prima di tutto descriviamo l architettura utilizzata. Abbiamo utilizzato VirtualBox per evitare di danneggiare fisicamente il disco della macchina. VirtualBox è stato configurato utilizzando due dischi virtuali: un disco IDE su cui abbiamo installato Ubuntu 12.10, kernel versione 3.5.7; un disco SATA su cui effettuare il monitoring; il controller SATA emulato è Intel I/O Controller Hub 8 (ICH8) Family, in particolare il chipset 82801HM/HEM (ICH8M/ICH8M-E) (vedi [Intb]); Abbiamo ipotizzato di inserire un difetto software all interno della funzione ahci_qc_issue in modo tale che il monitor possa rilevare questo malfunzionamento. In particolare, il codice è stato modificato come segue: s t a t i c unsigned i n t ahci_qc_issue ( struct ata_queued_cmd qc ) { i f ( qc >tf. protocol == ATA_PROT_NCQ ) // w r i t e l (1 << qc >tag, port_mmio + PORT_SCR_ACT) ; writel ( 0, port_mmio + PORT_SCR_ACT ) ; r e t u r n 0 ; } 203

218 Come si nota dal codice, la modifica è stata semplicemente sostituire all istruzione che scrive correttamente il bit TAG-esimo del comando da inviare nel registro PxSACT, un istruzione che scrive sempre 0 in quest ultimo, per qualunque comando si voglia inviare. Una volta compilato e installato il driver faulty, prima di caricare i moduli ahci e libahci, è stato avviato il monitor in modo da rilevare eventuali anomalie. Andiamo ad osservare le parti del kernel log generato durante quello che abbiamo appena descritto. Ad un certo punto dell esecuzione del driver, in Figura 6.5 possiamo notare che il monitor rileva che il driver invia dei comandi di READ FPDMA che però non rispettano la specifica del protocollo: infatti il registro Px- SACT non viene scritto con il bit TAG-esimo pari ad 1, ma da come ci si aspettava il contenuto del registro resta pari a 0. In Figura 6.6, invece vediamo come il monitor continui a rilevare malfunzionamenti durante l invio di comandi NCQ che portano anche ad errori nel sottosistema SCSI; infine, si può anche notare che il driver decide di non riuscire a supportare il meccanismo NCQ perchè si sono verificati troppi errori. A questo punto il driver, per continuare a funzionare correttamente, utilizzerà comandi che sfrutteranno il protocollo DMA semplice : è il caso del comando READ DMA (0xC8) utilizzato in sostituzione al comando READ FPDMA, come si nota dalla Figura 6.7. Quello che possiamo notare dal kernel log è che pur essendo comandi inviati e completati correttamente, il monitor rileva dei malfunzionamenti che in realtà sono dei falsi positivi: questo accade perchè nel modello comportamentale estratto, comandi di tipo DMA non sono mai stati appresi a causa del fatto che il driver ahci essendo fault free invierà sempre comandi NCQ quando richiesto i quali sostituiscono in un certo senso i comandi DMA. 204

219 Figura 6.5: Kernel Log parte 1 205

220 Figura 6.6: Kernel Log parte 2 206

221 Figura 6.7: Kernel Log parte 3 207

222 Conclusione e Sviluppi Futuri In questo lavoro di tesi è stato proposto un approccio per la creazione di un componente di monitoraggio che rilevi eventuali malfunzionamenti all interno dei device driver. In particolare l attenzione è stata focalizzata sui driver del disco nel kernel Linux, e più precisamente del driver per un disco SATA/AHCI. L idea alla base è stata quella di raccogliere delle tracce di esecuzione del driver e di creare un modello comportamentale su cui si è basata la generazione automatica del componente di monitoraggio. L analisi sperimentale ha mostrato che: Il monitor effettivamente riesce a rilevare tempestivamente l anomalia dovuta all introduzione di difetti software nel driver; L overhead introdotto dal monitor è relativamente basso. Gli sviluppi futuri di questo lavoro di tesi saranno orientati a: Estendere il monitoraggio ad altri registri e strutture dati gestiti dal driver; Utilizzare il monitor per diversi dispositivi e per diversi driver della stessa classe o di classi diverse; Analizzare rigorosamente i falsi positivi e i falsi negativi utilizzando workload e faultload realistici. 208

223 Appendice A Registri ATA e FIS A.1 ATA Command Block Register Figura A.1: Command Block Register (Shadow Register Block) Descriviamo i singoli registri. Alternate Status Register: questo registro contiene le stesse informazioni del registro Status. É un registro in sola lettura e se viene scritto verso l host, significa che il registro Device Control è scritto; Command Register: questo registro di 8 bit contiene il codice del comando inviato al dispositivo. Appena questo registro viene scritto il comando viene eseguito. Quando questo registro è scritto, i contenuti degli altri Command 209

224 Block Register diventano i parametri del comando. Per la lista dei comandi e dei relativi codici vedi [fitsi08]; Data Register: questo è il registro dati utilizzato per il trasferimento dei dati con la modalità PIO; Data Port: questo è il porto utilizzato per il trasferimento di dati utilizzando la modalità DMA; Device Register: questo registro di 8 bit è descritto in questo modo. Vediamo i campi (vedi Figura A.2): Figura A.2: Device Register Obsolete: indica bit obsoleti e non utilizzati. Alcuni host impostano questi bit a 1 ma i dispositivi ignorano il contenuto di questi bit; #: il contenuto di questi bit dipende dal particolare comando inviato; DEV : questo bit indica il device da selezionare. Se impostato a 0 seleziona il Device 0 se impostato a 1 seleziona il Device 1; Device Control Register: questo registro permette all host di effettuare un software reset dei dispositivi connessi e di abilitare o disabilitare l asserzione del segnale INTRQ del dispositivo selezionato. Quando il Device Control Register è scritto, entrambi i dispositivi connessi rispondono a prescindere da quale dispositivo è selezionato. Quando il bit SRST è settato a 1, entrambi i dispositivi eseguono il protocollo per il software reset. Il dispositivo risponderà al bit SRST quando è nella modalità SLEEP. Vediamo i campi di questo registro (vedi Figura A.3): Il bit HOB (High Order Byte) è definito nel 48-bit Address feature set e sostanzialmente è utilizzato per la lettura della più recente scrittura 210

225 Figura A.3: Device Control Register o della precedente quando si utilizza lo schema LBA48 (vedi feature set [fitsi08]); I bit 6:3 sono riservati. Il bit 2 (SRST) è utilizzato dall host per il software reset. Il bit 1 (nien ) è utilizzato per abilitare l asserzione del segnale INTRQ (Interrupt Request) verso l host da parte del device. Il bit 0 è impostato a zero. Error Register: il contenuto di questo registro è valido quando il bit ERR dello Status Register è settato a 1. Da come si può intuire, il registro contiene un codice diagnostico per indicare il tipo di errore che si è verificato. Vediamo i campi (vedi Figura A.4): Figura A.4: Error Register ABRT: è settato a 1 se il comando richiesto è stato abortito a causa di un codice del comando non valido, parametri del comando non validi, comando non supportato, un prerequisito richiesto dal comando non è stato verificato, oppure altri tipi di errore si sono verificati; #: il contenuto di questi bit dipende dal particolare comando; Feature Register: indica essenzialmente il feature set (vedi [fitsi08]) del comando e quindi il suo valore dipende dal comando stesso; LBA High/Byte Count High Register, LBA Mid/Byte Count Register, LBA Low Register sono registri che contengono il logical block address e il loro contenuto dipende dal particolare comando; 211

226 Sector Count/Interrupt Reason Register: per i dispositivi che non supportano PACKET, il registro è chiamato Sector Count è indica il numero di settori che devono essere trasferiti, altrimenti, se il dispositivo supporta PAC- KET, questo registro assume il nome di Interrupt Reason e il suo contenuto dipende dal particolare comando. A.1.1 Status Register Questo registro contiene lo stato del dispositivo e viene aggiornato ogni volta che lo stato corrente del dispositivo è cambiato. Vediamo i campi (vedi Figura A.5): Figura A.5: Status Register Descriviamo i bit: BSY (Busy): se settato a 1 indica che il dispositivo è occupato (busy). Dopo che l host ha scritto nel Command Register il dispositivo deve avere il bit BSY settato a 1, oppure il bit DRQ settato a 1, fino a che il comando non sarà stato completato oppure il dispositivo ha eseguito un rilascio del bus per un comando overlapped (vedere 4.19 del Volume 1 ATA7). Il bit BSY deve essere settato a 1 dal dispositivo solo se si verifica uno dei seguenti eventi: 1. dopo la negazione di RESET oppure dopo aver settato il bit SRST a 1; 2. dopo la scrittura del Command Register se il bit DRQ non è setatto a 1; 3. tra il trasferimento di blocchi di dati durante comandi di data-out utilizzando PIO prima che il bit DRQ sia messo a 0; 4. dopo il trasferimento di blocchi di dati durante comandi di data-out utilizzando PIO prima che il bit DRQ sia messo a 0; 212

227 5. durante il trasferimento dati utilizzando comandi DMA, il bit BSY o il bit DRQ o entrambi saranno settati a 1; 6. dopo che è ricevuto il pacchetto del comando durante l esecuzione di un comando di tipo PACKET; Da notare è che il bit BSY può essere settato a 1 e messo a 0 in maniera molto veloce e quindi la rilevazione di questo cambiamento, da parte dell host, non è sempre precisa. Quando il bit BSY è settato a 1, il dispositivo ha il controllo del Command Block Register e: una scrittura nel Command Block Register da parte dell host causerà un comportamento indeterminato tranne che per la scrittura di un comando di DEVICE RESET; una lettura dal Command Block Register da parte dell host può produrre contenuti non validi tranne che per il bit BSY stesso; Il bit BSY deve essere settato a zero dal dispositivo nei seguenti casi: dopo il settaggio di DRQ a 1 per indicare che il dispositivo è pronto a trasferire dati; al completamento di un comando; non appena il bus viene rilasciato per un comando overlapped; quando il dispositivo è pronto ad accettare comandi che non richiedono DRDY durante un power-on, hardwar o software reset; Quando il bit BSY è azzerato, l host ha il controllo dei Command Block Register, e il dispositivo: non deve impostare DRQ a 1; non deve cambiare il bit ERR; non deve cambiare il contenuto di altri Command Block Register; 213

228 deve impostare il bit SERV a 1 quando è pronto a continuare un comando overlapped che ha rilasciato il bus; deve azzerare il bit DSC quando l azione che usa questo bit è stata completata; DRDY (Device Ready): questo bit indica se il dispositivo è pronto o meno a rispondere ad un comando inviatogli. Esso deve essere settato a zero dal dispositivo quando sono eseguiti comandi di power-on, hardware o software reset o DEVICE RESET EXECUTE o DEVICE DIAGNOSTIC. Quando il bit DRDY è settato a zero, il dispositivo deve accettare e tentare di eseguire i comandi. Il bit DRDY deve essere settato a 1 dal dispositivo: quando il dispositivo è capace di accettare tutti i comandi per i dispositivi che non implementano il PACKET command feature set. prima che il comando sia completato tranne che per i comandi DEVI- CE RESET e EXECUTE DEVICE DIAGNOSTIC per i dispositivi che implementano il PACKET command feature set. Quando il bit DRDY è settato a 1: il dispositivo accetterà e tenterà di eseguire tutti i comandi implementati; i dispositivi che implementano la caratteristica di Power Management, manterranno il bit DRDY settato a 1 quando sono nelle modalità Idle o Standby; DF/SE (Device Fault/Stream Error): Un evento di Device Fault è qualsiasi tipo di evento che impedisce al dispositivo di completare un comando. La funzionalità di Device Fault è implementata dalla maggior parte dei comandi. Uno Stream Error è un evento che si verifica quando un comando appartenente alla Streaming feature set (approfondire) non va a buon fine. 214

229 Command Dependent bit: l uso del bit indicato con # è dipendente dal particolare comando. In precedenza questo bit 4 era utilizzato per il bit DSC (Device Seek Complete); DRQ (Data Request): questo bit indica che un dispositivo è pronto a trasferire dati (word o byte) tra lui e l host. Dopo che l host ha scritto nel Command Register (ovvero invia un comando) il dispositivo deve settare o il bit BSY a 1 oppure il bit DRQ a 1, fino a che il comando non è stato completato oppure il dispositivo ha eseguito un rilascio del bus a causa di un comando overlapped. Il bit DRQ deve essere settato ad 1 dal dispositivo: quando il bit BSY è settato ad 1 e i dati sono pronti per un trasferimento PIO; durante il trasferimento di dati tramite comandi DMA e sia il bit BSY, il bit DRQ o entrambi saranno settati ad 1; Quando il bit DRQ è settato ad 1, l host può: traferire dati attraverso la modalità PIO; trasferire dati attraverso la modalità DMA se i segnali DMARQ e DMACK sono asseriti; Il bit DRQ deve essere settato a zero dal dispositivo: quando si è arrivati all ultima word del trasferimento dati; quando si è arrivati all ultima word del trasferimento tramite un command packet; Quando il bit DRQ è azzerato, l host può: trasferire dati attraverso la modalità DMA se i segnali DMARQ e DMACK sono asseriti e il bit BSY è settato ad 1; Obsolete: questi bit erano definiti nei precedenti standard ATA ma sono stati dichiarati obsoleti in questo standard; 215

230 ERR/CHK (Error/Check): il bit ERR indica che si è verificato un errore durante l esecuzione di un precedente comando. Per i comandi di tipo PACKET e SERVICE, questo bit è chiamato CHK e indica che esiste una condizione di eccezione. Il bit ERR deve essere settato ad 1 dal dispositivo: quando il bit BSY o DRQ è settato ad 1 e si è verificato un errore durante l esecuzione del comando; Quando il bit ERR è settato ad 1: i bit nell Error Register saranno validi; il dispositivo non cambierà il contenuto dei seguenti registri fino a che un nuovo comando sarà accettato, il bit SRST è settato ad 1 o un segnale di RESET è asserito: Error Register; LBA High/Mid/Low Register; Sector Count Register; Device Register; Il bit ERR deve essere settato a zero dal dispositivo: quando un nuovo comando è scritto nel Command Register; quando il bit SRST è settato ad 1; quando il segnale di RESET è asserito; Quando il bit ERR è settato a zero alla fine di un comando, il contenuto dell Error Register sarà ignorato dall host. 216

231 A.2 Frame Information Structure (FIS) A Register FIS Host to Device Nella Figura A.6 è specificato il layout relativo al FIS di tipo Register Host To Device: Figura A.6: Register FIS - Host to Device Vediamo i campi nel dettaglio: FIS Type: è settato a 27h e indica il tipo di pacchetto FIS; C: questo bit è settato ad 1 quando il trasferimento è dovuto ad un aggiornamento del Command Register; il bit è settato a 0 quando invece il trasferimento è dovuto ad un cambiamento del Device Control Register; impostare il bit C ad 1 e il bit SRST ad 1, del Device Control Register, non è valido e questo porta a un comportamento indeterminato; R: Riservato; questo bit è posto a 0; PM Port: quando un dispositivo è connesso attraverso un Port Multiplier, specifica l indirizzo di porta del dispositivo al quale il pacchetto FIS dovrebbe essere consegnato. Questo campo deve essere settato dall host; Command: questo campo contiene il contenuto del Command Register all interno del Command Block Register; 217

232 Features(7:0): questo campo contiene il contenuto del Feature Register all interno del Command Block Register; LBA(7:0): contiene il contenuto del registro LBA Low all interno del Command Block Register; LBA(15:8): contiene il contenuto del registro LBA Mid all interno del Command Block Register; LBA(23:16): contiene il contenuto del registro LBA High all interno del Command Block Register; Device: contiene il contenuto del Device Register all interno del Command Block Register; LBA(24:31): utilizzato per il supporto a LBA48; LBA(39:32): utilizzato per il supporto a LBA48; LBA(47:40): utilizzato per il supporto a LBA48; Features(15:8): utilizzato per il supporto a LBA48; Count(7:0): contiene il contenuto del Sector Count Register all interno del Command Block Register; Count(15:8): utilizzato per il supporto a LBA48; ICC (Isochronous Command Completion): contiene un valore settato dall host che informa il device di un timeout per il completamento di un comando. Se un comando non definisce l uso di questo campo, allora sarà Reserved; Control: contiene il contenuto del Device Control Register all interno del Command Block Register; Da come possiamo vedere, questo pacchetto FIS contiene tutti i campi relativi al Command Block Register utilizzato in ATA(IDE). Quindi, Register Host to Device FIS è utilizzato per il trasferimento del contenuto del Command Block Register 218

233 dall host verso il device, ovvero è utilizzato per inviare un comando ATA o un controllo al dispositivo. A Register FIS Device to Host Nella Figura A.7 è specificato il layout relativo al FIS di tipo Register Device to Host: Figura A.7: Register FIS - Device to Host Vediamo i campi nel dettaglio: FIS Type: è settato a 34h e indica il tipo di pacchetto FIS; I : Interrupt Bit; questo bit riflette lo stato della linea di interrupt del dispositivo. I dispositivi non devono modificare questo bit basandosi sullo stato del bit nien ricevuto nel FIS Register Host to Device; R: Riservato; questo bit è posto a 0; PM Port: quando un dispositivo endpoint è connesso attraverso un Port Multiplier, specifica l indirizzo di porta del dispositivo dal quale il pacchetto FIS è ricevuto. Questo campo è settato dal Port Multiplier; dispositivi endpoint devono settare questo campo a 0h; Status: questo campo contiene il contenuto dello Status (e Alternate Status) Register all interno del Command Block Register; 219

234 Error: questo campo contiene il contenuto dell Error Register all interno del Command Block Register; LBA(7:0): contiene il contenuto del registro LBA Low all interno del Command Block Register; LBA(15:8): contiene il contenuto del registro LBA Mid all interno del Command Block Register; LBA(23:16): contiene il contenuto del registro LBA High all interno del Command Block Register; Device: contiene il contenuto del Device Register all interno del Command Block Register; LBA(24:31): utilizzato per il supporto a LBA48; LBA(39:32): utilizzato per il supporto a LBA48; LBA(47:40): utilizzato per il supporto a LBA48; Features(15:8): utilizzato per il supporto a LBA48; Count(7:0): contiene il contenuto del Sector Count Register all interno del Command Block Register; Count(15:8): utilizzato per il supporto a LBA48; Il pacchetto Register FIS - Device to Host, da come si può intuire, viene utilizzato dal device per notificare all host che qualche registro ATA del Command Block Register è cambiato. A Set Device Bits Device to Host Nella Figura A.8 è specificato il layout relativo al Set Device Bits - Device to Host. 220

235 Figura A.8: Set Device Bits - Device to Host Vediamo i campi nel dettaglio: FIS Type: è settato a A1h e indica il tipo di pacchetto FIS; I : Interrupt Bit; questo bit notifica all host adapter di entrare in uno stato di interrupt pendente. N : Notification Bit; questo bit notifica all host che il dispositivo fisico ha bisogno di essere gestito. Se tale bit è impostato ad 1, l host dovrebbe interrogare il dispositivo per capire quali azioni intraprendere. Se il bit è impostato a 0, il dispositivo non richiede nessuna attenzione da parte dell host; Error: contiene il valore dell Error Register del Shadow Register Block; PM Port: quando un dispositivo endpoint è connesso attraverso un Port Multiplier, specifica l indirizzo di porta del dispositivo dal quale il pacchetto FIS è ricevuto. Questo campo è settato dal Port Multiplier; dispositivi endpoint devono settare questo campo a 0h; Status Hi: contiene i valori aggiornati dei bit 6, 5 e 4 dello Status Register nel Shadow Block Register; Status Low: contiene i valori aggiornati dei bit 2, 1 e 0 dello Status Register nel Shadow Block Register; Protocol Specific: il valore di questo elemento è definito solo se si utilizzano comandi Native Command Queueing (NCQ) e deve essere pari a 0 altrimenti; Reserved: questo campo deve essere posto a 0; 221

236 A DMA Activate FIS Device to Host Nella Figura A.9 è specificato il layout di un pacchetto DMA Activate FIS Device to Host. Figura A.9: DMA Activate FIS Vediamo i campi nel dettaglio: FIS Type: è settato a 39h e indica il tipo di pacchetto FIS; R: Riservato; questo bit è posto a 0; PM Port: quando un dispositivo endpoint è connesso attraverso un Port Multiplier, specifica l indirizzo di porta del dispositivo dal quale il pacchetto FIS è ricevuto. Questo campo è settato dal Port Multiplier; dispositivi endpoint devono settare questo campo a 0h; Questo tipo di pacchetto è utilizzato dal dispositivo per segnalare all host di procedere con un trasferimento dati DMA dall host al device. Può crearsi una situazione in cui l host ha bisogno di inviare DATA FIS (vedere definizione dopo) multipli in modo da completare l intera richiesta di traferimento. L host dovrà aspettare la ricezione con successo di un DMA Activate FIS prima dell invio di ogni singolo DATA FIS di cui aveva bisogno. A DMA Setup FIS Bidirectional Nella Figura A.10è specificato il layout di un pacchetto DMA Setup FIS Device to Host. 222

237 Figura A.10: DMA Setup FIS - Bidirectional Vediamo i campi nel dettaglio: FIS Type: è settato a 41h e indica il tipo di pacchetto FIS; A: (Auto Activate); questo bit se settato ad 1, e il DMA Setup FIS è con direzione di traferimento dati Host-to-Device, comporta che l host deve iniziare il trasferimento del primo DATA FIS verso il dispositivo dopo che è stato stabilito il contesto DMA per il trasferimento stesso; il dispositivo non deve trasmettere un DMA Activate FIS per attivare la trasmissione del primo DATA FIS da parte dell host. Se il bit è settato a 0, un DMA Activate FIS è richiesto per l attivazione della trasmissione del primo DATA FIS dall host quando la direzione del trasferimento dati è Host-to-Device; I : Interrupt Bit; se questo bit è settato ad 1, un interrupt pendente deve essere generata quando il DMA Transfer Count (vedere definizione dopo) è esaurito. I dispositivi non devono modificare il comportamento di questo bit basandosi sullo stato del bit nien ricevuto nei FIS Register Host to Device; D: Direction; questo bit specifica se i successivi dati trasferiti dopo questo FIS sono dal trasmettitore al ricevitore o dal ricevitore al trasmettitore. Se il bit è 223

238 settato ad 1 la direzione del trasferimento è trasmettitore-ricevitore (Deviceto-Host, ovvero scrittura verso la memoria dell host), altrimenti se settato a 0 la direzione è ricevitore-trasmettitore (Host-to-Device, ovvero lettura dalla memoria dell host); R: Riservato; questo bit è posto a 0; PM Port: quando un dispositivo endpoint è connesso attraverso un Port Multiplier, specifica l indirizzo di porta del dispositivo al quale il pacchetto FIS dovrebbe essere consegnato a o ricevuto da. Questo campo è settato dall host se si ha una trasmissione Host-to-Device, altrimenti è settato dal Port Multiplier per una trasmissione Device-to-Host. I dispositivi endpoint coinvolti in una trasmissione Device-to-Host, devono settare questo campo a 0h; DMA Buffer Identifier Low/High: questo campo è utilizzato per identificare la regione DMA Buffer nella memoria dell host (RAM). Il contenuto di questo campo è ovviamente dipendente dall host. Il Buffer Identifier è fornito al dispositivo da parte dell host e il dispositivo lo rispedisce com è (echo) di nuovo all host; questo consente di passare un indirizzo fisico, oppure nelle implementazioni più complesse, il Buffer Identifier potrebbe essere una lista scatter/gather o ancora altre informazioni che possono identificare un channel DMA; DMA Buffer Offset: questo campo contiene il byte offset all interno del buffer. I bit (1:0) devono essere zero; DMA Transfer Count: questo campo contiene il numero di byte da leggere o scrivere. Il bit 0 deve essere zero; Il DMA Setup FIS Bidirectional è il meccanismo attraverso il quale viene avviato il First-Party DMA access, ovvero l accesso da parte del device alla memoria dell host. Quindi questo pacchetto è utilizzato per richiedere all host o al device di programmare il proprio DMA Controller prima di effettuare il trasferimento 224

239 dati. Il pacchetto FIS permette di astrarre le reali regioni di memoria dell host referenziandole attraverso un base memory descriptor che rappresenta una regione di memoria che l host ha garantito al dispositivo per l accesso. Nello standard l implementazione specifica di quest astrazione non è definita. A PIO Setup FIS Host to Device Nella Figura A.11 è specificato il layout di un pacchetto PIO Setup FIS Host to Device. Figura A.11: PIO Setup FIS - Host to Device Vediamo i campi nel dettaglio: FIS Type: è settato a 5Fh e indica il tipo di pacchetto FIS; I : Interrupt Bit; questo bit riflette lo stato della linea di interrupt del dispositivo. I dispositivi non devono modificare questo bit basandosi sullo stato del bit nien ricevuto nel FIS Register Host to Device; R: Riservato; questo bit è posto a 0; D: specifica la direzione del trasferimento dati. Quando è settato ad 1 il trasferimento è dal device all host, quando è settato a 0 il trasferimento è dall host al device; PM Port: quando un dispositivo endpoint è connesso attraverso un Port Multiplier, specifica l indirizzo di porta del dispositivo dal quale il pacchet- 225

Input/Output. Moduli di Input/ Output. gestiscono quantità di dati differenti a velocità diverse in formati diversi. n Grande varietà di periferiche

Input/Output. Moduli di Input/ Output. gestiscono quantità di dati differenti a velocità diverse in formati diversi. n Grande varietà di periferiche Input/Output n Grande varietà di periferiche gestiscono quantità di dati differenti a velocità diverse in formati diversi n Tutti più lenti della CPU e della RAM n Necessità di avere moduli di I/O Moduli

Dettagli

Il sistema di I/O. Hardware di I/O Interfacce di I/O Software di I/O. Introduzione

Il sistema di I/O. Hardware di I/O Interfacce di I/O Software di I/O. Introduzione Il sistema di I/O Hardware di I/O Interfacce di I/O Software di I/O Introduzione 1 Sotto-sistema di I/O Insieme di metodi per controllare i dispositivi di I/O Obiettivo: Fornire ai processi utente un interfaccia

Dettagli

La gestione di un calcolatore. Sistemi Operativi primo modulo Introduzione. Sistema operativo (2) Sistema operativo (1)

La gestione di un calcolatore. Sistemi Operativi primo modulo Introduzione. Sistema operativo (2) Sistema operativo (1) La gestione di un calcolatore Sistemi Operativi primo modulo Introduzione Augusto Celentano Università Ca Foscari Venezia Corso di Laurea in Informatica Un calcolatore (sistema di elaborazione) è un sistema

Dettagli

STRUTTURE DEI SISTEMI DI CALCOLO

STRUTTURE DEI SISTEMI DI CALCOLO STRUTTURE DEI SISTEMI DI CALCOLO 2.1 Strutture dei sistemi di calcolo Funzionamento Struttura dell I/O Struttura della memoria Gerarchia delle memorie Protezione Hardware Architettura di un generico sistema

Dettagli

Architettura di un calcolatore

Architettura di un calcolatore 2009-2010 Ingegneria Aerospaziale Prof. A. Palomba - Elementi di Informatica (E-Z) 7 Architettura di un calcolatore Lez. 7 1 Modello di Von Neumann Il termine modello di Von Neumann (o macchina di Von

Dettagli

Dispensa di Informatica I.1

Dispensa di Informatica I.1 IL COMPUTER: CONCETTI GENERALI Il Computer (o elaboratore) è un insieme di dispositivi di diversa natura in grado di acquisire dall'esterno dati e algoritmi e produrre in uscita i risultati dell'elaborazione.

Dettagli

Generazione Automatica di Asserzioni da Modelli di Specifica

Generazione Automatica di Asserzioni da Modelli di Specifica UNIVERSITÀ DEGLI STUDI DI MILANO BICOCCA FACOLTÀ DI SCIENZE MATEMATICHE FISICHE E NATURALI Corso di Laurea Magistrale in Informatica Generazione Automatica di Asserzioni da Modelli di Specifica Relatore:

Dettagli

Software relazione. Software di base Software applicativo. Hardware. Bios. Sistema operativo. Programmi applicativi

Software relazione. Software di base Software applicativo. Hardware. Bios. Sistema operativo. Programmi applicativi Software relazione Hardware Software di base Software applicativo Bios Sistema operativo Programmi applicativi Software di base Sistema operativo Bios Utility di sistema software Software applicativo Programmi

Dettagli

Sistemi Operativi IMPLEMENTAZIONE DEL FILE SYSTEM. D. Talia - UNICAL. Sistemi Operativi 9.1

Sistemi Operativi IMPLEMENTAZIONE DEL FILE SYSTEM. D. Talia - UNICAL. Sistemi Operativi 9.1 IMPLEMENTAZIONE DEL FILE SYSTEM 9.1 Implementazione del File System Struttura del File System Implementazione Implementazione delle Directory Metodi di Allocazione Gestione dello spazio libero Efficienza

Dettagli

Introduzione alle tecnologie informatiche. Strumenti mentali per il futuro

Introduzione alle tecnologie informatiche. Strumenti mentali per il futuro Introduzione alle tecnologie informatiche Strumenti mentali per il futuro Panoramica Affronteremo i seguenti argomenti. I vari tipi di computer e il loro uso Il funzionamento dei computer Il futuro delle

Dettagli

ARCHITETTURA DELL ELABORATORE

ARCHITETTURA DELL ELABORATORE 1 ISTITUTO DI ISTRUZIONE SUPERIORE ANGIOY ARCHITETTURA DELL ELABORATORE Prof. G. Ciaschetti 1. Tipi di computer Nella vita di tutti giorni, abbiamo a che fare con tanti tipi di computer, da piccoli o piccolissimi

Dettagli

Introduzione alla Virtualizzazione

Introduzione alla Virtualizzazione Introduzione alla Virtualizzazione Dott. Luca Tasquier E-mail: luca.tasquier@unina2.it Virtualizzazione - 1 La virtualizzazione è una tecnologia software che sta cambiando il metodo d utilizzo delle risorse

Dettagli

Sistemi Operativi STRUTTURA DEI SISTEMI OPERATIVI 3.1. Sistemi Operativi. D. Talia - UNICAL

Sistemi Operativi STRUTTURA DEI SISTEMI OPERATIVI 3.1. Sistemi Operativi. D. Talia - UNICAL STRUTTURA DEI SISTEMI OPERATIVI 3.1 Struttura dei Componenti Servizi di un sistema operativo System Call Programmi di sistema Struttura del sistema operativo Macchine virtuali Progettazione e Realizzazione

Dettagli

Approccio stratificato

Approccio stratificato Approccio stratificato Il sistema operativo è suddiviso in strati (livelli), ciascuno costruito sopra quelli inferiori. Il livello più basso (strato 0) è l hardware, il più alto (strato N) è l interfaccia

Dettagli

Il Sistema Operativo. C. Marrocco. Università degli Studi di Cassino

Il Sistema Operativo. C. Marrocco. Università degli Studi di Cassino Il Sistema Operativo Il Sistema Operativo è uno strato software che: opera direttamente sull hardware; isola dai dettagli dell architettura hardware; fornisce un insieme di funzionalità di alto livello.

Dettagli

Sistemi Operativi IMPLEMENTAZIONE DEL FILE SYSTEM. Implementazione del File System. Struttura del File System. Implementazione

Sistemi Operativi IMPLEMENTAZIONE DEL FILE SYSTEM. Implementazione del File System. Struttura del File System. Implementazione IMPLEMENTAZIONE DEL FILE SYSTEM 9.1 Implementazione del File System Struttura del File System Implementazione Implementazione delle Directory Metodi di Allocazione Gestione dello spazio libero Efficienza

Dettagli

Il database management system Access

Il database management system Access Il database management system Access Corso di autoistruzione http://www.manualipc.it/manuali/ corso/manuali.php? idcap=00&idman=17&size=12&sid= INTRODUZIONE Il concetto di base di dati, database o archivio

Dettagli

PROTOTIPAZIONE DI UN TRADUTTORE DA SORGENTE PLC AD ASSEMBLY DI UNA MACCHINA VIRTUALE

PROTOTIPAZIONE DI UN TRADUTTORE DA SORGENTE PLC AD ASSEMBLY DI UNA MACCHINA VIRTUALE PROTOTIPAZIONE DI UN TRADUTTORE DA SORGENTE PLC AD ASSEMBLY DI UNA MACCHINA VIRTUALE Relatore: prof. Michele Moro Laureando: Marco Beggio Corso di laurea in Ingegneria Informatica Anno Accademico 2006-2007

Dettagli

Capitolo 11 -- Silberschatz

Capitolo 11 -- Silberschatz Implementazione del File System Capitolo 11 -- Silberschatz Implementazione del File System File system: Definizione dell aspetto del sistema agli occhi dell utente Algoritmi e strutture dati che permettono

Dettagli

Contenuti. Visione macroscopica Hardware Software. 1 Introduzione. 2 Rappresentazione dell informazione. 3 Architettura del calcolatore

Contenuti. Visione macroscopica Hardware Software. 1 Introduzione. 2 Rappresentazione dell informazione. 3 Architettura del calcolatore Contenuti Introduzione 1 Introduzione 2 3 4 5 71/104 Il Calcolatore Introduzione Un computer...... è una macchina in grado di 1 acquisire informazioni (input) dall esterno 2 manipolare tali informazioni

Dettagli

Software di sistema e software applicativo. I programmi che fanno funzionare il computer e quelli che gli permettono di svolgere attività specifiche

Software di sistema e software applicativo. I programmi che fanno funzionare il computer e quelli che gli permettono di svolgere attività specifiche Software di sistema e software applicativo I programmi che fanno funzionare il computer e quelli che gli permettono di svolgere attività specifiche Software soft ware soffice componente è la parte logica

Dettagli

Lezione 2 Principi Fondamentali di SO Interrupt e Caching. Sommario

Lezione 2 Principi Fondamentali di SO Interrupt e Caching. Sommario Lezione 2 Principi Fondamentali di SO Interrupt e Caching Sommario Operazioni di un SO: principi fondamentali Una visione schematica di un calcolatore Interazione tra SO, Computer e Programmi Utente 1

Dettagli

Corso di Sistemi di Elaborazione delle informazioni

Corso di Sistemi di Elaborazione delle informazioni Corso di Sistemi di Elaborazione delle informazioni Sistemi Operativi Francesco Fontanella Complessità del Software Software applicativo Software di sistema Sistema Operativo Hardware 2 La struttura del

Dettagli

File system II. Sistemi Operativi Lez. 20

File system II. Sistemi Operativi Lez. 20 File system II Sistemi Operativi Lez. 20 Gestione spazi su disco Esiste un trade-off,tra spreco dello spazio e velocità di trasferimento in base alla dimensione del blocco fisico Gestione spazio su disco

Dettagli

Sistema operativo: Gestione della memoria

Sistema operativo: Gestione della memoria Dipartimento di Elettronica ed Informazione Politecnico di Milano Informatica e CAD (c.i.) - ICA Prof. Pierluigi Plebani A.A. 2008/2009 Sistema operativo: Gestione della memoria La presente dispensa e

Dettagli

Informatica - A.A. 2010/11

Informatica - A.A. 2010/11 Ripasso lezione precedente Facoltà di Medicina Veterinaria Corso di laurea in Tutela e benessere animale Corso Integrato: Matematica, Statistica e Informatica Modulo: Informatica Esercizio: Convertire

Dettagli

FONDAMENTI di INFORMATICA L. Mezzalira

FONDAMENTI di INFORMATICA L. Mezzalira FONDAMENTI di INFORMATICA L. Mezzalira Possibili domande 1 --- Caratteristiche delle macchine tipiche dell informatica Componenti hardware del modello funzionale di sistema informatico Componenti software

Dettagli

Il software impiegato su un computer si distingue in: Sistema Operativo Compilatori per produrre programmi

Il software impiegato su un computer si distingue in: Sistema Operativo Compilatori per produrre programmi Il Software Il software impiegato su un computer si distingue in: Software di sistema Sistema Operativo Compilatori per produrre programmi Software applicativo Elaborazione testi Fogli elettronici Basi

Dettagli

Il Software e Il Sistema Operativo. Prof. Francesco Accarino IIS Altiero Spinelli A.S. 09/10

Il Software e Il Sistema Operativo. Prof. Francesco Accarino IIS Altiero Spinelli A.S. 09/10 Il Software e Il Sistema Operativo Prof. Francesco Accarino IIS Altiero Spinelli A.S. 09/10 Cosa Impareremo Programmi e Processi Struttura del Sistema Operativo Sviluppo di Programmi I files e la loro

Dettagli

Architettura di un sistema operativo

Architettura di un sistema operativo Architettura di un sistema operativo Dipartimento di Informatica Università di Verona, Italy Struttura di un S.O. Sistemi monolitici Sistemi a struttura semplice Sistemi a livelli Virtual Machine Sistemi

Dettagli

Sistemi Operativi (modulo di Informatica II) Sottosistema di I/O

Sistemi Operativi (modulo di Informatica II) Sottosistema di I/O Sistemi Operativi (modulo di Informatica II) Sottosistema di I/O Patrizia Scandurra Università degli Studi di Bergamo a.a. 2009-10 Sommario L hardware di I/O Struttura Interazione tra computer e controllori

Dettagli

Architettura di un sistema di calcolo

Architettura di un sistema di calcolo Richiami sulla struttura dei sistemi di calcolo Gestione delle Interruzioni Gestione della comunicazione fra processore e dispositivi periferici Gerarchia di memoria Protezione. 2.1 Architettura di un

Dettagli

1. BASI DI DATI: GENERALITÀ

1. BASI DI DATI: GENERALITÀ 1. BASI DI DATI: GENERALITÀ BASE DI DATI (DATABASE, DB) Raccolta di informazioni o dati strutturati, correlati tra loro in modo da risultare fruibili in maniera ottimale. Una base di dati è usualmente

Dettagli

Esame di INFORMATICA

Esame di INFORMATICA Università di L Aquila Facoltà di Biotecnologie Esame di INFORMATICA Lezione 4 MACCHINA DI VON NEUMANN Anni 40 i dati e i programmi che descrivono come elaborare i dati possono essere codificati nello

Dettagli

DMA Accesso Diretto alla Memoria

DMA Accesso Diretto alla Memoria Testo di rif.to: [Congiu] - 8.1-8.3 (pg. 241 250) 08.a DMA Accesso Diretto alla Memoria Motivazioni Organizzazione dei trasferimenti DMA Arbitraggio del bus di memoria Trasferimento di un blocco di dati

Dettagli

Comunicazione tra Computer. Protocolli. Astrazione di Sottosistema di Comunicazione. Modello di un Sottosistema di Comunicazione

Comunicazione tra Computer. Protocolli. Astrazione di Sottosistema di Comunicazione. Modello di un Sottosistema di Comunicazione I semestre 04/05 Comunicazione tra Computer Protocolli Prof. Vincenzo Auletta auletta@dia.unisa.it http://www.dia.unisa.it/professori/auletta/ Università degli studi di Salerno Laurea in Informatica 1

Dettagli

ASPETTI GENERALI DI LINUX. Parte 2 Struttura interna del sistema LINUX

ASPETTI GENERALI DI LINUX. Parte 2 Struttura interna del sistema LINUX Parte 2 Struttura interna del sistema LINUX 76 4. ASPETTI GENERALI DEL SISTEMA OPERATIVO LINUX La funzione generale svolta da un Sistema Operativo può essere definita come la gestione dell Hardware orientata

Dettagli

Corso di Informatica

Corso di Informatica Corso di Informatica Modulo T2 3-Compilatori e interpreti 1 Prerequisiti Principi di programmazione Utilizzo di un compilatore 2 1 Introduzione Una volta progettato un algoritmo codificato in un linguaggio

Dettagli

LABORATORIO DI SISTEMI

LABORATORIO DI SISTEMI ALUNNO: Fratto Claudio CLASSE: IV B Informatico ESERCITAZIONE N : 1 LABORATORIO DI SISTEMI OGGETTO: Progettare e collaudare un circuito digitale capace di copiare le informazioni di una memoria PROM in

Dettagli

Il SOFTWARE DI BASE (o SOFTWARE DI SISTEMA)

Il SOFTWARE DI BASE (o SOFTWARE DI SISTEMA) Il software Software Il software Il software è la sequenza di istruzioni che permettono ai computer di svolgere i loro compiti ed è quindi necessario per il funzionamento del calcolatore. Il software può

Dettagli

Introduzione. Classificazione di Flynn... 2 Macchine a pipeline... 3 Macchine vettoriali e Array Processor... 4 Macchine MIMD... 6

Introduzione. Classificazione di Flynn... 2 Macchine a pipeline... 3 Macchine vettoriali e Array Processor... 4 Macchine MIMD... 6 Appunti di Calcolatori Elettronici Esecuzione di istruzioni in parallelo Introduzione... 1 Classificazione di Flynn... 2 Macchine a pipeline... 3 Macchine vettoriali e Array Processor... 4 Macchine MIMD...

Dettagli

Con il termine Sistema operativo si fa riferimento all insieme dei moduli software di un sistema di elaborazione dati dedicati alla sua gestione.

Con il termine Sistema operativo si fa riferimento all insieme dei moduli software di un sistema di elaborazione dati dedicati alla sua gestione. Con il termine Sistema operativo si fa riferimento all insieme dei moduli software di un sistema di elaborazione dati dedicati alla sua gestione. Compito fondamentale di un S.O. è infatti la gestione dell

Dettagli

Il Sistema Operativo (1)

Il Sistema Operativo (1) E il software fondamentale del computer, gestisce tutto il suo funzionamento e crea un interfaccia con l utente. Le sue funzioni principali sono: Il Sistema Operativo (1) La gestione dell unità centrale

Dettagli

Laboratorio di Informatica di Base Archivi e Basi di Dati

Laboratorio di Informatica di Base Archivi e Basi di Dati Laboratorio di Informatica di Base Archivi e Basi di Dati Introduzione La memorizzazione dei dati è un aspetto molto importante dell informatica Oggi, mediante i computer, è possibile memorizzare e modificare

Dettagli

Introduzione alla programmazione in C

Introduzione alla programmazione in C Introduzione alla programmazione in C Testi Consigliati: A. Kelley & I. Pohl C didattica e programmazione B.W. Kernighan & D. M. Ritchie Linguaggio C P. Tosoratti Introduzione all informatica Materiale

Dettagli

Reti di Telecomunicazione Lezione 8

Reti di Telecomunicazione Lezione 8 Reti di Telecomunicazione Lezione 8 Marco Benini Corso di Laurea in Informatica marco.benini@uninsubria.it Livello di trasporto Programma della lezione relazione tra lo strato di trasporto e lo strato

Dettagli

Telerilevamento e GIS Prof. Ing. Giuseppe Mussumeci

Telerilevamento e GIS Prof. Ing. Giuseppe Mussumeci Corso di Laurea Magistrale in Ingegneria per l Ambiente e il Territorio A.A. 2014-2015 Telerilevamento e GIS Prof. Ing. Giuseppe Mussumeci Strutture di dati: DB e DBMS DATO E INFORMAZIONE Dato: insieme

Dettagli

Indice generale. OOA Analisi Orientata agli Oggetti. Introduzione. Analisi

Indice generale. OOA Analisi Orientata agli Oggetti. Introduzione. Analisi Indice generale OOA Analisi Orientata agli Oggetti Introduzione Analisi Metodi d' analisi Analisi funzionale Analisi del flusso dei dati Analisi delle informazioni Analisi Orientata agli Oggetti (OOA)

Dettagli

C. P. U. MEMORIA CENTRALE

C. P. U. MEMORIA CENTRALE C. P. U. INGRESSO MEMORIA CENTRALE USCITA UNITA DI MEMORIA DI MASSA La macchina di Von Neumann Negli anni 40 lo scienziato ungherese Von Neumann realizzò il primo calcolatore digitale con programma memorizzato

Dettagli

Calcolatori Elettronici. La memoria gerarchica La memoria virtuale

Calcolatori Elettronici. La memoria gerarchica La memoria virtuale Calcolatori Elettronici La memoria gerarchica La memoria virtuale Come usare la memoria secondaria oltre che per conservare permanentemente dati e programmi Idea Tenere parte del codice in mem princ e

Dettagli

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A. 2015-16. Pietro Frasca.

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A. 2015-16. Pietro Frasca. Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A. 2015-16 Pietro Frasca Lezione 15 Martedì 24-11-2015 Struttura logica del sottosistema di I/O Processi

Dettagli

Database. Si ringrazia Marco Bertini per le slides

Database. Si ringrazia Marco Bertini per le slides Database Si ringrazia Marco Bertini per le slides Obiettivo Concetti base dati e informazioni cos è un database terminologia Modelli organizzativi flat file database relazionali Principi e linee guida

Dettagli

Sistema Operativo. Fondamenti di Informatica 1. Il Sistema Operativo

Sistema Operativo. Fondamenti di Informatica 1. Il Sistema Operativo Sistema Operativo Fondamenti di Informatica 1 Il Sistema Operativo Il Sistema Operativo (S.O.) è un insieme di programmi interagenti che consente agli utenti e ai programmi applicativi di utilizzare al

Dettagli

Linguaggi e Paradigmi di Programmazione

Linguaggi e Paradigmi di Programmazione Linguaggi e Paradigmi di Programmazione Cos è un linguaggio Definizione 1 Un linguaggio è un insieme di parole e di metodi di combinazione delle parole usati e compresi da una comunità di persone. È una

Dettagli

Architetture Applicative

Architetture Applicative Alessandro Martinelli alessandro.martinelli@unipv.it 6 Marzo 2012 Architetture Architetture Applicative Introduzione Alcuni esempi di Architetture Applicative Architetture con più Applicazioni Architetture

Dettagli

Il Software. Il software del PC. Il BIOS

Il Software. Il software del PC. Il BIOS Il Software Il software del PC Il computer ha grandi potenzialità ma non può funzionare senza il software. Il software essenziale per fare funzionare il PC può essere diviso nelle seguenti componenti:

Dettagli

SISTEMI DI ELABORAZIONE DELLE INFORMAZIONI

SISTEMI DI ELABORAZIONE DELLE INFORMAZIONI SISTEMI DI ELABORAZIONE DELLE INFORMAZIONI Prof. Andrea Borghesan venus.unive.it/borg borg@unive.it Ricevimento: martedì, 12.00-13.00. Dip. Di Matematica Modalità esame: scritto + tesina facoltativa 1

Dettagli

Scheduling della CPU. Sistemi multiprocessori e real time Metodi di valutazione Esempi: Solaris 2 Windows 2000 Linux

Scheduling della CPU. Sistemi multiprocessori e real time Metodi di valutazione Esempi: Solaris 2 Windows 2000 Linux Scheduling della CPU Sistemi multiprocessori e real time Metodi di valutazione Esempi: Solaris 2 Windows 2000 Linux Sistemi multiprocessori Fin qui si sono trattati i problemi di scheduling su singola

Dettagli

SISTEMI OPERATIVI. Prof. Enrico Terrone A. S: 2008/09

SISTEMI OPERATIVI. Prof. Enrico Terrone A. S: 2008/09 SISTEMI OPERATIVI Prof. Enrico Terrone A. S: 2008/09 Che cos è il sistema operativo Il sistema operativo (SO) è il software che gestisce e rende accessibili (sia ai programmatori e ai programmi, sia agli

Dettagli

12. Implementazione di un File System. 12.1.1 Struttura a livelli. 12.2.1 Allocazione contigua

12. Implementazione di un File System. 12.1.1 Struttura a livelli. 12.2.1 Allocazione contigua 12. Implementazione di un File System 1 Struttura del file system Metodi di allocazione Gestione dello spazio libero Implementazione delle directory Prestazioni ed efficienza 2 Utente 12.1.1 Struttura

Dettagli

Fondamenti di Informatica Ingegneria Clinica Lezione 16/10/2009. Prof. Raffaele Nicolussi

Fondamenti di Informatica Ingegneria Clinica Lezione 16/10/2009. Prof. Raffaele Nicolussi Fondamenti di Informatica Ingegneria Clinica Lezione 16/10/2009 Prof. Raffaele Nicolussi FUB - Fondazione Ugo Bordoni Via B. Castiglione 59-00142 Roma Docente Raffaele Nicolussi rnicolussi@fub.it Lezioni

Dettagli

L HARDWARE parte 1 ICTECFOP@GMAIL.COM

L HARDWARE parte 1 ICTECFOP@GMAIL.COM L HARDWARE parte 1 COMPUTER E CORPO UMANO INPUT E OUTPUT, PERIFERICHE UNITA DI SISTEMA: ELENCO COMPONENTI COMPONENTI NEL DETTAGLIO: SCHEDA MADRE (SOCKET, SLOT) CPU MEMORIA RAM MEMORIE DI MASSA USB E FIREWIRE

Dettagli

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A. 2014-15. Pietro Frasca. Parte II Lezione 5

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A. 2014-15. Pietro Frasca. Parte II Lezione 5 Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A. 2014-15 Parte II Lezione 5 Giovedì 19-03-2015 1 Intensità del traffico e perdita dei pacchetti La componente

Dettagli

Introduzione Ai Data Bases. Prof. Francesco Accarino IIS Altiero Spinelli Via Leopardi 132 Sesto San giovanni

Introduzione Ai Data Bases. Prof. Francesco Accarino IIS Altiero Spinelli Via Leopardi 132 Sesto San giovanni Introduzione Ai Data Bases Prof. Francesco Accarino IIS Altiero Spinelli Via Leopardi 132 Sesto San giovanni I Limiti Degli Archivi E Il Loro Superamento Le tecniche di gestione delle basi di dati nascono

Dettagli

Il Sistema Operativo

Il Sistema Operativo Il Sistema Operativo Il Sistema Operativo Il Sistema Operativo (S.O.) è un insieme di programmi interagenti che consente agli utenti e ai programmi applicativi di utilizzare al meglio le risorse del Sistema

Dettagli

INFORMATICA. Il Sistema Operativo. di Roberta Molinari

INFORMATICA. Il Sistema Operativo. di Roberta Molinari INFORMATICA Il Sistema Operativo di Roberta Molinari Il Sistema Operativo un po di definizioni Elaborazione: trattamento di di informazioni acquisite dall esterno per per restituire un un risultato Processore:

Dettagli

Architettura del calcolatore

Architettura del calcolatore Architettura del calcolatore La prima decomposizione di un calcolatore è relativa a due macro-componenti: Hardware Software Architettura del calcolatore L architettura dell hardware di un calcolatore reale

Dettagli

Procedura per la configurazione in rete di DMS.

Procedura per la configurazione in rete di DMS. Procedura per la configurazione in rete di DMS. Sommario PREMESSA... 2 Alcuni suggerimenti... 2 Utilizzo di NAS con funzione di server di rete - SCONSIGLIATO:... 2 Reti wireless... 2 Come DMS riconosce

Dettagli

L informatica INTRODUZIONE. L informatica. Tassonomia: criteri. È la disciplina scientifica che studia

L informatica INTRODUZIONE. L informatica. Tassonomia: criteri. È la disciplina scientifica che studia L informatica È la disciplina scientifica che studia INTRODUZIONE I calcolatori, nati in risposta all esigenza di eseguire meccanicamente operazioni ripetitive Gli algoritmi, nati in risposta all esigenza

Dettagli

Software di base. Corso di Fondamenti di Informatica

Software di base. Corso di Fondamenti di Informatica Dipartimento di Informatica e Sistemistica Antonio Ruberti Sapienza Università di Roma Software di base Corso di Fondamenti di Informatica Laurea in Ingegneria Informatica (Canale di Ingegneria delle Reti

Dettagli

Lezione 4 La Struttura dei Sistemi Operativi. Introduzione

Lezione 4 La Struttura dei Sistemi Operativi. Introduzione Lezione 4 La Struttura dei Sistemi Operativi Introduzione Funzionamento di un SO La Struttura di un SO Sistemi Operativi con Struttura Monolitica Progettazione a Livelli di un SO 4.2 1 Introduzione (cont.)

Dettagli

MODELLO CLIENT/SERVER. Gianluca Daino Dipartimento di Ingegneria dell Informazione Università degli Studi di Siena daino@unisi.it

MODELLO CLIENT/SERVER. Gianluca Daino Dipartimento di Ingegneria dell Informazione Università degli Studi di Siena daino@unisi.it MODELLO CLIENT/SERVER Gianluca Daino Dipartimento di Ingegneria dell Informazione Università degli Studi di Siena daino@unisi.it POSSIBILI STRUTTURE DEL SISTEMA INFORMATIVO La struttura di un sistema informativo

Dettagli

SISTEMI INFORMATIVI AVANZATI -2010/2011 1. Introduzione

SISTEMI INFORMATIVI AVANZATI -2010/2011 1. Introduzione SISTEMI INFORMATIVI AVANZATI -2010/2011 1 Introduzione In queste dispense, dopo aver riportato una sintesi del concetto di Dipendenza Funzionale e di Normalizzazione estratti dal libro Progetto di Basi

Dettagli

In un modello a strati il SO si pone come un guscio (shell) tra la macchina reale (HW) e le applicazioni 1 :

In un modello a strati il SO si pone come un guscio (shell) tra la macchina reale (HW) e le applicazioni 1 : Un Sistema Operativo è un insieme complesso di programmi che, interagendo tra loro, devono svolgere una serie di funzioni per gestire il comportamento del computer e per agire come intermediario consentendo

Dettagli

Architettura hardware

Architettura hardware Architettura dell elaboratore Architettura hardware la parte che si può prendere a calci Sistema composto da un numero elevato di componenti, in cui ogni componente svolge una sua funzione elaborazione

Dettagli

11. Evoluzione del Software

11. Evoluzione del Software 11. Evoluzione del Software Andrea Polini Ingegneria del Software Corso di Laurea in Informatica (Ingegneria del Software) 11. Evoluzione del Software 1 / 21 Evoluzione del Software - generalità Cosa,

Dettagli

Sistemi Operativi MECCANISMI E POLITICHE DI PROTEZIONE. D. Talia - UNICAL. Sistemi Operativi 13.1

Sistemi Operativi MECCANISMI E POLITICHE DI PROTEZIONE. D. Talia - UNICAL. Sistemi Operativi 13.1 MECCANISMI E POLITICHE DI PROTEZIONE 13.1 Protezione Obiettivi della Protezione Dominio di Protezione Matrice di Accesso Implementazione della Matrice di Accesso Revoca dei Diritti di Accesso Sistemi basati

Dettagli

MECCANISMI E POLITICHE DI PROTEZIONE 13.1

MECCANISMI E POLITICHE DI PROTEZIONE 13.1 MECCANISMI E POLITICHE DI PROTEZIONE 13.1 Protezione Obiettivi della Protezione Dominio di Protezione Matrice di Accesso Implementazione della Matrice di Accesso Revoca dei Diritti di Accesso Sistemi basati

Dettagli

Siamo così arrivati all aritmetica modulare, ma anche a individuare alcuni aspetti di come funziona l aritmetica del calcolatore come vedremo.

Siamo così arrivati all aritmetica modulare, ma anche a individuare alcuni aspetti di come funziona l aritmetica del calcolatore come vedremo. DALLE PESATE ALL ARITMETICA FINITA IN BASE 2 Si è trovato, partendo da un problema concreto, che con la base 2, utilizzando alcune potenze della base, operando con solo addizioni, posso ottenere tutti

Dettagli

API e socket per lo sviluppo di applicazioni Web Based

API e socket per lo sviluppo di applicazioni Web Based API e socket per lo sviluppo di applicazioni Web Based Cosa sono le API? Consideriamo il problema di un programmatore che voglia sviluppare un applicativo che faccia uso dei servizi messi a disposizione

Dettagli

Strumento per l iniezione di guasti software nel sistema operativo GNU/Linux

Strumento per l iniezione di guasti software nel sistema operativo GNU/Linux Tesi di laurea Strumento per l iniezione di guasti software nel sistema operativo GNU/Linux Anno Accademico 2009/2010 Relatore Ch.mo prof. Marcello Cinque Correlatore Ch.mo ing. Roberto Natella Candidato

Dettagli

Scopo della lezione. Informatica. Informatica - def. 1. Informatica

Scopo della lezione. Informatica. Informatica - def. 1. Informatica Scopo della lezione Informatica per le lauree triennali LEZIONE 1 - Che cos è l informatica Introdurre i concetti base della materia Definire le differenze tra hardware e software Individuare le applicazioni

Dettagli

Guida Compilazione Piani di Studio on-line

Guida Compilazione Piani di Studio on-line Guida Compilazione Piani di Studio on-line SIA (Sistemi Informativi d Ateneo) Visualizzazione e presentazione piani di studio ordinamento 509 e 270 Università della Calabria (Unità organizzativa complessa-

Dettagli

Gestione della memoria centrale

Gestione della memoria centrale Gestione della memoria centrale Un programma per essere eseguito deve risiedere in memoria principale e lo stesso vale per i dati su cui esso opera In un sistema multitasking molti processi vengono eseguiti

Dettagli

All interno del computer si possono individuare 5 componenti principali: SCHEDA MADRE. MICROPROCESSORE che contiene la CPU MEMORIA RAM MEMORIA ROM

All interno del computer si possono individuare 5 componenti principali: SCHEDA MADRE. MICROPROCESSORE che contiene la CPU MEMORIA RAM MEMORIA ROM Il computer è un apparecchio elettronico che riceve dati di ingresso (input), li memorizza e gli elabora e fornisce in uscita i risultati (output). Il computer è quindi un sistema per elaborare informazioni

Dettagli

Reti di calcolatori ed indirizzi IP

Reti di calcolatori ed indirizzi IP ITIS TASSINARI, 1D Reti di calcolatori ed indirizzi IP Prof. Pasquale De Michele 5 aprile 2014 1 INTRODUZIONE ALLE RETI DI CALCOLATORI Cosa è una rete di calcolatori? Il modo migliore per capire di cosa

Dettagli

I see you. fill in the blanks. created by

I see you. fill in the blanks. created by I see you. fill in the blanks created by I see you. fill in the blanks Si scrive, si legge I See You è. (Intensive Control Unit) è un servizio che guarda il vostro sistema informativo e svolge un azione

Dettagli

Ing. Paolo Domenici PREFAZIONE

Ing. Paolo Domenici PREFAZIONE Ing. Paolo Domenici SISTEMI A MICROPROCESSORE PREFAZIONE Il corso ha lo scopo di fornire i concetti fondamentali dei sistemi a microprocessore in modo semplice e interattivo. È costituito da una parte

Dettagli

Corso di Informatica

Corso di Informatica Corso di Informatica Modulo T2 1 Sistema software 1 Prerequisiti Utilizzo elementare di un computer Significato elementare di programma e dati Sistema operativo 2 1 Introduzione In questa Unità studiamo

Dettagli

Esercizi su. Funzioni

Esercizi su. Funzioni Esercizi su Funzioni ๒ Varie Tracce extra Sul sito del corso ๓ Esercizi funz_max.cc funz_fattoriale.cc ๔ Documentazione Il codice va documentato (commentato) Leggibilità Riduzione degli errori Manutenibilità

Dettagli

FPf per Windows 3.1. Guida all uso

FPf per Windows 3.1. Guida all uso FPf per Windows 3.1 Guida all uso 3 Configurazione di una rete locale Versione 1.0 del 18/05/2004 Guida 03 ver 02.doc Pagina 1 Scenario di riferimento In figura è mostrata una possibile soluzione di rete

Dettagli

Il memory manager. Gestione della memoria centrale

Il memory manager. Gestione della memoria centrale Il memory manager Gestione della memoria centrale La memoria La memoria RAM è un vettore molto grande di WORD cioè celle elementari a 16bit, 32bit, 64bit (2Byte, 4Byte, 8Byte) o altre misure a seconda

Dettagli

Scenario di Progettazione

Scenario di Progettazione Appunti del 3 Ottobre 2008 Prof. Mario Bochicchio SCENARIO DI PROGETTAZIONE Scenario di Progettazione Il Committente mette a disposizione delle risorse e propone dei documenti che solitamente rappresentano

Dettagli

MANUALE MOODLE STUDENTI. Accesso al Materiale Didattico

MANUALE MOODLE STUDENTI. Accesso al Materiale Didattico MANUALE MOODLE STUDENTI Accesso al Materiale Didattico 1 INDICE 1. INTRODUZIONE ALLA PIATTAFORMA MOODLE... 3 1.1. Corso Moodle... 4 2. ACCESSO ALLA PIATTAFORMA... 7 2.1. Accesso diretto alla piattaforma...

Dettagli

INFORMATICA 1 L. Mezzalira

INFORMATICA 1 L. Mezzalira INFORMATICA 1 L. Mezzalira Possibili domande 1 --- Caratteristiche delle macchine tipiche dell informatica Componenti hardware del modello funzionale di sistema informatico Componenti software del modello

Dettagli

3. Introduzione all'internetworking

3. Introduzione all'internetworking 3. Introduzione all'internetworking Abbiamo visto i dettagli di due reti di comunicazione: ma ce ne sono decine di tipo diverso! Occorre poter far comunicare calcolatori che si trovano su reti di tecnologia

Dettagli

COS È UN LINGUAGGIO? LINGUAGGI DI ALTO LIVELLO LA NOZIONE DI LINGUAGGIO LINGUAGGIO & PROGRAMMA

COS È UN LINGUAGGIO? LINGUAGGI DI ALTO LIVELLO LA NOZIONE DI LINGUAGGIO LINGUAGGIO & PROGRAMMA LINGUAGGI DI ALTO LIVELLO Si basano su una macchina virtuale le cui mosse non sono quelle della macchina hardware COS È UN LINGUAGGIO? Un linguaggio è un insieme di parole e di metodi di combinazione delle

Dettagli

Università degli Studi di Salerno

Università degli Studi di Salerno Università degli Studi di Salerno Facoltà di Scienze Matematiche Fisiche e Naturali Corso di Laurea in Informatica Tesi di Laurea Algoritmi basati su formule di quadratura interpolatorie per GPU ABSTRACT

Dettagli

Identità e autenticazione

Identità e autenticazione Identità e autenticazione Autenticazione con nome utente e password Nel campo della sicurezza informatica, si definisce autenticazione il processo tramite il quale un computer, un software o un utente,

Dettagli

clock DATA BUS ADDRESS BUS CONTROL BUS In realtà il bus del microprocessore si compone di 3 bus diversi: Bus indirizzi Bus di controllo

clock DATA BUS ADDRESS BUS CONTROL BUS In realtà il bus del microprocessore si compone di 3 bus diversi: Bus indirizzi Bus di controllo Schede a microprocessore Seconda parte Mondo esterno clock MEMORIA CPU PERIFERICA ADATTATORE DATA BUS ADDRESS BUS CONTROL BUS In realtà il bus del microprocessore si compone di 3 bus diversi: Bus dati

Dettagli