I/O Manager
- I/O 시스템은 IRP (I/O Request Packet)이라 불리는 패킷에 기반하여 동작한다
- 예외로, IRP생성을 생락하고 I/O를 수행하는 Fast I/O라는 기법이 있다.
- I/O Manager는 I/O작업을 표현하기 위해 메모리에 IRP를 생성하고, IRP에 대한 포인터를 드라이버에 전달한 후 I/O작업이 완료되면 패킷을 폐기한다.
- 드라이버는 요청된 I/O 작업이 완료되었거나, 추가 처리를 위해 다른 드라이버로 전달해야하기 때문에, IRP를 수신하고, IRP가 지정한 작업을 수행한 후, IRP를 I/O Manager에게 다시 전달한다.
- I/O Manager는 여러 드라이버가 공통으로 쓸 수 있는 I/O처리 수행코드를 제공하고, 드라이버가 제공하는 모듈식 인터페이스 덕에 I/O Manager는 구조나 내부 세부사항에 대한 특별한 정보없어도 모든 드라이버를 호출할 수 있다.
- 운영체제는 모든 I/O 요청을 파일에 대한 요청인 것처럼 취급하고, 드라이버는 가상 파일에 대한 요청을 하드웨어별로 요청으로 변경한다.
- 또한 i/O Manager를 사용하여 서로를 호출하여 I/O요청을 계층화하고 독립적으로 처리가능해진다.
일반적인 I/O 처리
- 일반적으로 I/O 요청은 애플리케이션의 I/O관련 함수를 실행하는 것으로 시작하여 I/ Manager와 여러 장치 드라이버 밒 HAL에 의해처리된다.
- 모든 I/O요청은 가상 파일에 대한 작업으로 추상화한다.
- I/O Manager는 FILE_OBJECT로 관리되는 파일외에는 아무것도 알지 못한다.
- 따라서 해당 파일을 장치별 명령으로 변환하는 것이 드라이버의 책임이다.
- 이러한 추상화를 통해 어플리케이션의 인터페이스를 디바이스에 일반화할 수 있다.
- 애플리케이션에서 함수를 호출하고 이 함수는 내부 I/O 시스템하수를 호출하여 파일에서 읽고, 파일에 쓰는 작업을 수행한다.
- I/O Manager는 이러한 가상파일 요청을 적절한 장치 드라이버로 동적으로 전달한다.
디바이스 드라이버
I/O 시스템은 장치 드라이버의 실행을 주도한다. 장치 드라이버는 I/O 요청의 다양한 단계를 처리하기 위해 호출되는 일련의 루틴으로 구성된다.
초기화 루틴(Initialization Routine)
I/O Manager는 드라이버를 운영체제에 로드할 때, 드라이버의 초기화 루틴을 실행한다. WDK에서 GSDriverEntry로 설정하며, GSDriverEntry는 SecurityCookie를 초기화한 후, 드라이버 작성자가 수현해야하는 DriverEntry()를 호출한다. 이 루틴은 드라이버의 루틴을 I/O Manager에 등록하고 필요한 전역 드라이버 초기화를 수행한다.
디바이스 추가 루틴(Add-device Routine)
PnP를 지원하는 드라이버는 장치 추가 루틴을 구현한다. 드라이버가 담당하는 장치가 감지될 때마다 PnP 관리자는 이 루틴을 통해 드라이버에 알밍을 보내고, 이루틴에서 드라이버는 일반적으로 장치를 나타내는 장치 객체를 생성한다.
디스패치 루틴 (Dispatch Routine)
디스패치 루틴은 디바이스 드라이버가 제공하는 main 진입점이다. 열기, 닫기, 읽기, 쓰기와 같은 장치, 파일시스템 또는 네트워크가 지원하는 기타 기능들을 제공한다. I/O Manager는 IRP를 생성하고 드라이버의 디스패치 루틴 중 하나를 통해 드라이버를 호출한다.
Start I/O 루틴 (Start I/O Routine)
드라이버는 StartIO 루틴을 사용하여 장치로의 데이터 송수신을 시작할 수 있다. 이 루틴은 I/O Manager를 사용하여 들어오는 I/O요청을 큐에 대기시키는 드라이버에만 정의된다.
대부분의 가장 낮은 수준의 드라이버는 StartIo 루틴을 제공하고 I/O 관리자를 사용하여 RP를 시스템에서 제공하는 디바이스 큐에 큐에 대기시킨다.
인터럽트 서비스 루틴 (Interrupt Service Rutine)
주로 ISR이라고 불리우며, 디바이스가 인터럽트를 발생시키면 커널의 인터럽트 디스패쳐가 이 루틴으로 제어권을 넘긴다. Windows I/O 모델에서 ISR은 장치 인터럽트 요청레벨(DIRQL)로 수행되므로 낮은 IRQL 인터럽트를 인터럽트하지 않기 위해 가능한 최소한의 작업만을 수행한다. 그리고 나머지 인터럽트를 처리하기 위해 더 낮은 IRQL에서 실행되는 DPC(Deffered Procedure Call)를 큐에 대기시킨다. 인터럽트 기반 장치용 드라이버에만 ISR이 정의되어 있다.
DPC Routine (Interrupt-servicing DPC Routine)
DPC 루틴은 ISR이 실행된 후 창치 인터럽트 처리와 관련된 대부분의 작업을 수행한다. DPC 루틴은 다른 인터럽트를 인터럽트 하지않기 위해 ISR보다 낮은 IRQL에서 실행된다. DPC 루틴은 I/O완료통지를 하고 장치에서 대기중인 다름 I/O 작업을 시작한다.
I/O 완료 루틴 (I/O Completion Routine)
IRP처리를 완료할 떄 이를 알려주는 I/O 완료 루틴이 있을 수 있다. 드라이버가 파일로 데이터를 전송을 환요한 후 I/O Manager는 파일 시스템 드라이버의 I/O Completion Routine을 호출한다.
빠른 디스패치 루틴 (Fast Dispatch Routine)
파일 시스템 드라이버와 같이 Windows에서 캐시 매니저를 사용하는 드라이버는 일반적으로 커널이 드라이버에 액세스할 때 일반적인 I/O 처리를 우회할 수 있도록 이러한 루틴을 제공한다. 읽기/쓰기와 같은 작업은 개별 I/O작업을 생성하는 I/O Manager의 일반적인 경로대신 캐시된 데이터에 직접 액세스하여 신속하게 수행할 수 있다.
빠른 디스패치 루틴은 메모리 관리자및 캐시 관리자에서 파일 시스템 드라이버로의 콜백 메커니즘으로도 사용한다. 섹션을 생성할 때, 메모리 관리자는 파일 시스템 드라이버를 다시 호출하여 파일을 독접적으로 확보한다.
WSASend, WSARecv의 동기 I/O동작(Recv의 경우 수신버퍼에 데이터가 들어있을 때, Send의 경우 송신버퍼의 여유가 있을 때)도 해당 빠른 디스패치 루틴으로 IRP의 생성을 거치지않고 처리될 가능성이 높아보인다.
드라이버 오브젝트와 디바이스 오브젝트
스레드가 FILE_OBJECT에 대한 핸들을 열면, I/O Manager는 파일 오브젝트의 이름에서 요청을 처리하기 위해 호출해야하는 드라이버를 결정해야한다. 또한 다음에 스레드가 동일한 파일 핸들을 사용할 때, 이 정보를 찾을 수 있어야한다. Driver Object와 Device Object가 이러한 요구사항을 충족시키기 위해 존재한다.
Driver Objcet
드라이버 오브젝트는 시스템의 개별 드라이버를 나타낸다. I/O Manager는 드라이버 오브젝트에서 각 드라이버의 디스패치 루틴의 주소를 가져온다.
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
UNICODE_STRING DriverName;
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT, *PDRIVER_OBJECT;
위와 같이 정의되어 있으며, I/O Manager는 드라이버가 시스템에 로드될 때, 드라이버 오브잭트를 생성한 다음, 드라이버의 초기화 루틴을 호출하여 해당 속성들을 채운다. I/O관리자는 드라이버의 마지막 드라이버 오브젝트가 삭제되고, 드라이버에 대한 참조가 남아 있지않을 때, 드라이버를 언로드한다.
드라이버 오브젝트에는 여러개의 디바이스 객체가 연관되어 있는 경우가 많다. 드라이버의 객체목록에는 드라이버가 제어하는 물리적, 논리적 디바이스를 나타낸다. 예를들어, 하드 디스크의 각 파티션에는 파티션별 정보가 포하된 별도의 디바이스 오브젝트가 있다. 그러나 모든 파티션을 핵세스하는 데에는 동일한 하드 디스크 드라이버가 사용되기 때문이다.
Device Object
디바이스 오브젝트는 시스템의 물리적, 논리적 장치를 나타내며 버퍼에 필요한 정렬과 수신 IRP를 보관하는 장치 대기열의 위치 등 해당 장치의 특성을 설명한다. 이 오브젝트는 들이 통신하는 대상이기 때문에 모든 I/O의 대상이 된다.
typedef struct _DEVICE_OBJECT {
CSHORT Type;
USHORT Size;
LONG ReferenceCount;
struct _DRIVER_OBJECT *DriverObject;
struct _DEVICE_OBJECT *NextDevice;
struct _DEVICE_OBJECT *AttachedDevice;
struct _IRP *CurrentIrp;
PIO_TIMER Timer;
ULONG Flags;
ULONG Characteristics;
__volatile PVPB Vpb;
PVOID DeviceExtension;
DEVICE_TYPE DeviceType;
CCHAR StackSize;
union {
LIST_ENTRY ListEntry;
WAIT_CONTEXT_BLOCK Wcb;
} Queue;
ULONG AlignmentRequirement;
KDEVICE_QUEUE DeviceQueue;
KDPC Dpc;
ULONG ActiveThreadCount;
PSECURITY_DESCRIPTOR SecurityDescriptor;
KEVENT DeviceLock;
USHORT SectorSize;
USHORT Spare1;
struct _DEVOBJ_EXTENSION *DeviceObjectExtension;
PVOID Reserved;
} DEVICE_OBJECT, *PDEVICE_OBJECT;
위와 같이 정의되어 있으며, 드라이버가 로드된 후, 언제든지 드라이버는 IoCreateDevice()를 호출하여 논리 또는 물리적 디바이스 또는 드라이버에 대한 논리적 인터페이스 또는 엔드포인트를 나타내는 디바이스 오브젝트를 생성할 수 있다. 드라이버가 디바이스 오브젝트를 생성할 때, 드라이버는 선택적으로 디바이스에 이름을 할당할 수 있으며, 이름은 오브젝트 매니저 네임스페이스에 배치한다.
디바이스 오브젝트는 자신이 속한 드라이버 오브젝트의 포인터를 가지고 있으며, 이를 통해 I/O Manager는 I/O요청을 받았을 때, 어떤 드라이버 루틴을 호출할 지 알 수 있다.
동작과정을 간단하게 서술하면 이렇다.
1. 디바이스 오브젝트를 사용하여 장치를 서비스하는 드라이버를 나타내는 드라이버 오브젝트를 찾는다.
2. 드라이버 오브젝트에서 요청에 대한 함수를 찾는다.
이렇게 객체를 사용하여 드라이버의 대한 정보를 기록한다는 것은 I/O Manager는 개별 드라이버에 대한 세부정보를 알 필요가 없음을 의미한다. I/O Manager는 포인터를 따라 드라이버를 찾기만 하면되므로 이식성이 좋다.
참고 자료
게임개발자를 꿈꾸는 대학생의 개발 공부 블로그
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!