MDL 사용

연속된 가상 메모리 주소의 범위에 걸쳐 있는 I/O 버퍼는 여러 실제 페이지에 분산될 수 있으며 이러한 페이지는 변조될 수 있습니다. 운영 체제는 MDL( 메모리 설명자 목록 )을 사용하여 가상 메모리 버퍼의 실제 페이지 레이아웃을 설명합니다.

MDL은 다음에 I/O 버퍼가 있는 실제 메모리를 설명하는 데이터 배열이 뒤따르는 MDL 구조로 구성됩니다. MDL의 크기는 MDL에서 설명하는 I/O 버퍼의 특성에 따라 달라집니다. 시스템 루틴은 MDL의 필요한 크기를 계산하고 MDL을 할당하고 해제하는 데 사용할 수 있습니다.

MDL 구조체는 반 불투명합니다. 드라이버는 이 구조체의 NextMdlFlags 멤버에만 직접 액세스해야 합니다. 이러한 두 멤버를 사용하는 코드 예제는 다음 예제 섹션을 참조하세요.

MDL의 나머지 멤버는 불투명합니다. MDL의 불투명 멤버에 직접 액세스하지 마세요. 대신 운영 체제에서 제공하는 다음 매크로를 사용하여 구조에 대한 기본 작업을 수행합니다.

MmGetMdlVirtualAddress 는 MDL에서 설명하는 I/O 버퍼의 가상 메모리 주소를 반환합니다.

MmGetMdlByteCount 는 I/O 버퍼의 크기(바이트)를 반환합니다.

MmGetMdlByteOffset 은 I/O 버퍼 시작 부분의 실제 페이지 내에서 오프셋을 반환합니다.

IoAllocateMdl 루틴을 사용하여 MDL을 할당할 수 있습니다. MDL을 해제하려면 IoFreeMdl 루틴을 사용합니다. 또는 MmInitializeMdl 루틴을 호출하여 페이지가 없는 메모리 블록을 할당한 다음 이 메모리 블록을 MDL로 포맷할 수 있습니다.

IoAllocateMdl 또는 MmInitializeMdl은 MDL 구조 바로 뒤에 있는 데이터 배열을 초기화하지 않습니다. 드라이버에서 할당한 비페이징 메모리 블록에 있는 MDL의 경우 MmBuildMdlForNonPagedPool 을 사용하여 이 배열을 초기화하여 I/O 버퍼가 있는 실제 메모리를 설명합니다.

페이지 가능 메모리의 경우 가상 메모리와 실제 메모리 간의 대응은 일시적이므로 MDL 구조를 따르는 데이터 배열은 특정 상황에서만 유효합니다. MmProbeAndLockPages를 호출하여 페이지 가능한 메모리를 제자리에 잠그고 현재 레이아웃에 대해 이 데이터 배열을 초기화합니다. 호출자가 MmUnlockPages 루틴을 사용할 때까지 메모리가 페이징되지 않습니다. 이때 데이터 배열의 내용이 더 이상 유효하지 않습니다.

MmGetSystemAddressForMdlSafe 루틴은 시스템 주소 공간에 아직 매핑되지 않은 경우 지정된 MDL에서 설명하는 실제 페이지를 시스템 주소 공간의 가상 주소에 매핑합니다. 원래 가상 주소는 원래 컨텍스트에서만 사용할 수 있고 언제든지 삭제할 수 있는 사용자 주소일 수 있으므로 이 가상 주소는 I/O를 수행하기 위해 페이지를 확인해야 하는 드라이버에 유용합니다.

MmGetMdlVirtualAddress가 부분 MDL의 원래 시작 주소를 반환하는 IoBuildPartialMdl 루틴을 사용하여 부분 MDL을 빌드하는 경우 주의하세요. 이 주소는 사용자 모드 요청의 결과로 MDL이 원래 만들어진 경우 사용자 모드 주소입니다. 따라서 주소는 요청이 시작된 프로세스의 컨텍스트 외부와 관련이 없습니다.

