[Previous] [Next]

Операции с плавающей точкой

Редко, но все-таки случаются ситуации, когда необходимо использовать операции с плавающей точкой и, соответственно, задействовать сопроцессор (FPU). Особенность этой ситуации состоит в том, что в режиме ядра программный поток, намеревающийся использовать сопроцессор, обязан сохранить текущее состояние всех регистров сопроцессора (ST0-ST7, MMX0-MMX7, XMM0-XMM7). Для сохранения этой информации в наборе системных вызовов режима ядра предусмотрены функции KeSaveFloatingPointState и KeRestoreFloatingPointState, первая из которых сохраняет состояние регистров сопроцессора в предоставляемом буфере, описываемом типом KFLOATING_SAVE, а вторая выполняет восстановление состояния сопроцессора по окончании работы с ним. Пример кода для работы с сопроцессором в режиме ядра приводится ниже:

double          Xfloat;
NTSTATUS        status;
KFLOATING_SAVE  savedFPUData;
status = KeSaveFloatingPointState(&savedFPUData);

if (NT_SUCCESS(status))
{// Работа с сопроцессором
	Xfloat = 0.;
	. . . . . . . . . .
	// Восстановление состояния сопроцессора из savedFPUData :
	KeRestoreFloatingPointState(&savedFPUData);
}

Следует обратить внимание на то, что работа с сопроцессором выполняется только в случае, если удачно завершен вызов функции KeSaveFloatingPointState, которая должна вызываться на уровне IRQL не выше DISPATCH_LEVEL. Вызов функции KeRestoreFloatingPointState для восстановления состояния сопроцессора должен выполняться на том же уровне, что было выполнено сохранение.

Операции с памятью

Операционная система Windows оперирует тремя типами адресов:

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

Адреса 4 гигабайтного виртуального пространства памяти 32-разрядных версий операционной системы Windows NT 5 (об отличиях для 64-разрядных версий было сказано в главе 4) делятся на 2 нижних гигабайта памяти пользовательского виртуального пространства, имеющего смысл только в контексте пользовательского приложения (процесса), которому оно выделено, и 2 верхних гигабайт системного виртуального пространства режима ядра. Системное адресное пространство доступно всем программным потокам режима ядра. (Иначе, как смогло бы работать программное обеспечение режима ядра собственно операционной системы?!) Все 4-х гигабайтное адресное пространство можно представить в виде книги с одной обложкой. Толстая обложка — это системное адресное пространство, тонкие бумажные листы — это виртуальные и автономные пользовательские адресные пространства.

Системное виртуальное пространство памяти режима ядра делится на диапазоны (обычная архитектура x86), представленные в таблице 7.1.

Адреса с 0xC0000000 по 0xC0800000 используются для хранения данных Менеджера памяти, который поддерживает механизм виртуальной памяти.

Диапазон с 0xFFBE0000 по 0xFFC00000 используются для хранения информации о страничном файле (файле подкачки), которая используется для сброса содержимого физической памяти в этот файл. (Методология crash дампа предусматривает создание crash dump файла из этого файла подкачки при следующей загрузке системы.)

Адреса с 0xE1000000 по 0xFFBE0000 занимают области странично и нестранично организованной памяти, что вместе составляет менее 500 мегабайт.

Какие трюки можно проделывать с виртуальной памятью? На этот не слишком конкретный вопрос существует ответ в виде вопроса: а для достижения чего именно?

Рассмотрим довольно незамысловатую ситуацию, предпосылки которой рассматривались ранее. Предположим, драйвер создал программный поток (вызовом PsCreateSystemThread), который в некоторой ситуации должен выполнять некоторую работу, например, по сигналу функции обработки IOCTL запросов — выполнить перенос данных в буфер, предоставленный пользовательским приложением. Предположим, что разработчик драйвера так задал IOCTL код (используя метод буферизации NEITHER), что в драйвер поступает пользовательский адрес буфера (значение меньше 0x80000000). Разработчик драйвера через внутренние переменные передает этот адрес программному потоку, который должен выполнить перенос, и... Наступает сбой системы.

Что случилось? Чтобы объяснить сложившуюся ситуацию и решить проблему, необходимо, прежде всего, согласиться с тем, что простые приемы пользовательского режима следует оставить пользовательскому режиму.

