Программирование драйверов Windows

       

Работа с MDL списками


Как было сказано выше, когда поступает IOCTL запрос от пользовательского приложения, в котором метод буферизации указан METHOD_IN_DIRECT либо METHOD_OUT_DIRECT, Диспетчер ввода/вывода выполняет построение MDL списка для выходного буфера (5-й параметр в пользовательском вызове DeviceIoControl). Указатель на этот MDL список передается в драйвер внутри структуры IRP пакета. Драйвер может зафиксировать (lock) область этого буфера в оперативной памяти вызовом MmProbeAndLockPages, после чего с этим буфером можно работать в разных контекстах, в том числе на высоких уровнях IRQL (DISPATCH_LEVEL и выше).

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

Таблица 7.17. Прототип вызова MmSizeOfMdl



ULONG MmSizeOfMdl IRQL &#8212 любой
Параметры Определяет размер структуры для MDL списка, который будет описывать область данного размера
IN PVOID Base Виртуальный адрес области, для которой следует определить размер предполагаемого MDL списка
IN ULONG Length Размер описываемой области в байтах
Возвращаемое значение Размер, необходимый для хранения MDL списка

Таблица 7.18. Прототип вызова MmCreateMdl

PVOID MmCreateMdl IRQL &#60 DISPATCH_LEVEL
Параметры Создает MDL список и инициализирует его (в документации DDK XP объявлена устаревшей функцией и вместо нее рекомендуется использовать вызов IoAllocateMdl)
IN PMDL OPTIONAL pMemoryDescriptorList Указатель на область размером не менее sizeof(MDL) или NULL. Во втором случае вызов самостоятельно выделяет память в нестраничной памяти.
IN PVOID Base Виртуальный адрес области, для которой следует построить MDL
IN ULONG Length Размер буфера в байтах
Возвращаемое значение Указатель на MDL список или NULL (при ошибке)
<
Таблица 7.19. Прототип вызова IoAllocateMdl

PVOID IoAllocateMdl IRQL &#60= DISPATCH_LEVEL
Параметры Создает MDL список и инициализирует его. При этом можно выполнить его привязку к нужному IRP пакету
IN PVOID VAddress Виртуальный адрес области, для которой будет построен MDL список
IN ULONG Length Размер виртуальной области, для которой следует построить MDL
IN BOOLEAN SecondaryBuffer Если pIrp равен NULL, см. ниже, то и параметр SecondaryBuffer должен быть равен NULL.

Если параметр pIrp не равен NULL, тогда при:

FALSE &#8212 указатель на созданную структуру MDL списка будет занесен в поле pIrp-&#62MdlAddress

TRUE &#8212 поле Next созданной MDL структуры будет указывать на MDL список, который прописан в поле pIrp-&#62MdlAddress
IN BOOLEAN ChargeQuota Если TRUE &#8212 вычесть из квоты текущего программного потока на создание MDL списков.

Обычно применяется FALSE
IN OUT PIRP pIrp NULL &#8212 не работаем с каким-либо IRP пакетом.

Иначе &#8212 см. описание SecondaryBuffer выше
Возвращаемое значение Указатель на MDL список.

Замечание. Инициализацию MDL списка можно считать завершенной только после вызова MmProbeAndLockPages, см. ниже
Поэтапный способ самостоятельного создания MDL списка состоит в следующем. Получив в результате вызова MmSizeOfMdl размер будущего MDL списка, драйвер должен выполнить выделение области памяти в нестраничном пуле, после чего можно переходить к инициализации MDL списка при помощи системного вызова MmInitializeMdl, описание которого приводится ниже.

Таблица 7.20. Прототип вызова MmInitializeMdl

VOID MmInitializeMdl IRQL &#60= DISPATCH_LEVEL
Параметры Выполняет инициализацию MDL списка
IN PMDL OPTIONAL pMdl Указатель на буфер для хранения MDL списка.
IN PVOID Base Виртуальный адрес области, для которой следует построить MDL
IN ULONG Length Размер буфера в байтах
Возвращаемое значение void

Замечание. Инициализацию MDL списка можно считать завершенной только после вызова MmProbeAndLockPages, см. ниже
<


Следует обратить внимание на то, как происходит завершение работы с MDL списками. Действия, выполненные функцией MmProbeAndLockPages, отменяются вызовом MmUnlockPages (см. ниже), а инициализированный MDL список следует "деактивировать" вызовом IoFreeMdl. Затем, если драйвер перед инициализацией самостоятельно выделял память под структуру MDL списка, то ее также следует явно освободить соответствующим системным вызовом (например, ExFreePool).

Таблица 7.21. Прототип вызова IoFreeMdl

