Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Создание эффективных приложений для Windows Джеффри Рихтер 2004 (Книга).pdf
Скачиваний:
350
Добавлен:
15.06.2014
Размер:
8.44 Mб
Скачать

pvAddress = ((PBYTE) vmq pvRgnBaseAddress + vmq.RgnSize);

}

}

Этот цикл начинает работу с виртуального адреса NULL и заканчивается, когда VMQuery возвращает FALSE, что указывает на невозможность дальнейшего просмотра адресного пространства процесса На каждой итерации цикла вызывается функция ConstructRgnlnfoLine; она заполняет символьный буфер информацией о регионе. По том эти данные вносятся в список.

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

ГЛАВА 15 Использование виртуальной памяти в приложениях

В Windows три механизма работы с памятью

виртуальная память — наиболее подходящая для операций с большими мас сивами обьектов или структур, проецируемые в память файлы — наиболее подходящие для операций с большими

потоками данных (обычно из файлов) и для совместного использова ния данных несколькими процессами на одном компьютере; кучи — наиболее подходящие для работы с множеством малых объектов

Б этой главе мы обсудим первый метод — виртуальную память Остальные два метода (проецируемые в память файлы и кучи) рассматриваются соответственно в главах 17 и 18

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

Резервирование региона в адресном пространстве

Для этого предназначена функция VirtualAlloc

PVOID VirtualAlloc( PVOID pvAddress, SIZE_T dwSize, DWORD fdwAllocationType, DWORD fdwProtecf);

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

нижних адресов или, Haoбopoт, с верхних Однако с помощью флага MEM_ TOP_DOWN (о нем речь впереди) Вы можете сказать свое веское слово

Для большинства программистов возможность выбора конкретного адреса резер вируемого региона — нечто совершенно новое Вспомните, как это делалось раньше операционная система просто находила подходящий по размеру блок памяти, выде ляла этот блок и возвращала его адрес Но поскольку каждый процесс владеет соб ственным адресным пространством, у Вас появляется возможность указывать опера ционной системе желательный базовый адрес резервируемого региона

Допустим, нужно выделить регион, начиная с «отметки» 50 Мб в адресном про странстве процесса. Тогда параметр pvAdress должен быть равен 52 428 800 (50 * 1024 * 1024). Если по этому адресу можно разместить регион требуемого размера, систе ма зарезервирует его

ивернет соответствующий адрес. Если же по этому адресу сво бодного пространства недостаточно или просто нет, система не удовлетворит зап рос, и функция VirtualAlloc вернет NULL Адрес, передаваемый pvAdress, должен ук ладываться в границы раздела пользовательского режима Вашего процесса, так как иначе VirtualAlloc потерпит неудачу

ивернет NULL.

Как я уже говорил в главе 13, регионы всегда резервируются с учетом грануляр ности выделения памяти (64 Кб для существующих реализаций Windows). Поэтому, если Вы попытаетесь зарезервировать регион по адресу 19668992 (300 x 65 536 + 8192), система округлит этот адрес до ближайшего меньшего числа, кратного 64 Кб, и на самом делс зарезервирует регион по адресу 19 660 800 (300 x 65 536).

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

Второй параметр функции VirtualAlloc dwSize — указывает размер резервируе мого региона в байтах. Поскольку система резервирует регионы только порциями, кратными размеру страницы, используемой данным процессором, то попытка заре зервировать, скажем, 62 Кб даст регион размером 64 Кб (если размер страницы со ставляет 4, 8 или l6 Кб).

Третий параметр ,fdwAllocationType, сообщает системе, что именно Вы хотите сде лать: зарезервировать регион или передать физическую память. (Такое разграниче ние необходимо, поскольку VirtualAlloc позволяет не только резервировать регионы, но и передавать им физическую память.) Поэтому, чтобы зарезервировать регион адресного пространства, в этом параметре нужно передать идентификатор MEM_RE SERVE.

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

VirtualAlloc в параметре pvAddress передайте NULL, а в параметреь fdwAlloccationType —

флаг MEM_RESERVE, скомбинированный с флагом MEM_TOP_DOWN.

NOTE:

В Windows 98 флаг MEM_TOP_DOWN игнорируется.

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

Резервируя регион, присваивайте сму тот атрибут защиты, который будет чаще всего использоваться с памятью, передаваемой региону. Скажем, если Вы собираетесь передать региону физическую память с атрибутом защиты PAGEREADWR!TE (этот атрибут самый распространенный), то и резервировать его следует с тем же атрибу том, Система работает эффективнее, когда атрибут защиты региона совпадает с ат рибутом защиты передаваемой памяти.

Вы можете использовать любой из следующих атрибутов защиты: PAGE_NOACCESS,

PAGE_READWRITE, PAGE_READONLY, PAGE_EXECUTE, PAGE_EXECUTE_READ

или PAGE_ EXECUTE_READWRITE. Но указывать атрибуты PAGE_WRITECOPY или

PAGE_EXECUTE_ WRITECOPY нельзя: иначе функция VirtualAtloc не зарезервирует регион и вернет NULL Кроме того, при резервировании региона флаги PAGE_GUARD, PAGE_WRITECOMBINE или PAGE_NOCACHE применять тоже нельзя — они присваиваются только передава емой памяти.

NOTE:

Windows 98 поддерживаетлишь атрибугы защиты PAGE_NOACCESS, PAGE_READONLY и PAGE_READWRITE Попытка резервирования региона с атрибутом PAGE_EXECUTE или PAGE_EXECUTE_READ дает регион с атрибутом

