IRP 큐 Cancel-Safe

자체 IRP 큐를 구현하는 드라이버는 취소 안전 IRP 큐 프레임워크를 사용해야 합니다. 취소 안전 IRP 큐는 IRP 처리를 다음 두 부분으로 분할합니다.

  1. 드라이버는 드라이버의 IRP 큐에서 표준 작업을 구현하는 콜백 루틴 집합을 제공합니다. 제공된 작업에는 큐에서 IRP 삽입 및 제거, 큐 잠금 및 잠금 해제가 포함됩니다. Cancel-Safe IRP 큐 구현을 참조하세요.

  2. 드라이버가 실제로 큐에서 IRP를 삽입하거나 제거해야 할 때마다 시스템에서 제공하는 IoCsqXxx 루틴을 사용합니다. 이러한 루틴은 드라이버에 대한 모든 동기화 및 IRP 취소 논리를 처리합니다.

취소 안전 IRP 큐를 사용하는 드라이버는 IRP 취소를 지원하기 위해 취소 루틴을 구현하지 않습니다.

프레임워크는 드라이버가 큐에서 IRP를 원자성으로 삽입하고 제거하도록 합니다. 또한 IRP 취소가 올바르게 구현되도록 합니다. 프레임워크를 사용하지 않는 드라이버는 삽입 및 삭제를 수행하기 전에 큐를 수동으로 잠그고 잠금을 해제해야 합니다. 또한 취소 루틴을 구현할 때 발생할 수 있는 경합 조건을 피해야 합니다. 발생할 수 있는 경합 조건에 대한 설명은 IRP 취소 동기화를 참조하세요.

취소 안전 IRP 큐 프레임워크는 Windows XP 이상 버전의 Windows에 포함되어 있습니다. Windows 2000 및 Windows 98/Me에서도 작동해야 하는 드라이버는 WDK(Windows 드라이버 키트)에 포함된 Csq.lib 라이브러리에 연결할 수 있습니다. Csq.lib 라이브러리는 이 프레임워크의 구현을 제공합니다.

IoCsqXxx 루틴은 Windows XP 이상 버전의 Wdm.h 및 Ntddk.h에서 선언됩니다. Windows 2000 및 Windows 98/Me에서도 작동해야 하는 드라이버는 선언에 Csq.h를 포함해야 합니다.

WDK의 \src\general\cancel 디렉터리에서 cancel-safe IRP 큐를 사용하는 방법에 대한 전체 데모를 볼 수 있습니다. 이러한 큐에 대한 자세한 내용은 Cancel-Safe IRP 큐에 대한 제어 흐름 백서를 참조하세요.

Cancel-Safe IRP 큐 구현

취소 안전 IRP 큐를 구현하려면 드라이버는 다음 루틴을 제공해야 합니다.

  • 큐에 IRP를 삽입하는 루틴 중 하나( CsqInsertIrp 또는 CsqInsertIrpEx) CsqInsertIrpExCsqInsertIrp의 확장 버전입니다. 큐는 하나 또는 다른 를 사용하여 구현됩니다.

  • 큐에서 지정된 IRP를 제거하는 CsqRemoveIrp 루틴입니다.

  • 큐에서 지정된 IRP 다음에 다음 IRP에 대한 포인터를 반환하는 CsqPeekNextIrp 루틴입니다. 여기서 시스템은 IoCsqRemoveNextIrp에서 수신하는 PeekContext 값을 전달합니다. 드라이버는 어떤 방식으로든 해당 값을 해석할 수 있습니다.

  • 시스템이 IRP 큐를 잠그고 잠금을 해제할 수 있도록 하는 두 루틴은 모두 CsqAcquireLockCsqReleaseLock입니다.

  • 취소된 IRP를 완료하는 CsqCompleteCanceledIrp 루틴입니다.

드라이버 루틴에 대한 포인터는 큐를 설명하는 IO_CSQ 구조에 저장됩니다. 드라이버는 IO_CSQ 구조에 대한 스토리지를 할당합니다. IO_CSQ 구조체는 고정된 크기로 유지되므로 드라이버는 디바이스 확장 내에 구조를 안전하게 포함할 수 있습니다.

드라이버는 IoCsqInitialize 또는 IoCsqInitializeEx 를 사용하여 구조를 초기화합니다. 큐가 CsqInsertIrp를 구현하는 경우 IoCsqInitialize를 사용하거나 큐가 CsqInsertIrpEx를 구현하는 경우 IoCsqInitializeEx를 사용합니다.

