Lowest-Level 드라이버의 StartIo 루틴
I/O 관리자가 드라이버의 디스패치 루틴을 호출하는 것은 디바이스 I/O 요청을 충족하는 첫 번째 단계입니다. StartIo 루틴은 두 번째 단계입니다. StartIo 루틴이 있는 모든 디바이스 드라이버는 DispatchRead 및 DispatchWrite 루틴에서 IoStartPacket을 호출할 수 있으며, 일반적으로 DispatchDeviceControl 루틴에서 지원하는 I/O 제어 코드의 하위 집합에 대해 호출할 수 있습니다. IoStartPacket 루틴은 디바이스의 시스템 제공 디바이스 큐에 IRP를 추가하거나 큐가 비어 있는 경우 즉시 드라이버의 StartIo 루틴을 호출하여 IRP를 처리합니다.
드라이버의 StartIo 루틴이 호출되면 대상 디바이스가 사용 중이 아니라고 가정할 수 있습니다. I/O 관리자가 두 가지 상황에서 StartIo 를 호출하기 때문입니다. 드라이버의 디스패치 루틴 중 하나가 방금 IoStartPacket 이라고 했고 디바이스 큐가 비어 있거나 드라이버의 DpcForIsr 루틴이 다른 요청을 완료하고 다음 IRP를 큐에 넣기 위해 IoStartNextPacket 을 호출했습니다.
최상위 디바이스 드라이버의 StartIo 루틴이 호출되기 전에 해당 드라이버의 디스패치 루틴은 필요한 경우 사용자 버퍼를 검색하고 잠가 서 StartIo 루틴에 큐에 대기 중인 IRP에서 유효한 매핑된 버퍼 주소를 설정해야 합니다. 최상위 디바이스 드라이버가 직접 I/O(또는 버퍼링되거나 직접 I/O 모두)에 대해 디바이스 개체를 설정하는 경우 드라이버는 사용자 버퍼를 StartIo 루틴으로 잠그는 것을 연기할 수 없습니다. 모든 StartIo 루틴은 IRQL = DISPATCH_LEVEL 임의 스레드 컨텍스트에서 호출됩니다.
참고
드라이버의 StartIo 루틴에서 액세스할 버퍼 메모리는 상주 시스템 공간 메모리에서 잠기거나 할당해야 하며 임의의 스레드 컨텍스트에서 액세스할 수 있어야 합니다.
일반적으로 하위 수준 디바이스 드라이버의 StartIo 루틴은 입력 IRP를 사용하여 IoGetCurrentIrpStackLocation 을 호출한 다음 해당 디바이스에서 I/O 작업을 시작하는 데 필요한 요청별 처리를 수행합니다. 요청별 처리에는 다음이 포함될 수 있습니다.
드라이버가 유지 관리하는 현재 요청에 대한 상태 정보를 설정하거나 업데이트합니다. 상태 정보는 대상 디바이스 개체의 디바이스 확장 또는 드라이버가 할당한 페이지가 없는 풀의 다른 곳에 저장될 수 있습니다.
예를 들어 디바이스 드라이버가 현재 전송 작업에 대해 InterruptExpected Boolean을 유지 관리하는 경우 StartIo 루틴에서 이 변수를 TRUE로 설정할 수 있습니다. 드라이버가 현재 작업에 대한 제한 시간 카운터를 유지 관리하는 경우 StartIo 루틴이 이 값을 설정하거나 StartIo 루틴이 드라이버의 CustomTimerDpc 루틴을 큐에 대기할 수 있습니다.
StartIo 루틴이 상태 정보 또는 하드웨어 리소스에 대한 액세스를 다른 드라이버 루틴과 공유하는 경우 상태 정보 또는 리소스는 스핀 잠금으로 보호되어야 합니다. ( 스핀 잠금을 참조하세요.)
StartIo 루틴이 드라이버의 InterruptService 루틴과 상태 정보 또는 리소스에 대한 액세스를 공유하는 경우 StartIo는 KeSynchronizeExecution을 사용하여 상태 또는 리소스 정보에 액세스하는 SynchCritSection 루틴을 호출해야 합니다. ( 중요 섹션 사용을 참조하세요.)
드라이버가 IRP를 처리하는 동안 디바이스 I/O 오류를 기록해야 하는 경우 IRP에 시퀀스 번호를 할당합니다.
자세한 내용은 로깅 오류를 참조하세요.
필요한 경우 드라이버의 I/O 스택 위치에 있는 매개 변수를 디바이스별 값으로 변환합니다.
예를 들어 디스크 드라이버는 전송 작업을 위해 시작 섹터 또는 바이트 오프셋을 실제 디스크 주소로 계산해야 할 수 있으며, 전송의 요청된 길이가 특정 섹터 경계를 넘거나 실제 디바이스의 전송 용량을 초과할지 여부를 계산해야 할 수 있습니다.
드라이버가 이동식 미디어 디바이스를 제어하는 경우 I/O용 디바이스를 프로그래밍하기 전에 미디어 변경 내용을 확인하고 미디어가 변경된 경우 해당 오버리싱 파일 시스템에 알립니다.
자세한 내용은 이동식 미디어 지원을 참조하세요.
디바이스가 DMA를 사용하는 경우 입력/출력 기술에 설명된 대로 요청된 길이(전송할 바이트 수, IRP의 드라이버 I/O 스택 위치에 있는 바이트 수)를 부분 전송 작업으로 분할해야 하는지 여부를 확인합니다.
이러한 디바이스 드라이버의 StartIo 루틴은 KeFlushIoBuffers를 호출하고 드라이버가 패킷 기반 DMA를 사용하는 경우 드라이버의 AdapterControl 루틴으로 AllocateAdapterChannel을 호출하는 역할을 할 수도 있습니다.
자세한 내용은 어댑터 개체 및 DMA 및 캐시 일관성 유지 관리를 참조하세요.
디바이스가 PIO를 사용하는 경우 Irp-MdlAddress>의 IRP에 설명된 버퍼의 기본 가상 주소를 MmGetSystemAddressForMdlSafe가 있는 시스템 공간 주소에 매핑합니다.
읽기 요청의 경우 PIO 작업이 시작되기 전에 디바이스 드라이버의 StartIo 루틴이 KeFlushIoBuffers 호출을 담당할 수 있습니다. 자세한 내용은 캐시 일관성 유지 관리를 참조하세요.
비 WDM 드라이버가 컨트롤러 개체를 사용하는 경우 IoAllocateController 를 호출하여 ControllerControl 루틴을 등록합니다.
드라이버가 취소 가능한 IRP를 처리하는 경우 입력 IRP가 이미 취소되었는지 확인합니다.
입력 IRP가 완료되기 전에 취소할 수 있는 경우 StartIo 루틴은 IRP 및 드라이버 취소 루틴의 진입점을 사용하여 IoSetCancelRoutine을 호출해야 합니다. StartIo 루틴은 IoSetCancelRoutine 호출에 대한 취소 스핀 잠금을 획득해야 합니다. 또는 드라이버가 IoSetStartIoAttributes를 사용하여 StartIo 루틴에 대한 NonCancelable 특성을 TRUE로 설정할 수 있습니다. 이렇게 하면 시스템에서 IoStartPacket을 호출하여 StartIo에 전달된 IRP를 취소하지 못하게 됩니다.
일반적으로 버퍼링된 I/O를 사용하는 드라이버에는 직접 I/O를 사용하는 것보다 더 간단한 StartIo 루틴이 있습니다. 버퍼링된 I/O를 사용하는 드라이버는 각 전송 요청에 대해 소량의 데이터를 전송하고, 직접 I/O(DMA 또는 PIO)를 사용하는 드라이버는 시스템 메모리의 실제 페이지 경계에 걸쳐 있을 수 있는 잠긴 버퍼 간에 대량의 데이터를 전송합니다.
물리적 디바이스 드라이버 위에 계층화된 상위 수준 드라이버는 일반적으로 해당 디바이스 드라이버와 일치하도록 디바이스 개체를 설정합니다. 그러나 최상위 드라이버, 특히 파일 시스템 드라이버는 직접 또는 버퍼링된 I/O 모두에 대해 디바이스 개체를 설정할 수 있습니다.
버퍼링된 I/O에 대해 디바이스 개체를 설정한 드라이버는 I/O 관리자를 사용하여 드라이버에 보내는 모든 IRP에서 유효한 버퍼를 전달할 수 있습니다. 직접 I/O용 디바이스 개체를 설정하는 하위 수준 드라이버는 체인의 최상위 드라이버를 사용하여 중간 드라이버를 통해 기본 하위 수준 디바이스 드라이버로 전송되는 모든 IRP의 유효한 버퍼를 전달할 수 있습니다.
StartIo 루틴에서 버퍼링된 I/O 사용
드라이버의 DispatchRead, DispatchWrite 또는 DispatchDeviceControl 루틴이 요청이 유효하다고 판단하고 IoStartPacket을 호출하는 경우 I/O 관리자는 드라이버의 StartIo 루틴을 호출하여 디바이스 큐가 비어 있는 경우 IRP를 즉시 처리합니다. 큐가 비어 있지 않으면 IoStartPacket 은 IRP를 큐에 대기합니다. 결국 드라이버의 DpcForIsr 또는 CustomDpc 루틴에서 IoStartNextPacket을 호출하면 I/O 관리자가 IRP를 큐에서 제거하고 드라이버의 StartIo 루틴을 호출합니다.
StartIo 루틴은 IoGetCurrentIrpStackLocation을 호출하고 요청을 충족하기 위해 수행해야 하는 작업을 결정합니다. I/O 요청을 수행하기 위해 물리적 디바이스를 프로그래밍하기 전에 필요한 방식으로 IRP를 전처리합니다.
물리적 디바이스(또는 디바이스 확장)에 대한 액세스를 InterruptService 루틴과 동기화해야 하는 경우 StartIo 루틴은 SynchCritSection 루틴을 호출하여 필요한 디바이스 프로그래밍을 수행해야 합니다. 자세한 내용은 중요 섹션 사용을 참조하세요.
버퍼링된 I/O를 사용하는 물리적 디바이스 드라이버는 I/O 관리자가 할당한 시스템 공간 버퍼에서 Irp-AssociatedIrp.SystemBuffer>의 각 IRP에서 찾은 데이터를 전송합니다.
StartIo 루틴에서 직접 I/O 사용
드라이버의 DispatchRead, DispatchWrite 또는 DispatchDeviceControl 루틴이 요청이 유효하다고 판단하고 IoStartPacket을 호출하는 경우 I/O 관리자는 드라이버의 StartIo 루틴을 호출하여 디바이스 큐가 비어 있는 경우 IRP를 즉시 처리합니다. 큐가 비어 있지 않으면 IoStartPacket 은 IRP를 큐에 대기합니다. 결국 드라이버의 DpcForIsr 또는 CustomDpc 루틴에서 IoStartNextPacket을 호출하면 I/O 관리자가 IRP를 큐에서 제거하고 드라이버의 StartIo 루틴을 호출합니다.
StartIo 루틴은 IoGetCurrentIrpStackLocation을 호출하고 요청을 충족하기 위해 수행해야 하는 작업을 결정합니다. 대규모 DMA 전송 요청을 부분 전송 범위로 분할하고 분할해야 하는 들어오는 전송 요청의 길이 에 대한 상태를 저장하는 등 필요한 방식으로 IRP를 전처리합니다. 그런 다음 I/O 요청을 수행하도록 물리적 디바이스를 프로그래밍합니다.
물리적 디바이스(또는 디바이스 확장)에 대한 액세스를 드라이버의 ISR과 동기화해야 하는 경우 StartIo 루틴은 드라이버 제공 SynchCritSection 루틴을 사용하여 필요한 프로그래밍을 수행해야 합니다. 자세한 내용은 중요 섹션 사용을 참조하세요.
직접 I/O를 사용하는 모든 드라이버는 Irp-MdlAddress>의 IRP에서 드라이버가 찾은 MDL(메모리 설명자 목록)에 설명된 잠긴 버퍼에서 데이터를 읽거나 씁니다. 이러한 드라이버는 일반적으로 디바이스 제어 요청에 버퍼링된 I/O를 사용합니다. 자세한 내용은 StartIo 루틴에서 I/O 제어 요청 처리를 참조하세요.
MDL 형식은 드라이버가 직접 액세스하지 않는 불투명 형식입니다. 대신 PIO를 사용하는 드라이버는 Irp-MdlAddress>를 매개 변수로 사용하여 MmGetSystemAddressForMdlSafe를 호출하여 사용자 공간 버퍼를 다시 매핑합니다. 또한 DMA를 사용하는 드라이버는 Irp-MdlAddress>를 전달하여 전송 작업 중에 루틴을 지원하여 버퍼 주소를 디바이스의 논리적 범위로 다시 매핑합니다.
긴밀하게 결합된 상위 수준 드라이버가 기본 디바이스 드라이버에 대한 큰 DMA 전송 요청을 분할하지 않는 한, 가장 낮은 수준의 디바이스 드라이버의 StartIo 루틴은 디바이스가 단일 전송 작업에서 관리할 수 있는 것보다 큰 각 전송 요청을 분할해야 합니다. 시스템 DMA를 사용하는 드라이버는 시스템 DMA 컨트롤러 또는 디바이스가 단일 전송 작업에서 처리하기에는 너무 큰 전송 요청을 분할해야 합니다.
디바이스가 종속 DMA 디바이스인 경우 해당 드라이버는 DMA 채널을 나타내는 드라이버 할당 어댑터 개체 및 드라이버 제공 AdapterControl 루틴과 시스템 DMA 컨트롤러를 통해 전송을 동기화해야 합니다. 또한 버스 master DMA 디바이스의 드라이버는 드라이버 할당 어댑터 개체를 사용하여 전송을 동기화해야 하며 시스템의 패킷 기반 DMA 지원을 사용하는 경우 AdapterControl 루틴 또는 시스템의 분산/수집 지원을 사용하는 경우 AdapterListControl 루틴을 제공해야 합니다.
드라이버의 디자인에 따라 물리적 디바이스의 전송 및 디바이스 제어 작업을 컨트롤러 개체와 동기화하고 ControllerControl 루틴을 제공할 수 있습니다.
자세한 내용은 어댑터 개체 및 DMA 및 컨트롤러 개체 를 참조하세요.
StartIo 루틴에서 I/O 제어 요청 처리
일반적으로 드라이버의 StartIo 루틴에서 추가 처리를 위해 디바이스 I/O 컨트롤 요청의 하위 집합만 드라이버의 DispatchDeviceControl 또는 DispatchInternalDeviceControl 루틴에서 전달됩니다. 드라이버의 StartIo 루틴은 디바이스 상태 변경이 필요하거나 현재 디바이스 상태에 대한 휘발성 정보를 반환해야 하는 유효한 디바이스 제어 요청만 처리해야 합니다.
각 새 드라이버는 동일한 종류의 디바이스에 대해 다른 모든 드라이버와 동일한 공용 I/O 제어 코드 집합을 지원해야 합니다. 시스템은 IRP_MJ_DEVICE_CONTROL 요청에 대한 공용 디바이스 유형별 I/O 제어 코드를 버퍼링된 요청으로 정의합니다.
따라서 물리적 디바이스 드라이버는 디바이스 제어 요청에 대한 Irp-ASSOCIATedIrp.SystemBuffer>의 IRP 에서 각 드라이버가 찾은 시스템 공간 버퍼로 또는 시스템 공간 버퍼에서 데이터를 전송합니다. 직접 I/O를 위해 디바이스 개체를 설정한 드라이버도 버퍼링된 I/O를 사용하여 공용 I/O 제어 코드로 디바이스 제어 요청을 충족합니다.
각 I/O 제어 코드의 정의는 해당 요청에 대해 전송된 데이터가 버퍼링되는지 여부를 결정합니다. 쌍을 이루는 드라이버 간의 드라이버별 IRP_MJ_INTERNAL_DEVICE_CONTROL 요청에 대한 비공개로 정의된 I/O 제어 코드는 메서드 버퍼링, 메서드 직접 또는 메서드를 사용하여 코드를 정의할 수 없습니다. 일반적으로 밀접하게 결합된 상위 수준 드라이버가 해당 요청에 대한 버퍼를 할당해야 하는 경우에는 비공개로 정의된 모든 I/O 제어 코드를 메서드로 정의해야 합니다.
I/O 작업을 위한 디바이스 프로그래밍
일반적으로 최저 수준 디바이스 드라이버의 StartIo 루틴은 KeSynchronizeExecution 을 사용하여 드라이버 제공 SynchCritSection 루틴을 호출하여 드라이버의 ISR과 공유하는 모든 메모리 또는 디바이스 레지스터에 대한 액세스를 동기화해야 합니다. 드라이버의 StartIo 루틴은 SynchCritSection 루틴을 사용하여 실제로 DIRQL에서 I/O용 물리적 디바이스를 프로그래밍합니다. 자세한 내용은 중요 섹션 사용을 참조하세요.
KeSynchronizeExecution을 호출하기 전에 StartIo 루틴은 요청에 필요한 모든 전처리를 수행해야 합니다. 전처리에는 초기 부분 전송 범위를 계산하고 다른 드라이버 루틴에 대한 원래 요청에 대한 상태 정보를 저장하는 것이 포함될 수 있습니다.
디바이스 드라이버가 DMA를 사용하는 경우 StartIo 루틴은 일반적으로 드라이버 제공 AdapterControl 루틴을 사용하여 AllocateAdapterChannel을 호출합니다. 이러한 상황에서 StartIo 루틴은 물리적 디바이스를 AdapterControl 루틴으로 프로그래밍하는 책임을 연기합니다. 그러면 KeSynchronizeExecution 을 호출하여 드라이버 제공 SynchCritSection 루틴 프로그램을 DMA 전송을 위한 디바이스로 만들 수 있습니다.