Share via


미니드라이버, 미니포트 드라이버 및 드라이버 쌍

미니드라이버 또는 미니포트 드라이버는 드라이버 쌍의 절반 역할을 합니다. (미니포트, 포트)와 같은 드라이버 쌍을 사용하면 드라이버 개발이 더 쉬워질 수 있습니다. 드라이버 쌍에서 한 드라이버는 전체 디바이스 컬렉션에 공통적인 일반 작업을 처리하고 다른 드라이버는 개별 디바이스와 관련된 작업을 처리합니다. 디바이스별 작업을 처리하는 드라이버는 미니포트 드라이버, 미니클래스 드라이버 및 미니드라이버를 비롯한 다양한 이름으로 이동합니다.

Microsoft는 일반 드라이버를 제공하며 일반적으로 독립 하드웨어 공급업체는 특정 드라이버를 제공합니다. 이 항목을 읽기 전에 디바이스 노드 및 디바이스 스택 및I/O 요청 패킷에 제시된 아이디어를 이해해야 합니다.

모든 커널 모드 드라이버는 드라이버가 로드된 직후 호출되는 DriverEntry라는 함수를 구현해야 합니다. DriverEntry 함수는 드라이버가 구현하는 여러 다른 함수에 대한 포인터로 DRIVER_OBJECT 구조체의 특정 멤버를 채웁니다. 예를 들어 DriverEntry 함수는 다음 다이어그램과 같이 드라이버의 Unload 함수에 대한 포인터로 DRIVER_OBJECT 구조체의 Unload 멤버를 채웁니다.

언로드 멤버가 있는 드라이버 개체 구조를 보여 주는 다이어그램

DRIVER_OBJECT 구조체의 MajorFunction 멤버는 다음 다이어그램과 같이 IRP(I/O 요청 패킷)를 처리하는 함수에 대한 포인터 배열입니다. 일반적으로 드라이버는 MajorFunction 배열의 여러 멤버를 다양한 종류의 IRP를 처리하는 함수(드라이버에서 구현)에 대한 포인터로 채웁니다.

majorfunction 멤버가 있는 드라이버 개체 구조를 보여 주는 다이어그램

IRP는 IRP_MJ_READ, IRP_MJ_WRITE 또는 IRP_MJ_PNP 같은 상수로 식별되는 주요 함수 코드에 따라 분류할 수 있습니다. 주 함수 코드를 식별하는 상수는 MajorFunction 배열의 인덱스 역할을 합니다. 예를 들어 드라이버가 주 함수 코드 가 IRP_MJ_WRITE IRP를 처리하는 디스패치 함수를 구현한다고 가정합니다. 이 경우 드라이버는 배열의 MajorFunction[IRP_MJ_WRITE] 요소를 디스패치 함수에 대한 포인터로 채워야 합니다.

일반적으로 드라이버는 MajorFunction 배열의 일부 요소를 채우고 나머지 요소를 I/O 관리자가 제공하는 기본값으로 설정합니다. 다음 예제에서는 !drvobj 디버거 확장을 사용하여 parport 드라이버에 대한 함수 포인터를 검사하는 방법을 보여 줍니다.

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

디버거 출력에서 parport.sys 드라이버의 진입점인 GsDriverEntry를 구현하는 것을 볼 수 있습니다. 드라이버가 빌드될 때 자동으로 생성된 GsDriverEntry는 초기화를 수행한 다음 드라이버 개발자가 구현한 DriverEntry를 호출합니다.

또한 Parport 드라이버( DriverEntry 함수)가 이러한 주요 함수 코드에 대한 디스패치 함수에 대한 포인터를 제공하는 것을 볼 수 있습니다.

  • 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

MajorFunction 배열의 나머지 요소는 기본 디스패치 함수 nt! IopInvalidDeviceRequest.

디버거 출력에서 parport 드라이버가 UnloadAddDevice에 대한 함수 포인터를 제공했지만 StartIo에 대한 함수 포인터를 제공하지 않았다는 것을 알 수 있습니다. AddDevice 함수는 함수 포인터가 DRIVER_OBJECT 구조에 저장되지 않으므로 비정상적입니다. 대신 DRIVER_OBJECT 구조체에 대한 확장의 AddDevice 멤버에 저장됩니다. 다음 다이어그램에서는 Parport 드라이버가 DriverEntry 함수에 제공한 함수 포인터를 보여 줍니다. parport에서 제공하는 함수 포인터는 음영 처리됩니다.

드라이버 개체 구조의 함수 포인터 다이어그램.

