WDF 드라이버에서 데이터 버퍼 액세스(KMDF 또는 UMDF)

WDF(Windows 드라이버 프레임워크) 드라이버가 읽기, 쓰기 또는 디바이스 I/O 제어 요청을 받으면 요청 개체에는 입력 버퍼, 출력 버퍼 또는 둘 다 포함됩니다.

입력 버퍼에는 드라이버에 필요한 정보가 포함됩니다. 쓰기 요청의 경우 이 정보는 일반적으로 함수 드라이버가 디바이스에 보내야 하는 데이터입니다. 디바이스 I/O 컨트롤 요청의 경우 입력 버퍼에 드라이버가 수행해야 하는 작업 유형을 나타내는 정보가 포함될 수 있습니다.

출력 버퍼는 드라이버에서 정보를 받습니다. 읽기 요청의 경우 이 정보는 일반적으로 함수 드라이버가 디바이스에서 수신하는 데이터입니다. 디바이스 I/O 컨트롤 요청의 경우 출력 버퍼는 요청의 I/O 제어 코드에 의해 지정된 상태 또는 기타 정보를 수신할 수 있습니다.

드라이버가 요청의 데이터 버퍼에 액세스하는 데 사용하는 기술은 디바이스의 데이터 버퍼에 액세스하는 드라이버의 방법에 따라 달라집니다. 세 가지 액세스 방법이 있습니다.

  • 버퍼링된 I/O. I/O 관리자는 드라이버와 공유하는 중간 버퍼를 만듭니다.
  • 직접 I/O. I/O 관리자는 버퍼 공간을 실제 메모리로 잠간 다음 드라이버에 버퍼 공간에 직접 액세스할 수 있도록 합니다.
  • 버퍼링되거나 직접 I/O가 아닙니다. I/O 관리자는 드라이버에 요청 버퍼 공간의 가상 주소를 제공합니다. I/O 관리자는 요청의 버퍼 공간의 유효성을 검사하지 않으므로 드라이버는 버퍼 공간에 액세스할 수 있는지 확인하고 버퍼 공간을 실제 메모리에 잠가야 합니다.

Kernel-Mode 드라이버 프레임워크(KMDF) 드라이버는 세 가지 액세스 방법 중에서 사용할 수 있습니다. UMDF(User-Mode Driver Framework) 드라이버는 읽기, 쓰기 및 IOCTL 요청에 버퍼링 또는 직접 I/O를 사용할 수 있으며 METHOD_NEITHER 메서드를 지정하는 요청을 변환할 수 있습니다.

버퍼 액세스 방법 지정

KMDF 드라이버

읽기 및 쓰기 요청의 경우 드라이버 스택의 모든 드라이버는 하위 드라이버에서 사용되는 메서드에 관계없이 "둘 다" 메서드를 사용할 수 있는 최상위 드라이버를 제외하고 디바이스의 버퍼에 액세스하는 데 동일한 메서드를 사용해야 합니다.

버전 1.13부터 KMDF 드라이버는 각 디바이스에 대해 WdfDeviceInitSetIoTypeEx 를 호출하여 모든 디바이스의 읽기 및 쓰기 요청에 대한 액세스 방법을 지정합니다. 예를 들어 드라이버가 해당 디바이스 중 하나에 대해 버퍼링된 I/O 메서드를 지정하는 경우 I/O 관리자는 해당 디바이스에 대한 드라이버에 읽기 및 쓰기 요청을 전달할 때 버퍼링된 I/O 메서드를 사용합니다.

디바이스 I/O 컨트롤 요청의 경우 I/O 제어 코드(IOCTL)에는 버퍼 액세스 방법을 지정하는 비트가 포함되어 있습니다. 따라서 KMDF 드라이버는 IOCTL에 대한 버퍼링 방법을 선택하는 작업을 수행할 필요가 없습니다. IOCTL에 대한 자세한 내용은 I/O 제어 코드 정의를 참조하세요. 읽기 및 쓰기 요청과 달리 디바이스의 모든 IOCTL은 동일한 액세스 방법을 지정할 필요가 없습니다.

UMDF 드라이버

