Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Программирование для Windows NT

© Александр Фролов, Григорий Фролов
Том 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр.

[Назад] [Содеожание] [Дальше]

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

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

Получение виртуальной памяти

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

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

Для чего может потребоваться резервирование диапазона адресов?

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

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

Для того чтобы зарезервировать или получить в свое распоряжение некоторое количество страниц виртуальной памяти, приложение должно воспользоваться функцией VirtualAlloc XE "VirtualAlloc" , прототип которой представлен ниже:


LPVOID VirtualAlloc(
  LPVOID lpvAddress,        // адрес области 
  DWORD  cbSize,            // размер области 
  DWORD  fdwAllocationType, // способ получения памяти 
  DWORD  fdwProtect);       // тип доступа 

Параметры lpvAddress и cbSize задают, соответственно, начальный адрес и размер резервируемой либо получаемой в пользование области памяти. При резервировании адрес округляется до ближайшей границы блока размером 64 Кбайт. В остальных случаях адрес округляется до границы ближайшей страницы памяти.

Заметим, что параметр lpvAddress можно указать как NULL. При этом операционная система выберет начальный адрес самостоятельно.

Что же касается параметра cbSize, то он округляется до целого числа страниц. Поэтому если вы пытаетесь с помощью функции VirtualAlloc XE "VirtualAlloc" получить область памяти размером в один байт, вам будет выделена страница размером 4096 байт. Аналогично, при попытке получить блок памяти размером 4097 байт вы получите две страницы памяти общим размером 8192 байта. Как мы уже говорили, программный интерфейс системы управления виртуальной памятью не предназначен для работы с областями малого размера.

Для параметра fdwAllocationType вы можете использовать одно из следующих значений:

 Значение

 Описание

 MEM_RESERVE XE "MEM_RESERVE"

 Функция VirtualAlloc XE "VirtualAlloc" выполняет резервирование диапазона адресов в адресном пространстве приложения

 MEM_COMMIT XE "MEM_COMMIT"

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

 MEM_TOP_DOWN XE "MEM_TOP_DOWN"

 Память выделяется в области верхних адресов адресного пространства приложения

С помощью параметра fdwProtect приложение может установить желаемй тип доступа для заказанных страниц. Можно использвать одно из следующих значений:

Значение

Разрешенный доступ

PAGE_READWRITE XE "PAGE_READWRITE"

Чтение и запись

PAGE_READONLY XE "PAGE_READONLY"

Только чтение

PAGE_EXECUTE XE "PAGE_EXECUTE"

Только исполнение программного кода

PAGE_EXECUTE_READ XE "PAGE_EXECUTE_READ"

Исполнение и чтение

PAGE_EXECUTE_READWRITE XE "PAGE_EXECUTE_READWRITE"

Исполнение, чтение и запись

PAGE_NOACCESS XE "PAGE_NOACCESS"

Запрещен любой вид доступа

PAGE_GUARD XE "PAGE_GUARD"

Сигнализация доступа к старнице. Это значение можно использовать вместе с любыми другими, кроме PAGE_NOACCESS XE "PAGE_NOACCESS"

PAGE_NOCACHE XE "PAGE_NOCACHE"

Отмена кэширования для страницы памяти. Используется драйверами устройств. Это значение можно использовать вместе с любыми другими, кроме PAGE_NOACCESS XE "PAGE_NOACCESS"

Если страница отмечена как PAGE_READONLY XE "PAGE_READONLY" , при попытке записи в нее возникает аппаратное прерывание защиты доступа (access violation). Эта страница также не может содержать исполнимый код. Попытка выполнения такого кода приведет к возникновению прерывания.

С другой стороны, у вас есть возможность получения страниц, предназначенных только для хранения исполнимого кода. Если такие страницы отмечены как PAGE_EXECUTE, для них не разрешаются операции чтения и записи.

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

В случае успешного завершения функция VirtualAlloc XE "VirtualAlloc" возвратит адрес зарезервированной или полученной области страниц. При ошибке будет возвращено значение NULL.

Приложение может вначале зарезервировать страницы, вызвав функцию VirtualAlloc XE "VirtualAlloc" с параметром MEM_RESERVE XE "MEM_RESERVE" , а затем получить их в пользование, вызвав эту же функцию еще раз для полученной области памяти, но уже с параметром MEM_COMMIT XE "MEM_COMMIT" .

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

После использования вы должны освободить полученную ранее виртуальную память, вызвав функцию VirtualFree XE "VirtualFree" :


BOOL VirtualFree(
  LPVOID lpvAddress,   // адрес области 
  DWORD  cbSize,       // размер области 
  DWORD  fdwFreeType); // выполняемая операция 

Через параметры lpvAddress и cbSize передаются, соответственно, адрес и размер освобождаемой области.