Когда драйверная функция обработки IOCTL запросов пользовательского режима (по вызову DeviceIoControl) получает адрес пользовательского буфера по методу буферизации NEITHER, то этот пользовательский виртуальный адрес имеет смысл в этой функции, поскольку она работает в контексте пользовательского программного потока и интерпретация пользовательского виртуального адреса не вызовет проблем. Другое дело, если этот адрес окажется в распоряжении программного потока, созданного драйвером по вызову PsCreateSystemThread, где интерпретация данного адреса вызовет ошибку, поскольку, как указано в документации DDK, этот системный программный поток не имеет никакого пользовательского контекста. Это одна из проблем.

HAL 0xFFFFFFFF

...

Информация CRASH DUMP 0xFFC00000
...
Нестраничный пул 0xFFBE0000
...
Страничный пул  ...
 0xE1000000
Файловый кэш  ...
 0xC1000000
Пространство файлового кэш-менеджера  ...
 0xC0C00000
Не используется  ...
 0xC0800000
Зарезервировано  ...
 0xC0400000
Элементы директории страниц (PDE)  ...
 0xC0300000
Элементы таблицы страниц (РТЕ)  ...
 0xC0000000
Не используется  ...
 0xA3000000
Memory Mapped files  
 
Копия операционной системы  ...
 0x80000000
Таблица 7.1. Диапазоны памяти системного адресного пространства Windows 2000

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

Чтобы решить задачу в данной постановке необходимо, во-первых, создать список MDL (структуру, хранящую отображение блока виртуальной памяти на физическую память), зафиксировать пользовательский буфер в физической оперативной памяти и передать MDL список (или соответствующий виртуальный адрес системного адресного пространства) рабочему потоку. По выполнении работы, следует разблокировать страницы пользовательского буфера и освободить структуру MDL списка. Соответственно, при этом делаются вызовы системных функций: IoAllocateMdl, MmProbeAndLockPages, MmGetSystemAddressForMdl, MmUnlockPages и IoFreeMdl.

Вызов IoAllocateMdl создает структуру MDL списка для указанного виртуального адреса (пользовательского или системного адресного пространства) с указанной длиной.

Вызов MmProbeAndLockPages проверяет присутствие страниц в физической памяти, подгружает (для страничной памяти), если они отсутствуют, и производит их фиксацию (после чего данные страницы не могут быть сброшены в страничный файл на жестком диске). Для корректного вызова MmProbeAndLockPages по поводу MDL, составленного для страничной памяти, программный поток должен работать на уровне IRQL ниже DISPATCH_LEVEL — чтобы позволить отработать системному коду, если страница окажется в страничном файле. (В случае, если исходный буфер находился бы в нестраничной памяти, то данный вызов можно было бы сделать с уровня IRQL равном DISPATCH_LEVEL или ниже.)

Функция MmGetSystemAddressForMdl возвращает виртуальный адрес, вычисленный из MDL списка, так, будто рассматриваемая область памяти находится в системном адресном пространстве, а именно — в нестраничном пуле. Этот адрес можно использовать в любом месте кода драйвера на любых уровнях IRQL, даже в процедуре обработки прерываний. Контекст выполнения для этого адреса не имеет никакого значения. (Справедливости ради, следует отметить, что перевод в системный адрес не является обязательной операцией, можно и далее использовать MDL список, что позволяют делать, в частности, вызовы нижних драйверов в стеке.) Вызов (точнее, макроопределение) MmGetSystemAddгessForMdl являeтcя устаревшим, и его следует использовать только в WDM драйверах, предназначенных для работы еще и в Windows 98. Использование макроопределения MmGetSystemAddressForMdlSafe является предпочтительным. Оба эти макроопределения могут быть вызваны из кода, работающего на уровне IRQL не выше DISPATH_LEVEL.

Вызов MmUnlockPages отменяет фиксацию страниц в оперативной памяти, а вызов IoFreeMdl уничтожает структуру MDL списка.

Чтобы отследить ошибки, связанные с фиксацией блока виртуальной памяти в памяти оперативной, рекомендуется выполнять вызов MmProbeAndLockPages внутри try-except блока, например:

__try { MmProbeAndLockPages( pMdl, UserMode, IoModifyAccess); }
__except( EXCEPTION_EXECUTE_HANDLER)
{
	pIrp->IoStatus.Status = STATUS_ACCESS_VIOLATION;
	pIrp->IoStatus.Information = 0;
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	return STATUS_CONFLICTING_ADDRESSES;
} 

На рассмотренном выше примере видно, что операционная система предоставляет достаточно инструментов для преобразования адресов пользовательского пространства в адреса системного пространства и физические адреса. Важно только корректно отслеживать, когда и что следует использовать.