드라이버는 각 콜백 루틴에서 필수 기능만 제공해야 합니다. 예를 들어 CsqAcquireLockCsqReleaseLock 루틴만 잠금 처리를 구현합니다. 시스템은 이러한 루틴을 자동으로 호출하여 필요에 따라 큐를 잠그고 잠금을 해제합니다.

적절한 디스패치 루틴이 제공되는 한 드라이버에서 모든 유형의 IRP 큐 메커니즘을 구현할 수 있습니다. 예를 들어 드라이버는 큐를 연결된 목록 또는 우선 순위 큐로 구현할 수 있습니다.

CsqInsertIrpExCsqInsertIrp보다 큐에 보다 유연한 인터페이스를 제공합니다. 드라이버는 반환 값을 사용하여 작업의 결과를 나타낼 수 있습니다. 오류 코드를 반환하면 삽입에 실패합니다. CsqInsertIrp 루틴은 값을 반환하지 않으므로 삽입이 실패했음을 나타내는 간단한 방법은 없습니다. 또한 CsqInsertIrpEx 는 큐 구현에서 사용할 추가 드라이버 관련 정보를 지정하는 데 사용할 수 있는 추가 드라이버 정의 InsertContext 매개 변수를 사용합니다.

드라이버는 CsqInsertIrpEx 를 사용하여 보다 정교한 IRP 처리를 구현할 수 있습니다. 예를 들어 보류 중인 IRP가 없는 경우 CsqInsertIrpEx 루틴은 오류 코드를 반환할 수 있으며 드라이버는 IRP를 즉시 처리할 수 있습니다. 마찬가지로 IRP를 더 이상 큐에 대기할 수 없는 경우 CsqInsertIrpEx 는 해당 사실을 나타내는 오류 코드를 반환할 수 있습니다.

드라이버는 모든 IRP 취소 처리로부터 격리됩니다. 시스템은 큐의 IRP에 대한 취소 루틴을 제공합니다. 이 루틴 은 CsqRemoveIrp 을 호출하여 큐에서 IRP를 제거하고 , CsqCompleteCanceledIrp 를 호출하여 IRP 취소를 완료합니다.

다음 다이어그램에서는 IRP 취소에 대한 제어 흐름을 보여 줍니다.

irp 취소에 대한 제어 흐름을 보여 주는 다이어그램

CsqCompleteCanceledIrp의 기본 구현은 다음과 같습니다.

VOID CsqCompleteCanceledIrp(PIO_CSQ Csq, PIRP Irp) {
  Irp->IoStatus.Status = STATUS_CANCELLED;
  Irp->IoStatus.Information = 0;

  IoCompleteRequest(Irp, IO_NO_INCREMENT);
}

드라이버는 운영 체제의 동기화 기본 형식을 사용하여 CsqAcquireLockCsqReleaseLock 루틴을 구현할 수 있습니다. 사용 가능한 동기화 기본 형식에는 스핀 잠금뮤텍스 개체가 포함됩니다.

다음은 드라이버가 스핀 잠금을 사용하여 잠금을 구현하는 방법의 예입니다.

/* 
  The driver has previously initialized the SpinLock variable with
  KeInitializeSpinLock.
 */

VOID CsqAcquireLock(PIO_CSQ IoCsq, PKIRQL PIrql)
{
    KeAcquireSpinLock(SpinLock, PIrql);
}

VOID CsqReleaseLock(PIO_CSQ IoCsq, KIRQL Irql)
{
    KeReleaseSpinLock(SpinLock, Irql);
}

시스템은 IRQL 변수에 대한 포인터를 CsqAcquireLockCsqReleaseLock에 전달합니다. 드라이버가 스핀 잠금을 사용하여 큐에 대한 잠금을 구현하는 경우 드라이버는 이 변수를 사용하여 큐가 잠겨 있을 때 현재 IRQL을 저장할 수 있습니다.

드라이버는 스핀 잠금을 사용할 필요가 없습니다. 예를 들어 드라이버는 뮤텍스를 사용하여 큐를 잠글 수 있습니다. 드라이버에서 사용할 수 있는 동기화 기술에 대한 설명은 동기화 기술을 참조하세요.

Cancel-Safe IRP 큐 사용

