이번 주제에서 우린 miniport, port같은 driver들을 어떻게 쉽게 개발 할 것인지 설명하고자 한다.
dirver의 쌍에서 하나의 driver는 전체 drive정보를 수집하는 등의 일반적일 일을 처리 하고 다른 driver는 개별 device의 특별한 일을 처리한다.
이 driver들은 miniport driver, miniclass driver, minidriver등의 많은 이름을 갖으며, drive의 특별한 일을 처리 한다.
대게 Micro$oft는 일반적인 driver를 제공 하고 개별 H/W vendor들은 특벽한 일을 처리하는 driver를 제공한다.
이번 주제를 읽기 전에 먼저 당신은 "Device nodes and device stack" 과 "I/O requst packets"에 대한 개념을 이해하고 있길 바란다.
모든 Kernel-mode driver는 드라이버가 로드된후 가장 먼저 호출 되는 DriverEntry라는 이름의 함수를 구현해놔야한다.
DriverEntry함수는 driver에 구현된 몇몇 함수들의 포인터로 DRIVER_OBJECT 자료구조의 특정 Member를 체워운다.
예를 들어, DriverEntry 함수는 다음 그림에서와 같이, Driver의 unload 함수에 대한 포인터로 DRIVER_OBJECT 구조체의 unload member를 채 운다.
DRIVER_OBJECT 구조체의 주요함수 Member는 아래 그림에서 볼 수 있듯이 I/O request packets(IRPs)를 처리하는 함수의 포인터들 이다.
일반적으로 driver는 IRPs의 다양한 종류를 처리 하는 함수들의 포인터들로 주요함수의 Member를 체운다.
IRP는 IRP_MJ_READ, IRP_MJ_WRITE 혹은 IRP_MJ_PNP와 같은 상수로 구분뒤는 주요함수 코드로 분류 할 수 있다.
주요 함수를 구분하는 이러한 상수들은 주요함수 배열의 index역할을 한다.
예를 들어, IRP_MJ_WIRTE 를 가지고 IRPs를 처리하는 Driver의 주요 기능을 구현하고자 한다고 가정해보자
이 경우 driver는 MajorFunxtion[IRP_MJ_WRITE]에 Dispatch 함수 포인터를 체울 것이다.
일반적으로 driver는 일부가 I/O manager에서 제공되는 기본값으로 남겨지고 나머지는 주요 함수들의 요소가 체운다.
(다시 말해보자면 함수 포인터로 체우고 나머지 는 default값으로 체운다는 말인듯)
아래의 예제는 parport driver를 위한 함수 포인터를 보기위한 !drvobj 디버거의 사용법을 보여준다.
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를 구현한 것을 볼수 있다.
driver가 생성될때 자동적으로 생선된 GsDriverEntry는 초기화를 하고 driver개발자가 구현한 DriverEntry를 호출한다.
여러분은 DriverEntry 함수에 있는 parport driver가 아래의 주요함수들이 dispatch 하기 위한 포인터를 볼수 있다.
- 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
나머지 주요함수의 요소들은 기본 dispatch함수(nt!IopInvalidDeviceRequst)로 남겨진다.
디버거의 결과에서 여러분은 parport dirver가 Unload와 AddDevice의 함수 포인터를 볼수 있다.
AddDevice 함수는 DRIVER_OBJECT 구조에체 포인터가 저장되지 않기 때문에 이래적이다.(?모르겠다.. 어떻게 해석 할지....)
대신, DRIVER_OBJECT 구조체의 확장 member가 AddDevice에 저장된다.
아래의 그림은 parport driver가 DriverEntry 함수에 제공한 함수 표인터를 나열하고 있다.
(parport 가 제공한 함수포인터는 색이 칠해저 있다.)
Driver paris를 사용해서 쉽게 만들기
오린 기간 동안 Windows Driver Model(WDM)에 익숙해져 있는 Micro$oft 내/외부의 드라이버 개발자들은 dispatch 함수에 관하여 몇가지를 깨달았다.
- Dispatch 함수들은 대게 상용구들이다. 예를들면, IRP_MJ_PNP dispatch 함수 코드는 모든 Driver가 동일하다. 이 코드들은 H/W의 각 부분을 제어하는 개별 driver에 고유 Plug and Play(PnP)의 일부분이다.
- Dispatch 함수는 바로 얻는 것은 어렵고 힘들다. Thread 동기화나 IRP queueing, IRP 취소 같은 기능을 구현하는 것은 O/S가 어떻게 작동하는지에 대한 깊은 이해가 필요하다.
Driver개발자가 쉽게 이들을 만는것을 돕기 위해 Micro$oft는 몇몇의 Technology-specific driver model을 만들었다.
언뜻, Technology-specific model은 서로 꽤나 다르게 보이지만, 이것들을 자세히 뜯어보면 상당부분 많은 것들이 이 Paradigm에 근거 함을 볼수 있다.
- Driver는 2부분으로 나뉘어진다. 하나는 일반적인 것을 처리 하는 부분이고 또 하나는 특정 device의 특정 일을 처리하는 부분이다.
- 일반적인 부분은 Mirco$oft가 만들어준다.
- 특정 부분은 Micro$oft혹은 개발 H/W vendor가 만들어준다.
Proseware와 Contoso 회사가 WDM driver를 요구하는 장난감 로버트를 만든다고 가정해보자. 또 Micro$oft가 GenralRobot.sys라는 일반적인 Robot driver를 제공 한다고 가정하자.
Proseware와 Contoso는 각각 그들의 로봇이 필요로하는 처리를 위한 작은 Driver를 작성 할 수 있다.
예를 들자면 Preseware는 ProsewareRobot.sys를 만들 수 있고 이를 가지고 ProsewareRobot.sys과 GeneralRobot.sys의 쌍으로 하나의 WDM Driver를 만들 수 있다.
비슷하게 ContosRobot.sys과 GeneralRobot.sys는 하나의 WDM driver를 만들 수 있겠다.
가장 일반적은 형태의 Idea는 Specifix.sys와 general.sys의 쌍을 이용하여 driver를 만들수 있다는 것이다.
Driver Pairs 에서의 함수 포인터
(Specific.sys, General.sys) Pair에서 Windows는 Specific.sys를 로드하고 해당 File의 DriverEntry함수를 호출 할 것이다.
Specific.sys 의 DriverEntry 함수는 DIRVER_OBJECT 구조체의 포인터를 받는다.
일반적으로 여러분은 주요 함수 배열의 몇몇 요소가 Dipatch 함수의 포인터로 체워진 DriverEntry를 기대할 것이다.
또한 여러분은 AddDevice member(driver object extion)와 DRIVER_OBJECT의 member(아마도 StartIo member일 것이다.)가 체워진 DriverEntry를 기대 할것이다.
하지만 Driver pair model의 DriverEntry는 그렇지 않다. 대신 specific.sys의 DriverEntry 함수는 General.sys에 구현된 초기화 함수에 따라 DRIVER_OBJECT 구조체를 전달한다.
아래의 예제 코드는 이 pair에서 호출되는 초기화 함수가 어떻게 호출되는지 보여준다.
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 구조체(그리고 확장)의 member에 접근하고 주요함수의 요소에 접근하는 함수 포인터를 작성한다.
I/O manager가 driver pair 에 IRP를 보낼때, IRP는 우선 GeneralRobots.sys에 구현된 Dispatch함수로 간다.
만약 GeneralRobot.sys가 스스로 IRP를 처리 한다면, ProsewareRobot.sys같은 특정 Driver는 연관될 필요가 없다.
만약 GeneralRobot.sys가 IRP의 전부가 아닌 일부분을 처리한다면, ProsewareRobot.sys에 구현된 callback 함수중 하나로부터 나머지 부분을 처리할 것이다.
GeneralRobot.sys는 GenerelRobotInit를 호출 할때 ProsewareRobot callback함수의 포인터 받는다.
DriverEntry가 반환된 후(실행을 마친 뒤), Proseware Robot device node를 위한 Device stack이 생성(구성)된다.
그 Device stack은 아마도 아래와 같을 것이다.
위 그림에서 볼수 있듯이. Proseware Robot의 device stack은 3 device object를 갖는다.
최상위의 Device object는 AfterThought.sys Filter driver와 연결된 filter device object(Filter DO)이다.
가운데이 있는 Device object는 Driver pair(ProsewareRobot.sys와 GeneralRobot.sys)와 연결된 functinoal Device object이다. 이 Device pair는 device stack에서 function device의 역할을 한다.
바닥에 있는 device object는 Pci.sys와 연결된 Physical device object(PDO)이다.
Device pair는 device stack에서 한 level에만 속하고 오직 하나의 device object (FDO)와 연결됨을 나타낸다.
GeneralRobot.sys가 IRP를 처리 할때, 부가적으로 ProsewareRobot.sys을 호출 할것이지만, device stack의 아래로 요청(request)를 전달하는 것과는 다르다.
( 주] GeneralRobot.sys가 IRP를 처리 할때 ProsewareRobot.sys를 호출 하지만 이건 기존에 살펴 보았던 Stack의 하부로 요청을 전달 하는 것과는 다른의미이다.)
Driver pair은 하나의 WDM driver를 형성하는데 이는 device stack에서 오직 한 level에 존재 한다.
( 주] 두개의 deivce pair는 비록 두개의 driver이지만 stack구조를 바탕으로 보면 하나의 device driver의 역할을 한다는 말이지 싶다.)
Driver pair는 IRP를 처리 하거나 IRP를 device stack의 하부에 있는 PDO(여기에선 pci.sys)로 전달한다.
Driver pair의 예
만약 여러분의 Laptop 컴퓨터에 무선 네트워크 카드가 있고, Device Manager를 살펴보고 네트워크 카드 드라이버가 newwlv64.sys임을 알았다고 하자(의역 만빵)
netwlv64.sys를 위한 function pointer를 살펴보기위해 !drvobj debugger extension을 사용 할 수 있다.
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가 구현되어 있음을 볼수 있다.
Driver가 제작될때 자동으로 생성된 GsDriverEntry는 초기화를 하고 Driver 개발자가 작성한 DriverEntry를 호출 할 것이다.
예제에서 netwlv64.sys는 DriverEntry를 구현되어 있고 ndis.sys는 AddDevice, Unload 그리고 몇몇 dispatch함수가 구현되어있다.
Netwlv64.sts는 NDIS miniport driver라 부르고 ndis.sys는 NDIS라이브러리라 부른다. 이 두 모듈은 모두(NDIS miniport, NDIS Library)쌍을 형성한다.
아래의 그림은 무선 렌카드의 device stack을 보여준다. Device pair(netwlv64.sys, ndis.sys)는 device stack에서 오직 한 level에만 관련되어 있고 그것은 오직 한device object(FDO)와 연결되어 있음을 나타낸다.
유효한 driver pair들
또 다른 technology-specific driver model들은 Driver pair의 일반적인 부분과 특정 부분을 구현할때 다양한 이름을 사용한다.(의역이다.)
많은 경우 이 pair의 특정 부분은 "mini" 접두어를 갖는다. 아래에 몇가지 pairs를 보여주겠다.
- (display miniport driver, display port driver)
- (audio miniport driver, audio port driver)
- (storage miniport driver, storage port driver)
- (battery miniclass driver, battery class driver)
- (HID minidriver, HID class driver)
- (changer miniclass driver, changer port driver)
- (NDIS miniport driver, NDIS library)
목록에서 볼수 있듯이, 몇몇 model들은 dirver pair의 일반적인 부분(port라고 쓴 부분들)을 class driver라는 용어로 사용한다.
이 class driver들은 standalone class driver와 다르고, class filter dirver와 다르다.
댓글 없음:
댓글 쓰기