Если вы зарезервировали область виртуальной памяти функцией VirtualAlloc XE "VirtualAlloc" с параметром MEM_RESERVE XE "MEM_RESERVE" для последующего получения страниц в пользование и затем вызвали эту функцию еще раз с параметром MEM_COMMIT XE "MEM_COMMIT" , вы можете либо совсем освободить область памяти, обозначив соответствующие страницы как свободные, либо оставить их зарезервированными, но не используемыми.

В первом случае вы должны вызвать функцию VirtualFree XE "VirtualFree" с параметром fdwFreeType, равным MEM_RELEASE XE "MEM_RELEASE" , во втором - с параметром MEM_DECOMMIT XE "MEM_DECOMMIT" .

Три состояния страниц виртуальной памяти

Страницы виртуальной памяти, принадлежащие адресному пространству процесса в Microsoft Windows NT, могут находиться в одном из трех состояний. Они могут быть свободными (free XE "free" ), зарезервированными (reserved) или выделенными для использования (committed). В адресном пространстве приложения есть также относительно небольшое количество страниц, зарезервированных для себя операционной системой. Эти страницы недоступны приложению.

Функция VirtualAlloc XE "VirtualAlloc" может либо зарезервировать свободные страницы памяти (для чего ее нужно вызвать с параметром MEM_RESERVE XE "MEM_RESERVE" ), либо выделить свободные или зарезервированные страницы для непосредственного использования (для этого функция вызывается с параметром MEM_COMMIT XE "MEM_COMMIT" ). Приложение может либо сразу получить страницы памяти в использование, либо предварительно зарезервировать их, обеспечив доступное сплошное адресное пространство достаточного размера.

Для того чтобы зарезервированная или используемая область памяти стала свободной, вы должны вызвать для нее функцию VirtualFree XE "VirtualFree" с параметром MEM_RELEASE XE "MEM_RELEASE" .

Вы можете перевести страницы используемой области памяти в зарезервированное состояние, не освобождая соответствующего адресного пространства. Это можно сделать при помощи функции VirtualFree XE "VirtualFree" с параметром MEM_DECOMMIT XE "MEM_DECOMMIT" .

На рис. 1.11 мы показали три состояния страниц виртуальной памяти и способы перевода страниц из одного состояния в другое.

Рис. 1.11. Три состояния страниц виртуальной памяти

Фиксирование страниц виртуальной памяти

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

В программном интерфейсе Microsoft Windows NT есть функция VirtualLock XE "VirtualLock" , с помощью которой нетрудно зафиксировать нужное вам количество страниц в физической памяти.

Прототип функции VirtualLock XE "VirtualLock" представлен ниже:


BOOL VirtualLock(
  LPVOID lpvAddress, // адрес начала фиксируемой 
                     // области памяти
  DWORD  cbSize);    // размер области в байтах 

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

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

Для расфиксирования страниц памяти следует вызвать функцию VirtualUnlock, имеющую аналогичное назначение параметров:


BOOL VirtualUnlock(
  LPVOID lpvAddress, // адрес начала расфиксируемой 
                     // области памяти
  DWORD  cbSize);    // размер области в байтах 

Сколько страниц памяти можно зафиксировать функцией VirtualLock XE "VirtualLock" ?

Не очень много. По умолчанию приложение может зафиксировать не более 30 страниц виртуальной памяти. И это сделано не зря - фиксирование большого количества страниц одним приложением уменьшает объем физической памяти, доступной для других приложений и может отрицательно сказаться на производительности всей системы в целом. Однако при необходимости вы можете увеличить это значение при помощи функции SetProcessWorkingSetSize XE "SetProcessWorkingSetSize" , описанной в SDK.

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

Изменение типа разрешенного доступа для страниц памяти

При получении страниц памяти в пользование функцией VirtualAlloc XE "VirtualAlloc" вы можете в последнем параметре указать тип доступа, разрешенного для этих страниц. В процессе работы приложение может изменять тип доступа для полученных им страниц при помощи функции VirtualProtect, прототип которой представлен ниже:


BOOL VirtualProtect(
  LPVOID lpvAddress,      // адрес области памяти 
  DWORD  cbSize,          // размер области памяти в байтах 
  DWORD  fdwNewProtect,   // новый тип разрешенного доступа 
  PDWORD pfdwOldProtect); // указатель на переменную, 
                // в которую будет записан прежний код доступа

Через параметр lpvAddress вы должны передать адрес области памяти, расположенной в готовых для использования страницах (а не в зарезервированных страницах).

Новый тип доступа передается через параметр fdwNewProtect. Здесь вы можете использовать все константы, что и для последнего параметра функции VirtualAlloc XE "VirtualAlloc" , например, PAGE_READWRITE XE "PAGE_READWRITE" или PAGE_READONLY XE "PAGE_READONLY" .

