메모리 버퍼 수명 주기

메모리 버퍼의 수명 주기는 버퍼가 만들어진 시점부터 삭제되는 시점까지의 시간에 걸쳐 있습니다. 이 항목에서는 버퍼 사용 시나리오 및 버퍼가 삭제되는 경우에 미치는 영향에 대해 설명합니다.

KMDF(커널 모드 드라이버 프레임워크)에서 요청 개체는 I/O 요청을 나타냅니다. 모든 요청 개체는 하나 이상의 메모리 개체와 연결되며 각 메모리 개체는 요청의 입력 또는 출력에 사용되는 버퍼를 나타냅니다.

프레임워크는 들어오는 I/O 요청을 나타내는 요청 및 메모리 개체를 만들 때 요청 개체를 연결된 메모리 개체의 부모로 설정합니다. 따라서 메모리 개체는 요청 개체의 수명보다 오래 지속되지 않을 수 있습니다. 프레임워크 기반 드라이버가 I/O 요청을 완료하면 프레임워크는 요청 개체와 메모리 개체를 삭제하므로 이러한 두 개체에 대한 핸들이 유효하지 않습니다.

그러나 기본 버퍼는 다릅니다. 버퍼를 만든 구성 요소와 버퍼를 만든 방법에 따라 버퍼에 참조 수가 있을 수 있으며 메모리 개체가 소유할 수도 있고 그렇지 않을 수도 있습니다. 메모리 개체가 버퍼를 소유하는 경우 버퍼에는 참조 수가 있고 해당 수명은 메모리 개체의 수명으로 제한됩니다. 다른 구성 요소가 버퍼를 만든 경우 버퍼와 메모리 개체의 수명은 관련이 없습니다.

프레임워크 기반 드라이버는 I/O 대상으로 보낼 자체 요청 개체를 만들 수도 있습니다. 드라이버에서 만든 요청은 드라이버가 I/O 요청에서 받은 기존 메모리 개체를 다시 사용할 수 있습니다. I/O 대상에 요청을 자주 보내는 드라이버는 생성 되는 요청 개체를 다시 사용할 수 있습니다.

드라이버가 잘못된 핸들 또는 버퍼 포인터를 참조하지 않도록 하려면 요청 개체, 메모리 개체 및 기본 버퍼의 수명을 이해하는 것이 중요합니다.

다음과 같은 사용 시나리오를 고려해 보세요.

시나리오 1: 드라이버가 KMDF에서 I/O 요청을 수신하고, 처리하고, 완료합니다.

가장 간단한 시나리오에서 KMDF는 I/O를 수행하고 요청을 완료하는 요청을 드라이버에 디스패치합니다. 이 경우 기본 버퍼는 사용자 모드 애플리케이션, 다른 드라이버 또는 운영 체제 자체에 의해 생성되었을 수 있습니다. 버퍼에 액세스하는 방법에 대한 자세한 내용은 Framework-Based 드라이버에서 데이터 버퍼 액세스를 참조하세요.

드라이버가 요청을 완료하면 프레임워크는 메모리 개체를 삭제합니다. 그러면 버퍼 포인터가 잘못되었습니다.

시나리오 2: 드라이버는 KMDF에서 I/O 요청을 받고 I/O 대상으로 전달합니다.

이 시나리오에서 드라이버는 I/O 대상에 요청을 전달합니다 . 다음 샘플 코드는 드라이버가 들어오는 요청 개체에서 메모리 개체에 대한 핸들을 검색하고, I/O 대상에 보낼 요청의 형식을 지정하고, 요청을 보내는 방법을 보여 줍니다.

VOID
EvtIoRead(
    IN WDFQUEUE Queue,
    IN WDFREQUEST Request,
    IN size_t Length
    )
{
    NTSTATUS status;
    WDFMEMORY memory;
    WDFIOTARGET ioTarget;
    BOOLEAN ret;
    ioTarget = WdfDeviceGetIoTarget(WdfIoQueueGetDevice(Queue));

    status = WdfRequestRetrieveOutputMemory(Request, &memory);
    if (!NT_SUCCESS(status)) {
        goto End;
    }

    status = WdfIoTargetFormatRequestForRead(ioTarget,
                                    Request,
                                    memory,
                                    NULL,
                                    NULL);
    if (!NT_SUCCESS(status)) {
        goto End;
    }

    WdfRequestSetCompletionRoutine(Request,
                                    RequestCompletionRoutine,
                                    WDF_NO_CONTEXT);

    ret = WdfRequestSend (Request, ioTarget, WDF_NO_SEND_OPTIONS);
    if (!ret) {
        status = WdfRequestGetStatus (Request);
        goto End;
    }

    return;

End:
    WdfRequestComplete(Request, status);
    return;

}

I/O 대상이 요청을 완료하면 프레임워크는 드라이버가 요청에 대해 설정한 완료 콜백을 호출합니다. 다음 코드는 간단한 완료 콜백을 보여 제공합니다.