드라이버 쌍을 사용하여 더 쉽게 만들기

일정 기간 동안 Microsoft 내부 및 외부의 드라이버 개발자가 WDM(Windows 드라이버 모델)에 대한 경험을 쌓으면서 디스패치 기능에 대한 몇 가지 사항을 깨달았습니다.

  • 디스패치 함수는 대체로 상용구입니다. 예를 들어 IRP_MJ_PNP 디스패치 함수의 코드 대부분은 모든 드라이버에서 동일합니다. 개별 하드웨어를 제어하는 개별 드라이버와 관련된 PnP(플러그 앤 플레이) 코드의 일부에 불과합니다.
  • 디스패치 함수는 복잡하고 제대로 하기 어렵습니다. 스레드 동기화, IRP 큐 및 IRP 취소와 같은 기능을 구현하는 것은 어렵고 운영 체제의 작동 방식을 깊이 이해해야 합니다.

드라이버 개발자가 더 쉽게 작업을 수행할 수 있도록 Microsoft는 여러 기술별 드라이버 모델을 만들었습니다. 언뜻 보기에 기술별 모델은 서로 매우 다른 것처럼 보이지만 자세히 살펴보면 많은 모델이 다음 패러다임을 기반으로 한다는 것을 알 수 있습니다.

  • 드라이버는 일반 처리를 처리하는 드라이버와 특정 디바이스와 관련된 처리를 처리하는 두 부분으로 나뉩니다.
  • 일반 조각은 Microsoft에서 작성했습니다.
  • 특정 조각은 Microsoft 또는 독립 하드웨어 공급업체에서 작성할 수 있습니다.

Proseware와 Contoso 회사 모두 WDM 드라이버가 필요한 토이 로봇을 만든다고 가정해 보겠습니다. 또한 Microsoft가 GeneralRobot.sys 라는 일반 로봇 드라이버를 제공한다고 가정해 보겠습니다. Proseware와 Contoso는 각각 특정 로봇의 요구 사항을 처리하는 작은 드라이버를 작성할 수 있습니다. 예를 들어 Proseware는 ProsewareRobot.sys 작성할 수 있으며 드라이버 쌍(ProsewareRobot.sys, GeneralRobot.sys)을 결합하여 단일 WDM 드라이버를 형성할 수 있습니다. 마찬가지로 드라이버 쌍(ContosoRobot.sys, GeneralRobot.sys)을 결합하여 단일 WDM 드라이버를 형성할 수 있습니다. 가장 일반적인 형태는 (specific.sys, general.sys) 쌍을 사용하여 드라이버를 만들 수 있다는 것입니다.

드라이버 쌍의 함수 포인터

(specific.sys, general.sys) 쌍에서 Windows는 specific.sys 로드하고 DriverEntry 함수를 호출합니다. specific.sys DriverEntry 함수는 DRIVER_OBJECT 구조체에 대한 포인터를 받습니다. 일반적으로 DriverEntryMajorFunction 배열의 여러 요소를 디스패치 함수에 대한 포인터로 채울 것으로 예상합니다. 또한 DriverEntryDRIVER_OBJECT 구조체의 Unload 멤버(및 StartIo 멤버)와 드라이버 개체 확장의 AddDevice 멤버를 채울 것으로 예상합니다. 그러나 드라이버 쌍 모델에서 DriverEntry 가 반드시 그렇게 하는 것은 아닙니다. 대신 specific.sys DriverEntry 함수는 general.sys 구현된 초기화 함수에 따라 DRIVER_OBJECT 구조를 전달합니다. 다음 코드 예제에서는 (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);
}

GeneralRobot.sys 초기화 함수는 DRIVER_OBJECT 구조체의 적절한 멤버(및 해당 확장명)와 MajorFunction 배열의 적절한 요소에 대한 함수 포인터를 씁니다. I/O 관리자가 IRP를 드라이버 쌍으로 보내면 IRP가 먼저 GeneralRobot.sys 구현된 디스패치 함수로 이동합니다. GeneralRobot.sys 자체적으로 IRP를 처리할 수 있는 경우 특정 드라이버인 ProsewareRobot.sys 연결할 필요가 없습니다. GeneralRobot.sys IRP 처리의 일부(전부는 아님)를 처리할 수 있는 경우 ProsewareRobot.sys 구현된 콜백 함수 중 하나에서 도움을 받습니다. GeneralRobot.sys GeneralRobotInit 호출에서 ProsewareRobot 콜백에 대한 포인터를 받습니다.