PAGE_READONLY. А указав PAGE_EXECUTE_READWRITE, Вы получите регион с атрибутом PAGE_READWRITE.

Передача памяти зарезервированному региону

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

Для передачи физической памяти вызовите VirtualAlloc еще раз, указав в парамет pe fdwAllocationtype не MEM_RESERVE, a MEM_COMMIT. Обычно указывают тот же ат рибут защиты, что и при резервировании региона, хотя можно задать и другой

Затем сообщите функции VirtuaMlloc, по какому адресу и сколько физической па мяти следует передать. Для этого в параметр pvAddress запишите желательный адрес, а в параметр dwSize — размер физической памяти в байтах Передавать физическую память сразу всему региону необязательно.

Посмотрим, как это делается на практике. Допустим, программа работает на про цессоре x86 и резервирует регион размером 512 Кб, начиная с адреса 5 242 880. За тем Вы передаете физическую память блоку размером 6 Кб, отстоящему от начала зарезервированного региона на 2 Кб. Тогда вызовите VirtualAlloc с флагом MEM_COM MIT так

VirtualAlloc((PVOID) (5242880 + (2 * 1024)), 6 * 1024, MEM_COMMIT, PAGE_READWRITE);

В этом случае система передаст 8 Кб физической памяти в диапазоне адресов от 5 242 880 до 5 251 071 (т. e. 5 242 880 + 8 Кб - 1 байт), и обе переданные страницы получат атрибут защиты PAGE_READWRITE. Страница является минимальной едини цей памяти, которой можно присвоить собственные атрибуты защиты. Следователь но, в регионе могут быть страницы с разными атрибутами защиты (скажем, одна - с атрибутом

PAGE_READWRITE, другая — с атрибутом PAGE_READONLY).

Резервирование региона с одновременной передачей физической памяти

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

PVOID pvMem = VirtualAlloc(NULL, 99 * 1024, MEM_PESERVE |

MFM_COMMIT, PAGE_READWRITE);1}

Этот вызов содержит запрос на выделение региона размером 99 Кб и передачу ему 99 Кб физической памяти. Обрабатывая этот запрос, система сначала просматривает адресное пространство Вашего процесса, пытаясь найти непрерывную незарезерви рованную область размерим не менее 100 Кб (на машинах с 4-килобайювыми стра ницами) или 104 Кб (на машинах с 8-килобайтовыми страницами).

Система просматривает адресное пространство потому, что в pvAddress указан NULL Если бы он содержал конкретный адрес памяти, система проверила бы только его — подходи! ли по размеру расположенное за ним адресное пространство Ока жись он недостаточным, функция VirtualAlloc вернула бы NULL.

Если системе удается зарезервировать подходящий регион, она передает ему фи зическую память. И регион, и переданная память получают один атрибут защиты — в данном случае PAGE_READWRITE.

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

VirtualAlloc вернет NULL

Конечно, при резервировании региона с одновременной передачей ему памяти можно указать в парметре pvAddress конкретный адрес или запросить систему подо брать свободное место в верхней части адресного пространства процесса. Последнее реализуют так- в параметр pvAddress заносят NULL, a значение парамегра fdwAlloca tionType комбинируют с флагом MEM_TOP_DOWN.

В какой момент региону передают физическую память

Допустим, Вы разрабатываете программу — электронную таблицу, которая поддержи ваетдо 200 строк при 256 колонках Для каждой ячейки необходима своя структура CELLDATA, описывающая ее (ячейки) содержимое. Простейший способ работы с двух мерной матрицей ячеек, казалось бы, — взять и объявить в программе такую пере менную:

CELLDATA CellData[200][256];

Но если размер структуры CELLDATA будет хотя бы 128 байтов, матрица потребу ет 6 553 600 (200 * 256 * 128) байтов физической памяти. Не многовато ли? Тем более что большинство пользователей заполняет данными всего несколько ячеек Выходит, матрицы здесь крайне неэффективны

Поэтому электронные таблицы реализуют на основе других методов управления структурами данных, используя, например, связанные списки В этом случае структу ры CELLDATA создаются только для ячеек, содержащих какие-то данные И поскольку большая часть ячеек в таблице остается незадействованной, Вы экономите колоссаль ные объемы памяти Но это значительно усложняет доступ к содержимому ячеек. Что бы, допустим, выяснить содержимое ячейки на пересечении строки 5 и колонки 10, придется пройти по всей цепочке связанных списков В итоге метод связанных спис ков работает медленнее, чем метод, основанный на объявлении матрицы.

К счастью, виртуальная память позволяет найти компромисс между «лобовым» объявлением двухмерной матрицы и реализацией связанных списков. Тем самым можно совместить простоту и высокую скорость доступа к ячейкам, предлагаемую "матричным" методом, с экономным расходованием памяти, заложенным в метод связанных списков.

Вот что надо сделать в своей программе.

1.Зарезервировать достаточно большой регион, чтобы при необходимости в него мог поместиться весь массив структур CELLDATA. Для резервирования региона физическая память не нужна

2.Когда пользователь вводит данные в ячейку, вычислить адрес в зарезервиро ванном регионе, по которомудолжна быть записана соответствующая cтpyк тура CELLDATA. Естественно, физическая память на этот регион пока не ото бражается, и поэтому любое обращение к памяти по данному адресу вызовет нарушение доступа.

3.Передать по адресу, полученному в п. 2, физическую память, необходимую для размещения одной структуры CELLDATA. (Так как система допускает передачу памяти отдельным частям зарезервированного региона, в нем могут находить ся и отображенные, и не отображенные на физическую память участки.)

4.Инициализировать элементы новой структуры CELLDATA.

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

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