IRP 란무엇인가? 윈도우프로그래밍을해보신분이라면윈도우프로그램이콘솔프로그램과다른가장큰차이점이메시지구동방식이라는것을알고계실것입니다. 즉, 일반콘솔프로그램이순차적으로진행되는반면에윈도우프로그램은사용자가특정작업 ( 마우스클릭, 키보드입력, 메뉴선택등등 ) 을하게되면그것에해당하는윈도우메시지라는것이발생되고그러면윈도우에서는해당메시지를현재활성화되어있는프로그램의메시지큐에집어넣게됩니다. 그러면프로그램은메시지큐에서메시지를가져와서적절한처리를하는것입니다. 드라이버역시이와비슷한동작을합니다. 드라이버는로딩이성공적으로이루어지면할당된메모리에대기하고있다가자신이컨트롤하고있는디바이스에특정한요청이왔을때윈도우에서보내주는요청정보를토대로적절한동작을처리하게됩니다. 이때윈도우프로그램이특정메시지를처리하기위해해당메시지값과그에관련된정보 ( 예를들어마우스버튼클릭메시지의경우해당마우스포인터의 X, Y 좌표값 ) 들이들어있는 MSG라고하는구조체를파라미터로받아처리하듯드라이버역시이와같은특정요청에관련된정보들을함수의파라미터로받게됩니다. 이러한정보들을담은미리정의된구조체가바로 IRP입니다. IRP는 ntddk.h에서아래와같이정의되어있습니다. typedef _IRP CSHORT Type; USHORT Size; PMDL MdlAddress; ULONG Flags; union _IRP *MasterIrp; LONG IrpCount; PVOID SystemBuffer; AssociatedIrp; LIST_ENTRY ThreadListEntry; IO_STATUS_BLOCK IoStatus; KPROCESSOR_MODE RequestorMode; BOOLEAN PendingReturned; CHAR StackCount; CHAR CurrentLocation; BOOLEAN Cancel; KIRQL CancelIrql;
CCHAR ApcEnvironment; UCHAR AllocationFlags; PIO_STATUS_BLOCK UserIosb; PKEVENT UserEvent; union PIO_APC_ROUTINE UserApcRoutine; PVOID UserApcContext; AsynchronousParameters; LARGE_INTEGER AllocationSize; Overlay; PDRIVER_CANCEL CancelRoutine; PVOID UserBuffer; union union KDEVICE_QUEUE_ENTRY DeviceQueueEntry; PVOID DriverContext[4]; ; ; PETHREAD Thread; PCHAR AuxiliaryBuffer; LIST_ENTRY ListEntry; union _IO_STACK_LOCATION *CurrentStackLocation;
ULONG PacketType; ; ; PFILE_OBJECT OriginalFileObject; Overlay; KAPC Apc; PVOID CompletionKey; Tail; IRP, *PIRP; 위에서볼수있듯이 IRP는굉장히많은정보를포함하고있으며 IRP의종류에따라다른형식의정보를가지고있도록여러개의 union을정의하고있는구조체입니다. 여기서모든멤버를다살펴볼필요는없고앞으로필요할때하나씩알아보도록하겠습니다. 우선알아야할것은 _IO_STACK_LOCATION *CurrentStackLocation 입니다. 이것은이름그대로현재의드라이버와연관된스택위치를가리키는포인터입니다. 나중에다시설명하겠지만디바이스드라이버는일반적으로여러개의드라이버가계층을이루고있습니다. 예를들어 USB 드라이버의경우 USB 버스드라이버가가장처음로드가되고그위에실제적으로 USB와연결된장치를컨트롤하는펑션드라이버가로드되게됩니다. 이러한구조에서 I/O manager가특정데이터를읽는동작을위해 IRP_MJ_READ라는요청을하게되면이 IRP는평션드라이버에먼저보내어지고다시평션드라이버에서는자신에게해당하는작업을하고나서해당 IRP를버스드라이버에게보내게되는것입니다. 그리고이렇게계층화된드라이버들은 - IRP는계층화된드라이버들이공통으로사용하는공간이므로 - 자신만의구별된 IRP처리를위해자신과관련정보들을참조할공간이필요할것입니다. 이러한공간이 I/O manager에의해스택으로처리되어있으며그자료공간이바로 IO_STACK_LOCATION구조체입니다. 그리고해당드라이버에게연결된스택포인터가 CurrentStackLocation에저장되어있습니다. 만약위의설명이잘이해가가지않는다면 CurrentStackLocation이라는것이 IRP 처리를위한또다른여러가지정보를포함하고있는구조체라고만우선알아두시기바랍니다. CurrentStackLocation의데이터타입인 IO_STACK_LOCATION 구조체는아래와같이정의되어있습니다. typedef _IO_STACK_LOCATION UCHAR MajorFunction; UCHAR MinorFunction; UCHAR Flags; UCHAR Control; union
PIO_SECURITY_CONTEXT SecurityContext; ULONG Options; USHORT POINTER_ALIGNMENT FileAttributes; USHORT ShareAccess; ULONG POINTER_ALIGNMENT EaLength; Create; ULONG Length; ULONG POINTER_ALIGNMENT Key; LARGE_INTEGER ByteOffset; Read; ULONG Length; ULONG POINTER_ALIGNMENT Key; LARGE_INTEGER ByteOffset; Write; ULONG Length; FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass; QueryFile; ULONG Length; FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass; PFILE_OBJECT FileObject; union BOOLEAN ReplaceIfExists; BOOLEAN AdvanceOnly; ; ULONG ClusterCount;
HANDLE DeleteHandle; ; SetFile; ULONG Length; FS_INFORMATION_CLASS POINTER_ALIGNMENT FsInformationClass; QueryVolume; ULONG OutputBufferLength; ULONG POINTER_ALIGNMENT InputBufferLength; ULONG POINTER_ALIGNMENT IoControlCode; PVOID Type3InputBuffer; DeviceIoControl; PVOID DoNotUse1; PDEVICE_OBJECT DeviceObject; MountVolume; PVOID DoNotUse1; PDEVICE_OBJECT DeviceObject; VerifyVolume; _SCSI_REQUEST_BLOCK *Srb; Scsi; DEVICE_RELATION_TYPE Type; QueryDeviceRelations; CONST GUID *InterfaceType; USHORT Size;
USHORT Version; PINTERFACE Interface; PVOID InterfaceSpecificData; QueryInterface; PDEVICE_CAPABILITIES Capabilities; DeviceCapabilities; PIO_RESOURCE_REQUIREMENTS_LIST IoResourceRequirementList; FilterResourceRequirements; ULONG WhichSpace; PVOID Buffer; ULONG Offset; ULONG POINTER_ALIGNMENT Length; ReadWriteConfig; BOOLEAN Lock; SetLock; BUS_QUERY_ID_TYPE IdType; QueryId; DEVICE_TEXT_TYPE DeviceTextType; LCID POINTER_ALIGNMENT LocaleId; QueryDeviceText; BOOLEAN InPath; BOOLEAN Reserved[3]; DEVICE_USAGE_NOTIFICATION_TYPE POINTER_ALIGNMENT Type;
UsageNotification; SYSTEM_POWER_STATE PowerState; WaitWake; PPOWER_SEQUENCE PowerSequence; PowerSequence; ULONG SystemContext; POWER_STATE_TYPE POINTER_ALIGNMENT Type; POWER_STATE POINTER_ALIGNMENT State; POWER_ACTION POINTER_ALIGNMENT ShutdownType; Power; PCM_RESOURCE_LIST AllocatedResources; PCM_RESOURCE_LIST AllocatedResourcesTranslated; StartDevice; ULONG_PTR ProviderId; PVOID DataPath; ULONG BufferSize; PVOID Buffer; WMI; PVOID Argument1; PVOID Argument2; PVOID Argument3; PVOID Argument4; Others; Parameters; PDEVICE_OBJECT DeviceObject;
PFILE_OBJECT FileObject; PIO_COMPLETION_ROUTINE CompletionRoutine; PVOID Context; IO_STACK_LOCATION, *PIO_STACK_LOCATION; 굉장히많은멤버를가지고있지만사실몇몇변수를제외하고는모두 union... 이런식으로특정메시지마다다른정보를포함하도록공용체로처리되어있습니다. 그리고이러한메시지는 MajorFunction과 MinorFunction, 이두개의변수에저장되어있습니다. 예를들어애플리케이션에서디바이스의데이터를읽기위해 ReadFile() API를사용하게되면 I/O manager는해당함수의수행을위해디바이스드라이버에게 IRP를내려보내게되고 IRP의멤버인 CurrentStackLocation구조체에는 MajorFunction변수에 IRP_MJ_READ 값이들어가게됩니다. 참고로 IRP_MJ_READ는 #define IRP_MJ_READ 0x03 이렇게정의된 UCHAR 값입니다. 그리고해당읽기동작을위해필요한정보들 ( 데이터를저장할버퍼포인터, 버퍼사이즈 ) 는각각 IRP->AssociatedIrp.SystemBuffer와 CurrentStackLocation->Read.Length에저장되어해당값을참조할수있습니다. 이 IRP에관련된자세한사항은실제소스를분석할때다시언급하기로하겠습니다. Symbolic Link Name 우리가프로그래밍을할때어떤변수를선언하고그변수에 1이라고하는데이터를집어넣게되면해당변수의이름을이용해서 1이라는값을언제든지참조할수있게됩니다. 함수역시해당함수를호출하기위해서는실제함수가위치한메모리주소대신그함수의이름을사용하며그이름은함수가정의되어있는위치를가리키는포인터값을상징하게됩니다. 마찬가지로우리가특정디바이스를컨트롤하기위해서는해당디바이스의이름을통해사용자가작업하기원하는디바이스가무엇인지윈도우에게알려줘야합니다. 이렇게사용자 ( 보다정확하게말하면유저모드프로그램 ) 에서사용가능토록외부로노출되어있는디바이스의이름을 Symbolic Link Name 이라고합니다.
한가지주의할점은이 symbolic link name이우리가이전소스에서정의했던 device name과는다르다는사실입니다. Device name은커널모드에서사용가능한이름이고 symbolic link name은유저모드에서사용가능한이름입니다. 이렇게굳이구분을둔이유는유저모드프로그램에서커널모드의구성요소들을직접컨트롤하는일을방지하기위한목적이아닌가생각됩니다.( 제추측이랍니다 ^^) 어쨌든 device name은이렇게유저모드에서직접적인호출이불가능하므로만약어플리케이션에서드라이버가컨트롤하고있는특정디바이스의핸들 ( 일종의제어권 ) 을가져오기위해서는드라이버에서 symbolic link name을따로정의해서해당 device name와연결시켜주는작업을해야합니다. 이와관련된자세한사항은아래소스에서더살펴보도록하겠습니다.( 그외에 symbolin link 에관련된사항은 Inside Windows 2000, chapter 3에 Object manager에관한부분에자세히설명이되어있습니다.) 커널메모리와유저메모리 커널모드프로그램과유저모드프로그램은사용하는메모리구역이틀립니다. 우선이부분에대해이야기하기전에 가상메모리 라는것에대해먼저언급하도록하겠습니다. 프로그램이실행이되면윈도우는먼저프로그램이실행되는데필요한기본메모리를할당해서해당위치에저장합니다. 그리고메모리상에서필요한명령어들을불러와원하는동작을진행하게됩니다. 따라서원래대로라면프로그램의코드사이즈와해당프로그램에서할당하는변수크기는컴퓨터의램크기를넘어서서는안됩니다. 물론기본적으로윈도우가메모리에로딩되어있고그외에여러가지다른프로그램이나드라이버들이메모리를차지하고있으므로실제로어떤프로그램이차지하게되는메모리양은램크기보다작게되며동시에실행할수있는프로그램의개수도제한될수밖에없습니다. 이러한문제점을해결하기위해만들어진방법이바로가상메모리입니다. 예를들어실제물리적인메모리의크기가 1Mbytes(1*1024*1024bytes) 이고최소저장가능한메모리크기가 1byte 라고한다면메모리번지수는 1*1024*1024개가됩니다. 하지만윈도우에서메모리주소를 4*1024*1024개로정하고나머지 3*1024*1024bytes만큼의메모리는디스크에따로공간을만들어놓고관리를하게되면컴퓨터는마치 4Mbytes의메모리를가진것처럼동작하게됩니다. 즉, 현재당장프로그램을실행하는데필요한메모리는실제물리메모리에올려놓고사용하고그외에당장사용하지않는내용들은디스크에옮겨놓음으로써메모리를효율적으로사용하는방식입니다. 이것은마치책상에책을모두쌓아놓기에는공간이부족하므로책장을따로마련해놓고필요한책들을가져오고필요없는책은다시책장에꽂아놓는것과비슷합니다. 윈도우는실제로컴퓨터가부팅이되는순간바로하드디스크의특정부분에이가상메모리를위한공간을만들어둡니다.( 그림 2_1 참조 )
< 그림 2_1> 위의그림에나와있는 pagefile.sys가바로그역할을하는파일입니다.( 이파일을보기위해서는도구-> 폴더옵션-> 보기에서보호된운영체제파일숨기기의체크를없애주셔야합니다.) 윈도우는해당사이즈만큼을가상메모리로사용하면서당장사용하지않는프로그램코드값이나자료들을 pagefile.sys에저장하고필요할때메모리로읽어오게됩니다.( 기타가상메모리에대한개념이나관련메커니즘은운영체제개론서를보시면잘나와있습니다.) 어쨌든윈도우는이러한가상메모리기법을사용해서최대 4GBytes(2^32bytes) 의메모리를관리할수있습니다. 그리고이전체메모리를반으로나눠서각각커널모드와유저모드에서사용하도록구분을지어놓고있습니다. 하위 2Gbytes가유저모드를위한메모리이며상위 2Gbytes가커널을위한메모리입니다. 유저모드프로그램에서디바이스드라이버와같은커널모드프로그램에데이터를전달하기위해서는 - 혹은전달받기위해서는 특별한방식이필요합니다. 그이유는두모드에서메모리를관리하는방식에있어차이가있기때문입니다. 유저모드는메모리관리에있어서다음과같은특성이있습니다. 1. 모든할당되는메모리는페이지라고하는일정크기의블록으로관리됩니다. 그이유는앞에서언급
한가상메모리방식때문입니다. 윈도우는가상메모리방식에의해현재사용되는않는메모리는하드디스크에옮겨놓고필요한메모리를가져오게되는데이때보다높은효율성과관리의편의성을위해일정사이즈로블록화시켜관리하게됩니다. 그리고이러한메모리를페이지메모리 (Paged Memory) 라고합니다. 2. 프로세스별로메모리가관리됩니다. 따라서현재사용되지않는프로세스 (Idle Process) 에할당된메모리는하드디스크로옮겨지고현재메모리에서삭제됩니다. 이것을 Swap Out이라고합니다. 반대로다시해당프로세스가사용되게되면다시메모리로옮겨오게되는데이것을 Swap In이라고하고이렇게 Swap Out과 In을하는동작을 Swapping이라고합니다. 3. 유저모드프로세스는커널모드메모리를참조하거나변경하지못합니다. 이것은윈도우의메모리보호정책에의해서보장되며이러한특성때문에유저모드프로그램에서발생된어떠한에러도시스템에문제를일으키지못합니다.( 우리가어떤프로그램을사용하다가에러에의해프로그램이다운되더라도해당프로그램을종료하면다시원상태로복구될수있는것은이러한이유때문입니다 ) 만약커널모드메모리를참조 / 쓰기하려고하게되면 Access Violation 에러를일으키며윈도우는해당프로세스를강제로종료시키게됩니다. 4. 3의특성에서하나더나아가다른프로세스들간에도메모리를함부로참조 / 변경하지못하도록윈도우에서보호해줍니다.(CreateFileMapping() 과같은 API를사용하면이러한제약을부분적으로없애줘서프로세스간에메모리공유가가능합니다.) 반면커널메모리는다음과같은특성이있습니다. 1. 할당되는메모리는페이지될수도있고그렇지않을수도있습니다. 페이지메모리는스와핑이일어나기때문에해당데이터가반드시현재메모리에있다는보장이없습니다. 이것은일반유저모드프로그램에서는큰문제가되지않지만 ( 스와핑에의해필요할때마다다시불려질수있으므로 ) 윈도우커널의핵심코드나데이터의경우큰문제가될수있습니다. ( 예를들어스와핑작업을담당하는커널명령루틴자체가메모리에서페이지아웃되어있다고생각해보시기바랍니다 ) 따라서커널모드프로세스에서는중요한루틴이나데이터들은항상메모리에있도록보장할수있어야합니다. 그리고그렇게하기위해서는중요한데이터나실행코드들은페이지되지않은메모리에저장해서스와핑을막게됩니다. ( 사실여기서설명한내용은정확하다고볼수없습니다. 커널모드에서스와핑이일어나서는안되는보다정확한이유는뒤에서설명하도록하겠습니다.) 2. 유저모드프로그램과달리커널모드프로세스들은다른프로세스들간에메모리보호가되지못합니다. 따라서메모리관리를잘못하게되면다른중요한시스템데이터가손상될수도있습니다.( 커널모드에서는윈도우가제공하는메모리보호정책이적용되지않습니다.) 3. 커널모드프로세스들은유저모드메모리를직접참조할수있습니다. 하지만여러가지위험이발생하는데이것에대해서는바로밑에서설명하겠습니다. 이러한특성들때문에커널모드에서동작하는디바이스드라이버와유저모드프로그램간에는데이터를주고받음에있어서여러가지제약이발생하게됩니다. 예를들어유저모드프로그램에서드라이버에게어떤데이터를주고자한다면커널모드메모리에직접데
이터를쓰지못하므로문제가생깁니다. 반대로드라이버에서데이터를가져오는것도불가능합니다. 반대로드라이버에서유저모드프로그램에게데이터를넘겨주거나읽어온다고할때도문제가생깁니다. 왜냐하면위에유저메모리특성에서설명했듯이유저모드프로세스의메모리는스와핑이일어나기때문입니다. 자세하게설명하자면, 윈도우는아시다시피멀티프로세싱 ( 예를들어현재인터넷검색을하면서워드작업 ) 이가능합니다. 이것은윈도우에서사용자가하나의프로그램을사용하는동안에도틈틈이다른프로세스의작업을수행하기때문입니다. 다시말하면여러개의프로세스가돌아가면서동작한다는것을뜻하며현재우리드라이버가데이터를참조하고자하는프로그램의메모리가스와핑될가능성을항상가지고있다는것을의미하기도합니다. 그러므로만약드라이버에서유저모드프로그램의메모리를직접참조하려고하면 물론운이좋아안일어날수도있지만 스와핑이이미일어나서드라이버가참조하고자하는물리메모리영역에는엉뚱한값들이들어있을수가있습니다. 때문에윈도우 ( 정확하게말하자면 I/O manager) 에서는커널모드와유저모드간에데이터를주고받기위해크게두가지방식을제공합니다. 1. Buffered I/O A. 만약유저모드프로그램과드라이버간에데이터를주고받는요청이발생하면 I/O manager에서는해당데이터의사이즈를먼저체크한후해당데이터가들어갈만한충분한사이즈의페이지되지않은버퍼메모리를생성합니다. B. 필요하다면 ( 드라이버에데이터를쓰는작업의경우 ) 유저모드데이터를이버퍼에복사합니다. C. 드라이버에서이버퍼메모리의값을참조하거나혹은 데이터읽기작업일경우 버퍼에드라이버가가지고있는데이터를저장합니다. D. 해당작업이완료되면 I/O manager는버퍼에있는데이터를다시유저모드메모리에복사하고버퍼메모리를해제합니다. 2. Direct I/O A. 데이터를주고받는요청이발생하면 I/O manager에서해당메모리가스와핑이되지않도록일종의잠금장치를합니다. B. 잠겨진 (Locked) 메모리를드라이버에서쉽게참조할수있도록관련정보가저장된 Memory Desctriptor List(MDL) 라는것을만듭니다. C. 드라이버에서는 MDL을가지고참조가능한메모리포인터를리턴해주는 MmGetSystem- AddresForMdl() 이라고하는 API를사용하여데이터를주고받을포인터를만들고이포인터를이용해서데이터처리를합니다. D. I/O manager는쓰기 / 읽기동작이완료되면 MDL을제거하고해당메모리의잠금장치를해제합니다. 이두가지방식이어떻게드라이버프로그래밍에서사용되는지는앞으로하나씩설명해나가도록하겠습니다.
유저모드프로그램과디바이스드라이버의통신방법 유저모드프로그램에서디바이스드라이버를제어하고데이터를주고받기위해서는해당디바이스의핸들을가져오는것을가장먼저해야합니다. 그리고이것을위해 CreateFile() 이라고하는 API를사용합니다. 이함수의원형은다음과같습니다. CreateFile( IN LPCSTR lpfilename, IN DWORD dwdesiredaccess, IN DWORD dwsharemode, IN LPSECURITY_ATTRIBUTES lpsecurityattributes, IN DWORD dwcreationdisposition, IN DWORD dwflagsandattributes, IN HANDLE htemplatefile ); 1. lpfilename : 이것은해당디바이스의 symbolic link name값이들어갑니다. Symbolic link name은. 사용자가정의한이름 이런식으로정의됩니다. 앞에들어가는. 는윈도우에서관리하는 symbolic link name의이름공간 (name space) 입니다. 자세한사항은디바이스드라이버관련책이나 Inside Windows 2000을참조하시기바랍니다. 2. dwdesireaccess : 해당디바이스에대한사용권한 ( 읽기 / 쓰기 / 실행 ) 을설정합니다. 3. dwsharemode : 해당디바이스를다른프로그램에서도공유할지여부를결정합니다. 4. lpsecurityattribute : security description 구조체의포인터값이들어갑니다. 보통 NULL로설정합니다. 5. dwcreationdisposition : 해당디바이스의핸들을얻어오는방식에대해결정합니다. 일반적으로해당디바이스가존재할때만핸들을얻어올수있도록 OPEN_EXISTING(3) 값을셋팅합니다. 6. dwflagsandattributes : 파일오브젝트의경우해당파일의특성값들을셋팅할수있습니다. 일반적으로파일오브젝트외의디바이스에서는 FILE_ATTRIBUTE_NORMAL값을셋팅합니다. 7. htemplatefile : 역시파일오브젝트의경우에해당하는사항입니다. 일반디바이스는 NULL값을셋팅합니다. CreateFile() 함수는해당디바이스의핸들값을리턴합니다. 그리고이핸들값을이용해서해당디바이스에읽기, 쓰기등의작업을수행하게됩니다. 우리드라이버의테스트프로그램에서의사용예는다음과같습니다. if (m_hdrvhandle) AfxMessageBox("Already create driver..."); return;
m_hdrvhandle = CreateFile( DEVICE_LINK_NAME, GENERIC_READ GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if ((m_hdrvhandle == NULL) (GetLastError()!= ERROR_SUCCESS)) DisplayErrorMsg("CreateFile"); m_hdrvhandle = NULL; return; 테스트프로그램에서는 ReadFile(), WriteFile() API 를이용해서디바이스에읽기 / 쓰기작업을수행합니다. 각 각의사용예는아래와같습니다. WriteFile( m_hdrvhandle, (PVOID)m_pWriteBuffer, m_dwbuffersize, &m_dwbuffersize, NULL ) ReadFile( m_hdrvhandle, (PVOID)m_pReadBuffer, m_dwbuffersize, &m_dwbuffersize, NULL ) 자세한파라미터에대한설명은 MSDN을참조하시기바랍니다. 여기서는이러한 API를이용하기위해서는 CreateFile() 에서넘어온핸들값 ( 여기서는 m_hdrvhandle 변수 ) 을이용해야한다는것을기억해두시기바랍
니다. 그럼기본적인사항에대한설명은이만마치고실제소스를실행해보고소스를분석하는단계에서보다자세한관련사항을언급하도록하겠습니다.
Hello Driver Ver 2 소스이번에작성하는프로그램은위에서만들어본 Hello Drvier에기본적인 I/O 기능이추가된드라이버입니다. 이드라이버는애플리케이션에서핸들을얻어와서간단한데이터를읽고쓸수있습니다. 아래에소스전문이나와있습니다. <Hello_DriverEntry.h> #ifndef DRIVER_ENTRY_H #define DRIVER_ENTRY_H typedef _DEVICE_EXTENSION PDEVICE_OBJECT pdevobj; UNICODE_STRING DevLinkName; UCHAR *pdevbuffer; ULONG dwbuffersize; DEVICE_EXTENSION, *PDEVICE_EXTENSION; #define MAX_BUFFER_SIZE 1024 #define DEVICE_NAME L" Device HELLODRV_Ver2" #define DEVICE_LINK_NAME L"?? HelloDrv" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath); VOID Hello_DriverUnload(IN PDRIVER_OBJECT DriverObject); NTSTATUS Hello_Dispatch(IN PDEVICE_OBJECT DevObj, IN PIRP irp); #endif <Hello_DriverEntry.c> #include "ntddk.h" #include "Hello_DriverEntry.h" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING DeviceName, DevLinkName;
PDEVICE_OBJECT pdeviceobject; PDEVICE_EXTENSION pdevext; int i; DriverObject->DriverUnload = Hello_DriverUnload; RtlInitUnicodeString(&DeviceName, DEVICE_NAME); RtlInitUnicodeString(&DevLinkName, DEVICE_LINK_NAME); status = IoCreateDevice( DriverObject, sizeof(device_extension), &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pdeviceobject ); if (!NT_SUCCESS (status)) return status; pdeviceobject->flags = DO_BUFFERED_IO; pdevext = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension; pdevext->devlinkname.buffer = (PWSTR)ExAllocatePool(NonPagedPool, DevLinkName.MaximumLength); pdevext->devlinkname.maximumlength = DevLinkName.MaximumLength; RtlCopyUnicodeString(&pDevExt->DevLinkName, &DevLinkName); pdevext->pdevobj = pdeviceobject; pdevext->pdevbuffer = (UCHAR *)ExAllocatePool(NonPagedPool, MAX_BUFFER_SIZE); pdevext->dwbuffersize = 0; status = IoCreateSymbolicLink(&pDevExt->DevLinkName, &DeviceName); if (!NT_SUCCESS(status)) KdPrint(("IoCreateSymbolicLink failed!!! nstatus : %x", status));
for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) DriverObject->MajorFunction[i] = Hello_Dispatch; KdPrint(("Hello Driver World!!!")); return status; VOID Hello_DriverUnload(IN PDRIVER_OBJECT DriverObject) NTSTATUS status = STATUS_SUCCESS; if(driverobject->deviceobject) PDEVICE_EXTENSION pdevext = (PDEVICE_EXTENSION)DriverObject->DeviceObject->DeviceExtension; status = IoDeleteSymbolicLink(&pDevExt->DevLinkName); if (!NT_SUCCESS(status)) KdPrint(("IoDeleteSymbolicLink failed!!! nstatus : %x", status)); RtlFreeUnicodeString(&pDevExt->DevLinkName); IoDeleteDevice(pDevExt->pDevObj); DriverObject->DeviceObject = NULL; KdPrint(("Hello Driver Unload...bye~")); NTSTATUS Hello_Dispatch(IN PDEVICE_OBJECT DevObj, IN PIRP irp) NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION pirpstack = IoGetCurrentIrpStackLocation(irp);
UCHAR majorfunc = pirpstack->majorfunction; UCHAR *psysbuffer; ULONG dwbuffersize = 0; PDEVICE_EXTENSION pdevext = (PDEVICE_EXTENSION)DevObj->DeviceExtension; irp->iostatus.information = 0; switch (majorfunc) case IRP_MJ_CREATE: KdPrint(("Hello Device Create!")); break; case IRP_MJ_CLOSE: KdPrint(("Hello Device Close!")); break; case IRP_MJ_CLEANUP: KdPrint(("Hello Device Clean Up!")); break; case IRP_MJ_READ: KdPrint(("Hello Device Read!")); psysbuffer = irp->associatedirp.systembuffer; dwbuffersize = pirpstack->parameters.read.length; dwbuffersize = (dwbuffersize < pdevext->dwbuffersize)? dwbuffersize : pdevext->dwbuffersize; RtlCopyMemory(pSysBuffer, pdevext->pdevbuffer, dwbuffersize); irp->iostatus.information = dwbuffersize; break; case IRP_MJ_WRITE: KdPrint(("Hello Device Write!")); psysbuffer = irp->associatedirp.systembuffer; dwbuffersize = pirpstack->parameters.write.length; if (dwbuffersize > MAX_BUFFER_SIZE) KdPrint(("Too Many Wrtie Buffer Size!!!"));
status = STATUS_INSUFFICIENT_RESOURCES; break; RtlCopyMemory(pDevExt->pDevBuffer, psysbuffer, dwbuffersize); pdevext->dwbuffersize = dwbuffersize; irp->iostatus.information = pdevext->dwbuffersize; break; default: KdPrint(("I can't dispatch this IRP")); break; irp->iostatus.status = status; IoCompleteRequest(irp, IO_NO_INCREMENT); return status; 이제해당드라이버를실행하기위해서아래와같이설정합니다. 1. 드라이버의실행파일은 HelloDrvVer2.sys로 ( 혹은자신이원하는드라이버이름으로 ) 설정한다. 2. 드라이버를빌드하고 system32 drivers 디렉토리에카피를한다. 3. 레지스트리편집기에서 HKLM SYSTEM CurrentControlSet Services에드라이버키를생성한다. 4. Start, Type, ErrorControl값을셋팅한다. 5. 컴퓨터를재부팅한다. 만약원래있던 HelloDriver 의셋팅을그대로이용하고싶다면 2~4 번의과정대신 HelloDriver 레지스트리 셋팅정보가들어있는키에다음과같은값을추가하면됩니다. ImagePath :?? c: windows system32 drivers hellodrv.sys 참고로 ImagePath 값의데이터형은확장가능한문자열 (REG_EXPAND_SZ) 형으로설정합니다.( 그림 2_2 참 조 )
< 그림 2_2> 이제 Chapter1에서설명한방식으로드라이버를로딩합니다. ( 레지스트리의 Start값을 1로설정해서리부팅 시자동으로로딩이되도록합니다.) 테스트프로그램의사용법은아래와같습니다. 1. Create 버튼을눌러서해당디바이스의핸들을얻어온다. 2. 에디트박스에스트링을입력한다. 3. 엔터혹은 Send 버튼을누른다. 4. 리스트박스에사용자가입력한내용이그대로출력된다. 5. 끝내기전에 Close 버튼을눌러해당디바이스의핸들을해제한다. 6. 프로그램을종료한다. 우리디바이스드라이버는단지사용자가입력한데이터를그대로저장하고있다가다시내보내는간단한기능을수행합니다. 그리고해당작업을수행할때적절한디버그메시지도출력해줍니다.( 그림 2_3 참조 ) 만약사용자가입력한데이터가지나치게크면적절한에러메시지를출력해줍니다.( 그림 2_4 참조 ) 이제드라이버소스를자세하게살펴보도록하겠습니다.( 테스트프로그램은소스가매우간단하고위에서어느정도필요한사항은언급했으므로생략하겠습니다.)
< 그림 2_3> < 그림 2_4>
예제 2 : Hello Driver Ver.2 소스분석 지난번에소개한소스에서새로추가된부분을중심으로설명하도록하겠습니다. typedef _DEVICE_EXTENSION PDEVICE_OBJECT pdevobj; UNICODE_STRING DevLinkName; UCHAR *pdevbuffer; ULONG dwbuffersize; DEVICE_EXTENSION, *PDEVICE_EXTENSION; DEVICE_EXTENSION 구조체는사용자가정의해주는구조체입니다. IoCreateDevice() 함수에서두번째파라미터값으로특정사이즈를설정해주게되면윈도우는디바이스를생성할때해당사이즈만큼의메모리를따로할당하고나서디바이스오브젝트구조체멤버중하나인 DeviceExtension에할당된메모리의첫번째주소값을셋팅해줍니다. 그러면해당메모리에사용자가필요한값들을저장해서언제든지불러서사용할수있는것입니다. 따라서위에소스처럼구조체형식으로정의를해놓고사용하게되면편리하게특정값들을저장 / 참조할수있는것입니다. 이구조체형식에는특별한제약이없습니다. pdeviceobject->flags = DO_BUFFERED_IO; Flags 멤버는디바이스오브젝트의속성을결정합니다. 이속성에는디바이스이름관리방식이나데이터입 / 출력방식등이있는데 DO_BUFFERED_IO를셋팅하게되면이디바이스는앞서설명했던 Buffered I/O 방식으로데이터를주고받도록설정하는것입니다. 참고로만약 DO_DIRECT_IO라고하면 Direct I/O방식을사용한다는뜻입니다. 이두가지방식은드라이버에서데이터를처리하는방법이다소차이가나는데우선여기서는 Buffered I/O방식에대해서설명하도록하겠습니다. pdevext = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension; 앞서설명한 DEVICE_EXTENSION 구조체는디바이스오브젝트의멤버인 DeviceExtension을통해참조가능합니다. 이부분은단순히해당구조체에값을집어넣기위해 pdevext라고하는변수를대신사용하고자하는사전작업입니다. pdevext->devlinkname.buffer = (PWSTR)ExAllocatePool(NonPagedPool, DevLinkName.MaximumLength); pdevext->devlinkname.maximumlength = DevLinkName.MaximumLength; RtlCopyUnicodeString(&pDevExt->DevLinkName, &DevLinkName); pdevext->pdevobj = pdeviceobject;
pdevext->pdevbuffer = (UCHAR *)ExAllocatePool(NonPagedPool, MAX_BUFFER_SIZE); pdevext->dwbuffersize = 0; 앞서선언한 DEVICE_EXTENSION 구조체에데이터를할당하는부분입니다. 우리드라이버에서는 Symbolic Link Name을집어넣을수있는 UNICODE_STRING구조체의공간과나중에유저모드프로그램과데이터를주고받는데사용하게될버퍼메모리를할당하고, 디바이스오브젝트의포인터값을셋팅하고현재버퍼사이즈를 0으로셋팅하는기본적인초기화작업을수행합니다. 이소스에서한가지새로운함수가등장하는데바로 ExAllocatePool() 함수입니다. 이것은 C에서사용하는 malloc() 함수처럼동적메모리를할당하는커널 API입니다. 다만 ExAllocatePool() 에는한가지확장된기능이있는데바로할당할메모리를페이지된메모리로할지그렇지않을지를사용자가설정할수있는점입니다.( 다시한번강조하자면페이지메모리는스와핑을허용하고페이지되지않은메모리는스와핑을허용하지않습니다.) 첫번째파라미터인 NonPagedPool이페이지되지않은메모리로할당을하도록설정해주는역할을합니다.( 만약페이지된메모리를할당하고자한다면 PagedPool이라고해주면됩니다.) 그렇다면여기서왜페이지되지않은메모리로할당을했으며이러한선택은어떤기준으로하는지에대해서는나중에자세히설명하도록하겠습니다. status = IoCreateSymbolicLink(&pDevExt->DevLinkName, &DeviceName); 앞부분에서어플리케이션에서디바이스드라이버와통신을하기위해서는 Symbolic Link Name이라는것이필요하고이것을만들기위해서는디바이스의이름과심볼릭링크이름을연결시켜주는작업이필요하다고설명한것을기억하실것입니다. 위에 IoCreateSymbolicLink() 가바로그역할을해주는 API입니다. 이렇게해주면어플리케이션에서 pdevext->devlinkname에들어있는이름 ( 여기서는?? HelloDrv) 을이용해서 CreateFile() 함수로디바이스의핸들을얻어올수있습니다. 한가지주의할점은디바이스드라이버에서는?? 이라고했지만어플리케이션에서는. 라고적어줘야한다는것입니다.( 이것은이름공간 (Name Space) 라고하는특성과관련이있는데이에대한자세한사항은다음기회에다시언급하도록하겠습니다.) for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) DriverObject->MajorFunction[i] = Hello_Dispatch; 첫부분에서드라이버는 IRP라고하는오브젝트구조체를통해메시지를받아처리한다고설명한부분을기억하실것입니다. 그리고이러한 IRP들을처리하려면각각의 IRP마다구분을지어처리할수있는루틴을미리정의해줘야합니다. DriverObject->MajorFunction은바로이러한 IRP들을처리하는루틴들을저장해놓는일종의펑션테이블입니다. 윈도우에서미리정의해놓은 IRP는총개수는 IRP_MJ_MAXIMUM_FUNCTION에정의되어있습니다.( 참고
로 XPDDK에서는이값이 28개입니다.) 그리고이 IRP들은각각 0 ~ IRP_MJ_MAXIMUM_FUNCTION 수까지차례로정의되어있어서 I/O manager 가특정요청을받았을때해당요청에상응하는 IRP를만들어서보내주면드라이버에서는해당 IRP숫자번호에해당하는디스패치함수를호출하게되는것입니다. 우리드라이버에서는 I/O manager에서보내주는 IRP 를 Hello_Dispath라고하는함수에서모두처리하도록정의되어있습니다. VOID Hello_DriverUnload(IN PDRIVER_OBJECT DriverObject) NTSTATUS status = STATUS_SUCCESS; if(driverobject->deviceobject) PDEVICE_EXTENSION pdevext = (PDEVICE_EXTENSION)DriverObject->DeviceObject ->DeviceExtension; status = IoDeleteSymbolicLink(&pDevExt->DevLinkName); if (!NT_SUCCESS(status)) KdPrint(("IoDeleteSymbolicLink failed!!! nstatus : %x", status)); RtlFreeUnicodeString(&pDevExt->DevLinkName); IoDeleteDevice(pDevExt->pDevObj); DriverObject->DeviceObject = NULL; KdPrint(("Hello Driver Unload...bye~")); 드라이버언로드함수에서는진입함수에서연결했던 symbolic link name의연결을해제하고 (IoDelete SymbolicLink함수 ), 할당한 Link name의리소스를해제하는역할 (RtlFreeUnicodeString함수) 을수행합니다. 이제우리드라이버의핵심기능을담당하는 Hello_Dispatch() 함수에대해서자세히알아보도록하겠습니다. PIO_STACK_LOCATION pirpstack = IoGetCurrentIrpStackLocation(irp); IRP의멤버중에 CurrentStackLocation멤버에대해앞에서잠깐언급을할때이멤버는드라이버가해당메시지를처리할때필요한몇몇정보를포함하고있다고했었습니다. 따라서 IRP를처리함에있어서가장먼저이멤버의데이터를참조는루틴이필요합니다. 그런데 IRP는디바이스오브젝트나드라이버오브젝트처럼
사용자가직접참조할수없는멤버들이몇몇있으며그중하나가바로 CurrentStackLocation멤버입니다. 따라서 DDK는해당멤버를참조하기위한매크로를제공해주며 IoGetCurrentIrpStackLocation() 이바로그것입니다. 즉, 위의루틴은 IRP의현재 IRP스택위치의포인터값을 pirpstack 변수에넘겨주는기능을수행합니다. 그리고이 pirpstack은 IRP를처리함에있어아주중요한몇몇정보들을참조하는데사용됩니다. UCHAR majorfunc = pirpstack->majorfunction; pirpstack 의가장중요한역할중하나가바로위구문입니다. pirpstack->majorfunction 에는현재 I/O manager가보내준 IRP가어떤요청인지에대한정보가포함되어있습니다. DDK에정의되어있는 MajorFunction값은아래와같습니다. #define IRP_MJ_CREATE #define IRP_MJ_CREATE_NAMED_PIPE #define IRP_MJ_CLOSE #define IRP_MJ_READ #define IRP_MJ_WRITE #define IRP_MJ_QUERY_INFORMATION #define IRP_MJ_SET_INFORMATION #define IRP_MJ_QUERY_EA #define IRP_MJ_SET_EA #define IRP_MJ_FLUSH_BUFFERS #define IRP_MJ_QUERY_VOLUME_INFORMATION #define IRP_MJ_SET_VOLUME_INFORMATION #define IRP_MJ_DIRECTORY_CONTROL #define IRP_MJ_FILE_SYSTEM_CONTROL #define IRP_MJ_DEVICE_CONTROL #define IRP_MJ_INTERNAL_DEVICE_CONTROL #define IRP_MJ_SHUTDOWN #define IRP_MJ_LOCK_CONTROL #define IRP_MJ_CLEANUP #define IRP_MJ_CREATE_MAILSLOT #define IRP_MJ_QUERY_SECURITY #define IRP_MJ_SET_SECURITY #define IRP_MJ_POWER #define IRP_MJ_SYSTEM_CONTROL #define IRP_MJ_DEVICE_CHANGE #define IRP_MJ_QUERY_QUOTA 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19
#define IRP_MJ_SET_QUOTA #define IRP_MJ_PNP 0x1a 0x1b 예를들어사용자가우리드라이버에게서데이터를읽기위해 ReadFile() 함수를호출하면대략다음과같은과정이이루어집니다. 1. I/O manager는 ReadFile() 함수에서넘어온파라미터들을참고하여 IRP구조체를생성합니다. 2. 드라이버를위한스택공간을생성하고드라이버에서 IRP를처리하는데필요한여러가지보조되는정보들을저장합니다.( 이때스택공간중 majorfunction이차지하는공간에 IRP_MJ_READ값이셋팅이됩니다.) 3. 현재가리키고있는스택포인터위치를 IRP->CurrentStackLocation에저장합니다. 4. 드라이버오브젝트의 MajorFunction[] 테이블을참조하여해당 IRP를처리하도록정의된디스패치함수를호출하면서디바이스오브젝트와 IRP구조체의포인터를파라미터로넘겨줍니다. 5. 디스패치함수에서는 IRP_MJ_READ에대한적절한동작을취하고 IoCompleteRequest() 라고하는함수를이용해서 I/O manager에게작업이완료되었음을알려주고받았던 IRP를 IoCompleteRequest 함수의파라미터로다시넘겨줍니다. 6. I/O manager는드라이버에게서완료되었다는메시지를받으면드라이버가돌려준 IRP에있는상태값을참고하여 ReadFile() 함수에적절한리턴값을넘겨주고 IRP를해제합니다. switch (majorfunc) case IRP_MJ_CREATE: KdPrint(("Hello Device Create!")); break; case IRP_MJ_CLOSE: KdPrint(("Hello Device Close!")); break; case IRP_MJ_CLEANUP: KdPrint(("Hello Device Clean Up!")); break;... default : KdPrint(("I can't dispatch this IRP")); break;
위의구문이바로 IRP의 majorfunction값을토대로각각의적절한동작들을수행하도록하는기본루틴입니다. 윈도우프로그래밍에서 WndProc() 함수의기본루틴과흡사합니다. Majorfunction의기본형식은 IRP_MJ_XXX 로되어있으며 XXX부분이해당동작을설명합니다. 우리드라이버는테스트프로그램에서디바이스의핸들을얻기위해 CreateFile() 을호출할때 IRP_MJ_CREATE 값을받게되고마지막에얻어온핸들을반환하기위해 CloseHandle() 을호출할때 IRP_MJ_CLEANUP과 IRP_MJ_CLOSE값을받게됩니다. 우리드라이버에서는특별히해줘야할작업이없으므로제대로메시지를받았다는디버그메시지만을출력하고넘어가도록정의되어있습니다. 그외에받을수있는 majorfunction값으로 IRP_MJ_READ(ReadFile() 함수호출시 ) 과 IRP_MJ_WRITE(WriteFile() 함수호출시 ) 이있는데우선 IRP_MJ_WRITE처리루틴부터살펴보겠습니다. case IRP_MJ_WRITE: KdPrint(("Hello Device Write!")); psysbuffer = irp->associatedirp.systembuffer; dwbuffersize = pirpstack->parameters.write.length; if (dwbuffersize > MAX_BUFFER_SIZE) KdPrint(("Too Many Wrtie Buffer Size!!!")); status = STATUS_INSUFFICIENT_RESOURCES; break; RtlCopyMemory(pDevExt->pDevBuffer, psysbuffer, dwbuffersize); pdevext->dwbuffersize = dwbuffersize; irp->iostatus.information = pdevext->dwbuffersize; break; 테스트프로그램에서 WriteFile() 함수호출시디바이스에쓰고자하는데이터의버퍼포인터와데이터사이즈를파라미터로넘겨줄것입니다. 우리디바이스는 Buffered I/O 기능수행하도록정의되어있으므로앞에서언급했듯이 I/O manager에서는쓰기작업요청이들어오면데이터사이즈를참조하여유저데이터를충분히수용할만한버퍼를생성합니다. 그리고디바이스드라이버에서그버퍼를참조할수있도록 IRP- >AssociatedIrp.SystemBuffer 에해당버퍼포인터를, 그리고 pirpstack->parameters.write.length에버퍼의사이즈를집어넣습니다. 따라서드라이버에서는이값들을이용하여이멤버의포인터에위치한데이터를미리할당한 pdevext->pdevbuffer에복사하게됩니다.(rtlcopymemory() 함수는유저모드에서사용하는 memcpy() 와같은기능을수행합니다.) Irp->IoStatus.Information 값은나중에작업이완료된후 I/O manager에서참조하는값으로써여기서는테스트프로그램에서호출한 WriteFile() 함수의파라미터중하나인 lpnumberofbytestowrite 값으로들어가게됩니다.
이제 IRP_MJ_READ 처리루틴을살펴보겠습니다. case IRP_MJ_READ: KdPrint(("Hello Device Read!")); psysbuffer = irp->associatedirp.systembuffer; dwbuffersize = pirpstack->parameters.read.length; dwbuffersize = (dwbuffersize < pdevext->dwbuffersize)? dwbuffersize : pdevext- >dwbuffersize; RtlCopyMemory(pSysBuffer, pdevext->pdevbuffer, dwbuffersize); irp->iostatus.information = dwbuffersize; break; WriteFile() 함수와마찬가지로 I/O manager는읽기요청이들어오면버퍼를생성하고 SystemBuffer멤버에포인터를저장하며버퍼사이즈값을 pirpstack->parameters.read.length에저장합니다. 디바이스는단순히저장하고있던데이터를자신이가지고있는버퍼에카피를해주면 ReadFile() 에대한작업이완료됩니다. 여기서한가지주의할사항은 irp->iostatus.information에정확하게복사한데이터사이즈를저장해줘야한다는점입니다. 그이유는읽기요청이완료되면 I/O manager가자신이생성한버퍼에서 IoStatus.Information 에저장된사이즈만큼만유저메모리에복사를하기때문입니다. 따라서이값을 0( 혹은실제복사한데이터사이즈보다작은값 ) 으로셋팅하게되면디바이스드라이버에서복사를했다하더라도유저메모리에그데이터가정확하게저장되지않게됩니다. irp->iostatus.status = status; irp->iostatus.status 는 I/O manager가넘겨준 IRP요청이성공했는지여부를나타내는멤버입니다. 만약이값이 STATUS_SUCCESS라면 I/O manager는 ReadFile() 이나 WriteFile() 과같은유저가호출한함수에성공했다는리턴값 (0이아닌값 ) 을넘겨줄것이고그외에값을넘겨준다면실패값 (0) 을리턴할것입니다. 예를들어 IRP_MJ_WRITE 처리구문에서보여주는것처럼유저모드에서넘겨준데이터가디바이스에서할당된버퍼크기보다도크면조건문에의해 status값을 STATUS_INSUFFICIENT_RESOURCES로셋팅하고넘겨주게되고 WriteFile() 은 0을리턴하게됩니다. 이때 GetLastError() 함수를통해해당에러의원인을찾으면리소스가부족하다는메시지를얻게됩니다.( 그림 2_4 참조 ) IoCompleteRequest(irp, IO_NO_INCREMENT); IoCompleteRequest() 함수는해당 irp 처리가완료되었음을 I/O manager에게알려주는역할을수행합니다. 이함수를호출하지않으면 Hello_Dispatch() 함수를완료하더라도해당요청이완료되었음을 I/O manager나유저프로그램은알지못하고계속완료메시지를기다리게됩니다. 첫번째파라미터는어떠한 IRP의처리가완료되었는지를 I/O manager에게알려주는역할을하며두번째파라미터는해당완료메시지가처리돼야
할우선순위를가리킵니다.( 이것은특별한경우가아니면 IO_NO_INCREMENT로셋팅합니다. 이렇게해서 Hello Driver Ver.2에대한설명을마쳤습니다. 사실이드라이버소스에대해자세하게설명하려면 I/O manager가하는역할이나수행과정에대해좀더자세한설명이필요하고디바이스이름과 Symbolic Link Name에관련하여오브젝트관리자 (Object Manager) 라고하는또다른윈도우의구성요소에대해서도설명이필요합니다. 그외에 CreateFile(), ReadFile(), WriteFile() 과같은 API가어떻게해서드라이버와통신을할수있는지에대해서도좀더자세한설명을위해 Windows Subsystem에대한이해도필요할것입니다. 이런것들에대해서는 Inside Windows 2000에너무나잘설명이되어있으므로꼭읽어보시고그수행메커니즘이나구성에대해서이해하시고넘어가시기바랍니다. 물론다른분야의프로그래밍도마찬가지일테지만 - 디바이스드라이버프로그래밍은윈도우의수행원리에대한정확한이해가없이는공부하기가힘든분야이기때문입니다. 그럼다음번에는드라이버와 App간에통신을할수있는또다른방법인 DeviceIoControl() 에대해알아보도록하겠습니다.