DriverEntry가 반환된 후 어느 시점에서 Proseware Robot 디바이스 노드에 대한 디바이스 스택이 생성됩니다. 디바이스 스택은 다음과 같을 수 있습니다.

디바이스 스택에 afterthought.sys(필터 수행), prosewarerobot.sys, generalrobot.sys(fdo) 및 pci.sys(pdo)의 세 가지 디바이스 개체를 보여 주는 proseware 로봇 디바이스 노드의 다이어그램.

앞의 다이어그램에 표시된 것처럼 Proseware Robot의 디바이스 스택에는 세 개의 디바이스 개체가 있습니다. 최상위 디바이스 개체는 필터 드라이버 AfterThought.sys 연결된 필터 DO(필터 디바이스 개체)입니다. 중간 디바이스 개체는 드라이버 쌍(ProsewareRobot.sys, GeneralRobot.sys)과 연결된 FDO(기능 디바이스 개체)입니다. 드라이버 쌍은 디바이스 스택의 함수 드라이버 역할을 합니다. 아래쪽 디바이스 개체는 Pci.sys 연결된 PDO(물리적 디바이스 개체)입니다.

드라이버 쌍은 디바이스 스택에서 한 수준만 차지하며 하나의 디바이스 개체인 FDO와만 연결됩니다. GeneralRobot.sys IRP를 처리할 때 지원을 위해 ProsewareRobot.sys 호출할 수 있지만, 이는 디바이스 스택 아래로 요청을 전달하는 것과 다릅니다. 드라이버 쌍은 디바이스 스택의 한 수준에 있는 단일 WDM 드라이버를 형성합니다. 드라이버 쌍은 IRP를 완료하거나 디바이스 스택 아래로 Pci.sys 연결된 PDO에 전달합니다.

드라이버 쌍의 예

노트북 컴퓨터에 무선 네트워크 카드 있고 장치 관리자 확인하여 netwlv64.sys 네트워크 카드 드라이버라고 판단한다고 가정합니다. !drvobj 디버거 확장을 사용하여 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

디버거 출력에서 netwlv64.sys 드라이버의 진입점인 GsDriverEntry를 구현하는 것을 볼 수 있습니다. 드라이버가 빌드될 때 자동으로 생성된 GsDriverEntry는 초기화를 수행한 다음 드라이버 개발자가 작성한 DriverEntry를 호출합니다.

이 예제에서 netwlv64.sys DriverEntry를 구현하지만 ndis.sys AddDevice, Unload 및 여러 디스패치 함수를 구현합니다. Netwlv64.sys NDIS 미니포트 드라이버라고 하며 ndis.sys NDIS 라이브러리라고 합니다. 두 모듈은 함께 (NDIS 미니포트, NDIS 라이브러리) 쌍을 형성합니다.

이 다이어그램은 무선 네트워크 카드 대한 디바이스 스택을 보여줍니다. 드라이버 쌍(netwlv64.sys, ndis.sys)은 디바이스 스택에서 한 수준만 차지하며 하나의 디바이스 개체인 FDO와만 연결됩니다.

fdo와 연결된 드라이버 쌍으로 ndis.sys netwlv64.sys, pdo와 연결된 pci.sys 보여 주는 무선 네트워크 카드 디바이스 스택의 다이어그램.

사용 가능한 드라이버 쌍

다양한 기술별 드라이버 모델은 드라이버 쌍의 특정 및 일반 부분에 다양한 이름을 사용합니다. 대부분의 경우 쌍의 특정 부분에는 접두사 "mini"가 있습니다. 다음은 사용할 수 있는 몇 가지(특정, 일반) 쌍입니다.

  • (디스플레이 미니포트 드라이버, 디스플레이 포트 드라이버)
  • (오디오 미니포트 드라이버, 오디오 포트 드라이버)
  • (스토리지 미니포트 드라이버, 스토리지 포트 드라이버)
  • (배터리 미니클래스 드라이버, 배터리 클래스 드라이버)
  • (HID 미니드라이버, HID 클래스 드라이버)
  • (체인저 미니클래스 드라이버, 체인저 포트 드라이버)
  • (NDIS 미니포트 드라이버, NDIS 라이브러리)

참고 목록에서 볼 수 있듯이 여러 모델은 드라이버 쌍의 일반 부분에 클래스 드라이버 라는 용어를 사용합니다. 이러한 종류의 클래스 드라이버는 독립 실행형 클래스 드라이버와 다르며 클래스 필터 드라이버와 다릅니다.

모든 드라이버 개발자를 위한 개념

디바이스 노드 및 디바이스 스택

드라이버 스택