UMDF 드라이버는 프레임워크가 읽기 및 쓰기 요청뿐만 아니라 디바이스 I/O 제어 요청에 사용하는 액세스 메서드에 대한 기본 설정을 지정합니다. UMDF 드라이버에서 제공하는 값은 기본 설정일 뿐이며 프레임워크에서 사용하도록 보장되지 않습니다. 자세한 내용은 UMDF 드라이버에서 버퍼 액세스 메서드 관리를 참조하세요.

UMDF 드라이버는 각 디바이스에 대해 WdfDeviceInitSetIoTypeEx 를 호출하여 디바이스의 모든 읽기, 쓰기 및 IOCTL 요청에 대한 액세스 방법을 지정합니다. 예를 들어 드라이버가 해당 디바이스 중 하나에 대해 버퍼링된 I/O 메서드를 지정하는 경우 프레임워크는 해당 디바이스에 대한 드라이버에 읽기, 쓰기 및 IOCTL 요청을 전달할 때 버퍼링된 I/O 메서드를 사용합니다.

KMDF와 UMDF 간의 IOCTL에 대한 버퍼 액세스 기술의 차이점을 확인합니다. KMDF 드라이버는 IOCTL에 대한 버퍼 액세스 방법을 지정하지 않는 반면, UMDF 드라이버는 IOCTL에 대한 버퍼 액세스 방법을 지정합니다.

WDF 드라이버가 I/O 대상에서 사용하는 I/O 메서드에 대해 잘못된 기술을 사용하여 I/O 요청의 버퍼를 설명하는 경우 프레임워크는 버퍼 설명을 수정합니다. 예를 들어 드라이버가 MDL을 사용하여 WdfIoTargetSendReadSynchronously로 전달하는 버퍼를 설명하고 I/O 대상이 버퍼링된 I/O를 사용하는 경우(MDL 대신 가상 주소를 사용하여 버퍼를 지정해야 하는 경우) 프레임워크는 MDL에서 가상 주소 및 길이로 버퍼 설명을 변환합니다. 그러나 드라이버가 버퍼를 올바른 형식으로 지정하면 더 효율적입니다.

프레임워크 메모리 개체, lookaside 목록, MDL 및 로컬 버퍼에 대한 자세한 내용은 메모리 버퍼 사용을 참조하세요.

메모리 버퍼가 삭제되는 시기에 대한 자세한 내용은 메모리 버퍼 수명 주기를 참조하세요.

버퍼링된 I/O에 대한 데이터 버퍼 액세스

드라이버가 버퍼링된 I/O를 사용하는 경우 데이터 요청 유형 및 KMDF 또는 UMDF 사용 여부에 따라 동작이 변경됩니다.

KMDF 드라이버

KMDF 드라이버가 버퍼링된 I/O를 사용하는 경우 I/O 관리자는 드라이버가 모든 유형의 요청에 액세스할 수 있는 중간 버퍼 하나를 만듭니다. 다음과 같은 상황이 발생합니다.

  • 요청을 작성합니다. I/O 관리자는 드라이버 스택을 호출하기 전에 호출 앱의 입력 버퍼에서 입력 정보를 전송합니다. 그런 다음 KMDF 드라이버는 중간 버퍼에서 입력 정보를 읽고 디바이스에 씁니다.
  • 요청을 읽습니다. KMDF 드라이버는 디바이스에서 정보를 읽고 중간 버퍼에 저장합니다. 그런 다음 I/O 관리자는 중간 버퍼의 출력 데이터를 앱의 출력 버퍼로 복사합니다.
  • 디바이스 I/O 컨트롤 요청. KMDF 드라이버는 중간 버퍼에서 해당 요청에 대한 데이터를 읽거나 씁니다.

UMDF 드라이버