VOID
RequestCompletionRoutine(
    IN WDFREQUEST                  Request,
    IN WDFIOTARGET                 Target,
    PWDF_REQUEST_COMPLETION_PARAMS CompletionParams,
    IN WDFCONTEXT                  Context
    )
{
    UNREFERENCED_PARAMETER(Target);
    UNREFERENCED_PARAMETER(Context);

    WdfRequestComplete(Request, CompletionParams->IoStatus.Status);

    return;

}

드라이버가 완료 콜백에서 WdfRequestComplete 를 호출하면 프레임워크는 메모리 개체를 삭제합니다. 드라이버가 검색한 메모리 개체 핸들이 이제 유효하지 않습니다.

시나리오 3: 드라이버가 기존 메모리 개체를 사용하는 I/O 요청을 실행합니다.

일부 드라이버는 자체 I/O 요청을 발급하고 I/O 대상 개체로 표시되는 I/O 대상으로 보냅니다. 드라이버는 자체 요청 개체를 만들거나 프레임워크에서 만든 요청 개체를 다시 사용할 수 있습니다. 드라이버는 두 방법 중 하나를 사용하여 이전 요청의 메모리 개체를 다시 사용할 수 있습니다. 드라이버는 기본 버퍼를 변경하지 않아야 하지만 새 I/O 요청의 형식을 지정할 때 버퍼 오프셋을 전달할 수 있습니다.

기존 메모리 개체를 사용하는 새 I/O 요청의 형식을 지정하는 방법에 대한 자세한 내용은 일반 I/O 대상에 I/O 요청 보내기를 참조하세요.

프레임워크가 I/O 대상에 보내는 요청의 형식을 지정하면 I/O 대상 개체를 대신하여 재활용된 메모리 개체에 대한 참조를 가져옵니다. I/O 대상 개체는 다음 작업 중 하나가 발생할 때까지 이 참조를 유지합니다.

  • 요청이 완료되었습니다.
  • 드라이버는 WdfIoTargetFormatRequestXxx 또는 WdfIoTargetSendXxxSynchronously 메서드 중 하나를 호출하여 요청 개체의 서식을 다시 지정합니다. 이러한 메서드에 대한 자세한 내용은 프레임워크 I/O 대상 개체 메서드를 참조하세요.
  • 드라이버는 WdfRequestReuse를 호출합니다.

새 I/O 요청이 완료되면 프레임워크는 드라이버가 이 요청에 대해 설정한 I/O 완료 콜백을 호출합니다. 이 시점에서 I/O 대상 개체는 여전히 메모리 개체에 대한 참조를 보유합니다. 따라서 I/O 완성 콜백에서 드라이버는 메모리 개체를 검색한 원래 요청을 완료하기 전에 드라이버에서 만든 요청 개체에서 WdfRequestReuse 를 호출해야 합니다. 드라이버가 WdfRequestReuse를 호출하지 않으면 추가 참조로 인해 버그 검사 발생합니다.

시나리오 4: 드라이버가 새 메모리 개체를 사용하는 I/O 요청을 실행합니다.

프레임워크는 드라이버가 기본 버퍼의 원본에 따라 새 메모리 개체를 만드는 세 가지 방법을 제공합니다. 자세한 내용은 메모리 버퍼 사용을 참조하세요.

버퍼가 프레임워크 또는 드라이버에서 만든 lookaside 목록에서 할당된 경우 메모리 개체는 버퍼를 소유하므로 메모리 개체가 있는 한 버퍼 포인터가 유효한 상태로 유지됩니다. 비동기 I/O 요청을 실행하는 드라이버는 항상 메모리 개체가 소유한 버퍼를 사용해야 하므로 프레임워크는 I/O 요청이 발급 드라이버로 다시 완료될 때까지 버퍼가 유지되도록 해야 합니다.

드라이버가 WdfMemoryCreatePreallocated를 호출하여 이전에 할당된 버퍼를 새 메모리 개체에 할당하는 경우 메모리 개체는 버퍼를 소유하지 않습니다. 이 경우 메모리 개체의 수명 및 기본 버퍼의 수명은 관련이 없습니다. 드라이버는 버퍼의 수명을 관리해야 하며 잘못된 버퍼 포인터를 사용하려고 시도해서는 안됩니다.

시나리오 5: 드라이버가 만든 요청 개체를 다시 사용합니다.

드라이버는 만든 요청 개체를 다시 사용할 수 있지만 각 개체를 다시 사용하려면 WdfRequestReuse 를 호출하여 이러한 각 개체를 다시 초기화해야 합니다. 자세한 내용은 프레임워크 요청 개체 재사용을 참조하세요.

요청 개체를 다시 초기화하는 샘플 코드는 KMDF 릴리스와 함께 제공되는 ToasterNdisEdge 샘플을 참조하세요.