Sincronizzazione del codice di interruzione
I fattori seguenti complicano il codice del driver che gestisce gli interrupt hardware nei sistemi multiprocessore:
Ogni volta che un dispositivo interrompe, fornisce informazioni specifiche dell'interruzione volatili perché possono essere sovrascritte alla successiva interruzione del dispositivo.
I dispositivi interrompono a irQLs relativamente elevati e le routine del servizio di interruzione (ISR) possono interrompere l'esecuzione di altro codice driver.
Per gli interrupt DIRQL, l'ISR deve essere eseguito in DIRQL mantenendo un blocco spin fornito dal driver, in modo che l'ISR possa impedire interruzioni aggiuntive durante il salvataggio di informazioni volatili. DIRQL impedisce l'interruzione del processore corrente e il blocco di rotazione impedisce l'interruzione da parte di un altro processore.
L'ISR deve essere eseguito rapidamente perché il dispositivo non può interrompere durante l'esecuzione dell'ISR. Tempi di esecuzione ISR lunghi possono rallentare il sistema o causare la perdita di dati.
Sia la routine ISR che la chiamata di procedura posticipata (DPC) devono in genere accedere a un'area di archiviazione in cui l'ISR archivia i dati volatili del dispositivo. Queste routine devono essere sincronizzate tra loro in modo che non accingano contemporaneamente all'area di archiviazione.
A causa di tutti questi fattori, è necessario usare le regole seguenti durante la scrittura di codice driver che gestisce gli interrupt:
Solo la funzione di callback EvtInterruptIsr accede ai dati di interrupt volatili, ad esempio i registri dei dispositivi che contengono informazioni di interruzione.
La funzione di callback EvtInterruptIsr deve spostare i dati volatili in un buffer di dati interrupt definito dal driver a cui può accedere la funzione di callback EvtInterruptDpc del driver, la funzione di callback EvtInterruptWorkItem o più funzioni di callback EvtDpcFunc .
Se il driver fornisce funzioni di callback EvtInterruptDpc o EvtInterruptWorkItem per gli oggetti interrupt, la posizione migliore per archiviare i dati di interrupt è lo spazio di contesto dell'oggetto interrupt. Le funzioni di callback dell'oggetto interrupt possono accedere allo spazio di contesto dell'oggetto usando l'handle oggetto ricevuto.
Se il driver fornisce più funzioni di callback EvtDpcFunc per ogni funzione di callback EvtInterruptIsr , è possibile archiviare i dati di interrupt nello spazio di contesto di ogni oggetto DPC.
Tutto il codice driver che accede al buffer dei dati di interrupt deve essere sincronizzato in modo che solo una routine accesa ai dati alla volta.
Per gli oggetti interrupt DIRQL, la funzione di callback EvtInterruptIsr accede a questo buffer di dati in IRQL = DIRQL mantenendo il blocco spin fornito dal driver dell'oggetto interrupt. Pertanto, tutte le routine che accedono al buffer devono essere eseguite anche in DIRQL mantenendo il blocco di rotazione. In genere, la funzione di callback EvtInterruptDpc o EvtDpcFunc dell'interrupt è l'unica altra routine che deve accedere al buffer.
Tutte le routine che accedono a un buffer di dati di interrupt, ad eccezione della funzione di callback EvtInterruptIsr , devono eseguire una delle operazioni seguenti:
- Chiamare WdfInterruptSynchronize per pianificare una funzione di callback EvtInterruptSynchronize che accede al buffer dei dati di interrupt.
- Inserire il codice che accede al buffer dei dati di interrupt tra le chiamate a WdfInterruptAcquireLock e WdfInterruptReleaseLock.
Entrambe queste tecniche consentono alla funzione EvtInterruptDpc o EvtDpcFunc di accedere ai dati di interrupt in DIRQL mantenendo il blocco di rotazione dell'interrupt. DIRQL impedisce l'interruzione del processore corrente e il blocco di rotazione impedisce l'interruzione da parte di un altro processore.
Se il dispositivo supporta più vettori di interruzione o messaggi e se si desidera sincronizzare la gestione di questi interrupt del driver, è possibile assegnare un singolo blocco spin a più oggetti interrupt DIRQL. Il framework determina il valore DIRQL più elevato del set di interrupt e acquisisce sempre il blocco di rotazione in corrispondenza di tale DIRQL in modo che il codice sincronizzato non possa essere interrotto da vettori di interruzione o messaggi nel set.
Per gli oggetti interrupt a livello passivo, il framework acquisisce il blocco interrupt a livello passivo prima di chiamare la funzione di callback EvtInterruptIsr del driver in IRQL = PASSIVE_LEVEL. Di conseguenza, tutte le routine che accedono al buffer devono acquisire il blocco di interrupt o sincronizzare internamente l'accesso al buffer. In genere, la funzione di callback EvtInterruptWorkItem dell'interrupt è l'unica altra routine che accede al buffer. Per informazioni sull'acquisizione del blocco interrupt da una funzione di callback EvtInterruptWorkItem , vedere la sezione Osservazioni di tale pagina.
È anche possibile sincronizzare la gestione del driver di più vettori di interrupt assegnando un singolo blocco di attesa a più oggetti interrupt a livello passivo.
Se alcuni del codice che gestisce gli interrupt DIRQL devono essere eseguiti in IRQL = PASSIVE_LEVEL, la funzione di callback EvtInterruptDpc o EvtDpcFunc può creare uno o più elementi di lavoro in modo che il codice venga eseguito come funzioni di callback EvtWorkItem .
In alternativa, nelle versioni 1.11 e successive di KMDF il driver può richiedere un elemento di lavoro interrupt chiamando WdfInterruptQueueWorkItemForIsr. Tenere presente che la funzione di callback EvtInterruptIsr di un driver può chiamare WdfInterruptQueueWorkItemForIsr o WdfInterruptQueueDpcForIsr, ma non entrambi.
Se è importante sincronizzare le funzioni di callback EvtInterruptDpc e EvtDpcFunc di un driver con altre funzioni di callback associate a un dispositivo, il driver può impostare il membro AutomaticSerialization su TRUE nella struttura WDF_INTERRUPT_CONFIG dell'interrupt e nella struttura WDF_DPC_CONFIG dell'oggetto DPC. In alternativa, il driver può usare blocchi di rotazione del framework. L'impostazione del membro AutomaticSerialization su TRUE non sincronizza una funzione di callback EvtInterruptIsr con altre funzioni di callback. Usare WdfInterruptSynchronize o WdfInterruptAcquireLock per sincronizzare una funzione di callback EvtInterruptIsr , come descritto in precedenza in questo argomento.
Per altre informazioni sulla sincronizzazione delle routine dei driver, vedere Tecniche di sincronizzazione per i driver Framework-Based.