Usando a Sincronização Automática
Quase todo o código em um driver baseado em estrutura reside em funções de retorno de chamada de evento. A estrutura sincroniza automaticamente a maioria das funções de retorno de chamada de um driver, da seguinte maneira:
A estrutura sempre sincroniza o objeto de dispositivo geral, o FDO (objeto de dispositivo funcional) e as funções de retorno de chamada de evento PDO (objeto de dispositivo físico) entre si para que apenas uma das funções de retorno de chamada (exceto EvtDeviceSurpriseRemoval, EvtDeviceQueryRemove e EvtDeviceQueryStop) possa ser chamada por vez para cada dispositivo. Essas funções de retorno de chamada dão suporte a eventos de gerenciamento de Plug and Play (PnP) e de energia e são chamadas em IRQL = PASSIVE_LEVEL.
Opcionalmente, a estrutura pode sincronizar a execução das funções de retorno de chamada que lidam com as solicitações de E/S de um driver, para que essas funções de retorno de chamada sejam executadas uma de cada vez. Especificamente, a estrutura pode sincronizar as funções de retorno de chamada para objetos de fila, interrupção, DPC (chamada de procedimento adiado),temporizador, item de trabalho e arquivo , juntamente com a função de retorno de chamada EvtRequestCancel do objeto de solicitação. A estrutura chama a maioria dessas funções de retorno de chamada em IRQL = DISPATCH_LEVEL, mas você pode forçar as funções de retorno de chamada de objeto de fila e arquivo a serem executadas em IRQL = PASSIVE_LEVEL. (As funções de retorno de chamada de item de trabalho sempre são executadas em PASSIVE_LEVEL.)
A estrutura implementa essa sincronização automática usando um conjunto de bloqueios de sincronização internos. A estrutura garante que dois ou mais threads não possam chamar a mesma função de retorno de chamada ao mesmo tempo, pois cada thread deve aguardar até que possa adquirir um bloqueio de sincronização antes de chamar uma função de retorno de chamada. (Opcionalmente, os drivers também podem adquirir esses bloqueios de sincronização quando necessário. Para obter mais informações, consulte Usando bloqueios de estrutura.)
O driver deve armazenar dados específicos do objeto no espaço de contexto do objeto. Se o driver usar apenas interfaces definidas pela estrutura, somente as funções de retorno de chamada que recebem um identificador para o objeto poderão acessar esses dados. Se a estrutura estiver sincronizando chamadas para as funções de retorno de chamada do driver, apenas uma função de retorno de chamada será chamada por vez e o espaço de contexto do objeto estará acessível a apenas uma função de retorno de chamada por vez.
A menos que o driver implemente o tratamento de interrupção de nível passivo, o código que os serviços interrompem e acessam os dados de interrupção deve ser executado no IRQL (DIRQL) do dispositivo e requer sincronização adicional. Para obter mais informações, consulte Sincronizando código de interrupção.
Se o driver habilitar a sincronização automática das funções de retorno de chamada que lidam com solicitações de E/S, a estrutura sincronizará essas funções de retorno de chamada para que elas executem uma de cada vez. A tabela a seguir lista as funções de retorno de chamada que a estrutura sincroniza.
Tipo de objeto | Funções de retorno de chamada sincronizadas |
---|---|
Objeto Queue |
Manipuladores de solicitação, EvtIoQueueState, EvtIoResume, EvtIoStop |
Objeto de arquivo |
Todas as funções de retorno de chamada |
Objeto da solicitação |
Opcionalmente, a estrutura também pode sincronizar essas funções de retorno de chamada com qualquer interrupção, DPC, item de trabalho e funções de retorno de chamada de objeto de temporizador que o driver fornece para o dispositivo (excluindo a função de retorno de chamada EvtInterruptIsr do objeto de interrupção). Para habilitar essa sincronização adicional, o driver deve definir o membro AutomaticSerialization das estruturas de configuração desses objetos como TRUE.
Em resumo, a funcionalidade de sincronização automática da estrutura fornece os seguintes recursos:
A estrutura sempre sincroniza as funções de retorno de chamada de gerenciamento de energia e PnP de cada dispositivo.
Opcionalmente, a estrutura pode sincronizar os manipuladores de solicitação de uma fila de E/S e algumas funções de retorno de chamada adicionais (consulte a tabela anterior).
Um driver pode pedir à estrutura para sincronizar funções de retorno de chamada para objetos de interrupção, DPC, item de trabalho e temporizador.
Os drivers devem sincronizar o código que os serviços interrompem e acessam dados de interrupção usando as técnicas descritas em Sincronizando código de interrupção.
A estrutura não sincroniza outras funções de retorno de chamada de um driver, como a função de retorno de chamada CompletionRoutine do driver ou as funções de retorno de chamada definidas pelo objeto de destino de E/S. Em vez disso, a estrutura fornece bloqueios adicionais que os drivers podem usar para sincronizar essas funções de retorno de chamada.
Escolhendo um escopo de sincronização
Você pode optar por fazer com que a estrutura sincronize todas as funções de retorno de chamada associadas a todas as filas de E/S de um dispositivo. Como alternativa, você pode optar por fazer com que a estrutura sincronize separadamente as funções de retorno de chamada para cada uma das filas de E/S de um dispositivo. As opções de sincronização disponíveis para o driver são as seguintes:
Sincronização no nível do dispositivo
A estrutura sincroniza as funções de retorno de chamada que a tabela anterior contém, para todas as filas de E/S do dispositivo, para que elas executem uma de cada vez. A estrutura alcança essa sincronização adquirindo o bloqueio de sincronização do dispositivo antes de chamar uma função de retorno de chamada.
Sincronização no nível da fila
A estrutura sincroniza as funções de retorno de chamada que a tabela anterior contém, para cada fila de E/S individual, para que elas executem uma de cada vez. A estrutura alcança essa sincronização adquirindo o bloqueio de sincronização da fila antes de chamar uma função de retorno de chamada.
Sem sincronização
A estrutura não sincroniza a execução das funções de retorno de chamada que a tabela anterior contém e não adquire um bloqueio de sincronização antes de chamar as funções de retorno de chamada. Se a sincronização for necessária, o driver deverá fornecê-la.
Para especificar se você deseja que a estrutura forneça sincronização no nível do dispositivo, sincronização no nível da fila ou nenhuma sincronização para o driver, você pode especificar um escopo de sincronização para o objeto de driver, objetos de dispositivo ou objetos de fila. O membro SynchronizationScope da estrutura WDF_OBJECT_ATTRIBUTES de um objeto identifica o escopo de sincronização do objeto. Os valores de escopo de sincronização que o driver pode especificar são:
WdfSynchronizationScopeDevice
A estrutura é sincronizada obtendo o bloqueio de sincronização de um objeto de dispositivo.
WdfSynchronizationScopeQueue
A estrutura é sincronizada obtendo o bloqueio de sincronização de um objeto de fila.
WdfSynchronizationScopeNone
A estrutura não sincroniza e não obtém um bloqueio de sincronização.
WdfSynchronizationScopeInheritFromParent
A estrutura obtém o valor SynchronizationScope do objeto do objeto pai.
Em geral, não recomendamos usar a sincronização no nível do dispositivo.
Para obter mais informações sobre os valores de escopo de sincronização, consulte WDF_SYNCHRONIZATION_SCOPE.
O escopo de sincronização padrão para objetos de driver é WdfSynchronizationScopeNone. O escopo de sincronização padrão para objetos de dispositivo e fila é WdfSynchronizationScopeInheritFromParent.
Se você quiser que a estrutura forneça sincronização no nível do dispositivo para todos os dispositivos, use as seguintes etapas:
Defina SynchronizationScope como WdfSynchronizationScopeDevice na estrutura WDF_OBJECT_ATTRIBUTES do objeto de driver do driver.
Use o valor padrão WdfSynchronizationScopeInheritFromParent para cada objeto de dispositivo .
Como alternativa, para fornecer sincronização no nível do dispositivo para dispositivos individuais, você pode usar as seguintes etapas:
Use o valor padrão WdfSynchronizationScopeNone para o objeto de driver .
Defina SynchronizationScope como WdfSynchronizationScopeDevice na estrutura WDF_OBJECT_ATTRIBUTES de objetos de dispositivo individuais.
Se você quiser que a estrutura forneça sincronização no nível da fila para um dispositivo, as seguintes técnicas estarão disponíveis:
Para versões de estrutura 1.9 e posteriores, você deve habilitar a sincronização no nível da fila para filas individuais definindo WdfSynchronizationScopeQueue na estrutura WDF_OBJECT_ATTRIBUTES do objeto de fila. Essa é a técnica preferida.
Como alternativa, você pode usar as seguintes etapas em todas as versões da estrutura:
- Defina SynchronizationScope como WdfSynchronizationScopeQueue na estrutura WDF_OBJECT_ATTRIBUTES do objeto do dispositivo .
- Use o valor padrão WdfSynchronizationScopeInheritFromParent para os objetos de fila de cada dispositivo.
Se você não quiser que a estrutura sincronize as funções de retorno de chamada que lidam com as solicitações de E/S do driver, use o valor padrão SynchronizationScope para os objetos de driver, dispositivo e fila do driver. Nesse caso, a estrutura não sincroniza automaticamente as funções de retorno de chamada relacionadas à solicitação de E/S do driver e as funções de retorno de chamada podem ser chamadas em IRQL <= DISPATCH_LEVEL.
Observe que definir um valor SynchronizationScope sincroniza apenas as funções de retorno de chamada que a tabela anterior contém. Se você quiser que a estrutura também sincronize as funções de retorno de chamada de objeto de interrupção, DPC, item de trabalho e temporizador do driver, o driver deverá definir o membro AutomaticSerialization das estruturas de configuração desses objetos como TRUE.
No entanto, você poderá definir AutomaticSerialization como TRUE somente se todas as funções de retorno de chamada que você deseja sincronizar forem executadas no mesmo IRQL. Escolher um nível de execução, que é descrito a seguir, pode resultar em níveis IRQL incompatíveis. Nessa situação, o driver deve usar bloqueios de estrutura em vez de definir AutomaticSerialization. Para obter mais informações sobre as estruturas de configuração para objetos de interrupção, DPC, item de trabalho e temporizador e para obter mais informações sobre restrições que se aplicam à definição de AutomaticSerialization nessas estruturas, consulte WDF_INTERRUPT_CONFIG, WDF_DPC_CONFIG, WDF_WORKITEM_CONFIG e WDF_TIMER_CONFIG.
Se você definir AutomaticSerialization como TRUE, deverá selecionar sincronização no nível da fila.
Escolhendo um nível de execução
Quando um driver cria alguns tipos de objetos de estrutura, ele pode especificar um nível de execução para o objeto . O nível de execução especifica o IRQL no qual a estrutura chamará as funções de retorno de chamada de evento do objeto que lidam com as solicitações de E/S de um driver.
Se um driver fornecer um nível de execução, o nível fornecido afetará as funções de retorno de chamada para objetos de fila e arquivo. Normalmente, se o driver estiver usando a sincronização automática, a estrutura chamará essas funções de retorno de chamada em IRQL = DISPATCH_LEVEL. Ao especificar um nível de execução, o driver pode forçar a estrutura a chamar essas funções de retorno de chamada em IRQL = PASSIVE_LEVEL. A estrutura usa as seguintes regras ao definir o IRQL no qual as funções de retorno de chamada de objeto de fila e arquivo são chamadas:
Se um driver usar a sincronização automática, suas funções de retorno de chamada de objeto de fila e arquivo serão chamadas em IRQL = DISPATCH_LEVEL a menos que o driver peça à estrutura para chamar suas funções de retorno de chamada em IRQL = PASSIVE_LEVEL.
Se um driver não estiver usando a sincronização automática e não especificar um nível de execução, as funções de retorno de chamada de objeto de arquivo e fila do driver poderão ser chamadas em IRQL <= DISPATCH_LEVEL.
Observe que, se o driver fornecer funções de retorno de chamada de objeto de arquivo, provavelmente você desejará que a estrutura chame essas funções de retorno de chamada em IRQL = PASSIVE_LEVEL porque alguns dados de arquivo, como o nome do arquivo, são pagináveis.
Para fornecer um nível de execução, o driver deve especificar um valor para o membro ExecutionLevel da estrutura WDF_OBJECT_ATTRIBUTES de um objeto. Os valores de nível de execução que o driver pode especificar são:
WdfExecutionLevelPassive
A estrutura chama as funções de retorno de chamada do objeto em IRQL = PASSIVE_LEVEL.
WdfExecutionLevelDispatch
A estrutura pode chamar as funções de retorno de chamada do objeto em IRQL <= DISPATCH_LEVEL. (Se o driver estiver usando a sincronização automática, a estrutura sempre chamará as funções de retorno de chamada em IRQL = DISPATCH_LEVEL.)
WdfExecutionLevelInheritFromParent
A estrutura obtém o valor ExecutionLevel do objeto do pai do objeto.
O nível de execução padrão para objetos de driver é WdfExecutionLevelDispatch. O nível de execução padrão para todos os outros objetos é WdfExecutionLevelInheritFromParent.
Para obter mais informações sobre os valores de nível de execução, consulte WDF_EXECUTION_LEVEL.
A tabela a seguir mostra o nível IRQL no qual a estrutura pode chamar as funções de retorno de chamada de um driver para objetos de fila e objetos de arquivo.
Escopo de sincronização | Nível de execução | IRQL de funções de retorno de chamada de fila e arquivo |
---|---|---|
WdfSynchronizationScopeDevice |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeDevice |
WdfExecutionLevelDispatch |
DISPATCH_LEVEL |
WdfSynchronizationScopeQueue |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeQueue |
WdfExecutionLevelDispatch |
DISPATCH_LEVEL |
WdfSynchronizationScopeNone |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeNone |
WdfExecutionLevelDispatch |
<= DISPATCH_LEVEL |
Você pode definir o nível de execução como WdfExecutionLevelPassive ou WdfExecutionLevelDispatch para driver, dispositivo, arquivo, fila, temporizador e objetos gerais. Para outros objetos, somente WdfExecutionLevelInheritFromParent é permitido.
Você deve especificar WdfExecutionLevelPassive se:
As funções de retorno de chamada do driver devem chamar métodos de estrutura ou rotinas do WDM (Modelo de Driver do Windows) que só podem ser chamadas em IRQL = PASSIVE_LEVEL.
As funções de retorno de chamada do driver devem acessar código ou dados pagináveis. (Por exemplo, as funções de retorno de chamada de objeto de arquivo normalmente acessam dados pagináveis.)
Em vez de definir WdfExecutionLevelPassive, o driver pode definir WdfExecutionLevelDispatch e fornecer uma função de retorno de chamada que cria itens de trabalho se precisar manipular algumas operações em IRQL = PASSIVE_LEVEL.
Antes de decidir se o driver deve definir o nível de execução de um objeto como WdfExecutionLevelPassive, você deve determinar o IRQL no qual o driver e outros drivers na pilha de driver são chamados. Considere as seguintes situações:
Se o driver estiver na parte superior da pilha de driver do modo kernel, o sistema normalmente chamará o driver em IRQL = PASSIVE_LEVEL. O cliente desse driver pode ser um driver baseado em UMDF ou um aplicativo de modo de usuário. Especificar WdfExecutionLevelPassive não afeta negativamente o desempenho do driver, pois a estrutura não precisa enfileirar as chamadas do driver para itens de trabalho que são chamados em IRQL = PASSIVE_LEVEL.
Se o driver não estiver na parte superior da pilha, o sistema provavelmente não chamará o driver em IRQL = PASSIVE_LEVEL. Portanto, a estrutura deve enfileirar as chamadas do driver para itens de trabalho, que mais tarde são chamados em IRQL = PASSIVE_LEVEL. Esse processo pode causar baixo desempenho do driver, em comparação com permitir que as funções de retorno de chamada do driver sejam chamadas em IRQL <= DISPATCH_LEVEL.
Para objetos DPC e para objetos de temporizador que não representam temporizadores de nível passivo, observe que não é possível definir o membro AutomaticSerialization da estrutura de configuração como TRUE se você tiver definido o nível de execução do dispositivo pai como WdfExecutionLevelPassive. Isso ocorre porque a estrutura adquirirá os bloqueios de sincronização de retorno de chamada do objeto de dispositivo em IRQL = PASSIVE_LEVEL e, portanto, os bloqueios não podem ser usados para sincronizar as funções de retorno de chamada de objeto DPC ou timer, que devem ser executadas em IRQL = DISPATCH_LEVEL. Nesse caso, o driver deve usar bloqueios de rotação de estrutura em qualquer dispositivo, DPC ou funções de retorno de chamada de objeto de temporizador que devem ser sincronizadas entre si.
Observe também que, para objetos de temporizador que representam temporizadores de nível passivo, você pode definir o membro AutomaticSerialization da estrutura de configuração como TRUE somente se o nível de execução do dispositivo pai estiver definido como WdfExecutionLevelPassive.