Зачем вам может пригодиться изменение кода доступа?

Например, вы можете получить страницы страницы памяти, доступные на запись, а затем, записав туда данные, разрешить доступ только на чтение или на исполнение. Устанавливая тип доступа PAGE_NOACCESS XE "PAGE_NOACCESS" для страниц, которые в данный момент не используются приложением, вы можете, например, обнаружить во время исполнения кода ошибки, связанные с использованием неправильных указателей. Для решения аналогичных задач можно также устанавливать тип доступа PAGE_GUARD XE "PAGE_GUARD" .

Заметим, что функция VirtualProtect позволяет изменить код доступа только для тех страниц, которые созданы вызывающим ее процессом. При необходимости приложение может изменить код доступа страниц другого процесса, имеющего код доступа PROCESS_VM_OPERATION XE "PROCESS_VM_OPERATION" (например, процесса, созданного приложением). Это можно сделать при помощи функции VirtualProtectEx XE "VirtualProtectEx" , прототип которой представлен ниже:


BOOL VirtualProtectEx(
  HANDLE hProcess,        // идентификатор процесса
  LPVOID lpvAddress,      // адрес области памяти 
  DWORD  cbSize,          // размер области памяти в байтах 
  DWORD  fdwNewProtect,   // новый тип разрешенного доступа 
  PDWORD pfdwOldProtect); // указатель на переменную, 
                // в которую будет записан прежний код доступа

Через параметр hProcess функции VirtualProtectEx XE "VirtualProtectEx" следует передать идентификатор процесса. Подробнее об этом идентификаторе вы узнаете из главы, посвященной мультизадачности в операционной системе Microsoft Windows NT.

Получение информации об использовании виртуальной памяти

В программном интерфейсе Microsoft Windows NT есть средства для получения справочной информации об использовании процессами виртуальной памяти. Это функции VirtualQuery XE "VirtualQuery" и VirtualQueryEx XE "VirtualQueryEx" . С помощью них процесс может исследовать, соответственно, собственное адресное пространство и адресное пространство других процессов, определяя такие характеристики областей виртуальной памяти, как размеры, тип доступа, состояние и так далее. Из-за ограниченного объема книги мы не будем рассматривать эти функции. При необходимости вы сможете найти их подробное описание в справочной системе, поставляющейся вместе с SDK или системой разработки Microsoft Visual C++.

С помощью приложения Process Walker XE "приложение Process Walker" , которое поставляется в составе SDK, вы сможете визуально исследовать распределение виртуальной памяти для процессов. На рис. 1.12 показан фрагмент такого распределения для приложения CALC.EXE. Приложение вызывает функции VirtualQuery XE "VirtualQuery" и VirtualQueryEx XE "VirtualQueryEx" (исходные тексты приложения поставляется в составе SDK; вы найдете их в каталоге win32sdk\mstools\samples\sdktools\winnt\pviewer).

Рис. 1.12. Исследование распределения виртуальной памяти при помощи приложения Process Walker

Для загрузки и исследования процесса вы должны выбрать из меню Process строку Load Process. Затем при помощи диалоговой панели Open executable image следует выбрать загрузочный файл нужного вам приложения. В окне появится распределение памяти в виде таблицы.

В столбце Address отобажается логический адрес областей памяти. Обратите внимание, что в модели памяти FLAT XE "FLAT" он состоит только из смещения. Базовый адрес отображается в столбце BaseAddr и имеет смысл только для зарезервированных (Reserve) или готовых к использованию (Commit) страниц. Для свободных страниц (Free) он равен нулю.

В столбце Prot в виде двухбуквенного сокращения имен сответствующих констант отображается тип доступа, разрешенный для страниц. Например, страницы, доступные только на чтение, отмечены в этом столбце как RO (PAGE_READONLY XE "PAGE_READONLY" ).

В линейном адресном пространстве процесса находятся не только код и данные самого процесса. В него также отображаются страницы системных библиотек динамической загрузки DLL (как это видно из рис. 1.12). При этом в столбце Object может отображаться тип объекта (библиотека DLL или исполняемый EXE-модуль), а в столбцах Section и Name, соответственно, имя секции и имя библиотеки DLL.

Если сделать двойной щелчок левой клавишей мыши по строке, соответствующей страницам памяти, отмеченным как Commit, на экране появится окно с дампом содержимого этих страниц (рис. 1.13).

Рис. 1.13. Просмотр содержимого страниц, готовых для использования

Вы можете использовать приложение Process Walker для отладки создаваемых вами приложений, например, контролируя использование ими виртуальной памяти.

[Назад] [Содеожание] [Дальше]