1) Come viene gestita la struttura IO_STACK_LOCATION?
L' I/O Manager si occupa della gestione degli IRP. Ogni volta che si rende necessario l'invio di un IRP, ad una periferica, procede in questo modo:
- Determina il numero di driver contenuti nello stack dei driver per la periferica. Ogni periferica è associata ad uno stack dei driver che può essere modificato dinamicamente. (Vedremo in seguito...)
- Per ogni driver nello stack mette a disposizione lo spazio per contenere una struttura IO_STACK_LOCATION e ne esegue l'associazione. [Struttura] <-1 ad 1-> [Driver]
- Crea un Array indicizzato che conterrà tutte le strutture IO_STACK_LOCATION.
- Genera l'IRP per la periferica. (ad esempio IRP_MJ_READ)
- Incapsula l'array di strutture IO_STACK_LOCATION nell'IRP.
Ogni driver gestisce SOLO la struttura IO_STACK_LOCATION che gli viene associata e può accedervi utilizzando la funzione IoGetCurrentIrpStackLocation passandole come parametro la struttura IRP. Il parametro IRP viene SEMPRE passato al driver dal Kernel quando viene richiamata una funzione di gestione di IRP.
In questo modo è facile capire che se una periferica è gestita da 3 driver, di alto, medio e basso livello, l'IRP dovrà contenere 3 strutture IO_STACK_LOCATION, una per ogni driver.
Ogni driver nello stack è responsabile della chiamata alla funzione IoGetNextIrpStackLocation.
Ogni driver che passa un IRP ad un driver di livello più basso DEVE SEMPRE settare la struttura IO_STACK_LOCATION per il driver successivo. (Mi sembra di intuire che lo spazio per contenerla sia istanziato ma il riferimento è NULL nell'IRP)
Il driver, tramite la funzione IoGetNextIrpStackLocation, recupera il puntatore alla prossima stuttura nella quale inserirà informazioni (i lavori che il prossimo driver dovrà eseguire, quindi penso una lista di SUB-REQUEST) o nei casi più semplici passerà lo stesso IO_STACK_LOCATION che ha utilizzato.
Esistono due funzioni IoCopyCurrentIrpStackLocationToNext o IoSkipCurrentIrpStackLocation che copiano l'IO_STACK_LOCATION corrente nel successivo.
2) Cosa contiene la struttura IO_STACK_LOCATION?
Come mostrato nella figura precedente, ogni struttura IO_STACK_LOCATION contenuta in un IRP contiene le informazioni seguenti:
- L'IRP_MAJOR_CODE indicante l'operazione che il driver dovrebbe effettuare
- Per alcuni IRP_MAJOR_CODE saranno presenti anceh degli IRP_MINOR_CODE che indicheraano la sottofunzione.
- Un insieme di argomenti specifici per l'operazione da effettuare come ad esempio la lunghezza o il puntatore
all'inizio di un buffer nel caso di operazione di I/O
- Un puntatore al DEVICE_OBJECT rappresentante l'oggetto fisico o logico per il quale è richiesta l'operazione.
- Un puntatore all'oggetto file rappresentante un oggetto aperto ( file,periferica,directory o volume)
Generalmente questo puntatore è usato dai driver per il filesystem. Gli altri driver lo ignorano.
L'insieme di IRP (MAJOR e MINOR) che un particolare driver deve gestire è specifico della perifericha che il driver gestirà. Tuttavia i driver di basso livello e i driver di livello medio usualmente gestiscono i seguenti IRP:
- IRP_MJ_CREATE — Apertura della periferica indicando che è pronta per operazioni di I/O
- IRP_MJ_READ — Trasferimento dati DALLA PERIFERICA
- IRP_MJ_WRITE — Trasferimento dati VERSO LA PERIFERICA
- IRP_MJ_DEVICE_CONTROL — Setup,gestione e reset della periferica, in base ai tipi di I/O Control Code definiti dal kernel. Gestione degli IOCTL definiti dalla periferica.
- IRP_MJ_CLOSE — Chiusura della periferica.
- IRP_MJ_PNP — Esecuzione di operazioni PnP. Il PnP Manager invia l'IRP tramite l'I/O manager.
- IRP_MJ_POWER — Esecuzione di operazioni di PowerManagement. Il PowerManager invia IRP tramite l'I/O Manager.
Per maggiori informazioni sugli IRP è possibile consultare MSDN.
3) Ulteriori informazioni
L'I/O manager fornisce il supporto per poter aggiungere nuovi driver ad uno stack già esistente. Ad esempio è possibile aggiungere un driver di MIRRORING tra i driver middle e low-level del Filesystem. (Il posizionamento del driver è un'operazione molto delicata ma chi sviluppa driver è libero di fare ciò che vuole)
In questo modo il driver middle non chiamerà direttamente il driver low-level bensì chiamerà prima il driver di mirroring passandogli l'IRP e il driver di MIRRORING sarà responsabile di passare l'IRP al filesystem.
Facciamo un esempio:
Una IRP_MJ_WRITE (scrittura di un file) verrà passata al driver middle.
1) Il driver middle farà delle operazioni (di alto livello sul FS) e passera l'IRP al driver successivo nello stack che è il driver di MIRRORING(che si occuperà di fare la sua bella copia su un HD di backup). Il driver di mirroring alla fine passera l'IRP al driver di basso livello che scriverà sul disco. Durante questo passaggio saranno usate 3 strutture IO_STACK_LOCATION.
Quando un nuovo driver viene aggiunto allo stack l'I/O manager ricalcola il numero di IO_STACK_LOCATION necessari. Dopo l'attach del driver di mirroring si avrà una struttura in più.
L'aggiunta di nuovi driver allo stack deve tener conto di MOLTI FATTORI:
- Un driver di alto livello nella catena può accedere in modo sicuro SOLO alla sua struttura IO_STACK_LOCATION e alla successiva nello stack. In qualsiasi momento un nuovo driver potrebbe essere aggiunto quindi E' ASSOLUTAMENTE DA EVITARE FARE QUALSIASI TIPO DI IPOTESI CIRCA LO STACK. IL DRIVER VA' SVILUPPATO SENZA ASSUMERE NULLA SULLO STATO DEL SISTEMA.. NON VANNO FATTE ASSUNZIONI NEANCHE SULLA SICUREZZA DEI DATI CHE I DRIVER A LIVELLO PIU ALTO MANDERANNO AL NOSTRO DRIVER. (Qualcuno potrebbe sostituire un nostro driver e tentare di corrompere la catena.)
- Il driver di livello più basso può accedere in modo sicuro SOLO alla propria struttura IO_STACK_LOCATION. Essendo l'ultimo driver non esiste la successiva.
Tuttavia non è detto l'ultimo driver RESTERA' sempre in questa posizione. In qualsiasi momento un nuovo driver di livello ancora più basso potrebbe essere aggiunto. E' NECESSARIO PROGETTARE IL DRIVER DI LIVELLO PIU' BASSO ASSUMENdO CHE IL DRIVER CONTINUERA' AD PROCESSARE GLI IRP USANDO LE INFORMAZIONI LETTE NEL SUO STACK LOCATION qualunque sia la fonte dell'IRP. In parole povere NON BISOGNA FARE ASSUNZIONI CIRCA IL DRIVER PRECEDENTE. Se qualche programmatore scrive un driver e lo va a registrare tra il nostro middle e il nostro low NON SONO PROBLEMI NOSTRI perchè sarà compito suo scrivere un driver capace di interoperare con i nostri.