Ниже приводятся более полные сведения о системных функциях, которые полезны для работы с виртуальной и физической памятью.

Вызовы для выделения и освобождения областей виртуальной памяти

Для манипуляций с областями памяти (выделение и освобождение) в режиме ядра используются специальные системные вызовы, отличающиеся от API вызовов режима ядра: ExAllocatePool, ExAllocatePoolWithTag, ExFreePool. В режиме ядра возможно выделение и освобождение области физически непрерывной памяти, что выполняется при помощи вызовов MmAllocateContiguousMemory и, соответственно, MmFreeContiguousMemory. Эти вызовы рассмотрены ниже.

Таблица 7.2. Прототип вызова ExAllocatePool

PVOID ExAllocatePool IRQL < DISPATCH_LEVEL
Параметры

Выполняет выделение области памяти

IN POOL_TYPE PoolType

Тип виртуальной памяти, в которой следует выделять область. Наиболее употребительны значения:
PagedPool — страничная
NonPagedPool — нестраничная, тогда данную функцию можно вызывать при любом уровне IRQL

IN ULONG NumberOfBytes Размер запрашиваемой области
Возвращаемое значение Указатель на выделенную область либо NULL (в случае, если память выделить невозможно). Выделенная область всегда выравнивается на 8 байт. В случае, если запрашиваемый размер превышает PAGE_SIZE, то выделяемая область выравнивается на размер страницы.

Таблица 7.3. Прототип вызова ExAllocatePoolWithTag

PVOID ExAllocatePoolWithTag IRQL < DISPATCH_LEVEL
Параметры Выполняет выделение области памяти
IN POOL_TYPE PoolType См. описание ExAllocatePool выше.
IN ULONG NumberOfBytes Размер запрашиваемой области
IN ULONG Tag Метка (тег) для данной области, можно задавать как 4 символа, например, 'ABCD'. Удобно для отладки.
Возвращаемое значение См. описание ExAllocatePool выше.

Таблица 7.4. Прототип вызова ExFreePool

VOID ExFreePool IRQL < DISPATCH_LEVEL
Параметры Выполняет освобождение области памяти
IN PVOID pBuffer Указатель на освобождаемую область памяти, выделенную вызовами ExAllocatePool или ExAllocatePoolWithTag. Если освобождается нестраничная память, вызов может быть сделан из кода на уровне DISPATCH_LEVEL IRQL
Возвращаемое значение void

Таблица 7.5. Прототип вызова MmAllocateContiguousMemory

PVOID MmAllocateContiguousMemory IRQL == PASSIVE_LEVEL
Параметры Выполняет выделение физически непрерывной области памяти
IN ULONG NumberOfBytes Размер запрашиваемой области
IN PHYSICAL_ADDRESS maxAcceptableAddress Верхний предел адресов для запрашиваемой области. Поле HighPart=0, поле LowPart принимает значения, например:
• 0x000FFFFF (до 1 МБ)
• 0x00FFFFFF (до 16 МБ)
• 0xFFFFFFFF(до 4ГБ)
Возвращаемое значение Виртуальный адрес или NULL (при неудаче).
(Для повышения вероятности успешного завершения рекомендуется выполнять вызов в DriverEntry, поскольку память при работе системы быстро становится сильно дефрагментированной)

Таблица 7.6. Прототип вызова MmFreeContiguousMemory

VOID MmFreeContiguousMemory IRQL == PASSIVE_LEVEL
Параметры Выполняет освобождение области памяти
IN PVOID pBuffer Указатель на область памяти, выделенную ранее с использованием системного вызова MmAllocateContiguousMemory
Возвращаемое значение void

Таблица 7.7. Прототип вызова MmIsAddressValid

BOOLEAN MmlsAddressValid IRQL <= DISPATCH_LEVEL
Параметры Выполняет проверку виртуального адреса
IN PVOID VirtualAddress Виртуальный адрес, который следует проверить
Возвращаемое значение TRUE — если присутствует в оперативной памяти
FALSE — если вызовет прерывание PAGE FAULT
Замечание. Если адрес не находится в нестраничной памяти (или не зафиксирован в оперативной памяти), возврат TRUE не гарантирует отсутствие проблем при работе на повышенных уровнях IRQL (поскольку к моменту использования данного виртуального адреса ситуация может измениться).

Таблица 7.8. Прототип вызова MmGetPhysicalAddress

