디바이스 큐 관리
일반적으로 I/O 관리자(FSD 제외)는 드라이버가 IoCreateDevice를 호출할 때 연결된 디바이스 큐 개체를 만듭니다. 또한 I/O 관리자가 연결된 디바이스 큐에 IRP를 삽입하거나 StartIo 루틴을 호출하도록 호출할 수 있는 IoStartPacket 및 IoStartNextPacket을 제공합니다.
따라서 드라이버가 IRP에 대한 자체 디바이스 큐 개체를 설정하는 것은 거의 필요하지 않습니다(또는 특히 유용함). 가능성이 높은 후보는 SCSI 포트 드라이버와 같은 드라이버로, 단일 컨트롤러 또는 버스 어댑터를 통해 서비스되는 다른 유형의 디바이스에 대해 몇 가지 밀접하게 결합된 클래스 드라이버에서 들어오는 IRP를 조정해야 합니다.
즉, 디스크 배열 컨트롤러용 드라이버는 추가 디바이스 큐 개체를 설정하는 것보다 드라이버에서 만든 컨트롤러 개체를 사용할 가능성이 더 높고, 추가 기능 버스 어댑터 및 클래스 드라이버 집합의 드라이버는 보조 디바이스 큐를 사용할 가능성이 약간 더 높습니다.
StartIo 루틴과 함께 추가 디바이스 큐 사용
IoStartPacket 및 IoStartNextPacket을 호출하면 드라이버의 디스패치 및 DpcForIsr(또는 CustomDpc) 루틴은 드라이버가 디바이스 개체를 만들 때 I/O 관리자가 만든 디바이스 큐를 사용하여 StartIo 루틴에 대한 호출을 동기화합니다. StartIo 루틴이 있는 포트 드라이버의 경우 IoStartPacket 및 IoStartNextPacket은 포트 드라이버의 공유 디바이스 컨트롤러/어댑터에 대한 디바이스 큐에서 IRP를 삽입하고 제거합니다. 또한 포트 드라이버가 긴밀하게 결합된 상위 수준 클래스 드라이버에서 들어오는 요청을 보관하도록 추가 디바이스 큐를 설정하는 경우 들어오는 IRP를 해당 추가 디바이스 큐(일반적으로 StartIo 루틴)로 "정렬"해야 합니다.
포트 드라이버는 해당 IRP를 적절한 큐에 삽입하기 전에 각 IRP가 속한 추가 디바이스 큐를 결정해야 합니다. 대상 디바이스 개체에 대한 포인터는 IRP와 함께 드라이버의 Dispatch 루틴에 전달됩니다. 드라이버는 들어오는 IRP를 "정렬"하는 데 사용할 포인터를 저장해야 합니다. StartIo 루틴에 전달된 디바이스 개체 포인터는 디바이스 컨트롤러/어댑터를 나타내는 드라이버의 자체 디바이스 개체이므로 이 용도로 사용할 수 없습니다.
IRP를 큐에 추가한 후 드라이버는 공유 컨트롤러/어댑터를 프로그래밍하여 요청을 수행합니다. 따라서 포트 드라이버는 KeInsertDeviceQueue 호출이 특정 클래스 드라이버의 디바이스 큐에 IRP를 배치할 때까지 선착순으로 모든 디바이스에 대한 들어오는 요청을 처리할 수 있습니다.
기본 포트 드라이버는 StartIo 루틴을 통해 처리할 모든 IRP에 자체 디바이스 큐를 사용하여 공유 디바이스(또는 버스) 컨트롤러/어댑터를 통해 연결된 모든 디바이스로 작업을 직렬화합니다. 지원되는 각 디바이스에 대한 IRP를 별도의 디바이스 큐에 보관하는 경우 이 포트 드라이버는 공유 하드웨어를 통해 I/O를 수행하는 다른 모든 디바이스에 대한 I/O 처리량을 늘리면서 이미 사용 중인 디바이스에 대한 IRP 처리를 억제합니다.
포트 드라이버의 디스패치 루틴에서 IoStartPacket 호출에 대한 응답으로 I/O 관리자는 해당 드라이버의 StartIo 루틴을 즉시 호출하거나 IRP를 포트 드라이버의 공유 컨트롤러/어댑터에 대한 디바이스 개체와 연결된 디바이스 큐에 넣습니다.
포트 드라이버는 공유 디바이스 컨트롤러/어댑터를 통해 서비스하는 다른 유형의 각 디바이스에 대한 자체 상태 정보를 유지해야 합니다.
추가 디바이스 큐를 사용하여 클래스/포트 드라이버를 디자인할 때 다음 사항에 유의하세요.
드라이버는 디바이스 스택의 맨 위에 있는 디바이스 개체를 제외하고 자체 위에 계층화된 드라이버에서 만든 디바이스 개체에 대한 포인터를 쉽게 가져올 수 없습니다.
기본적으로 I/O 관리자는 이러한 포인터를 가져오기 위한 지원 루틴을 제공하지 않습니다. 또한 드라이버가 로드되는 순서는 하위 드라이버가 상위 수준 드라이버의 디바이스 개체에 대한 포인터를 가져오는 것을 불가능하게 만듭니다. 이 포인터는 하위 수준 드라이버가 디바이스를 추가할 때 아직 만들어지지 않았습니다.
IoGetAttachedDeviceReference는 드라이버 스택에서 가장 높은 수준의 디바이스 개체에 대한 포인터를 반환하지만 드라이버는 이 포인터를 사용하여 해당 스택에 대한 I/O 요청의 대상을 지정해야 합니다. 드라이버는 디바이스 개체를 읽거나 쓰려고 시도해서는 안 됩니다.
드라이버는 자체 디바이스 스택의 맨 위에 요청을 보내는 것을 제외하고 자체 위에 계층화된 드라이버에서 만든 디바이스 개체에 대한 포인터를 사용할 수 없습니다.
다중 프로세서로부터 안전한 방식으로 두 드라이버 간에 단일 디바이스 개체(및 해당 디바이스 확장)에 대한 액세스를 동기화할 수 있는 방법은 없습니다. 두 드라이버 모두 다른 드라이버가 현재 수행하는 I/O 처리에 대해 어떠한 가정도 할 수 없습니다.
밀접하게 결합된 클래스/포트 드라이버의 경우에도 각 클래스 드라이버는 포트 드라이버의 디바이스 개체에 대한 포인터를 사용하여 IoCallDriver를 사용하여 IRP를 전달해야 합니다. 기본 포트 드라이버는 포트 드라이버의 디바이스 확장에서 밀접하게 결합된 클래스 드라이버의 디바이스에 대해 처리하는 요청에 대해 자체 상태를 유지해야 합니다.
드라이버 루틴에서 추가 디바이스 큐 관리
밀접하게 결합된 클래스 드라이버 집합에 대한 추가 디바이스 큐에서 IRP를 큐에 대기하는 모든 포트 드라이버도 다음 상황을 효율적으로 처리해야 합니다.
디스패치 루틴은 해당 디바이스에 대해 드라이버에서 만든 디바이스 큐에 특정 디바이스에 대한 IRP를 삽입했습니다.
다른 디바이스에 대한 IRP는 계속 들어오고, IoStartPacket을 사용하여 드라이버의 StartIo 루틴에 큐에 대기하고, 공유 디바이스 컨트롤러를 통해 처리됩니다.
디바이스 컨트롤러는 유휴 상태가 되지 않지만 드라이버에서 만든 디바이스 큐에 보관된 각 IRP도 가능한 한 빨리 드라이버의 StartIo 루틴에 큐에 대기해야 합니다.
따라서 포트 드라이버의 DpcForIsr 루틴은 다음과 같이 포트 드라이버가 IRP를 완료할 때마다 특정 디바이스의 드라이버 내부 디바이스 큐에서 공유 어댑터/컨트롤러의 디바이스 큐로 IRP를 전송하려고 시도해야 합니다.
DpcForIsr 루틴은 IoStartNextPacket을 호출하여 StartIo 루틴이 공유 디바이스 컨트롤러에 대기 중인 다음 IRP 처리를 시작하도록 합니다.
DpcForIsr 루틴은 KeRemoveDeviceQueue를 호출하여 IRP를 완료하려고 하는 디바이스의 내부 디바이스 큐에 보관 중인 다음 IRP(있는 경우)를 큐에서 제거합니다.
KeRemoveDeviceQueue가 NULL이 아닌 포인터를 반환하는 경우 DpcForIsr 루틴은 큐에서 제거된 IRP가 있는 IoStartPacket을 호출하여 공유 디바이스 컨트롤러/어댑터에 큐에 대기하도록 합니다. 그렇지 않으면 KeRemoveDeviceQueue 를 호출하면 디바이스 큐 개체의 상태가 비중(Not-Busy)으로 다시 설정되고 DpcForIsr 루틴은 IoStartPacket에 대한 호출을 생략합니다.
그런 다음 DpcForIsr 루틴은 I/O 상태 블록을 오류로 설정하거나 I/O 요청을 충족하여 포트 드라이버가 I/O 처리를 방금 완료한 입력 IRP를 사용하여 IoCompleteRequest를 호출합니다.
앞의 시퀀스는 DpcForIsr 루틴이 IRP의 내부 큐를 효율적으로 관리하기 위해 현재(입력) IRP를 완료하는 디바이스를 결정해야 한다는 것을 의미합니다.
포트 드라이버가 공유 컨트롤러/어댑터가 유휴 상태일 때까지 기다렸다가 추가 디바이스 큐에 보관된 IR을 큐에서 제거하려고 하면 현재 I/O 수요가 실제로 훨씬 더 가벼운 다른 모든 디바이스를 즉시 서비스하는 동안 드라이버가 I/O 수요가 많은 디바이스를 굶어 굶어 버리게 될 수 있습니다.