UMDF 드라이버가 버퍼링된 I/O를 사용하는 경우 드라이버 호스트 프로세스는 요청 유형에 따라 하나 또는 두 개의 중간 버퍼를 만듭니다. 다음과 같은 상황이 발생합니다.

  • 요청을 작성합니다. 프레임워크는 하나의 버퍼를 만들고 호출 앱의 입력 버퍼에서 입력 정보를 전송한 다음 드라이버 스택을 호출합니다. UMDF 드라이버는 중간 버퍼에서 입력 정보를 읽고 디바이스에 씁니다.
  • 요청을 읽습니다. UMDF 드라이버는 디바이스에서 정보를 읽고 프레임워크가 만든 버퍼에 저장합니다. 드라이버 호스트 프로세스는 중간 버퍼의 출력 데이터를 앱의 출력 버퍼로 복사합니다.
  • 디바이스 I/O 컨트롤 요청. 프레임워크는 드라이버가 액세스할 수 있는 IOCTL의 입력 및 출력 버퍼에 해당하는 두 개의 버퍼를 만듭니다. 프레임워크는 IOCTL의 입력 정보를 새 중간 버퍼에 복사하여 드라이버에서 사용할 수 있도록 합니다. 프레임워크는 출력 버퍼의 내용을 복사하지 않으므로 드라이버가 해당 버퍼에서 읽으려고 시도하면 안 됩니다(그렇지 않으면 가비지 데이터를 읽게 됩니다). 드라이버가 출력 버퍼에 쓰는 모든 데이터는 원래 IOCTL 버퍼로 다시 복사되고 I/O 요청이 성공적으로 완료되면 앱에 반환됩니다. 드라이버가 입력 버퍼에 쓰는 모든 데이터는 삭제되고 호출 앱에 반환되지 않습니다.

KMDF 및 UMDF 드라이버는 버퍼를 나타내는 프레임워크 메모리 개체에 대한 핸들을 검색하기 위해 읽기 또는 쓰기 요청인지에 따라 WdfRequestRetrieveInputMemory 또는 WdfRequestRetrieveOutputMemory를 호출합니다. 그런 다음, 드라이버는 WdfMemoryGetBuffer를 호출하여 버퍼에 대한 포인터를 검색할 수 있습니다. 버퍼를 읽고 쓰기 위해 드라이버는 WdfMemoryCopyFromBuffer 또는 WdfMemoryCopyToBuffer를 호출합니다.

버퍼의 가상 주소와 길이를 검색하기 위해 드라이버는 WdfRequestRetrieveInputBuffer 또는 WdfRequestRetrieveOutputBuffer를 호출합니다.

KMDF 드라이버는 버퍼에 대한 MDL(메모리 설명자 목록)을 할당하고 빌드하기 위해 WdfRequestRetrieveInputWdmdl 또는 WdfRequestRetrieveOutputWdmMdl을 호출합니다.

직접 I/O용 데이터 버퍼 액세스

KMDF 드라이버

드라이버가 직접 I/O를 사용하는 경우 I/O 관리자는 I/O 요청의 생성자(일반적으로 사용자 모드 애플리케이션)가 지정한 버퍼 공간의 접근성을 확인하고 버퍼 공간을 실제 메모리로 잠급니다. 그런 다음 드라이버에 버퍼 공간에 직접 액세스할 수 있도록 합니다.

UMDF 드라이버

드라이버가 직접 I/O에 대한 기본 설정을 지정하고 직접 I/O에 대한 모든 UMDF 요구 사항이 충족된 경우( UMDF 드라이버에서 버퍼 액세스 메서드 관리 참조) 프레임워크는 I/O 관리자에서 수신하는 메모리 버퍼를 드라이버의 호스트 프로세스 주소 공간에 직접 매핑하여 드라이버에 버퍼 공간에 직접 액세스할 수 있도록 합니다.

버퍼 공간을 나타내는 프레임워크 메모리 개체에 대한 핸들을 검색하기 위해 드라이버는 WdfRequestRetrieveInputMemory 또는 WdfRequestRetrieveOutputMemory를 호출합니다. 그런 다음, 드라이버는 WdfMemoryGetBuffer를 호출하여 버퍼에 대한 포인터를 검색할 수 있습니다. 버퍼를 읽고 쓰기 위해 드라이버는 WdfMemoryCopyFromBuffer 또는 WdfMemoryCopyToBuffer를 호출합니다.

버퍼 공간의 가상 주소와 길이를 검색하기 위해 드라이버는 WdfRequestRetrieveInputBuffer 또는 WdfRequestRetrieveOutputBuffer를 호출합니다.

디바이스 드라이버가 직접 I/O를 사용하는 경우 I/O 관리자는 MDL을 사용하여 버퍼를 설명합니다. 버퍼의 MDL에 대한 포인터를 검색하기 위해 KMDF 드라이버는 WdfRequestRetrieveInputWdmdl 또는 WdfRequestRetrieveOutputWdmMdl을 호출합니다. UMDF 드라이버는 MDL에 액세스할 수 없습니다.