VOID IoFreeMdl IRQL &#60= DISPATCH_LEVEL
Параметры Выполняет очистку MDL списка
IN PMDL pMdl Указатель на структуру, описывающую MDL список
Возвращаемое значение void
Таблица 7.22. Прототип вызова MmProbeAndLockPages

VOID MmProbeAndLockPages IRQL &#60 DISPATCH_LEVEL
Параметры Выполняет фиксацию страниц, описанных в MDL списке, в физической памяти
IN OUT PMDL pMdl Указатель на MDL список
IN KPROCESSOR_MODE AccessMode Режим, в котором будет проверяться доступ к анализируемым страницам:

KernelMode

UserMode
IN LOCK_OPERATION

Operation
Права доступа после того, как страницы будут зафиксированы. Одно из значений:

IoReadAccess

IoWriteAccess

IoModifyAccess
Возвращаемое значение void

Замечание. Часть MDL списка, описывающая физические страницы, после данного вызова, скорее всего, будет обновлена.
Таблица 7.23. Прототип вызова MmUnlockPages

VOID MmUnlockPages IRQL &#60= DISPATCH_LEVEL
Параметры Отменяет фиксацию страниц страничной памяти в оперативной памяти
IN PMDL pMdl Указатель на структуру, описывающую MDL список, соответствующий набору зафиксированных в оперативной памяти страниц
Возвращаемое значение void
После того как выполнена фиксация страниц в физической памяти, описываемых MDL списком (то есть описываемая область стала практически частью нестранично организованной памяти), имеет смысл поинтересоваться: какой же виртуальный адрес теперь у этой области памяти в терминах системного адресного пространства (адреса &#8212 более 0x8000000)? На этот вопрос может ответить системный вызов (макроопределение) MmGetSystemAddressForMdl, описываемый ниже.



Таблица 7.24. Прототип вызова MmGetSystemAddressForMdl

PVOID MmGetSystemAddressForMdl IRQL &#60= DISPATCH_LEVEL
Параметры Показывает системный виртуальный адрес начала буфера, описываемого MDL списком
IN PMDL pMdl Указатель на структуру MDL списка
Возвращаемое значение Адрес диапазона системных адресов
Непосредственный доступ к полям структуры MDL списка не приветствуется разработчиком Windows &#8212 фирмой Microsoft. (Хотя эта структура детально описана в заголовочных файлах wdm.h и ntddk.h, представленных в пакете DDK). Вместо этого предлагается использовать следующие функции.

Таблица 7.25. Прототип вызова MmGetMdlByteCount

ULONG MmGetMdlByteCount IRQL &#60= DISPATCH_LEVEL
Параметры Показывает размер буфера, описываемого MDL списком
IN PMDL pMdl Указатель на структуру MDL списка
Возвращаемое значение Размер области, описываемой MDL списком
Таблица 7.26. Прототип вызова MmGetMdlByteOffset

ULONG MmGetMdlByteOffset IRQL &#60= DISPATCH_LEVEL
Параметры Показывает смещение начала буфера на первой странице буферной области, описываемой данным MDL списком
IN PMDL pMdl Указатель на структуру MDL списка
Возвращаемое значение Смещение
Таблица 7.27. Прототип вызова MmGetMdlVirtualAddress

PVOID MmGetMdlVirtualAddress IRQL &#8212 любой
Параметры Показывает виртуальный адрес начала буфера, описываемого MDL списком
IN PMDL pMdl Указатель на структуру MDL списка
Возвращаемое значение Исходный виртуальный адрес описываемой данным MDL списком области памяти.

Замечание. В том случае, если область была предоставлена пользовательским приложением, здесь можно увидеть виртуальный адрес из диапазона пользовательского адресного пространства (для интерпретации которого требуется контекст этого приложения).
Наконец, как поступить, если некоторая структура MDL стала ненужной, но для работы необходима новая? Если просмотреть набор описанных выше функций, то может показаться, что следует разрушить старый MDL список до основания, затем создать новую (выделив соответствующую область памяти) и приступать к ее инициализации.


На самом деле, в составе пакета DDK имеется вызов, который позволит избежать многих бесполезных этапов в подготовке новой MDL структуры, если имеется другая, бывшая в употреблении ранее. Соответствующая функция называется MmPrepareMdlForReuse, см. описание ниже.

Таблица 7.28. Прототип вызова MmPrepareMdlForReuse

VOID MmPrepareMdlForReuse IRQL &#60= DISPATCH_LEVEL
Параметры Подготавливает MDL список к повторному использованию
IN PMDL pMdl Указатель на структуру MDL списка
Возвращаемое значение void

Содержание раздела