이번에는 IRP(I/O Request Packet)에 대해서 알아보겠습니다. 처음 공부하는 입장이라 용어들이 매우 낯설게 느껴집니다. 그래도 계속해서 보고, 또 다른부분을 보다가도 눈에 띄이다 보니 자연스레 조금은 친숙해지는것 같습니다.
IRP(I/O Request Packet) Windows2000이상의 OS에서 거의 모든 I/O는 패킷 구동 방식으로 이루어집니다. 개별적인 I/O요청은 각각 드라이버가 무엇을 해야 할지를 말하고, I/O 서브시스템을 통한 요구 처리 과정을 추적하는 작업 지시의 형태로 구현됩니다. 이런한 작업 지시들은 I/O Request Packet(IRP)라는 데이터 구조체 형태로 이루어집니다.
IRP처리과정
I/O를 위한 사용자 모드 각각의 요구에 따라 I/O 관리자는 하나의 IRP를 nonpaged 시스템 메모리에 할당한다. I/O관리자는 파일 핸들과 사용자에 의해 요청된 I/O함수를 기반으로 IRP를 적절한 디바이스 드라이버의 Dispatch 루틴으로 전달합니다.
Dispatch 루틴은 요청에 대한 Parameter를 체크하고, 만약 유효하다면 그 IRP를 드라이버의 Start I/O루틴에 전달합니다.
Start I/O 루틴은 IRP의 내용을 이해하여 디바이스의 동작을 시작합니다.
동작이 완료되었을 때 드라이버의 DpcForlsr 루틴은 IRP에 최종상태 코드를 저장하고 이를 I/O 관리자에게 전달합니다.
I/O 관리자는 IRP의 정보를 이용하여 그 요구를 완료하고 최종 상태를 사용자에게 보냅니다.
참고 IRP 이름에서 혼동되는 부분이 발생 될 수 도 있습니다. 소켓도 아닌 커널모드 프로그램인데 패킷을 주고 받는것이 그렇습니다. 이는 NT설계자들이 운영체제의 견고성과 신뢰성을 위해 핵심 구현(Subsystem구조)을 클라이언트-서버 구조로 개발하였기 때문이라고 합니다.
typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _IRP { CSHORT Type; USHORT Size; // // Define the common fields used to control the IRP. // // // Define a pointer to the Memory Descriptor List (MDL) for this I/O // request. This field is only used if the I/O is "direct I/O". // PMDL MdlAddress; // // Flags word - used to remember various flags. // ULONG Flags; // // The following union is used for one of three purposes: // // 1. This IRP is an associated IRP. The field is a pointer to a master // IRP. // // 2. This is the master IRP. The field is the count of the number of // IRPs which must complete (associated IRPs) before the master can // complete. // // 3. This operation is being buffered and the field is the address of // the system space buffer. // union { struct _IRP *MasterIrp; __volatile LONG IrpCount; PVOID SystemBuffer; } AssociatedIrp; // // Thread list entry - allows queueing the IRP to the thread pending I/O // request packet list. // LIST_ENTRY ThreadListEntry; // // I/O status - final status of operation. // IO_STATUS_BLOCK IoStatus; // // Requestor mode - mode of the original requestor of this operation. // KPROCESSOR_MODE RequestorMode; // // Pending returned - TRUE if pending was initially returned as the // status for this packet. // BOOLEAN PendingReturned; // // Stack state information. // CHAR StackCount; CHAR CurrentLocation; // // Cancel - packet has been canceled. // BOOLEAN Cancel; // // Cancel Irql - Irql at which the cancel spinlock was acquired. // KIRQL CancelIrql; // // ApcEnvironment - Used to save the APC environment at the time that the // packet was initialized. // CCHAR ApcEnvironment; // // Allocation control flags. // UCHAR AllocationFlags; // // User parameters. // PIO_STATUS_BLOCK UserIosb; PKEVENT UserEvent; union { struct { union { PIO_APC_ROUTINE UserApcRoutine; PVOID IssuingProcess; }; PVOID UserApcContext; } AsynchronousParameters; LARGE_INTEGER AllocationSize; } Overlay; // // CancelRoutine - Used to contain the address of a cancel routine supplied // by a device driver when the IRP is in a cancelable state. // __volatile PDRIVER_CANCEL CancelRoutine; // // Note that the UserBuffer parameter is outside of the stack so that I/O // completion can copy data back into the user's address space without // having to know exactly which service was being invoked. The length // of the copy is stored in the second half of the I/O status block. If // the UserBuffer field is NULL, then no copy is performed. // PVOID UserBuffer; // // Kernel structures // // The following section contains kernel structures which the IRP needs // in order to place various work information in kernel controller system // queues. Because the size and alignment cannot be controlled, they are // placed here at the end so they just hang off and do not affect the // alignment of other fields in the IRP. // union { struct { union { // // DeviceQueueEntry - The device queue entry field is used to // queue the IRP to the device driver device queue. // KDEVICE_QUEUE_ENTRY DeviceQueueEntry; struct { // // The following are available to the driver to use in // whatever manner is desired, while the driver owns the // packet. // PVOID DriverContext[4]; } ; } ; // // Thread - pointer to caller's Thread Control Block. // PETHREAD Thread; // // Auxiliary buffer - pointer to any auxiliary buffer that is // required to pass information to a driver that is not contained // in a normal buffer. // PCHAR AuxiliaryBuffer; // // The following unnamed structure must be exactly identical // to the unnamed structure used in the minipacket header used // for completion queue entries. // struct { // // List entry - used to queue the packet to completion queue, among // others. // LIST_ENTRY ListEntry; union { // // Current stack location - contains a pointer to the current // IO_STACK_LOCATION structure in the IRP stack. This field // should never be directly accessed by drivers. They should // use the standard functions. // struct _IO_STACK_LOCATION *CurrentStackLocation; // // Minipacket type. // ULONG PacketType; }; }; // // Original file object - pointer to the original file object // that was used to open the file. This field is owned by the // I/O system and should not be used by any other drivers. // PFILE_OBJECT OriginalFileObject; } Overlay; // // APC - This APC control block is used for the special kernel APC as // well as for the caller's APC, if one was specified in the original // argument list. If so, then the APC is reused for the normal APC for // whatever mode the caller was in and the "special" routine that is // invoked before the APC gets control simply deallocates the IRP. // KAPC Apc; // // CompletionKey - This is the key that is used to distinguish // individual I/O operations initiated on a single file handle. // PVOID CompletionKey; } Tail; } IRP, *PIRP;
IRP Layout 하나의 IRP는 가변의 크기의 구조체이며, 이는 nopaged pool에 할당됩니다. 아래의 그림과 같이 크게 2부분(헤더, 스택: 하위 요청파리미터)으로 구성됩니다.
<IRP가 메모리에 로드되었을 때 구조>
IRP Header IRP Header 영역은 전체 I/O 요구에 대한 다양한 정보를 가지고 있습니다. 이 헤더 정보를 드라이버에서 직접 접근할 수 도 있고, 이와 반대로 다른 부분은 I/O관리자에 대해 배타적 특성을 가질 수 있습니다.
입력을 읽고 출력을 쓸 수 있는 버퍼가 있습니다.
현재 드라이버가 소유하는 IRP 메모리 영역을 가지고 있습니다.
현재 드라이버가 소유하는 IRP에 대해서 운영체제가 취소를 호출 했을 때 동작하는 루틴이 있어야합니다.
하위 요청(sub-request)에 대한 파라미터가 있습니다.
IRP Header는 특정 데이터에 대해서 추가 포인터를 가질 수 있습니다.
위 IRP 구조체 필드 중에서 중요한 부분에 대해서 정리한 표이다.
이제 어느정도 IRP에 대해서 알아본듯 합니다. 결코 쉽지 않은 용어들로 즐비하지만 포기하지 않고 꾸준히만 한다면 승산이 있을 것이라고 생각합니다.