Использование многомерных списков

Буфер ввода-вывода, охватывающий диапазон смежных адресов виртуальной памяти, может быть распределен по нескольким физическим страницам, и эти страницы могут быть несогласовательными. Операционная система использует список дескрипторов памяти (MDL) для описания физического макета страницы для буфера виртуальной памяти.

MDL состоит из структуры MDL , за которой следует массив данных, описывающий физическую память, в которой находится буфер ввода-вывода. Размер MDL зависит от характеристик буфера ввода-вывода, который описывает MDL. Доступны системные подпрограммы для вычисления требуемого размера MDL, а также для выделения и освобождения MDL.

Структура MDL является полупрозрачной. Драйвер должен напрямую обращаться только к элементам Next и MdlFlags этой структуры. Пример кода, в котором используются эти два элемента, см. в следующем разделе Пример.

Остальные элементы MDL непрозрачны. Не обращаться к непрозрачным членам MDL напрямую. Вместо этого используйте следующие макросы, которые предоставляет операционная система для выполнения основных операций со структурой:

MmGetMdlVirtualAddress возвращает адрес виртуальной памяти буфера ввода-вывода, описанный в MDL.

MmGetMdlByteCount возвращает размер буфера ввода-вывода в байтах.

MmGetMdlByteOffset возвращает смещение в пределах физической страницы начала буфера ввода-вывода.

MDL можно выделить с помощью подпрограммы IoAllocateMdl . Чтобы освободить MDL, используйте процедуру IoFreeMdl . Кроме того, можно выделить блок непагрегируемой памяти, а затем отформатировать этот блок памяти как MDL, вызвав подпрограмму MmInitializeMdl .

Ни IoAllocateMdl , ни MmInitializeMdl не инициализирует массив данных, который сразу следует за структурой MDL. Для MDL, который находится в блоке выделенной драйвером памяти, используйте MmBuildMdlForNonPagedPool , чтобы инициализировать этот массив, чтобы описать физическую память, в которой находится буфер ввода-вывода.

Для страничной памяти соответствие между виртуальной и физической памятью является временным, поэтому массив данных, следующий за структурой MDL, действителен только при определенных обстоятельствах. Вызовите MmProbeAndLockPages , чтобы заблокировать память, доступную для страниц, и инициализировать этот массив данных для текущего макета. Память не будет выпукла до тех пор, пока вызывающий объект не будет использовать подпрограмму MmUnlockPages , после чего содержимое массива данных станет недействительным.

Подпрограмма MmGetSystemAddressForMdlSafe сопоставляет физические страницы, описанные указанным MDL, с виртуальным адресом в системном адресном пространстве, если они еще не сопоставлены с системным адресным пространством. Этот виртуальный адрес полезен для драйверов, которым может потребоваться просматривать страницы для выполнения операций ввода-вывода, так как исходный виртуальный адрес может быть адресом пользователя, который может использоваться только в исходном контексте и может быть удален в любое время.

Обратите внимание, что при сборке частичного MDL с помощью подпрограммы IoBuildPartialMdlmmGetMdlVirtualAddress возвращает исходный начальный адрес для частичного MDL. Этот адрес является адресом в пользовательском режиме, если MDL изначально был создан в результате запроса в пользовательском режиме. Таким образом, адрес не имеет отношения за пределами контекста процесса, в котором поступил запрос.

Обычно драйвер создает адрес в системном режиме, вызывая макрос MmGetSystemAddressForMdlSafe для сопоставления частичного MDL. Это гарантирует, что драйвер сможет продолжить безопасный доступ к страницам независимо от контекста процесса.

Когда драйвер вызывает IoAllocateMdl, он может связать IRP с вновь выделенным MDL, указав указатель на IRP в качестве параметра IRPioAllocateMdl. С IRP может быть связан один или несколько многомерных списков. Если с IRP связан один MDL, член MdlAddress IRP указывает на этот MDL. Если с IRP связано несколько многомерных списков, MdlAddress указывает на первый MDL в связанном списке, связанном с IRP, который называется цепочкой MDL. Многомерные списки связываются следующими членами. Элемент Next последнего MDL в цепочке имеет значение NULL.

Если драйвер вызывает IoAllocateMdl, он указывает false для параметра SecondaryBuffer , член MdlAddress IRP указывает на новый MDL. Если параметр SecondaryBuffer имеет значение TRUE, подпрограмма вставляет новый MDL в конец цепочки MDL.

После завершения IRP система разблокирует и освобождает все многомерные библиотеки, связанные с IRP. Система разблокирует многомерные списки, прежде чем помещает в очередь подпрограмму завершения ввода-вывода, и освобождает их после выполнения процедуры завершения ввода-вывода.

Драйверы могут проходить по цепочке MDL, используя элемент Next каждого MDL для доступа к следующему MDL в цепочке. Драйверы могут вручную вставлять многомерные списки в цепочку, обновив элементы Next .

Цепочки MDL обычно используются для управления массивом буферов, связанных с одним запросом ввода-вывода. (Например, сетевой драйвер может использовать один буфер для каждого IP-пакета в сетевой операции.) Каждый буфер в массиве имеет собственный MDL в цепочке. Когда драйвер завершает запрос, он объединяет буферы в один большой буфер. Затем система автоматически очищает все выделенные mdls для запроса.

Диспетчер ввода-вывода является частым источником запросов ввода-вывода. Когда диспетчер операций ввода-вывода завершает запрос ввода-вывода, он освобождает IRP и все многомерные списки, присоединенные к IRP. Некоторые из этих списков MDL могли быть подключены к IRP драйверами, расположенными под диспетчером ввода-вывода в стеке устройств. Аналогичным образом, если драйвер является источником запроса ввода-вывода, драйвер должен очистить IRP и все многомерные списки, подключенные к IRP, после завершения запроса ввода-вывода.

Пример

В следующем примере кода реализована функция, реализованная драйвером, которая освобождает цепочку MDL из IRP:

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.