일반적으로 드라이버는 MmGetSystemAddressForMdlSafe 매크로를 호출하여 부분 MDL을 매핑하여 시스템 모드 주소를 만듭니다. 이렇게 하면 드라이버가 프로세스 컨텍스트에 관계없이 안전하게 페이지에 계속 액세스할 수 있습니다.

드라이버가 IoAllocateMdl을 호출할 때 IRP에 대한 포인터를 IoAllocateMdlIrp 매개 변수로 지정하여 IRP를 새로 할당된 MDL과 연결할 수 있습니다. IRP에는 하나 이상의 MDL이 연결되어 있을 수 있습니다. IRP에 연결된 단일 MDL이 있는 경우 IRP의 MdlAddress 멤버는 해당 MDL을 가리킵니다. IRP에 연결된 MDL이 여러 개 있는 경우 MdlAddress 는 MDL 체인이라고 하는 IRP와 연결된 MDL의 연결된 목록에서 첫 번째 MDL을 가리킵니다. MDL은 다음 멤버에 의해 연결됩니다. 체인의 마지막 MDL의 Next 멤버가 NULL로 설정됩니다.

드라이버가 IoAllocateMdl을 호출할 때 SecondaryBuffer 매개 변수에 FALSE를 지정하는 경우 IRP의 MdlAddress 멤버가 새 MDL을 가리키도록 설정됩니다. SecondaryBufferTRUE이면 루틴은 MDL 체인 끝에 새 MDL을 삽입합니다.

IRP가 완료되면 시스템이 잠금을 해제하고 IRP와 연결된 모든 MDL을 해제합니다. 시스템은 I/O 완료 루틴을 큐에 넣기 전에 MDL의 잠금을 해제하고 I/O 완료 루틴이 실행된 후 해제합니다.

드라이버는 각 MDL의 Next 멤버를 사용하여 체인의 다음 MDL에 액세스하여 MDL 체인을 트래버스할 수 있습니다. 드라이버는 Next 멤버를 업데이트하여 체인에 MDL을 수동으로 삽입할 수 있습니다.

MDL 체인은 일반적으로 단일 I/O 요청과 연결된 버퍼 배열을 관리하는 데 사용됩니다. (예를 들어 네트워크 드라이버는 네트워크 작업에서 각 IP 패킷에 대해 하나의 버퍼를 사용할 수 있습니다.) 배열의 각 버퍼에는 체인에 자체 MDL이 있습니다. 드라이버가 요청을 완료하면 버퍼를 단일 큰 버퍼로 결합합니다. 그런 다음, 시스템은 요청에 할당된 모든 MDL을 자동으로 정리합니다.

I/O 관리자는 I/O 요청의 빈번한 원본입니다. I/O 관리자가 I/O 요청을 완료하면 I/O 관리자가 IRP를 해제하고 IRP에 연결된 모든 MDL을 해제합니다. 이러한 MDL 중 일부는 디바이스 스택의 I/O 관리자 아래에 있는 드라이버에 의해 IRP에 연결되었을 수 있습니다. 마찬가지로 드라이버가 I/O 요청의 원본인 경우 IRP 및 I/O 요청이 완료되면 IRP에 연결된 모든 MDL을 클린 합니다.

예제

다음 코드 예제는 IRP에서 MDL 체인을 해제하는 드라이버 구현 함수입니다.

VOID MyFreeMdl(PMDL Mdl)
{
    PMDL currentMdl, nextMdl;

    for (currentMdl = Mdl; currentMdl != NULL; currentMdl = nextMdl) 
    {
        nextMdl = currentMdl->Next;
        if (currentMdl->MdlFlags & MDL_PAGES_LOCKED) 
        {
            MmUnlockPages(currentMdl);
        }
        IoFreeMdl(currentMdl);
    }
} 

체인의 MDL에서 설명하는 실제 페이지가 잠겨 있는 경우 예제 함수는 MmUnlockPages 루틴을 호출하여 IoFreeMdl 을 호출하여 MDL을 해제하기 전에 페이지의 잠금을 해제합니다. 그러나 예제 함수는 IoFreeMdl을 호출하기 전에 명시적으로 페이지 매핑을 해제할 필요가 없습니다. 대신 IoFreeMdl 은 MDL을 해제할 때 자동으로 페이지 매핑을 해제합니다.