PHYSICAL_ADDRESS MmGetPhysicalAddress IRQL — любой
Параметры Определяет физический адрес, соответствующий данному виртуальному
IN PVOID VirtualAddress Анализируемый виртуальный адрес
Возвращаемое значение

Физический адрес. Перед данным вызовом следует воспользоваться MmlsAddressValid

Работа с ассоциативными списками

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

Такую возможность предоставляет механизм ассоциативных списков (lookaside list), который реализован в системных вызовах, представленных ниже. Поначалу, ассоциативный список — это всего лишь заранее созданный заголовок, который должен хранить информацию о состоянии списка, и не содержит никаких выделенных блоков памяти. По мере выполнения вызовов ExAllocateFrom(N)PagedLookasideList (таблицы 7.9 и 7.10) такие блоки создаются — либо системным вызовом ExAllocatePoolWithTag, либо внутри предоставленной драйвером функции (указанной драйвером параметром pAllocFunction). По мере создания и, возможно, последующего освобождения выделенных ранее блоков (системным вызовом ExFreePool, либо предоставленной драйвером функцией), ассоциативный список может оказаться держателем некоторого количества блоков фиксированного размера в страничной либо нестраничной памяти (в зависимости от способа инициализации).

Таблица 7.9. Прототип вызова ExInitializePagedLookasideList

PVOID ExInitializePagedLookasideList IRQL < DISPATCH_LEVEL
Параметры Создание ассоциативного списка блоков страничной памяти
IN PPAGED_LOOKASIDE_LIST pLookasideListHeader Указатель на предварительно выделенную драйвером область размером sizeof(PAGED_LOOKASIDE_LIST)
Остальные параметры совпадают с параметрами вызова ExInitializeNPagedLookasideList, см. ниже таблицу 7.10
Возвращаемое значение void

В настоящий момент, Windows XP и Server 2003 самостоятельно и динамически определяет максимальное число элементов в ассоциативном списке. Для Windows 2000 в качестве такого параметра был анонсирован параметр Depth. (K сожалению, в документации способ определения значения этого параметра умалчивается, начиная с версии DDK Win98.) Следует отметить, что до достижения данного максимума освобождаемые драйвером блоки не возвращаются в системную память, оставаясь в составе списка. Если в списке имеются ранее освобожденные блоки, и поступил запрос на новый блок, то предоставляется указатель на один из них. Ситуация кардинально меняется если максимум достигнут. При запросе нового блока (и при этом ранее освобожденных блоков в списке нет) память под него берется непосредственно из системной памяти и при освобождении сразу же возвращается в соответствующий пул системной памяти — иными словами, исчезают преимущества использования ассоциативного списка.

Таблица 7.10. Прототип вызова ExInitializeNPagedLookasideList

VOID ExInitializeNPagedLookasideList IRQL <= DISPATCH_LEVEL
Параметры Создание ассоциативного списка блоков нестраничной памяти
IN PNPAGED_LOOKASIDE_LIST
pLookasideListHeader
Указатель на предварительно выделенную драйвером область размером sizeof(NPAGED_LOOKASIDE_LIST)
IN OPTIONAL PALLOCATE_FUNCTION
pAllocFunction
NULL или указатель на предоставляемую драйвером функцию, которая будет заниматься выделением блоков из массива системной нестраничной памяти (если NULL — будет использован системный вызов ExAllocatePoolWithTag)
IN OPTIONAL PFREE_FUNCTION
pFreeFunction
NULL или указатель на предоставляемую драйвером функцию, которая будет заниматься освобождением блоков (если NULL — будет использован вызов ExFreePool)
IN ULONG Flags Зарезервировано. Указывать 0
IN ULONG ByteSize Размер отдельных блоков, поддерживаемых данным списком
IN ULONG Tag Метка (тег) для создаваемых блоков, можно задавать как 4 символа, например, 'ABCD'
IN USHORT Depth Зарезервировано. Указывать 0
Возвращаемое значение void

Перед вызовом процедуры инициализации, для хранения заголовка ассоциативного списка драйвер должен получить область в нестраничной памяти размером sizeof(PAGED_LOOKASIDE_LIST) или sizeof(NPAGED_LOOKASIDE_LIST) — в зависимости от того, какой список инициализируется. Для этих целей можно использовать описанные выше системные вызовы ExAllocatePool или ExAllocatePoolWithTag. В конце работы со списком обязательно следует выполнить вызов ExDelete(N)PagedLookasideList.