버퍼링되지 않거나 직접 I/O에 대한 데이터 버퍼 액세스

KMDF 드라이버

드라이버가 버퍼링된 I/O 또는 직접 I/O 메서드 (또는 "둘 다" 메서드)로 알려진 버퍼 액세스 메서드를 사용하는 경우 I/O 관리자는 요청의 버퍼 공간에 대해 지정된 I/O 요청의 발신자가 지정한 가상 주소를 드라이버에 제공합니다. I/O 관리자는 I/O 요청의 버퍼 공간의 유효성을 검사하지 않으므로 드라이버는 버퍼 공간에 액세스할 수 있는지 확인하고 버퍼 공간을 실제 메모리에 잠가야 합니다.

I/O 관리자가 제공하는 가상 주소는 I/O 요청의 생성자의 프로세스 컨텍스트에서만 액세스할 수 있습니다. 드라이버 스택의 최상위 드라이버만 생성자의 프로세스 컨텍스트에서 실행되도록 보장됩니다.

I/O 요청의 버퍼 공간에 대한 액세스 권한을 얻으려면 최상위 드라이버는 EvtIoInCallerContext 콜백 함수를 제공해야 합니다. 프레임워크는 드라이버에 대한 I/O 요청을 받을 때마다 이 콜백 함수를 호출합니다.

요청의 버퍼 액세스 방법이 "둘 다 아님"인 경우 KMDF 드라이버는 각 버퍼에 대해 다음을 수행해야 합니다.

  1. WdfRequestRetrieveUnsafeUserInputBuffer 또는 WdfRequestRetrieveUnsafeUserOutputBuffer를 호출하여 버퍼의 가상 주소를 가져옵니다.

  2. WdfRequestProbeAndLockUserBufferForRead 또는 WdfRequestProbeAndLockUserBufferForWrite를 호출하여 버퍼를 검색 및 잠그고 버퍼에 대한 프레임워크 메모리 개체에 대한 핸들을 가져옵니다.

  3. 메모리 개체 핸들을 요청의 컨텍스트 공간에 저장합니다.

  4. 프레임워크에 요청을 반환하는 WdfDeviceEnqueueRequest를 호출합니다.

프레임워크는 이후 드라이버의 I/O 큐 중 하나에 요청을 추가합니다. 드라이버가 요청 처리기를 제공한 경우 프레임워크는 결국 적절한 요청 처리기를 호출합니다.

요청 처리기는 요청의 컨텍스트 공간에서 요청의 메모리 개체 핸들을 검색할 수 있습니다. 드라이버는 핸들을 WdfMemoryGetBuffer 에 전달하여 버퍼의 주소를 가져올 수 있습니다.

경우에 따라 드라이버가 "둘 다" 액세스 메서드를 사용하지 않더라도 최상위 드라이버는 이전 단계를 사용하여 사용자 모드 버퍼에 액세스해야 합니다. 예를 들어 드라이버가 버퍼링된 I/O를 사용하고 있다고 가정합니다. 버퍼링된 액세스 메서드를 사용하는 I/O 컨트롤 코드는 사용자 모드 버퍼에 대한 포함된 포인터가 포함된 구조를 전달할 수 있습니다. 이러한 경우 드라이버는 구조체에서 포인터를 추출한 다음 이전 단계 2~4를 사용하는 EvtIoInCallerContext 콜백 함수를 제공해야 합니다.

UMDF 드라이버

UMDF는 버퍼링되거나 직접 I/O 형식 버퍼를 지원하지 않으므로 UMDF 드라이버는 이 유형의 버퍼를 직접 처리할 필요가 없습니다.

그러나 프레임워크가 I/O 관리자로부터 읽기 또는 쓰기를 위해 이러한 버퍼를 수신하는 경우 드라이버에서 선택한 액세스 방법에 따라 UMDF 드라이버에서 버퍼링된 I/O 또는 직접 I/O로 사용할 수 있습니다. 프레임워크가 "둘 다" 버퍼 메서드를 지정하는 IOCTL을 수신하는 경우 선택적으로 IOCTL 요청의 버퍼 액세스 메서드를 버퍼링된 I/O 또는 INF 지시문의 존재 여부에 따라 직접 I/O로 변환할 수 있습니다. 자세한 내용은 UMDF 드라이버에서 버퍼 액세스 메서드 관리를 참조하세요.