드라이버는 IRP를 큐에 대기 및 큐에서 해제할 때 다음 시스템 루틴을 사용합니다.

  • 큐에 IRP를 삽입하려면 다음 중 하나를 수행합니다. IoCsqInsertIrp 또는 IoCsqInsertIrpEx.

  • IoCsqRemoveNextIrp - 큐에서 다음 IRP를 제거합니다. 드라이버는 필요에 따라 키 값을 지정할 수 있습니다.

다음 다이어그램에서는 IoCsqRemoveNextIrp에 대한 제어 흐름을 보여 줍니다.

iocsqremovenextirp에 대한 제어 흐름을 보여 주는 다이어그램

다음 다이어그램에서는 IoCsqRemoveIrp에 대한 제어 흐름을 보여 줍니다.

iocsqremoveirp에 대한 제어 흐름을 보여 주는 다이어그램

이러한 루틴은 드라이버 제공 루틴으로 디스패치됩니다.

IoCsqInsertIrpEx 루틴은 CsqInsertIrpEx 루틴의 확장된 기능에 대한 액세스를 제공합니다. CsqInsertIrpEx에서 반환된 상태 값을 반환합니다. 호출자는 이 값을 사용하여 IRP가 성공적으로 큐에 대기되었는지 여부를 확인할 수 있습니다. 또한 IoCsqInsertIrpEx 를 사용하면 호출자가 CsqInsertIrpEx의 InsertContext 매개 변수에 대한 값을 지정할 수 있습니다.

큐에 CsqInsertIrp 루틴 또는 CsqInsertIrpEx 루틴이 있는지 여부에 관계없이 모든 취소 안전 큐에서 IoCsqInsertIrp 및 IoCsqInsertIrpEx를 모두 호출할 수 있습니다. IoCsqInsertIrp 는 두 경우 모두 동일하게 동작합니다. IoCsqInsertIrpExCsqInsertIrp 루틴이 있는 큐에 전달되면 IoCsqInsertIrp와 동일하게 동작합니다.

다음 다이어그램에서는 IoCsqInsertIrp에 대한 제어 흐름을 보여 줍니다.

iocsqinsertirp에 대한 제어 흐름을 보여 주는 다이어그램

다음 다이어그램에서는 IoCsqInsertIrpEx에 대한 제어 흐름을 보여 줍니다.

iocsqinsertirpex에 대한 제어 흐름을 보여 주는 다이어그램

IoCsqXxx 루틴을 사용하여 IRP를 큐에 대기 및 큐에서 해제하는 몇 가지 자연스러운 방법이 있습니다. 예를 들어 드라이버는 수신된 순서대로 처리할 IRP를 큐에 대기시킬 수 있습니다. 드라이버는 다음과 같이 IRP를 큐에 대기시킬 수 있습니다.

    status = IoCsqInsertIrpEx(IoCsq, Irp, NULL, NULL);

드라이버가 특정 IRP를 구분할 필요가 없는 경우 다음과 같이 큐에 대기된 순서대로 큐에 넣기만 하면 됩니다.

    IoCsqRemoveNextIrp(IoCsq, NULL);

또는 드라이버가 특정 IRP를 큐에 대기시키고 큐를 해제할 수 있습니다. 루틴은 불투명 IO_CSQ_IRP_CONTEXT 구조를 사용하여 큐의 특정 IRP를 식별합니다. 드라이버는 다음과 같이 IRP를 큐에 대기합니다.

    IO_CSQ_IRP_CONTEXT ParticularIrpInQueue;
    IoCsqInsertIrp(IoCsq, Irp, &ParticularIrpInQueue);

그런 다음 드라이버는 IO_CSQ_IRP_CONTEXT 값을 사용하여 동일한 IRP를 큐에서 제거할 수 있습니다.

    IoCsqRemoveIrp(IoCsq, Irp, &ParticularIrpInQueue);

드라이버는 특정 기준에 따라 큐에서 IRP를 제거해야 할 수도 있습니다. 예를 들어 드라이버는 우선 순위가 높은 IRP가 먼저 큐에서 해제되도록 각 IRP와 우선 순위를 연결할 수 있습니다. 드라이버는 PeekContext 값을 IoCsqRemoveNextIrp에 전달할 수 있습니다. 이 값은 시스템에서 큐에서 다음 IRP를 요청할 때 드라이버로 다시 전달됩니다.