Функции, на которые указывает pAllocFunction, имеют прототип:

PVOID MyAllocateFunction (
	IN_POOL_TYPE PoolType,  // PagedPool или NonPagedPool
	IN ULONG NumberOfBytes, // размер
	IN ULONG Tag            // тег
	); 

Функции, на которые указывает pFreeFunction, имеют прототип:

PVOID MyFreeFunction (PVOID pBuffer); 

Таблица 7.11. Прототип вызова ExAllocateFromNPagedLookasideList

PVOID ExAllocateFromNPagedLookasideList IRQL <= DISPATCH_LEVEL
Параметры Выполняет выделение блока памяти из нестраничного списка
IN PNPAGED_LOOKASIDE_LIST
pLookasideList
Указатель на инициализированный ассоциативный список
Возвращаемое значение Указатель на блок фиксированного размера или NULL (если функция выделения памяти не смогла получить очередной блок)

Таблица 7.12. Прототип вызова ExAllocateFromPagedLookasideList

PVOID ExAllocateFromPagedLookasideList IRQL < DISPATCH_LEVEL
Параметры Выполняет выделение блока памяти из страничного списка
IN PPAGED_LOOKASIDE_LIST
pLookasideList
Указатель на инициализированный ассоциативный список
Возвращаемое значение Указатель на блок фиксированного размера или NULL (если функция выделения памяти не смогла получить очередной блок)

Таблица 7.13. Прототип вызова ExFreeToNPagedLookasideList

VOID ExFreeToNPagedLookasideList IRQL <= DISPATCH_LEVEL
Параметры Возвращает блок в нестраничный ассоциативный список
IN PNPAGED_LOOKASIDE_LIST pLookasideList Указатель на инициализированный ассоциативный список
IN PVOID pEntry Указатель на ранее полученный из списка блок фиксированного размера
Возвращаемое значение void

Таблица 7.14. Прототип вызова ExFreeToPagedLookasideList

VOID ExFreeToPagedLookasideList IRQL < DISPATCH_LEVEL
Параметры Возвращает блок в страничный ассоциативный список
IN PPAGED_LOOKASIDE_LIST
pLookasideList
Указатель на инициализированный ассоциативный список
IN PVOID pEntry Указатель на ранее полученный из списка блок фиксированного размера
Возвращаемое значение void

Таблица 7.15. Прототип вызова ExDeleteNPagedLookasideList

VOID ExDeleteNPagedLookasideList IRQL <= DISPATCH_LEVEL
Параметры Выполняет удаление нестраничного ассоциативного списка
IN PNPAGED_LOOKASIDE_LIST
pLookasideList
Указатель на ассоциативный список
Возвращаемое значение void

Таблица 7.16. Прототип вызова ExDeletePagedLookasideList

VOID ExDeletePagedLookasideList IRQL < DISPATCH_LEVEL
Параметры Выполняет удаление страничного ассоциативного списка
IN PPAGED_LOOKASIDE_LIST
pLookasideList
Указатель на ассоциативный список
Возвращаемое значение void

Работа с 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 — любой
Параметры Определяет размер структуры для MDL списка, который будет описывать область данного размера
IN PVOID Base Виртуальный адрес области, для которой следует определить размер предполагаемого MDL списка
IN ULONG Length Размер описываемой области в байтах
Возвращаемое значение Размер, необходимый для хранения MDL списка

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

PVOID MmCreateMdl IRQL < 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 <= DISPATCH_LEVEL
Параметры Создает MDL список и инициализирует его. При этом можно выполнить его привязку к нужному IRP пакету
IN PVOID VAddress Виртуальный адрес области, для которой будет построен MDL список
IN ULONG Length Размер виртуальной области, для которой следует построить MDL
IN BOOLEAN SecondaryBuffer Если pIrp равен NULL, см. ниже, то и параметр SecondaryBuffer должен быть равен NULL.
Если параметр pIrp не равен NULL, тогда при:
FALSE — указатель на созданную структуру MDL списка будет занесен в поле pIrp->MdlAddress
TRUE — поле Next созданной MDL структуры будет указывать на MDL список, который прописан в поле pIrp->MdlAddress
IN BOOLEAN ChargeQuota Если TRUE — вычесть из квоты текущего программного потока на создание MDL списков.
Обычно применяется FALSE
IN OUT PIRP pIrp NULL — не работаем с каким-либо IRP пакетом.
Иначе — см. описание SecondaryBuffer выше
Возвращаемое значение Указатель на MDL список.
Замечание. Инициализацию MDL списка можно считать завершенной только после вызова MmProbeAndLockPages, см. ниже

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

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

