Minidrivers, drivers miniport e pares de motoristas
Um minidriver ou um motorista de miniporta atua como metade de um par de motoristas. Pares de driver como (miniport, porta) podem facilitar o desenvolvimento do driver. Em um par de drivers, um driver lida com tarefas gerais que são comuns a uma coleção inteira de dispositivos, enquanto o outro driver lida com tarefas específicas de um dispositivo individual. Os drivers que lidam com tarefas específicas do dispositivo passam por uma variedade de nomes, incluindo driver de miniporta, driver de miniclasse e minidriver.
A Microsoft fornece o driver geral e, normalmente, um fornecedor de hardware independente fornece o driver específico. Antes de ler este tópico, você deve entender as ideias apresentadas em Nós de dispositivo e pilhas de dispositivos e pacotes de solicitação de E/S.
Cada driver de modo kernel deve implementar uma função chamada DriverEntry, que é chamada logo após o driver ser carregado. A função DriverEntry preenche determinados membros de uma estrutura DRIVER_OBJECT com ponteiros para várias outras funções que o driver implementa. Por exemplo, a função DriverEntry preenche o membro Unload da estrutura DRIVER_OBJECT com um ponteiro para a função Unload do driver, conforme mostrado no diagrama a seguir.
O membro MajorFunction da estrutura DRIVER_OBJECT é uma matriz de ponteiros para funções que lidam com pacotes de solicitação de E/S (IRPs), conforme mostrado no diagrama a seguir. Normalmente, o driver preenche vários membros da matriz MajorFunction com ponteiros para funções (implementadas pelo driver) que lidam com vários tipos de IRPs.
Um IRP pode ser categorizado de acordo com seu código de função principal, que é identificado por uma constante, como IRP_MJ_READ, IRP_MJ_WRITE ou IRP_MJ_PNP. As constantes que identificam o código de função principal servem como índices na matriz MajorFunction . Por exemplo, suponha que o driver implemente uma função de expedição para lidar com IRPs que têm o código de função principal IRP_MJ_WRITE. Nesse caso, o driver deve preencher o elemento MajorFunction[IRP_MJ_WRITE] da matriz com um ponteiro para a função dispatch.
Normalmente, o driver preenche alguns dos elementos da matriz MajorFunction e deixa os elementos restantes definidos como valores padrão fornecidos pelo gerenciador de E/S. O exemplo a seguir mostra como usar a extensão do depurador !drvobj para inspecionar os ponteiros de função para o driver de relatório.
0: kd> !drvobj parport 2
Driver object (fffffa80048d9e70) is for:
\Driver\Parport
DriverEntry: fffff880065ea070 parport!GsDriverEntry
DriverStartIo: 00000000
DriverUnload: fffff880065e131c parport!PptUnload
AddDevice: fffff880065d2008 parport!P5AddDevice
Dispatch routines:
[00] IRP_MJ_CREATE fffff880065d49d0 parport!PptDispatchCreateOpen
[01] IRP_MJ_CREATE_NAMED_PIPE fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE fffff880065d4a78 parport!PptDispatchClose
[03] IRP_MJ_READ fffff880065d4bac parport!PptDispatchRead
[04] IRP_MJ_WRITE fffff880065d4bac parport!PptDispatchRead
[05] IRP_MJ_QUERY_INFORMATION fffff880065d4c40 parport!PptDispatchQueryInformation
[06] IRP_MJ_SET_INFORMATION fffff880065d4ce4 parport!PptDispatchSetInformation
[07] IRP_MJ_QUERY_EA fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0b] IRP_MJ_SET_VOLUME_INFORMATION fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0d] IRP_MJ_FILE_SYSTEM_CONTROL fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0e] IRP_MJ_DEVICE_CONTROL fffff880065d4be8 parport!PptDispatchDeviceControl
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL fffff880065d4c24 parport!PptDispatchInternalDeviceControl
[10] IRP_MJ_SHUTDOWN fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[11] IRP_MJ_LOCK_CONTROL fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[12] IRP_MJ_CLEANUP fffff880065d4af4 parport!PptDispatchCleanup
[13] IRP_MJ_CREATE_MAILSLOT fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[15] IRP_MJ_SET_SECURITY fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[16] IRP_MJ_POWER fffff880065d491c parport!PptDispatchPower
[17] IRP_MJ_SYSTEM_CONTROL fffff880065d4d4c parport!PptDispatchSystemControl
[18] IRP_MJ_DEVICE_CHANGE fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[1a] IRP_MJ_SET_QUOTA fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[1b] IRP_MJ_PNP fffff880065d4840 parport!PptDispatchPnp
Na saída do depurador, você pode ver que parport.sys implementa GsDriverEntry, o ponto de entrada do driver. GsDriverEntry, que foi gerado automaticamente quando o driver foi criado, executa alguma inicialização e, em seguida, chama DriverEntry, que foi implementado pelo desenvolvedor do driver.
Você também pode ver que o driver de parport (em sua função DriverEntry ) fornece ponteiros para expedir funções para esses códigos de função principais:
- IRP_MJ_CREATE
- IRP_MJ_CLOSE
- IRP_MJ_READ
- IRP_MJ_WRITE
- IRP_MJ_QUERY_INFORMATION
- IRP_MJ_SET_INFORMATION
- IRP_MJ_DEVICE_CONTROL
- IRP_MJ_INTERNAL_DEVICE_CONTROL
- IRP_MJ_CLEANUP
- IRP_MJ_POWER
- IRP_MJ_SYSTEM_CONTROL
- IRP_MJ_PNP
Os elementos restantes da matriz MajorFunction mantêm ponteiros para a função de expedição padrão nt! IopInvalidDeviceRequest.
Na saída do depurador, você pode ver que o driver de parport forneceu ponteiros de função para Unload e AddDevice, mas não forneceu um ponteiro de função para StartIo. A função AddDevice é incomum porque seu ponteiro de função não é armazenado na estrutura DRIVER_OBJECT . Em vez disso, ele é armazenado no membro AddDevice de uma extensão para a estrutura DRIVER_OBJECT . O diagrama a seguir ilustra os ponteiros de função fornecidos pelo driver de portabilidade em sua função DriverEntry . Os ponteiros de função fornecidos pelo parport são sombreados.
Facilitando o uso de pares de driver
Durante um período de tempo, como desenvolvedores de driver dentro e fora da Microsoft ganharam experiência com o Modelo de Driver do Windows (WDM), eles perceberam algumas coisas sobre funções de expedição:
- As funções de expedição são em grande parte clichês. Por exemplo, grande parte do código na função de expedição para IRP_MJ_PNP é o mesmo para todos os drivers. É apenas uma pequena parte do código de Plug and Play (PnP) que é específica para um driver individual que controla uma parte individual do hardware.
- As funções de expedição são complicadas e difíceis de acertar. Implementar recursos como sincronização de thread, enfileiramento de IRP e cancelamento de IRP é um desafio e requer uma compreensão profunda de como o sistema operacional funciona.
Para facilitar as coisas para desenvolvedores de driver, a Microsoft criou vários modelos de driver específicos da tecnologia. À primeira vista, os modelos específicos da tecnologia parecem bem diferentes uns dos outros, mas um olhar mais atento revela que muitos deles se baseiam nesse paradigma:
- O driver é dividido em duas partes: uma que manipula o processamento geral e outra que manipula o processamento específico de um dispositivo específico.
- A peça geral é escrita pela Microsoft.
- A peça específica pode ser escrita pela Microsoft ou por um fornecedor de hardware independente.
Suponha que as empresas Proseware e Contoso criem um robô de toy que exija um driver WDM. Suponha também que a Microsoft forneça um Driver Robô Geral chamado GeneralRobot.sys. A Proseware e a Contoso podem escrever pequenos drivers que lidam com os requisitos de seus robôs específicos. Por exemplo, a Proseware poderia escrever ProsewareRobot.sys e o par de drivers (ProsewareRobot.sys, GeneralRobot.sys) poderia ser combinado para formar um único driver WDM. Da mesma forma, o par de drivers (ContosoRobot.sys, GeneralRobot.sys) poderia ser combinado para formar um único driver WDM. Em sua forma mais geral, a ideia é que você possa criar drivers usando pares (specific.sys, general.sys).
Ponteiros de função em pares de driver
Em um par (specific.sys, general.sys), o Windows carrega specific.sys e chama sua função DriverEntry . A função DriverEntry de specific.sys recebe um ponteiro para uma estrutura DRIVER_OBJECT . Normalmente, você esperaria que DriverEntry preenchesse vários elementos da matriz MajorFunction com ponteiros para expedir funções. Além disso, você esperaria que DriverEntry preenchesse o membro Unload (e possivelmente o membro StartIo ) da estrutura DRIVER_OBJECT e o membro AddDevice da extensão do objeto driver. No entanto, em um modelo de par de driver, DriverEntry não necessariamente faz isso. Em vez disso, a função DriverEntry de specific.sys passa a estrutura DRIVER_OBJECT para uma função de inicialização implementada por general.sys. O exemplo de código a seguir mostra como a função de inicialização pode ser chamada no par (ProsewareRobot.sys, GeneralRobot.sys).
PVOID g_ProsewareRobottCallbacks[3] = {DeviceControlCallback, PnpCallback, PowerCallback};
// DriverEntry function in ProsewareRobot.sys
NTSTATUS DriverEntry (DRIVER_OBJECT *DriverObject, PUNICODE_STRING RegistryPath)
{
// Call the initialization function implemented by GeneralRobot.sys.
return GeneralRobotInit(DriverObject, RegistryPath, g_ProsewareRobottCallbacks);
}
A função de inicialização no GeneralRobot.sys grava ponteiros de função para os membros apropriados da estrutura de DRIVER_OBJECT (e sua extensão) e os elementos apropriados da matriz MajorFunction . A ideia é que, quando o gerente de E/S envia um IRP para o par de driver, o IRP vai primeiro para uma função de expedição implementada por GeneralRobot.sys. Se GeneralRobot.sys puder lidar com o IRP por conta própria, o driver específico, ProsewareRobot.sys, não precisará estar envolvido. Se GeneralRobot.sys puder lidar com alguns, mas não todos, do processamento de IRP, ele obterá ajuda de uma das funções de retorno de chamada implementadas por ProsewareRobot.sys. GeneralRobot.sys recebe ponteiros para os retornos de chamada prosewareRobot na chamada GeneralRobotInit.
Em algum momento após o retorno de DriverEntry , uma pilha de dispositivos é construída para o nó do dispositivo Proseware Robot. A pilha do dispositivo pode ter esta aparência.
Conforme mostrado no diagrama anterior, a pilha de dispositivos para o Proseware Robot tem três objetos de dispositivo. O objeto de dispositivo superior é um objeto de dispositivo de filtro (Filter DO) associado ao driver de filtro AfterThought.sys. O objeto de dispositivo intermediário é um FDO (objeto de dispositivo funcional) associado ao par de driver (ProsewareRobot.sys, GeneralRobot.sys). O par de driver serve como o driver de função para a pilha do dispositivo. O objeto de dispositivo inferior é um PDO (objeto de dispositivo físico) associado a Pci.sys.
Observe que o par de driver ocupa apenas um nível na pilha do dispositivo e está associado a apenas um objeto de dispositivo: o FDO. Quando GeneralRobot.sys processa um IRP, ele pode chamar ProsewareRobot.sys para obter assistência, mas isso não é o mesmo que passar a solicitação para baixo na pilha do dispositivo. O par de driver forma um único driver WDM que está em um nível na pilha do dispositivo. O par de drivers conclui o IRP ou passa-o para baixo na pilha do dispositivo para o PDO, que está associado a Pci.sys.
Exemplo de um par de drivers
Suponha que você tenha uma rede sem fio cartão em seu computador laptop e examinando Gerenciador de Dispositivos, determine que netwlv64.sys é o driver do cartão de rede. Você pode usar a extensão do depurador !drvobj para inspecionar os ponteiros de função para netwlv64.sys.
1: kd> !drvobj netwlv64 2
Driver object (fffffa8002e5f420) is for:
\Driver\netwlv64
DriverEntry: fffff8800482f064 netwlv64!GsDriverEntry
DriverStartIo: 00000000
DriverUnload: fffff8800195c5f4 ndis!ndisMUnloadEx
AddDevice: fffff88001940d30 ndis!ndisPnPAddDevice
Dispatch routines:
[00] IRP_MJ_CREATE fffff880018b5530 ndis!ndisCreateIrpHandler
[01] IRP_MJ_CREATE_NAMED_PIPE fffff88001936f00 ndis!ndisDummyIrpHandler
[02] IRP_MJ_CLOSE fffff880018b5870 ndis!ndisCloseIrpHandler
[03] IRP_MJ_READ fffff88001936f00 ndis!ndisDummyIrpHandler
[04] IRP_MJ_WRITE fffff88001936f00 ndis!ndisDummyIrpHandler
[05] IRP_MJ_QUERY_INFORMATION fffff88001936f00 ndis!ndisDummyIrpHandler
[06] IRP_MJ_SET_INFORMATION fffff88001936f00 ndis!ndisDummyIrpHandler
[07] IRP_MJ_QUERY_EA fffff88001936f00 ndis!ndisDummyIrpHandler
[08] IRP_MJ_SET_EA fffff88001936f00 ndis!ndisDummyIrpHandler
[09] IRP_MJ_FLUSH_BUFFERS fffff88001936f00 ndis!ndisDummyIrpHandler
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION fffff88001936f00 ndis!ndisDummyIrpHandler
[0b] IRP_MJ_SET_VOLUME_INFORMATION fffff88001936f00 ndis!ndisDummyIrpHandler
[0c] IRP_MJ_DIRECTORY_CONTROL fffff88001936f00 ndis!ndisDummyIrpHandler
[0d] IRP_MJ_FILE_SYSTEM_CONTROL fffff88001936f00 ndis!ndisDummyIrpHandler
[0e] IRP_MJ_DEVICE_CONTROL fffff8800193696c ndis!ndisDeviceControlIrpHandler
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL fffff880018f9114 ndis!ndisDeviceInternalIrpDispatch
[10] IRP_MJ_SHUTDOWN fffff88001936f00 ndis!ndisDummyIrpHandler
[11] IRP_MJ_LOCK_CONTROL fffff88001936f00 ndis!ndisDummyIrpHandler
[12] IRP_MJ_CLEANUP fffff88001936f00 ndis!ndisDummyIrpHandler
[13] IRP_MJ_CREATE_MAILSLOT fffff88001936f00 ndis!ndisDummyIrpHandler
[14] IRP_MJ_QUERY_SECURITY fffff88001936f00 ndis!ndisDummyIrpHandler
[15] IRP_MJ_SET_SECURITY fffff88001936f00 ndis!ndisDummyIrpHandler
[16] IRP_MJ_POWER fffff880018c35e8 ndis!ndisPowerDispatch
[17] IRP_MJ_SYSTEM_CONTROL fffff880019392c8 ndis!ndisWMIDispatch
[18] IRP_MJ_DEVICE_CHANGE fffff88001936f00 ndis!ndisDummyIrpHandler
[19] IRP_MJ_QUERY_QUOTA fffff88001936f00 ndis!ndisDummyIrpHandler
[1a] IRP_MJ_SET_QUOTA fffff88001936f00 ndis!ndisDummyIrpHandler
[1b] IRP_MJ_PNP fffff8800193e518 ndis!ndisPnPDispatch
Na saída do depurador, você pode ver que netwlv64.sys implementa GsDriverEntry, o ponto de entrada do driver. GsDriverEntry, que foi gerado automaticamente quando o driver foi criado, executa alguma inicialização e chama DriverEntry, que foi escrito pelo desenvolvedor do driver.
Neste exemplo, netwlv64.sys implementa DriverEntry, mas ndis.sys implementa AddDevice, Unload e várias funções de expedição. Netwlv64.sys é chamado de driver de miniporta NDIS e ndis.sys é chamada de Biblioteca NDIS. Juntos, os dois módulos formam um par (miniporto NDIS, Biblioteca NDIS).
Este diagrama mostra a pilha de dispositivos para o cartão de rede sem fio. Observe que o par de drivers (netwlv64.sys, ndis.sys) ocupa apenas um nível na pilha do dispositivo e está associado a apenas um objeto de dispositivo: o FDO.
Pares de driver disponíveis
Os diferentes modelos de driver específicos da tecnologia usam uma variedade de nomes para as partes específicas e gerais de um par de drivers. Em muitos casos, a parte específica do par tem o prefixo "mini". Aqui estão alguns dos pares (específicos, gerais) que estão disponíveis:
- (exibir driver de miniporta, exibir driver de porta)
- (driver de miniporta de áudio, driver de porta de áudio)
- (driver de miniporta de armazenamento, driver de porta de armazenamento)
- (driver de miniclasse de bateria, driver de classe de bateria)
- (Minidriver HID, driver de classe HID)
- (driver de miniclasse do alterador, driver de porta do alterador)
- (Driver de miniporta NDIS, biblioteca NDIS)
Nota Como você pode ver na lista, vários dos modelos usam o termo driver de classe para a parte geral de um par de drivers. Esse tipo de driver de classe é diferente de um driver de classe autônomo e diferente de um driver de filtro de classe.
Tópicos relacionados
Conceitos para todos os desenvolvedores de driver