Uso della sincronizzazione automatica
Quasi tutto il codice in un driver basato su framework risiede nelle funzioni di callback degli eventi. Il framework sincronizza automaticamente la maggior parte delle funzioni di callback di un driver, come indicato di seguito:
Il framework sincronizza sempre l'oggetto dispositivo generale, l'oggetto dispositivo funzionale (FDO) e le funzioni di callback degli eventi (PDO) e dell'oggetto dispositivo fisico (PDO) in modo che solo una delle funzioni di callback (ad eccezione di EvtDeviceSurpriseRemoval, EvtDeviceQueryRemove e EvtDeviceQueryStop) possa essere chiamata alla volta per ogni dispositivo. Queste funzioni di callback supportano Plug and Play (PnP) e gli eventi di risparmio energia e vengono chiamati in IRQL = PASSIVE_LEVEL.
Facoltativamente, il framework può sincronizzare l'esecuzione delle funzioni di callback che gestiscono le richieste di I/O di un driver, in modo che queste funzioni di callback vengano eseguite una alla volta. In particolare, il framework può sincronizzare le funzioni di callback per la coda, l'interruzione, la chiamata di procedura posticipata (DPC),il timer, l'elemento di lavoro e gli oggetti file, insieme alla funzione di callback evtRequestCancel dell'oggetto richiesta. Il framework chiama la maggior parte di queste funzioni di callback in IRQL = DISPATCH_LEVEL, ma è possibile forzare le funzioni di callback della coda e dell'oggetto file da eseguire in IRQL = PASSIVE_LEVEL. Le funzioni di callback dell'elemento di lavoro vengono sempre eseguite in PASSIVE_LEVEL.
Il framework implementa questa sincronizzazione automatica usando un set di blocchi di sincronizzazione interni. Il framework garantisce che due o più thread non possano chiamare la stessa funzione di callback contemporaneamente, perché ogni thread deve attendere fino a quando non può acquisire un blocco di sincronizzazione prima di chiamare una funzione di callback. Facoltativamente, i driver possono anche acquisire questi blocchi di sincronizzazione quando necessario. Per altre informazioni, vedere Uso di blocchi framework.
Il driver deve archiviare dati specifici dell'oggetto nello spazio di contesto dell'oggetto. Se il driver usa solo interfacce definite dal framework, solo le funzioni di callback che ricevono un handle per l'oggetto possono accedere a questi dati. Se il framework esegue la sincronizzazione delle chiamate alle funzioni di callback del driver, viene chiamata una sola funzione di callback alla volta e lo spazio di contesto dell'oggetto sarà accessibile a una sola funzione di callback alla volta.
A meno che il driver implementi la gestione degli interruzioni a livello passivo, il codice che i servizi interrompono e accedono ai dati di interruzione devono essere eseguiti presso irQL (DIRQL) del dispositivo e richiedono una sincronizzazione aggiuntiva. Per altre informazioni, vedere Sincronizzazione del codice di interruzione.
Se il driver abilita la sincronizzazione automatica delle funzioni di callback che gestiscono le richieste di I/O, il framework sincronizza queste funzioni di callback in modo che vengano eseguite una alla volta. Nella tabella seguente sono elencate le funzioni di callback sincronizzate dal framework.
Tipo oggetto | Funzioni di callback sincronizzate |
---|---|
Oggetto Queue |
Gestori di richieste, EvtIoQueueState, EvtIoResume, EvtIoStop |
File (oggetto) |
Tutte le funzioni di callback |
Oggetto della richiesta |
Facoltativamente, il framework può anche sincronizzare queste funzioni di callback con qualsiasi interruzione, DPC, elemento di lavoro e funzioni di callback dell'oggetto timer fornite dal driver (esclusi la funzione di callback dell'oggetto di interruzione EvtInterruptIsr ). Per abilitare questa sincronizzazione aggiuntiva, il driver deve impostare il membro AutomaticSerialization delle strutture di configurazione di questi oggetti su TRUE.
In riepilogo, la funzionalità di sincronizzazione automatica del framework offre le funzionalità seguenti:
Il framework sincronizza sempre le funzioni di callback PnP e power management di ogni dispositivo.
Facoltativamente, il framework può sincronizzare i gestori delle richieste di I/O e alcune funzioni di callback aggiuntive (vedere la tabella precedente).
Un driver può chiedere al framework di sincronizzare le funzioni di callback per gli oggetti di interruzione, DPC, elemento di lavoro e timer.
I driver devono sincronizzare il codice che i servizi interrompono e accedono ai dati di interruzione usando le tecniche descritte in Sincronizzazione del codice di interruzione.
Il framework non sincronizza altre funzioni di callback di un driver, ad esempio la funzione di callback completamento del driverRoutine o le funzioni di callback definite dall'oggetto di destinazione I/O. Il framework fornisce invece blocchi aggiuntivi che i driver possono usare per sincronizzare queste funzioni di callback.
Scelta di un ambito di sincronizzazione
È possibile scegliere di sincronizzare il framework di tutte le funzioni di callback associate a tutte le code di I/O di un dispositivo. In alternativa, è possibile scegliere di avere il framework separatamente sincronizzare le funzioni di callback per ognuna delle code di I/O di un dispositivo. Le opzioni di sincronizzazione disponibili per il driver sono le seguenti:
Sincronizzazione a livello di dispositivo
Il framework sincronizza le funzioni di callback contenute nella tabella precedente, per tutte le code I/O del dispositivo, in modo che vengano eseguite una alla volta. Il framework ottiene questa sincronizzazione acquisendo il blocco di sincronizzazione del dispositivo prima di chiamare una funzione di callback.
Sincronizzazione a livello di coda
Il framework sincronizza le funzioni di callback contenute nella tabella precedente, per ogni singola coda di I/O, in modo che vengano eseguite una alla volta. Il framework ottiene questa sincronizzazione acquisendo il blocco di sincronizzazione della coda prima di chiamare una funzione di callback.
Nessuna sincronizzazione
Il framework non sincronizza l'esecuzione delle funzioni di callback contenute nella tabella precedente e non acquisisce un blocco di sincronizzazione prima di chiamare le funzioni di callback. Se è necessaria la sincronizzazione, il driver deve specificarlo.
Per specificare se si vuole che il framework fornisca sincronizzazione a livello di dispositivo, sincronizzazione a livello di coda o nessuna sincronizzazione per il driver, è possibile specificare un ambito di sincronizzazione per l'oggetto driver, gli oggetti del dispositivo o gli oggetti coda. Il membro SyncScope della struttura WDF_OBJECT_ATTRIBUTES di un oggetto identifica l'ambito di sincronizzazione dell'oggetto. I valori dell'ambito di sincronizzazione che il driver può specificare sono:
WdfSynchronizationScopeDevice
Il framework viene sincronizzato ottenendo il blocco di sincronizzazione di un oggetto dispositivo.
WdfSynchronizationScopeQueue
Il framework viene sincronizzato ottenendo il blocco di sincronizzazione di un oggetto queue.
WdfSynchronizationScopeNone
Il framework non viene sincronizzato e non ottiene un blocco di sincronizzazione.
WdfSynchronizationScopeInheritFromParent
Il framework ottiene il valore SyncScope dell'oggetto dall'oggetto padre dell'oggetto.
In generale, non è consigliabile usare la sincronizzazione a livello di dispositivo.
Per altre informazioni sui valori dell'ambito di sincronizzazione, vedere WDF_SYNCHRONIZATION_SCOPE.
L'ambito di sincronizzazione predefinito per gli oggetti driver è WdfSynchronizationScopeNone. L'ambito di sincronizzazione predefinito per gli oggetti del dispositivo e della coda è WdfSynchronizationScopeInheritFromParent.
Se si vuole che il framework fornisca la sincronizzazione a livello di dispositivo per tutti i dispositivi, è possibile seguire questa procedura:
Impostare SyncScope su WdfSynchronizationScopeDevice nella struttura WDF_OBJECT_ATTRIBUTES dell'oggetto driver del driver del driver .
Usare il valore predefinito WdfSynchronizationScopeInheritFromParent per ogni oggetto dispositivo .
In alternativa, per fornire la sincronizzazione a livello di dispositivo per i singoli dispositivi, è possibile usare la procedura seguente:
Usare il valore predefinito WdfSynchronizationScopeNone per l'oggetto driver .
Impostare SyncScope su WdfSynchronizationScopeDevice nella struttura WDF_OBJECT_ATTRIBUTES di singoli oggetti dispositivo .
Se si vuole che il framework fornisca la sincronizzazione a livello di coda per un dispositivo, sono disponibili le tecniche seguenti:
Per le versioni del framework 1.9 e versioni successive, è necessario abilitare la sincronizzazione a livello di coda per le singole code impostando WdfSynchronizationScopeQueue nella struttura WDF_OBJECT_ATTRIBUTES dell'oggetto coda. Questa è la tecnica preferita.
In alternativa, è possibile usare i passaggi seguenti in tutte le versioni del framework:
- Impostare SyncScope su WdfSynchronizationScopeQueue nella struttura WDF_OBJECT_ATTRIBUTES dell'oggetto dispositivo .
- Usare il valore predefinito WdfSynchronizationScopeInheritFromParent per gli oggetti coda di ogni dispositivo.
Se non si vuole che il framework sincronizza le funzioni di callback che gestiscono le richieste di I/O del driver, usare il valore di SyncScope predefinito per gli oggetti driver, dispositivo e coda del driver del driver. In questo caso, il framework non sincronizza automaticamente le funzioni di callback relative alle richieste di I/O del driver e le funzioni di callback possono essere chiamate in IRQL <= DISPATCH_LEVEL.
Si noti che l'impostazione di un valore SyncScope sincronizza solo le funzioni di callback contenute nella tabella precedente. Se si vuole che il framework sincronizzi anche l'interruzione del driver, il DPC, l'elemento di lavoro e le funzioni di callback dell'oggetto timer, il driver deve impostare il membro AutomaticSerialization delle strutture di configurazione di questi oggetti su TRUE.
È tuttavia possibile impostare AutomaticSerialization su TRUE solo se tutte le funzioni di callback che si desidera sincronizzare vengono eseguite nello stesso IRQL. La scelta di un livello di esecuzione, descritto di seguito, potrebbe comportare livelli IRQL incompatibili. In tale situazione, il driver deve usare i blocchi del framework anziché impostare AutomaticSerialization. Per altre informazioni sulle strutture di configurazione per gli oggetti interrupt, DPC, elementi di lavoro e timer e per altre informazioni sulle restrizioni applicabili all'impostazione di AutomaticSerialization in queste strutture, vedere WDF_INTERRUPT_CONFIG, WDF_DPC_CONFIG, WDF_WORKITEM_CONFIG e WDF_TIMER_CONFIG.
Se si imposta AutomaticSerialization su TRUE, è necessario selezionare la sincronizzazione a livello di coda.
Scelta di un livello di esecuzione
Quando un driver crea alcuni tipi di oggetti framework, può specificare un livello di esecuzione per l'oggetto. Il livello di esecuzione specifica il runtime di integrazione in corrispondenza del quale il framework chiamerà le funzioni di callback degli eventi dell'oggetto che gestiscono le richieste di I/O di un driver.
Se un driver fornisce un livello di esecuzione, il livello fornito influisce sulle funzioni di callback per gli oggetti coda e file. In genere, se il driver usa la sincronizzazione automatica, il framework chiama queste funzioni di callback in IRQL = DISPATCH_LEVEL. Specificando un livello di esecuzione, il driver può forzare il framework a chiamare queste funzioni di callback in IRQL = PASSIVE_LEVEL. Il framework usa le regole seguenti quando si imposta IRQL in cui vengono chiamate le funzioni di callback di coda e oggetto file:
Se un driver usa la sincronizzazione automatica, le funzioni di callback delle code e degli oggetti file vengono chiamate in IRQL = DISPATCH_LEVEL a meno che il driver non richieda al framework di chiamare le funzioni di callback in IRQL = PASSIVE_LEVEL.
Se un driver non usa la sincronizzazione automatica e non specifica un livello di esecuzione, le funzioni di callback della coda e dell'oggetto file del driver possono essere chiamate in IRQL <= DISPATCH_LEVEL.
Si noti che se il driver fornisce funzioni di callback degli oggetti file, è molto probabile che il framework chiami queste funzioni di callback in IRQL = PASSIVE_LEVEL perché alcuni dati di file, ad esempio il nome file, sono disponibili per la pagina.
Per fornire un livello di esecuzione, il driver deve specificare un valore per il membro ExecutionLevel della struttura WDF_OBJECT_ATTRIBUTES di un oggetto. I valori del livello di esecuzione che il driver può specificare sono:
WdfExecutionLevelPassive
Il framework chiama le funzioni di callback dell'oggetto in IRQL = PASSIVE_LEVEL.
WdfExecutionLevelDispatch
Il framework può chiamare le funzioni di callback dell'oggetto in IRQL <= DISPATCH_LEVEL. Se il driver usa la sincronizzazione automatica, il framework chiama sempre le funzioni di callback in IRQL = DISPATCH_LEVEL.
WdfExecutionLevelInheritFromParent
Il framework ottiene il valore ExecutionLevel dell'oggetto dall'elemento padre dell'oggetto.
Il livello di esecuzione predefinito per gli oggetti driver è WdfExecutionLevelDispatch. Il livello di esecuzione predefinito per tutti gli altri oggetti è WdfExecutionLevelInheritFromParent.
Per altre informazioni sui valori del livello di esecuzione, vedere WDF_EXECUTION_LEVEL.
La tabella seguente illustra il livello IRQL in cui il framework può chiamare le funzioni di callback di un driver per oggetti coda e oggetti file.
Ambito di sincronizzazione | Livello di esecuzione | IRQL delle funzioni di callback di coda e file |
---|---|---|
WdfSynchronizationScopeDevice |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeDevice |
WdfExecutionLevelDispatch |
DISPATCH_LEVEL |
WdfSynchronizationScopeQueue |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeQueue |
WdfExecutionLevelDispatch |
DISPATCH_LEVEL |
WdfSynchronizationScopeNone |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeNone |
WdfExecutionLevelDispatch |
<= DISPATCH_LEVEL |
È possibile impostare il livello di esecuzione su WdfExecutionLevelPassive o WdfExecutionLevelDispatch per driver, dispositivo, file, coda, timer e oggetti generali. Per altri oggetti, è consentito solo WdfExecutionLevelInheritFromParent .
È necessario specificare WdfExecutionLevelPassive se:
Le funzioni di callback del driver devono chiamare metodi del framework o routine WDM (Windows Driver Model) che possono essere chiamate solo in IRQL = PASSIVE_LEVEL.
Le funzioni di callback del driver devono accedere a codice o dati di paging. Ad esempio, le funzioni di callback degli oggetti file accedono in genere a dati di paging.
Invece di impostare WdfExecutionLevelPassive, il driver può impostare WdfExecutionLevelDispatch e fornire una funzione di callback che crea elementi di lavoro se deve gestire alcune operazioni in IRQL = PASSIVE_LEVEL.
Prima di decidere se il driver deve impostare il livello di esecuzione di un oggetto su WdfExecutionLevelPassive, è necessario determinare il runtime di integrazione in corrispondenza del quale vengono chiamati il driver e altri driver nello stack di driver. Si considerino le situazioni seguenti:
Se il driver si trova all'inizio dello stack di driver in modalità kernel, il sistema chiama in genere il driver in IRQL = PASSIVE_LEVEL. Il client di tale driver potrebbe essere un driver basato su UMDF o un'applicazione in modalità utente. La specifica di WdfExecutionLevelPassive non influisce negativamente sulle prestazioni del driver, perché il framework non deve accodare le chiamate del driver agli elementi di lavoro chiamati in IRQL = PASSIVE_LEVEL.
Se il driver non si trova all'inizio dello stack, il sistema probabilmente non chiamerà il driver in IRQL = PASSIVE_LEVEL. Pertanto, il framework deve accodare le chiamate del driver agli elementi di lavoro, che vengono chiamati successivamente in IRQL = PASSIVE_LEVEL. Questo processo può causare prestazioni del driver scarse, rispetto alla possibilità di chiamare le funzioni di callback del driver in IRQL <= DISPATCH_LEVEL.
Per gli oggetti DPC e per gli oggetti timer che non rappresentano timer a livello passivo, si noti che non è possibile impostare il membro AutomaticSerialization della struttura di configurazione su TRUE se è stato impostato il livello di esecuzione del dispositivo padre su WdfExecutionLevelPassive. Questo perché il framework acquisirà i blocchi di sincronizzazione del callback dell'oggetto dispositivo in IRQL = PASSIVE_LEVEL e pertanto i blocchi non possono essere usati per sincronizzare le funzioni di callback degli oggetti DPC o timer, che devono essere eseguite in IRQL = DISPATCH_LEVEL. In tal caso, il driver deve usare blocchi di rotazione del framework in qualsiasi dispositivo, DPC o funzioni di callback dell'oggetto timer che devono essere sincronizzate tra loro.
Si noti anche che per gli oggetti timer che rappresentano timer a livello passivo, è possibile impostare il membro AutomaticSerialization della struttura di configurazione su TRUE solo se il livello di esecuzione del dispositivo padre è impostato su WdfExecutionLevelPassive.