VOID MmInitializeMdl IRQL <= 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 <= DISPATCH_LEVEL
Параметры Выполняет очистку MDL списка
IN PMDL pMdl Указатель на структуру, описывающую MDL список
Возвращаемое значение void

Таблица 7.22. Прототип вызова MmProbeAndLockPages

VOID MmProbeAndLockPages IRQL < 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 <= DISPATCH_LEVEL
Параметры Отменяет фиксацию страниц страничной памяти в оперативной памяти
IN PMDL pMdl Указатель на структуру, описывающую MDL список, соответствующий набору зафиксированных в оперативной памяти страниц
Возвращаемое значение void

После того как выполнена фиксация страниц в физической памяти, описываемых MDL списком (то есть описываемая область стала практически частью нестранично организованной памяти), имеет смысл поинтересоваться: какой же виртуальный адрес теперь у этой области памяти в терминах системного адресного пространства (адреса — более 0x8000000)? На этот вопрос может ответить системный вызов (макроопределение) MmGetSystemAddressForMdl, описываемый ниже.

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

PVOID MmGetSystemAddressForMdl IRQL <= DISPATCH_LEVEL
Параметры Показывает системный виртуальный адрес начала буфера, описываемого MDL списком
IN PMDL pMdl Указатель на структуру MDL списка
Возвращаемое значение Адрес диапазона системных адресов

Непосредственный доступ к полям структуры MDL списка не приветствуется разработчиком Windows — фирмой Microsoft. (Хотя эта структура детально описана в заголовочных файлах wdm.h и ntddk.h, представленных в пакете DDK). Вместо этого предлагается использовать следующие функции.

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

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

Таблица 7.26. Прототип вызова MmGetMdlByteOffset

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

Таблица 7.27. Прототип вызова MmGetMdlVirtualAddress

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

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

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

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

Функции библиотеки времени выполнения для работы с памятью

Операционная система Windows предоставляет набор Rtl (библиотека времени выполнения) функций для работы с памятью, которые в режиме ядра заменяют столь привычные программистам пользовательских приложений вызовы memcpy, memset и т.п. Некоторые наиболее употребительные вызовы описаны ниже.

Таблица 7.29. Прототип вызова RtlFillMemory

VOID RtlFillMemory IRQL — любой (если это допускает тип памяти заполняемого буфера)
Параметрам Заполняет область памяти значением Pattern
IN VOID UNALIGNED
*Destination
Указатель на буфер-приемник (область без выравнивания)
IN ULONG Length Размер заполняемой области в байтах
IN UCHAR Pattern Значение, которым будет заполнена указанная область (байт)
Возвращаемое значение void

Таблица 7.30. Прототип вызова RtlZeroMemory

VOID RtlZeroMemory IRQL — любой (если это допускает тип памяти обнуляемого буфера)
Параметры Обнуляет область памяти
IN VOID UNALIGNED
*Destination
Указатель на буфер-приемник (область без выравнивания)
IN ULONG Length Размер обнуляемой области в байтах
Возвращаемое значение void

Таблица 7.31. Прототип вызова RtlCopyMemory

VOID RtlCopyMemory IRQL — любой (если это допускают типы памяти копируемых буферов)
Параметры Копирует содержимое одного буфера в другой
IN VOID UNALIGNED
*Destination
Указатель на буфер-приемник (область без выравнивания)
IN CONST VOID UNALIGNED
*Source
Указатель на буфер-источник (область без выравнивания)
IN ULONG Length Размер копируемой области в байтах
Возвращаемое значение void
Замечание. Области источника и приемника не должны перекрываться.

Таблица 7.32. Прототип вызова RtlMoveMemory

VOID RtlMoveMemory IRQL — любой (если это допускают типы памяти копируемых буферов)
Параметры Копирует содержимое одного буфера в другой
IN VOID UNALIGNED
*Destination
Указатель на буфер-приемник (область без выравнивания)
IN CONST VOID UNALIGNED
*Source
Указатель на буфер-источник (область без выравнивания)
IN ULONG Length Размер копируемой области в байтах
Возвращаемое значение void
Замечание. Допускается перекрытие областей источника и приемника.

Существует также вызов RtlCopyBytes, совершенно идентичный приведенному выше вызову RtlMoveMemory.