Атрибуты защиты
Отдельным страницам физической памяти можно присвоить свои атрибуты защиты показанные в следующей таблице. Я
Атрибут защиты |
Описание |
PAGE_NOACCESS |
Попытки чтения, записи или исполнения содержимого памяти на этой странице вызывают нарушение доступа |
PAGE_READONLY |
Попытки записи или исполнения содержимого памяти на этой странице вызывают нарушение доступа |
PAGE_READWRI'1E |
Попытки исполнения содержимого памяти на этой странице вызывают нарушение доступа |
PAGE_EXECUTE |
Попытки чтения или записи на этой странице вызывают нарушение доступа |
PAGE_EXECITTK_READ |
Попытки записи на этой сфанице вызывают нарушение доступа |
PAGE_EXECUTE_READWRITE |
На этой странице возможны любые операции |
PAGE_WRITECOPY |
Попытки исполнения содержимого памяти на этой странице выбывают нарушение доступа, попытка записи приводит к тому, что процессу предоставляется «личная» копия данной страницы |
PAGE_EXECUTE_WRITECOPY |
На этой странице возможны любые операции, попытка записи приводит к тому, что процессу предоставляется «личная» копия данной страницы |
На процессорных платформах x86 и Alpha атрибут PAGE_EXECUTE не поддерживается, хотя в операционных системах такая поддержка предусмотрена Перечисленные процессоры воспринимают запрос на чтение как запрос на исполнение Поэтому присвоение памяти атрибута PAGE_EXECUTE приводит к тому, что на этих процессорах она считается доступной и для чтения, Но полагаться на эту особенность не стоит, поскольку в реализациях Windows на других процессорах все может встать на свои места.
WINDOWS 98
В Windows 98 страницам физической памяти можно присвоить только три атрибута защиты: PAGE_NOACCESS, PAGE_READONLY и PAGE_READWRITE.
Блоки внутри регионов
Попробуем увеличить детализацию адресного пространства (по сравнению с тем, что показано в таблице 13-2). Например, таблица 13-3 показывает ту же карту адресного пространства, но в другом «масштабе»: по ней можно узнать, из каких блоков состоит каждый регион.
Базовый адрес |
Тип |
Размер |
Блоки |
Атрибут(ы) защиты |
Описание |
00000000 |
Free |
65536 |
|||
00010000 |
Private |
4096 |
1 |
-RW- |
|
00010000 |
Private |
4096 |
-RW- |
||
00011000 |
Free |
61440 |
|||
00020000 |
Private |
4096 |
1 |
-HW- |
|
00020000 |
Private |
4096 |
-HW- --- |
||
00021000 |
Free |
61440 |
|||
00030000 |
Private |
1048576 |
3 |
-RW- |
Стек потока |
00030000 |
Reserve |
905216 |
-RW- s— |
||
0010D000 |
Private |
4096 |
-RW- G- |
||
0010E000 |
Private |
139264 |
-RW- --- |
||
00130000 |
Private |
1048576 |
? |
-RW- |
|
00130000 |
Private |
36864 |
-RW- --- |
||
00139000 |
Reserve |
1011712 |
-RW- --- |
||
00230000 |
Mapped |
65536 |
2 |
-RW- |
|
00230000 |
Mapped |
4096 |
-RW- —— |
||
00231000 |
Reserve |
61440 |
-RW- --- |
||
00240000 |
Mapped |
90112 |
1 |
-R — |
\Device\HarddiskVoluume1\WTNNT\system32\unicode.nls |
00240000 |
Happed |
90112 |
R |
||
00256000 |
Free |
409GO |
|||
00260000 |
Mapped |
208896 |
1 |
-R-- |
\Device\HarddiskVoluume1\WTNNT\system32\locale.nls |
00260000 |
Mapped |
208896 |
-R-- --- |
||
00293000 |
Free |
53248 |
|||
002А0000 |
Happed |
266240 |
1 |
-R — |
\Device\HarddiskVoluume1\WTNNT\system32\sortkey.nls |
002А0000 |
Mapped |
266240 |
-R-- --- |
||
002Е1000 |
Free |
61440 |
|||
002F0000 |
Mapped |
16384 |
1 |
-R- |
\Device\HarddiskVoluume1\WTNNT\system32\sorttbls.nls |
002F0000 |
Mapped |
16384 |
-R-- --- |
||
002F4000 |
Free |
49152 |
|||
00300000 |
Mapped |
819200 |
4 |
ER- |
|
00300000 |
Mapped |
16384 |
ЕR-- —-- |
||
00304000 |
Reserve |
770048 |
ER-- --- |
||
003C0000 |
Mapped |
8192 |
ER-- --- |
||
ОО3С2000 |
Reserve |
24576 |
ER-- --- |
||
ОО3С8000 |
Free |
229376 |
|||
00400000 |
Image |
106496 |
5 |
ERWC |
С:\CD\x86\Debug\14_VMMap.exe |
00400000 |
Image |
4096 |
-R-- --- |
||
00401000 |
Image |
81920 |
ЕR-- —-- |
||
00415000 |
Image |
4096 |
-R-- --- |
||
00416000 |
Image |
8192 |
-RW- --- |
||
00418000 |
Image |
8192 |
-R-- --- |
||
0041А000 |
Free |
24576 |
|||
00420000 |
Mapped |
274432 |
1 |
-R- |
|
00420000 |
Mapped |
274432 |
-R- --- |
||
00463000 |
Free |
53248 |
|||
00470000 |
Mapped |
3145726 |
2 |
ER-- |
|
00470000 |
Mapped |
274432 |
ER-- --- |
||
004B3000 |
Reserve |
2871296 |
ER-- --- |
||
00770000 |
Private |
4096 |
1 |
-RW- --- |
|
00770000 |
Privale |
4096 |
-RW- --- |
||
00771000 |
Free |
61440 |
|||
00780000 |
Pr ivate |
4096 |
1 |
-RW- --- |
|
00780000 |
Private |
4096 |
-RW- --- |
||
00781000 |
Free |
61440 |
|||
00790000 |
Private |
65536 |
2 |
-RW- --- |
|
00790000 |
Private |
20480 |
-RW- --- |
||
00795000 |
Reserve |
45056 |
-RW- --- |
||
007А0000 |
Mapped |
8192 |
1 |
-R-- --- |
\Device\HarddiskVolume1\WINNT\system32\ctype.nls |
007А0000 |
Mapped |
8192 |
-R-- --- |
||
007A2000 |
Free |
57344 |
|||
007В0000 |
Private |
524288 |
2 |
-RW- --- |
|
007В0000 |
Private |
4096 |
-RW- --- |
||
007В1000 |
Reserve |
520192 |
-RW- --- |
||
00830000 |
Free |
1763311616 |
|||
699D0000 |
Image |
45056 |
4 |
ERWC |
С:\WINNT\Systern32\PSAPI.dll |
699D0000 |
Image |
4096 |
-R-- --- |
||
69901000 |
Image |
16384 |
ER- --- |
||
699D5000 |
Image |
16384 |
-RWC --- |
||
699D9000 |
Image |
8192 |
-R-- --- |
||
699DB000 |
Free |
238505984 |
|||
77D50000 |
Imago |
450560 |
4 |
ERWC |
C:\WINNT\system32\RPCRT4.DLL |
77D50000 |
Image |
4096 |
-R-- --- |
||
77D51000 |
image |
421888 |
ER-- --- |
||
77DB8000 |
Image |
409G |
-RW- --- |
||
77DB9000 |
Image |
20480 |
-R-- --- |
||
77DBE000 |
Free |
8192 |
|||
77DC0000 |
Image |
344064 |
5 |
ERWC |
С:\WINNT\syatem32\ADVAPI32.dll |
77DC0000 |
Image |
4096 |
-R-- --- |
||
77DС1000 |
Image |
307200 |
ER-- --- |
||
77Е0С000 |
Image |
4096 |
-RW- --- |
||
77E00000 |
Image |
4096 |
-RWC --- |
||
77Е0E000 |
Image |
24576 |
-R-- --- |
||
77Е14000 |
Free |
49152 |
|||
77E20000 |
Image |
401408 |
4 |
ERWC |
С:\WINNT\system32\USER32.dll |
/7Е20000 |
Image |
4096 |
-R-- --- |
||
77Е21000 |
Image |
348160 |
ER-- --- |
||
77Е76000 |
Image |
4096 |
-RW- --- |
||
77Е77000 |
Image |
45056 |
-R-- --- |
||
77Е82000 |
Free |
57344 |
|||
77Е90000 |
Image |
720896 |
5 |
ERWC |
С \WINNT\system32\KERNEL32.dll |
77Е90000 |
Image |
4096 |
-R-- --- |
||
77Е91000 |
Image |
368640 |
ER-- --- |
||
77ЕЕВ000 |
Image |
8192 |
-RW- --- |
||
77EED000 |
Image |
4096 |
-RWC --- |
||
77ЕЕЕ000 |
Image |
335872 |
-R-- --- |
||
77F40000 |
Image |
241664 |
4 |
ERWC |
С \WINNT\system32\GDI32.DLL |
77F40000 |
Image |
4096 |
-R-- --- |
||
77F41000 |
Image |
221184 |
ER-- --- |
||
77F77000 |
Image |
4096 |
-RW- --- |
||
77F78000 |
Image |
12288 |
-R-- --- |
||
77F7B000 |
Free |
20480 |
|||
77F80000 |
Image |
483328 |
5 |
ERWC |
С \WINT\System32\ntdll.dll |
77F80000 |
Image |
409b |
-R-- --- |
||
77F81000 |
Image |
299008 |
ER-- --- |
||
77FCA000 |
Image |
8192 |
RW- --- |
||
77FCC000 |
Image |
4096 |
-RWC --- |
||
77FCD000 |
Image |
167936 |
-R-- --- |
||
77FF6000 |
Free |
40960 |
|||
78000000 |
Image |
290816 |
6 |
ERWC |
С \WINNT\system32\MSVCRT.dll |
78000000 |
Image |
4096 |
-R-- --- |
||
78001000 |
Image |
208896 |
ER-- --- |
||
78031000 |
Image |
32768 |
-R-- --- |
||
7803С000 |
Image |
12288 |
-RW- --- |
||
7803F000 |
Image |
16384 |
-RWC --- |
||
78043000 |
Image |
16384 |
-R-- --- |
||
78047000 |
Free |
124424192 |
|||
7F6F0000 |
Mapped |
1048576 |
2 |
ER-- --- |
|
7F6F0000 |
Mapped |
28672 |
ER-- --- |
||
7F6F7000 |
Reserve |
1019904 |
ER-- --- |
||
7F7F0000 |
Free |
8126464 |
|||
7FFB0000 |
Mapped |
147456 |
1 |
-R-- --- |
|
7FFB0000 |
Mapped |
147456 |
-R-- --- |
||
7FFD4000 |
Free |
40960 |
|||
7FFDE000 |
Private |
4096 |
1 |
ERW --- |
|
7FFDE000 |
Private |
4096 |
ERW --- |
||
7FFDF000 |
Private |
4096 |
1 |
ERW --- |
|
7FFDF000 |
Private |
4096 |
ERW --- |
||
7FFF0000 |
Private |
65536 |
2 |
-R-- --- |
|
7FFE0000 |
Private |
4096 |
-R-- --- |
||
7FFE1000 |
Reserve |
61440 |
-R-- --- |
Таблица 13-3. Образец карты адресного пространства процесса (с указанием блоков внутри регионов) в Windows 2000 на 32-разрядном процессоре типа x86
Разумеется, в свободных регионах блоков нет, поскольку им не переданы страни цы физической памяти. Строки с описанием блоков состоят из пяти полей.
В первом поле показывается адрес группы страниц с одинаковыми состоянием и атрибутами защиты. Например, по адресу 0x77E20000 передана единственная страница (4096 байтов) физической памяти с атрибутом защиты, разрешающим только чтение. А по адресу 0x77E21000 присутствует блок размером 85 страниц (348 160 байтов) переданной памяти с атрибутами, разрешающими и чтение, и исполнение. Если бы атрибуты защиты этих блоков совпадали, их можно было бы объединить, и тогда на карте памяти появился бы единый элемент размером в 86 страниц (352 256 байтов).
Во втором поле сообщается тип физической памяти, с которой связан тот или иной блок, расположенный в границах зарезервированного региона. В нем появляется одно из пяти возможных значений: Free (свободный), Private (закрытый), Mapped (проецируемый), Image (образ) или Reserve (резервный). Значения Private, Mapped и Image говорят о том, что блок поддерживается физической памятью соответственно из страничного файла, файла данных, загруженного EXE- или DLL-модуля. Если же в поле указано значение Free или Reserve, блок вообще не связан с физической памятью.
Чаще всего блоки в пределах одного региона связаны с однотипной физической памятью. Однако регион вполне может содержать несколько блоков, связанных с физической памятью разных типов. Например, образ файла, проецируемого в память, может быть связан с EXE- или DLL-файлом. Если Вам понадобится что-то записать на одну из страниц в таком регионе с атрибутом защиты PAGEWRITECOPY или PAGE_EXECUTE_WRITECOPY, система подсунет Вашему процессу закрытую копию, связанную со страничным файлом, а не с образом файла. Эта новая страница получит те же атрибуты, что и исходная, но без защиты по типу «копирование при записи».
В третьем поле проставляется размер блока. Все блоки непрерывны в границах региона, и никаких разрывов между ними быть не может.
В четвертом поле показывается количество блоков внутри зарезервированного региона.
В пятом поле выводятся атрибуты защиты и флаги атрибутов защиты текущего блока. Атрибуты защиты блока замещают атрибуты защиты региона, содержащего данный блок. Их допустимые значения идентичны применяемым для регионов; кроме того, блоку могут быть присвоены флаги PAGE_GUARD, PAGE_WRITECOMBINE и PAGE_NOCACHE, недопустимые для региона.
Физическая память и страничный файл
В старых операционных системах физической памятью считалась вся оперативная Я память (RAM), установленная в компьютере. Иначе говоря, если в Вашей машине было Я 16 Мб оперативной памяти, Вы могли загружать и выполнять приложения, использу-
ющие вплоть до 16 Мб памяти Современные операционные системы умеют имитировать память за счет дискового пространства. При этом на диске создается страничный файл (paging file), который и содержит виртуальную память, доступную всем процессам.
Разумеется, операции с виртуальной памятью требуют соответствующей поддержки от самого процессора. Когда поток пытается обратиться к какому-то байту, процессор должен знать, где находится этот байт — в оперативной памяти или на диске.
С точки зрения прикладной программы, страничный файл просто увеличивает объем памяти, которой она может пользоваться Если в Вашей машине установлено 64 Мб оперативной памяти, а размер страничного файла на жестком диске составляет 100 Мб, приложение считает, что объем оперативной памяти равен l64 Мб.
Конечно, l64 Мб оперативной памяти у Вас на самом деле нст Операционная система в тесной координации с процессором сбрасывает содержимое части оперативной памяти в страничный файл и по мере необходимости подгружает его порции обратно в память Если такого файла нет, система просто считает, что приложениям доступен меньший объем памяти, — вот и все Но, поскольку страничный файл явным образом увеличивает объем памяти, доступный приложениям, его применение весьма желательно. Это позволяет приложениям работать с большими наборами данных.
Рис. 13-1. Примеры адресных пространств процессов для разных типов процессоров
Физическую память следует рассматривать как данные, хранимые в дисковом файле со страничной структурой. Поэтому, когда приложение передает физическую память какому-нибудь региону адресного пространства (вызывая VirtualAttoc), она на самом деле выделяется из файла, размещенного на жестком диске Размер страничного файла в системе — главный фактор, определяющий количество физической памяти, доступное приложениям Реальный объем оперативной памяти имеет гораздо меньшее значение
Теперь посмотрим, что происходит, когда поток пытается получитьдоступ к блокуданных в адресном пространстве своего процесса. А произойти может одно из двух (рис. 13-2).
Рис. 13-2. Трансляция виртуального адреса на физический
В первом сценарии данные, к которым обращается поток, находятся в оперативной памяти. В этом случае процессор проецирует виртуальный адрес данных на физический, и поток получает доступ к нужным ему данным.
Во втором сценарии данные, к которым обращается поток, отсутствуют в оперативной памяти, но размещены где-то в страничном файле. Попытка доступа к данным генерирует ошибку страницы (page fault), и процессор таким образом уведом ляет операционную систему об этой попытке. Тогда операционная система начинае! искать свободную страницу в оперативной памяти; если таковой нет, система вынуждена освободить одну из занятых страниц. Если занятая страница не модифицировалась, она просто освобождается; в ином случае она сначала копируется из оператив-
ной памяти в страничный файл После этого система переходит к страничному файлу, отыскивает в нем запрошенный блок данных, загружает этот блок на свободную страницу оперативной памяти и, наконец, отображает (проецирует) адрес данных в виртуальной памяти на соответствующий адрес в физической памяти
Чем чаще системе приходится копировать страницы памяти в страничный файл и наоборот, тем болыпе нагрузка на жесткий диск и тем медленнее работает операционная система (При этом может получиться так, что операционная система будет гратить все свое время на подкачку страниц вместо выполнения программ.) Поэтому, добавив компьютеру оперативной памяти, Вы снизите частоту обращения к жесткому диску и тем самым увеличите общую производительность системы Кстати, во многих случаях увеличение оперативной памяти дает больший выигрыш в производительности, чем зяменя старого процессора на новый
Физическая память в страничном файле не хранится
Прочитав предыдущий раздел, Вы, должно быть, подумали, что страничный файл сильно разбухнет при одновременном выполнении в системе нескольких программ, — особенно если Вы сочли, будто при каждом запуске приложения система резервирует регионы адресного пространства для кода и данных процесса, передает им физическую память, а затем копирует код и данные из файла программы (расположенного на жестком диске) в физическую память, переданную из страничного файла
WINDOWS 2000
Windows 2000 может использовать несколько страничных файлов, и, если они расположены на разных физических дисках, операционная система работает гораздо быстрее, поскольку способна вести запись одновременно на нескольких дисках Чтобы добавить или удалить страничный файл, откройте в Control Panel апплет System, выберите вкладку Advanced и щелкните кнопку Performance Options IIa экране появится следующее диалоговое окно.
Однако система действуег не так, иначе на загрузку и подготовку программы к запуску уходило бы слишком много времени На самом деле происходит вот что. при запуске приложения система открывает его исполняемый файл и определяет объем кода и данных Затем резервирует регион адресного пространства и помечает, что
физическая память, связанная с этим регионом, — сям ЕХЕ-файл. Да да, правильно вместо выделения какого-то пространства из страничного файла система использует истинное содержимое, или образ (image) ЕХЕ-файла как зарезервированный регион адресного пространства программы. Благодаря этому приложение загружается очень быстро, а размер страничного файла удается заметно уменьшить.
Образ исполняемого файла (т. e. EXE- или DLL-файл), размещенный на жестком диске и применяемый как физическая памятьдля того или иного региона адресного пространства, называется проецируемым в память файлом (memory-mapped file) При загрузке EXF, или DLL система автоматически резервирует регион адресного пространства и проецирует на него образ файла. Помимо этого, система позволяет (с помо щью набора функций) проецировать на регион адресного пространства еще и файлы данных. (О проецируемых в память файлах мы поговорим в главе 17.)
Как адресное пространство разбивается на разделы
Виртуальное адресное пространство каждого процесса разбивается на разделы. Их размер и назначение в какой-то мере зависят от конкретного ядра Windows (таблица 13-1)
Как видите, ядра 32- и 64-разрядной Windows 2000 создают разделы, почти одинаковые по назначению, но отличающиеся по размеру и расположению. Однако ядро Windows 98 формирует другие разделы. Давайте рассмотрим, как система использует каждый из этих разделов.
Раздел |
32-разрядная Windows 2000 (на х86 и Alpha) |
32-разрядная Windows 2000 (на х86 с ключом /3GB) |
64-разрядная Windows 2000 (на Alpha и А-64) |
Windows 98 |
Для выявления |
0x00000000 |
0x00000000 |
0x00000000 00000000 |
0x00000000 |
нулевых указателей |
0x0000FFFF |
0x0000FFFF |
0x00000000 0000FFFF |
0x00000FFF |
Для совместимости с программами DOS и 16-разрядной Windows |
Hет |
Нет |
Нет |
0x00001000 0x003FFFFF |
Для кода и данных |
0x00010000 |
0x00010000 |
0x00000000 00010000 |
0x00400000 |
пол ьзовател ьс кого режима |
0x7FFEFFFF |
0xBFFFFFFF |
0x000003FF FFFEFFFF |
0x7FFFFFFF |
Закрытый, |
0x7FFF0000 |
0xBFFF0000 |
0x000003FF FFFF0000 |
Нет |
размером 64 Кб |
0x7FFFFFFF |
0xBFFFFFFF |
0x000003FF FFFFFFFF |
|
Для общих MMF (файлов, проецируемых в память) |
Нет |
Нет |
Нет |
0x80000000 0xBFFFFFFF |
Для кода и данных |
0x800000000 |
0xC0000000 |
0x00000400 00000000 |
0xC0000000 |
режима ядра |
0xFFFFFFFF |
0xFFFFFFFF |
0xFFFFFFFF FFFFFFFF |
0xFFFFFFFF |
Таблица 13-1. Так адресное пространство процесса разбивается на разделы
NOTE:
Microsoft активно работает над 64-разрядной Windows 2000. На момент напиcания книги эта система все еще находилась в разработке. Информацию по 64разрядной Windows 2000 следует учитывать при проектировании и реализации текущих проектов Однако Вы должны понимать, что какие-то детали скорее всего изменятся к моменту выхода 64-разрядной Windows 2000. То же самое относится и к конкретным диапазонам разделов виртуального адресного пространства и размеру страниц памяти на процессорах IA-64 (64-разрядной архитектуры Intel).
показана карта адресного пространства
В таблице 1 3- 4 показана карта адресного пространства при выполнении все той же программы VMMap, но уже под управлением Windows 98. Для экономии места диапазон виртуальных адресов между 0x80018000 и 0x85620000 не приведен.
Базовый адрес | Тип | Размер | Блоки | Атрибут(ы) защиты | Описание |
00000000 | Free | 4194304 | |||
00400000 | Private | 131072 | 6 | ---- | C:\CD\X86\DEBUG\14_VMMAP.EXE |
00400000 | Private | 8192 | -R-- --- | ||
00402000 | Private | 8192 | -RW- --- | ||
00404000 | Private | 73728 | -R-- --- | ||
00416000 | Private | 8192 | -RW- --- | ||
00418000 | Private | 8192 | -R-- --- | ||
0041А000 | Reserve | 24576 | |||
00420000 | Private | 1114112 | 4 | - | |
00420000 | Private | 20480 | -RW- --- | ||
00425000 | Reserve | 1028096 | - | ||
00520000 | Private | 4096 | -RW- --- | ||
00521000 | Reserve | 61440 | |||
00530000 | Private | 65536 | 2 | -RW- --- | |
00530000 | Private | 4096 | -RW- --- | ||
00531000 | Reserve | 61440 | -RW- --- | ||
00540000 | Private | 1179648 | 6 | Стек потока | |
00540000 | Reserve | 942080 | -- | ||
00626000 | Private | 4096 | -RW- --- | ||
00627000 | Reserve | 24576 | -- - | ||
00620000 | Private | 4096 | — -- | ||
0062Е000 | Private | 139261 | -RW- --- | ||
00650000 | Reserve | 65536 | |||
00660000 | Private | 1114112 | 4 | - _ | |
00660000 | Private | 20480 | -RW- --- | ||
00665000 | Reserve | 1028096 | --- -- | ||
00760000 | Private | 4096 | -RW- --- | ||
00761000 | Reserve | 61440 | |||
00770000 | Private | 10485/6 | 2 | -RW- --- | |
00770000 | Private | 32768 | -RW- --- | ||
00778000 | Reserve | 1015808 | -RW- --- | ||
00870000 | Free | 2004418560 | |||
78000000 | Private | 262144 | 3 | ---- | С \WINDOWS\SYSTEM\MSVCRT.DLL |
78000000 | Private | 188416 | -R-- --- | ||
7802E000 | Private | 57344 | -RW- --- | ||
7803С000 | Private | 16384 | -R-- - | ||
78040000 | Free | 133955584 | |||
80000000 | Private | 4096 | 1 | --- | |
80000000 | Reserve | 4096 | -- --- | ||
80001000 | Private | 4096 | 1 | ||
80001000 | Private | 4096 | -RW- --- | ||
80002000 | Private | 4096 | 1 | ---- | |
80002000 | Private | 4096 | -RW- --- | ||
80003000 | Private | 4096 | 1 | ||
80003000 | Private | 4096 | -RW- --- | ||
80004000 | Private | 65536 | 2 | ||
80004000 | Private | 32768 | -RW- --- | ||
8000С000 | Reserve | 32768 | ---- --- | ||
80014000 | Private | 4096 | 1 | ||
80014000 | Private | 4096 | -RW- --- | ||
80015000 | Private | 4096 | 1 | ---- | |
80015000 | Private | 4096 | -RW- --- | ||
80016000 | Private | 4096 | 1 | ---- | |
80016000 | Private | 4096 | -RW- --- | ||
80017000 | Private | 4096 | 1 | ||
80017000 | Private | 4096 | -RW- --- | ||
85620000 | Free | 9773056 | |||
85F72000 | Private | 151552 | 1 | ---- | |
8bF72000 | Private | 151552 | -RW- --- | ||
85F97000 | Private | 327680 | 1 | ---- | |
85F97000 | Private | 327680 | -RW- --- | ||
85FE7000 | Free | 22052864 | |||
874EF000 | Private | 4194304 | 1 | ---- | |
874EF000 | Reserve | 4194304 | |||
878EF000 | Free | 679219200 | |||
B00B0000 | Private | 880640 | 3 | ||
B00B0000 | Private | 233472 | -RW- --- | ||
В00Е9000 | Private | 20480 | -RW- --- | ||
В00ЕЕ000 | Private | 626688 | -R- --- | ||
В0187000 | Free | 177311744 | |||
ВААА0000 | Private | 315392 | 7 | ---- | |
ВАAА0000 | Private | 4096 | -R- --- | ||
ВААA1000 | Private | 4096 | -RW- --- | ||
ВАAА2000 | Private | 241664 | -R- --- | ||
BAADD000 | Private | 4096 | -RW- --- | ||
BAADE000 | Private | 4096 | -R- --- | ||
BAAOF000 | Private | 32788 | -RW- --- | ||
ВААЕ7000 | Private | 24576 | -R- --- | ||
BAAED000 | Free | 86978560 | |||
BFDE0000 | Private | 20480 | 1 | ||
BFOE0000 | Private | 20480 | -R- --- | ||
BFDE5000 | Free | 45056 | |||
BFDF0000 | Private | 65536 | 3 | ---- | |
BFDF0000 | Private | 40960 | -R- --- | ||
BFDFA000 | Private | 4096 | -RW- --- | ||
BFDFB000 | Private | 20480 | -R- --- | ||
BFE00000 | Free | 131072 | |||
BFE20000 | Private | 16384 | 3 | --- | |
BFE20000 | Private | 8192 | -R- --- | ||
BFE22000 | Private | 4096 | -RW- --- | ||
BFE23000 | Private | 4096 | -R- --- | ||
BFE24000 | Free | 245760 | |||
BFE60000 | Private | 24576 | 3 | ---- | |
BFE60000 | Private | 8192 | -R-- --- | ||
BFE62000 | Private | 4096 | -RW- --- | ||
BFE63000 | Private | 12288 | -R-- --- | ||
BFE66000 | Free | 40960 | |||
BFE70000 | Private | 24I376 | 3 | ---- | |
BFE70000 | Private | 8192 | -R-- --- | ||
BFE72000 | Private | 4096 | -RW- --- | ||
BFE73000 | Private | 12288 | -R-- --- | ||
BFE76000 | Free | 40960 | |||
BFE80000 | Private | 65536 | 3 | ---- | С:\WINDOWS\SYSTEM\ADVAPI32.DLL |
BFE80000 | Private | 49152 | -R-- --- | ||
BFE8C000 | Private | 4096 | -RW- —— | ||
BFE8D000 | Private | 12288 | -R-- --- | ||
BFE90000 | Private | 573440 | 3 | ||
BFE90000 | Private | 425984 | -R-- --- | ||
BFEF8000 | Private | 4096 | -RW- --- | ||
BFEF9000 | Private | 143360 | -R-- --- | ||
BFF1C000 | Free | 16384 | |||
BFF20000 | Private | 155648 | 5 | ---- | С:\WINDOWS\SYSTEM\GDI32.DLL |
BFF20000 | Private | 126976 | -R-- --- | ||
BFF3F000 | Private | 8192 | -RW- --- | ||
BFF41000 | Private | 4096 | -R-- --- | ||
BFF42000 | Private | 1096 | -RW- --- | ||
BFF43000 | Private | 12288 | -R-- --- | ||
BFF46000 | Free | 40960 | |||
BFF50000 | Private | 69632 | 3 | С:\WINDOWS\SYSTEM\USER32.DLL | |
BFF50000 | Private | 53248 | -R-- --- | ||
BFF5D000 | Private | 4096 | -RW- — | ||
BFF5E000 | Private | 12288 | -R-- --- | ||
BFF61000 | Free | 61440 | |||
BFF70000 | Private | 585728 | 5 | ---- | С:\WINDOWS\SYSTEM\KERNEL32.DLL |
BFF70000 | Private | 352256 | -R-- --- | ||
BFFC6000 | Reserve | 12288 | ---- -- | ||
BFFC9000 | Private | 16384 | -RW- --- | ||
BFFCD000 | Private | 90112 | -R-- --- | ||
BFFE3000 | Reserve | 114688 | ---- --- | ||
BFFFF000 | Free | 4006 |
Таблица 13-4. Образец карты адресного пространства процесса (с указанием блоков внутри регионов) в Windows 98
Главное отличие двух карт адресного пространства в том, что под управлением Windows 98 информации получзешь значительно меньше Например, о регионах и блоках можно узнать лишь, свободные они, резервные или закрытые Распознать тип физической памяти Mapped или Image нельзя, Windows 98 не позволяет получить дополнительную информацию, по которой можно было бы судить, что с регионом связан проецируемый в память файл или образ исполняемого файла.
Наверное, Вы заметили, что размер большинства регионов кратен 64 Кб (это значение определяется гранулярностью выделения памяти) Если размеры блоков, составляющих регион, не дают в сумме величины, кратной 64 Кб, то в конце региона часто появляется резервный блок адресного пространства. Его размер выбирается системой так, чтобы довести общий объем региона до величины, кратной 64 Кб Например, регион, который начинается с ядреса 0x00530000, включает в себя два блока: четырехкилобайтовый блок переданной памяти и резервный блок, занимающий 60 Кб адресного пространства.
Заметьте также, что на последней карте не встречаются атрибуты защиты, разрешающие исполнение или копирование при записи, поcкoлькy Windowы 98 не поддерживает их. Кроме того, она не поддерживает и флаги атрибутов защиты (PAGE_GUARD, PAGE_WRITECOMBINE и PAGE_NOCACHE) Из-за этого программе VMMap приходится использовать более сложный метод, чтобы опредслить, не выделен ли данный регион под стек потока.
И последнее. В Windows 98 (в отличие от Windows 2000) можно исследовать регион адресного пространства 0x80000000-0xBFFFFFFF. Это раздел, в котором находится адресное пространство, общее для всех 32-разрядных приложений По карте видно, что в него загружены четыре системные DLL., и поэтому они доступны любому процессу
Передача региону физической памяти
Чтобы зарезервированный регион адресного пространства можно было использовать, Вы должны выделить физическую память и спроецировать cc на этот регион. Такая операция называется передачей физической памяти (committing physical storage) Чтобы передать физическую память зарезервированному региону, Вы обращаетесь все к той жс функции VirtualAlloc
Передавая физическую память регионам, нет нужды отводить ее целому региот Можно, скажем, зарезервировать регион размером 64 Кб и нередать физическую па мять только его второй и четвертой страницам Парие. 13-1 представлен пример того, как может выглядеть адресное пространство процесса Как видите, структура адресного пространства зависит oт архитектуры процессора. Слева показано, чтo происходит с адресным пространством на процессоре x86 (страницы по 4 Кб), а справа — 1 на процессоре Alpha (страницы по 8 Кб)
Когда физическая память, переданная зарезервированному региону, больше не нужна, ее освобождают. Эта операция — возврат физической памяти (decommitting physical storage) — выполняется вызовом функции VirtualFree
Подводя итоги
А теперь попробуем осмыслить понятия адресных пространств, разделов, регионов, блоков и страниц как единое целое. Лучше всего начать с изучения карты виртуальной памяти, на которой изображены все регионы адресного пространства В пределах одного процесса. В качестве примера мы воспользуемся программой VMMap из главы 14. Чтобы в полной мере разобраться в адресном пространстве процесса, рассмотрим его в том виде, в каком оно формируется при запуске VMMap под управлением Windows 2000 на 32-разрядной процессорной платформе x86 Образец карты адресного пространства VMMap показан в таблице 13-2. На отличиях адресных пространств в Windows 2000 и Windows 98 я остановлюсь чуть позже.
Карта в таблице 13-2 показывает регионы, расположенные в адресном пространстве процесса. Каждому региону соответствует своя строка в таблице, а каждая строка состоит из шести полей.
В первом (крайнем слева) поле проставляется базовый адрес региона. Наверное, Вы заметили, что просмотр адресного пространства мы начали с региона по адресу 0x00000000 и закончили последним регионом используемого адресного простран-
ства, который начинается по адресу 0x7FFE0000. Все регионы непрерывны. Почти все базовые адреса занятых регионов начинаются со значений, кратных 64 Кб. Это связано с гранулярностью выделения памяти в адресном пространстве. А если Вы увидите какой-нибудь регион, начало которого не выровнено по значению, кратному 64 Кб, значит, он выделен кодом операционной системы для управления Вашим процессом.
Базовый адрес | Тип | Размер |
Блоки | Атрибут( ы) защиты | Описание | ||||||
00000000 | Free | 65536 | |||||||||
00010000 | Private | 4096 | 1 | -RW- | |||||||
00011000 | Free | G1440 | |||||||||
00020000 | Private | 4096 | 1 | -RW- | |||||||
000? 1000 | Free | 61440 | |||||||||
00030000 | Private | 1048576 | 3 | -HW- | Стек потока | ||||||
00130000 | Private | 1048576 | 2 | -RW- | |||||||
00230000 | Mapped | 65536 | 2 | -RW- | |||||||
00240000 | Mapped | 90112 | 1 | -R- | \Device\HarddiskVolume1\WINN7\system32\unicode.nls | ||||||
00256000 | Free | 40960 | |||||||||
00260000 | Mapped | 208896 | 1 | -R- | \Device\HarddiskVolume1\WINNT\system32\locale.nIs | ||||||
00293000 | Free | 53248 | |||||||||
002A0000 | Happed | 266240 | 1 | -R- | \Pevicc\HarddiskVolume1\WINNT\system32\sortkey.nls | ||||||
002E1000 | Free | 61440 | |||||||||
002F0000 | Mapped | 16384 | 1 | -R- | \Device\HarddiskVolume1\WINNT\system32\sorttbls.nls | ||||||
002F4000 | Free | 49152 | |||||||||
00300000 | Mapped | 819200 | 4 | ER- | |||||||
0003С8000 | Free | 229376 | |||||||||
00400000 | Image | 106496 | 5 | ERWC | С \CD\x86\Debug\14_VMMap.ехе | ||||||
0041A000 | Free | 24576 | |||||||||
00420000 | Mapped | 274432 | 1 | -R- | |||||||
00463000 | Free | 53248 | |||||||||
00470000 | Mapped | 3145728 | 2 | ER | |||||||
00770000 | Private | 4096 | 1 | -RW- | |||||||
00771000 | Free | 61440 | |||||||||
00780000 | Private | 4096 | 1 | -RW- | |||||||
00781000 | Free | 61440 | |||||||||
00790000 | Private | 65536 | 2 | -RW- | |||||||
007A0000 | Mapped | 8192 | 1 | -R- | \Device\HarddiskVolume1\WINNT\system32\ctype.nls | ||||||
007А2000 | Free | 1763893248 | |||||||||
699D0000 | Image | 45056 | 4 | ERWC | C:\WINNT\Systpm32\PSAPI dll | ||||||
6990В000 | Free | 238505984 | |||||||||
77D50000 | Image | 450560 | 4 | ERWC | С:\WINNT\system32\RPCRT4 DLL | ||||||
770ВЕ000 | Free | 8192 | |||||||||
770С0000 | Image | 344064 | 5 | ERWC | С:\WINNT\system32\ADVAPI32 dll | ||||||
77Е14000 | Free | 49152 | |||||||||
77E20000 | Image | 401408 | 4 | ERWC | C:\WINNT\system32\USER32 dll | ||||||
77Е82000 | Free | 57344 | |||||||||
77Е90000 | Image | 720896 | 5 | ERWC | С \WINNT\system32\KERNEL32.dll | ||||||
77F40000 | Image | 241664 | 4 | ERWC | С \WINKT\system32\GUI32 DLL | ||||||
77F7В000 | Free | 20480 | |||||||||
77FB0000 | image | 483328 | 5 | ERWC | С \WINNT\System32\ntdll.dll | ||||||
77FF000 | Free | 40960 | |||||||||
78000000 | Image | 290816 | 6 | bMWC | С \WINNT\system32\MSVCRI.dll | ||||||
78047000 | Free | 124424192 | |||||||||
7F6F0000 | Mapped | 1048576 | 2 | ER-- | |||||||
7F7F0000 | Free | 8126464 | |||||||||
7FFB0000 | Mapped | 147456 | 1 | -R-- | |||||||
7FFD4000 | Free | 40960 | |||||||||
7FFDE000 | Private | 4096 | 1 | ERW- | |||||||
7FFDF000 | Private | 4096 | 1 | ERW- | |||||||
7FFE0000 | Private | 65536 | 2 | -R-- |
Таблица 13-2. Образец карты адресного пространства процесса в Windows 2000 на 32-разрядном процессоре типа x86
Во втором поле показывается тип региона Free (свободный), Private (закрытый), Image (образ) или Mapped (проецируемый) Эти типы описаны в следующей таблице,
Тип | Описание |
Free | Этот диапазон виртуальных адресов не сопоставлен ни с каким типом физической памяти. Его адресное пространство не зарезервировано, приложение может зарезервировать регион по указанному базовому адресу или в любом месте в границах свободного региона |
Private | Этот диапазон виртуальных адресов сопоставлен со страничным файлом. |
Image | Этот диапазон виртуальных адресов изначально был сопоставлен с образом ЕХЕ- или DLL-файла, проецируемого в память, но теперь, возможно, уже нет Например, при записи в глобальную переменную из образа модуля механизм поддержки «копирования при записи» выделяет соответствующую страницу памяти из страничного файла, а не исходною образа файла |
Mapped | Этот диапазон виртуальных адресов изначально был сопоставлен с файлом данных, проецируемым в память, но теперь, возможно, уже нет. Например, файл данных мог быть спроецирован с использованием механизма поддержки «копирования при записи" Любые операции записи в этот файл приведут к тому, что соответствующие страницы памяти будут выделены из страничного файла, а не из исходного файла данных. |
В третьем поле сообщается размер региона в байтах. Например, система спроецировала образ User32.dll по адресу 0x77E20000. Когда она резервировала адресное
пространство для этого образа, ей понадобилось 401 408 байтов. Не забудьте, что в третьем поле всегда содержатся значения, кратные размеру страницы, характерному для данного процессора (4096 байтов для x86).
В четвертом поле показано количество блоков в зарезервированном регионе. Блок — это неразрывная группа страниц с одинаковыми атрибутами защиты, связанная с одним и тем же типом физической памяти (подробнее об этом мы поговорим в следующем разделе). Для свободных регионов это значение всегда равно 0, так как им не передается физическая память. (Поэтому в четвертой графе никаких данных для свободных регионов не приводится.) Но для занятых регионов это значение может колебаться в пределах от 1 до максимума (его вычисляют делением размера региона на размер страницы). Скажем, у региона, начинающегося с адреса Ox77E20000, размер — 401 408 байтов. Поскольку процесс выполняется на процессоре x86 (страницы памяти по 4096 байтов), максимальное количество блоков в этом регионе равно 98 (401 408/4096); ну а, судя по карте, в нем содержится 4 блока.
В пятом поле — атрибуты защиты региона. Здесь используются следующие сокращения: E = execute (исполнение), R = read (чтение), W= write (запись), С = copy-onwrite (копирование при записи). Если ни один из атрибутов в этой графе не указан, регион доступен без ограничений. Атрибуты защиты не присваиваются и свободным регионам. Кроме того, здесь Вы никогда не увидите флагов атрибутов защиты PAGE_ GUARD или PAGE_NOCACHE — они имеют смысл только для физической памяти, а не для зарезервированного адресного пространства. Атрибуты защиты присваиваются регионам только эффективности ради и всегда замещаются атрибутами защиты, присвоенными физической памяти.
В шестом (и последнем) поле кратко описывается содержимое текущего региона. Для свободных регионов оно всегда пустое, а для закрытых — обычно пустое, так как у VMMap нет возможности выяснить, зачем приложение зарезервировало данный закрытый регион. Однако VMMap все жe распознает назначение тех закрытых регионов, в которых содержатся стеки потоков. Стеки потоков выдают себя тем, что содержат блок физической памяти с флагом атрибутов защиты PAGE_GUARD. Если же стек полностью заполнен, такого блока у него нет, и тогда VMMap не в состоянии распознать стск потока.
Для регионов типа Image программе VMMap удается определить полное имя файла, проецируемого на этот регион. Она получает эту информацию с помощью ToolHelp-функций, о которых я упоминал в конце главы 4. В Windows 2000 программа VMMap может идентифицировать регионы, сопоставленные с файлами данных; для этого она вызывает функцию GetMappedFileName (ее нет в Windows 98).
Раздел для кода и данных пользовательского режима (Windows 2000 и Windows 98)
В этом разделе располагается закрытая (неразделяемая) часть адресного пространства процесса. Ни один процесс не может получить доступ к данным другого процесса, размещенным в этом разделе. Основной объем данных, принадлежащих процессу, хранится именно здесь (это касается всех приложений) Поэтому приложения менее зависимы от взаимных «капризов", и вся система функционирует устойчивее
WINDOWS2000
В Windows 2000 сюда загружаются все EXE- и DLL-модули В каждом процессе эти DLL можно загружать по разным адресам в пределах данного раздела, но так делается крайне редко. На этот же раздел отображаются все проецируемые в память файлы, доступные данному процессу
WINDOWS 98
В Windows 98 основные 32-разрядные системные DLL (Kernel32.dll, AdvAPI32.dll, User32.dll и GDI32.dll) загружаются в раздел для общих MMF (проецируемых в память файлов), a EXE- и остальные DLL-модули — в раздел для кода и данных пользовательского режима. Общие DLL располагаются по одному и тому же виртуальному адресу во всех процессах, но другие DLL могут загружать их (общие DLL) по разным адресам в границах раздела для кода и данных пользовательского режима (хотя это маловероятно). Проецируемые в память файлы в этот раздел никогда не помещаются
Впервые увидев адресное пространство своего 32-разрядного процесса, я был удивлен тем, что его полезный объем чуть ли не вдвое меньше. Неужели раздел для кода и данных режима ядра должен занимать столько места? Оказывается — да .Это пространство нужно системе для кода ядра, драйверов устройств, кэш-буферов ввода-вывода, областей памяти, не сбрасываемых в файл подкачки, таблиц, используемых для контроля страниц памяти в процессе и т д По сути, Microsoft едва-едва втиснула ядро в эти виртуальные два гигабайта. В 64-разрядной Windows 2000 ядро наконец получит то пространство, которое ему нужно на самом деле.
Увеличение раздела для кода и данных пользовательского режима до 3 Гб на процессорах x86 (только Windows 2000)
Многие разработчики уже давно сетовали на нехватку адресного пространства для пользовательского режима. Microsoft пошла навстречу и предусмотрела в версиях Windows 2000 Advanced Server и Windows 2000 Data Center для процессоров x86 возможность увеличения этого пространства до 3 Гб. Чтобы все процессы использовали раздел для кода и данных пользовательского режима размером 3 Гб, а раздел для кода и данных режима ядра — объемом 1 Гб, Вы должны добавить ключ /3GB к нужной записи в системном файле Boot.ini. Как выглядит адресное пространство процесса в этом случае, показано в графе «32-разрядная Windows 2000 (на x86 с ключом /3GB)" таблицы 13-1.
Раньше, когда такого ключа не было, программа не видела адресов памяти по указателю с установленным старшим битом. Некоторые изобретательные разработчики самостоятельно использовали этот бит как фляг, который имел смысл только в их приложениях. При обращении программы no адресам за пределами 2 Гб предварительно выполнялся специальный код, который сбрасывал старший бит указателя Но, как Вы понимаете, когда приложение на свой страх и риск создает себе трехгигабайтовую среду пользовательского режима, оно может с треском рухнуть.
Microsoft пришлось придумать решение, которое позволило бы подобным приложениям работать в трехгигабайтовой среде Теперь система в момент запуска приложения проверяет, не скомпоновано ли оно с ключом /LARGEADDRESSAWARE. Если да, приложение как бы заявляет, что обязуется корректно обращаться с этими адресами памяти и действительно готово к использованию трехгигабайтового адресного пространства пользовательского режима. А если пет, операционная система резервирует область памяти размером 1 Гб в диапазоне адресов от 0x80000000 до 0xBFFFFFFF. Это предотвращает выделение памяти по адресам с установленным старшим битом.
Заметьте, что ядро и так с трудом умещается в двухгигабайтовом разделе. Но при использовании ключа /3GB ядру остается Bceго 1 Гб. Тем самым уменьшается количество потоков, стеков и других ресурсов, которые система могла бы предоставить приложению. Кроме того, система в этом случае способна задействовать максимум 16 Гб оперативной памяти против 64 Гб в нормальных условиях — из-за нехватки виртуального адресного пространства для кода и данных режима ядра, необходимого для управления дополнительной оперативной памятью
NOTE:
Флаг LARGEADDRESSAWARE в исполняемом файле проверяется в тот момент, когда операционная система создает адресное пространство процесса. Для DLL этот флаг игнорируется При написании DLL Вы должны сами позаботиться об их корректном поведении в трехгигабайтовом разделе пользовательского режима18}
Уменьшение раздела для кода и данных пользовательского режима до 2 Гб в 64-разрядной Windows 2000
Microsoft понимает, что многие разработчики захотят как можно быстрее перенести свои 32-разрндные приложения в 64-разрядную среду Но в исходном кодс любых программ полно таких мест, где предполагается, что указатели являются 32-разрядными значениями Простая перекомпиляция исходного кода приведет к ошибочному усечению указателей и некорректному обращению к памяти
Однако, если бы система как-то гарантировала, что память никогда не будет выделяться по адресам выше 0x00000000 7FFFFFFF, приложение работало бы нормально. И усечение 64-разрядного адреса до 32-разрядного, когда старшие 33 бита равны 0, не создало бы никаких проблем. Так вот, система дает такую гарантию при запуске приложения в «адресной песочнице" (address space sandbox), которая ограничивает полезное адресное пространство процесса до нижних 2 Гб
По умолчанию, когда Вы запускаете 64-разрядное приложение, система резервирует все адресное пространство пользовательского режима, начиная с 0x0000000 80000000, что обеспечивает выделение памяти исключительно в нижних 2 Гб 64-разрядного адресного пространства. Это и есть «адресная песочница». Большинству приложений этого пространства более чем достаточно. А чтобы 64-разрядное приложение могло адресоваться ко всему разделу пользовательского режима (объемом 4 Тб), его следует скомпоновать с ключом /LARGEADDRESSAWARE.
NOTE:
Флаг LARGEADDRESSAWARE в исполняемом файле проверяется в тот момент, когда операционная система создает адресное пространство 64-разрядного процесса Для DLL этот флаг игнорируется. При написании DLL Вы должны сами позаботиться об их корректном поведении в четырехтерабайтовом разделе пользовательского режима.
Раздел для кода и данных режима ядра (Windows 2000 и Windows 98)
В этот раздел помещается код операционной системы, в том числе драйверы устройств и код низкоуровневого управления потоками, памятью, файловой системой, сетевой поддержкой. Все, что находится здесь, доступно любому процессу. В Windows 2000 эти компоненты полностью защищены Поток, который попытается обратиться по одному из адресов памяти в этом разделе, вызовет нарушение доступа, а это приведет к тому, что система в конечном счете просто закроет его приложение. (Подробнее на эту тему см. главы 23, 24 и 25.)
WINDOWS2000
В 64-разрядной Wmclows 2000 раздел пользовательского режима (4 Тб) выглядит непропорционально малым по сравнению с 16 777 212 Тб, отведенными под раздел для кода и данных режима ядра Дело не в том, что ядру так уж необходимо все это виртуальное пространство, a просто 64-разрядное адресное пространство настолько огромно, что его большая часть не задействована Система разрешает нашим программам использовать 4 Тб, а ядру — столько, сколько ему нужно. К счастью, какие-либо внутренние структуры данных для управления незадействованными частями раздела для кода и данных режима ядра не требуются.
WINDOWS98
В Windows 98 данные, размещенные в этом разделе, увы, не защищены — любое приложение может что-то считать или записать в нем и нарушить нормальную работу операционной системы.
Раздел для общих MMF (только Windows 98)
В этом разделе размером 1 Гб система хранитданные, разделяемые всеми 32-разрядными процессами. Сюда, например, загружаются все системные DLL (Kernel32.dll, | AdvAPI32 dll, User32.dll и GDI32 dll), и поэтому они доступны любому 32-разрядному I процессу Кроме того, эти DI.I. загружаются в каждом процессе по одному и тому же I адресу памяти. На этот раздел система также отображает все проецируемые в память я файлы. Об этих файлах мы поговорим в главе 17
Раздел для совместимости с программами DOS и 16-разрядной Windows (только Windows 98)
Этот регион размером 4 Мб в адресном пространстве процесса необходим Windows 98 для поддержки совместимости с программами MS-DOS и 16-разрядной Windows. He пытайтесь обращаться к нему из 32-разрядных Windows-приложений. В идеале процессор должен был бы генерировать нарушение доступа при обращении потока к этому участку адресного пространства, но по техническим причинам Microsoft не смогла заблокировать эти 4 Мб адресного пространства.
В Windows 2000 программы для MS-DOS и 16-разрядной Windows выполняются в собственных адресных пространствах; 32-разрядные приложения повлиять на них пе могут.
Раздел для выявления нулевых указателей (Windows 2000 и Windows 98)
Этот раздел адресного пространства резервируется для того, чтобы программисты могли выявлять нулевые указатели. Любая попытка чтения или записи в память по этим адресам вызывает нарушение доступа.
Довольно часто в программах, написанных на С/С++, отсутствует скрупулезная обработки ошибок. Например, в следующем фрагменте кода такой обработки вообще нет:
int* pnSomeInteger = (int*) malloc(sizeof(int));
*pnSomeInteger = 5;
При нехватке памяти malloc вернет NULL. Ho код не учитывает эту возможность и при ошибке обратится к памяти по адресу 0x00000000 А поскольку этот раздел адресного пространства заблокирован, возникнет нарушение доступа и данный процесс завершится Эта особенность помогает программистам находить «жучков* в своих приложениях.
Регионы в адресном пространстве
Адресное пространство, выделяемое процессу в момент создания, практически все свободно (незарезервировано). Поэтому, чтобы воспользоваться какой-нибудь его частью, нужно выделить в нем определенные регионы через функцию WirtualAlloc (о ней — в главе 15) Операция выделения региона называется резервированием (reserving). При резервировании система обязательно выравнивает начало региона с учетом так называемой гранулярности выделения памяти (allocation granularity). Последняя величина в принципе зависит от типа процессора, но для процессоров, рассматриваемых в книге (x86, 32- и 64-разрядных Alpha и IA-64), — она одинакова и составляет 64 Кб.
Резервируя регион в адресном пространстве, система обеспечивает еще и кратность размера региона размеру страницы. Так называется единица объема памяти, используемая системой при управлении памятью. Как и гранулярность выделения ресурсов, размер страницы зависит от типа процессора В частности, для процессоров x86 он равен 4 Кб, а для Alpha (под управлением как 32-разрядной, так и 64-разядной Windows 2000) — 8 Кб. На момент написания книги предполагалось, что IA-64
тоже будет работать со страницами размером 8 Кб. Однако в зависимости от результатов тестирования Microsoft может выбрать для него страницы большего размера (от ( 16 Кб).
NOTE:
Иногда система сама резервирует некоторые регионы адресного пространства в интересах Вашего процесса, например, для хранения блока переменных окружения процесса (process environment block, PEB). Этот блок — небольшая структура данных, создаваемая, контролируемая и разрушаемая исключительно операционной системой. Выделение региона под РЕВ-блок осуществляется в момент создания процесса.
Кроме того, для управления потоками, существующими на данный момент в процессе, система создает блоки переменных окружения потоков (thread environment blocks, TEBs). Регионы под эти блоки резервируются и освобождаются по мере создания и разрушения потоков в процессе.
Но, требуя от Вас резервировать регионы с учетом гранулярности выделения памяти (а эта гранулярность на сегодняшний день составляет 64 Кб), сама система этих правил нс придерживается Поэтому вполне вероятно, что грапицы региона, зарезервированного под РЕВ- и ТЕВ-блоки, не будут кратны 64 Кб. Тем не менее размер такого региона обязательно кратен размеру страниц, характерному для данного типа процессора.
Если Вы попытаетесь зарезервировать регион размером 10 Кб, система автомати чески округлит заданное Вами значение до большей кратной величины. А зто значит что на x86 будет выделен регион размером 12 Кб, а на AIpha — 16 Кб.
И последнее в этой связи Когда зарезервированный регион адресного простран ства становится не нужен, ею следусч вернуть в общие ресурсы системы. Эта опера ция — освобождение (releasing) региона — осуществляется вызовом функции VirtualFree
Специальные флаги атрибутов защиты
Кроме рассмотренных атрибутов защиты, существует три флага атрибутов защиты PAGE_NOCACHE, PAGE_WRITECOMBINE и PAGE_GUARU. Они комбинируются с любыми атрибутами защиты (кроме PAGE_NOACCESiS) побитовой операцией OR.
Флаг PAGE_NOCACHE отключает кэширование переданных страниц. Как правило, использовать этот флаг не рекомендуется; он предусмотрен главным образом для разработчиков драйверов устройств, которым нужно манипулировать буферами памяти.
Флаг PAGE_WRITECOMBINE тоже предназначен для разработчиков драйверов ycтройств. Он позволяет объединять несколько операций записи на устройство в одип пакет, что увеличивает скорость передачи данных
Флаг PAGE_GUARD позволнет приложениям получать уведомление (через механизм исключений) в тот момент, когда на страницу записывается какой-нибудь баит Window.s 2000 использует этот флаг при создании стека потока. Подробнее на эту тему см. раздел «Стек потока» в главе 16.
WINDOWS 98
Windows 98 игнорирует флаги атрибутов защиты PAGE_NOCACHE, PAGE_WRITECOMBINE и PAGE_GUARD
Виртуальное адресное пространство процесса
Каждому процессу выделяется собственное виртуальное адресное пространство. Для 32-разрядных процессов его размер составляет 4 Гб. Соответственно 32-битный указатель может быть любым числом от 0x00000000 до 0xFFFFFFFF Всего, таким образом, указатель может принимать 4 294 967 296 значений, что как раз и перекрывает четырехгигабайтовый диапазон. Для 64-разрядных процессов размер адресного пространства равен 16 экзабайтам, поскольку 64-битный указатель может быть любым числом от 0x00000000 00000000 до 0xFFFFFFFF FFFFFFFF и принимать 18 446 744 073 709 551 616 значений, охватывая диапазон в 16 экзабайтов, Весьма впечатляюще!
Поскольку каждому процессу отводится закрытое адресное пространство, то, ког да в процессе выполняется какой-нибудь поток, он получает доступ только к той памяти, которая принадлежит его процессу Память, отведенная другим процессам, скрыта от этого потока и недоступна ему.
NOTE:
В Windows 2000 память, принадлежащая собственно операционной системе, тоже скрыта от любого выполняемого потока. Иными словами, ни один поток не может случайно повредить ее данные. А в Windows 98 последнее, увы, не реализовано, и есть вероятность, что выполняемый поток, случайно получив доступ к данным операционной системы, тем самым нарушит ее нормальную работу. И все-таки в Windows 98, как и в Windows 2000, ни один поток не может получить доступ к памяти чужого процесса.
Итак, как я уже говорил, адресное пространство процесса закрыто. Отсюда вытекает, что процесс А в своем адресном пространстве может хранить какую-то структуру данных по адресу 0x12345678, и одновременно у процесса В по тому же адресу — но уже в его адресном пространстве — может находиться совершенно иная структура данных. Обращаясь к памяти по адресу 0x12345678, потоки, выполняемые в процессе А, получают доступ к структуре данных процесса А, Но, когда по тому же адресу
обращаются потоки, выполняемые в процессе В, они получают доступ к структуре данных процесса В. Иначе говоря, потоки процесса А не могут обратиться к структуре данных в адресном пространстве процесса В, и наоборот
А теперь, пока Вы не перевозбудились от колоссального объема адресного пространства, предоставляемого Вашей программе, вспомните, что оно — виртуальное, а не физическое. Другими словами, адресное пространство — всего лишь диапазон адресов памяти И, прежде чем Вы сможете обратиться к каким-либо данным, не вызвав нарушения доступа, придется спроецировать нужную часть адресного пространства на конкретный участок физической памяти. (Об этом мы поговорим чуть позже.)
Выравнивание данных
Здесь мы отвлечемся от виртуального адресного пространства процесса и обсудим такую важную тему, как выравнивание данных. Кстати, выравнивание данных — не столько часть архитектуры памяти в операционной системе, сколько часть архитектуры процессора
Процессоры работают эффективнее, когда имеютдело с правильно выровненными данными. Например, значение типа WORD всегда должно начинаться с четного адреса, кратного 2, значение типа DWORD - с четного адреса, кратного 4, и т. д. При попытке считать невыровненные данные процессор сделает одно из двух: либо возбудит исключение, либо считает их в несколько приемов.
Вот фрагмент кода, обращающийся к невыровненным данным:
VOID SomeFunc(PVOID pvDataBuffer)
{
// первый байт в буфере содержит значение типа BYTE
char с = * (PBYTE) pvDataBuffer;
// увеличиваем указатель для перехода за этот байт
pvDataBuffer = (PVOID)((PBYTE) pvDataBuffer + 1);
// байты 2-5 в буфере содержат значение типа DWORD
DWORD dw = * (DWORD *) pvDataBuffer;
// на процессорах Alpha предыдущая строка приведет к исключению
// из-за некорректного выравнивания данных
...
}
Очевидно, что быстродействие программы снизится, если процессору придется обращаться к памяти в несколько приемов В лучшем случае система потратит на доступ к невыровненному значению в 2 раза больше времени, чем на доступ к выров-
ненному! Так что, если Вы хотите оптимизировать работу своей программы, позаботьтесь о правильном выравнивании данных.
Рассмотрим, как справляется с выравниванием данных процессор типа x86. Такой процессор в регистре EFLAGS содержит специальный битовый флаг, называемый флагом AC (alignment check). По умолчанию, при первой подаче питания на процессор он сброшен Когда этот флаг равен 0, процессор автоматически выполняет инструкции, необходимые для успешного доступа к невыровненным данным. Однако, если этот флаг установлен (равен 1), то при каждой попытке доступа к невыровненным данным процессор инициирует прерывание INT 17h. Версия Windows 2000 для процессоров типа x86 и Windows 98 никогда не изменяют этот битовый флаг процессора. Поэтому в программе, работающей на процессоре типа x86, исключения, связанные с попыткой доступа к невыровненным данным, никогда не возникают.
Теперь обратим внимание на процессор Alpha. Он не умеет оперировать с невыровненными данными. Когда происходит попытка доступа к таким данным, этот процессор уведомляет операционную систему. Далее Windows 2000 решает, что делать — генерировать соответствующее исключение или самой устранить возникшую проблему, выдав процессору дополнительные инструкции. По умолчанию Windows 2000, установленная на компьютере с процессором Alpha, сама исправляет все ошибки обращения к невыровненным данным. Однако Вы можете изменить ее поведение. При загрузке Windows 2000 проверяет раздел реестра:
HKEY_LOCAL_MACHINE\CurrentControlSet\Control\Session Manager
В этом разделе может присутствовать параметр EnableAlignmentFaultExceptions Если его нет (что чаще всего и бывает), Windows 2000 сама исправляет ошибки, связанные с доступом к невыровненным данным. Но, если он есть, система учитывает сго значение При его нулевом значении система действует так же, как и в отсутствие этого параметра. Если же он равен 1, система нс исправляет такие ошибки, а генерирует исключения. Никогда не модифицируйте этот параметр в реестре бсз особой необходимости, потому что иначе некоторые приложения будут вызывать исключения из-за доступа к невыровненным данным и аварийно завершаться.
Чтобы упростить изменение этого параметра реестра, с Microsoft Visual C++ для платформы Alpha поставляется утилита AXPAlign.exe. Она используется так, как показано ниже.
Alpha AXP alignment fault exception control Usage axpalign [option]
Options:
/enable to enable alignment fault exceptions
/disable to disable alignment fault exceptions.
/show to display the current alignment exception setting
Enable alignment fault exceptions.
В этом режиме любое обращение к невыровненным данным приведет к исключению Приложение может быть закрыто. В своем коде Вы можете найти источник ошибок, связанных с выравниванием данных, с помощью отладчика. Действие этого параметра распространяется на все выполняемые процессы, и использовать его следует с осторожностью, так как в старых приложениях могут возникать необрабатываемые ими исключения.
Заметьте, что SetErrorMode(SEM_HOALIGNMENTFAULTEXCEPT) позволяет подавить генерацию таких исключений даже в этом режиме.
Disable alignment fault exceptions
Этот режим действует no умолчанию в Windows NT for Alpha AXP версий 3.1 и 3.5 Операционная система сама исправляет любые ошибки связанные с доступом к невыровненным данным (если таковые ошибки возникают) и приложения или отладчики их не замечают Если программа часто обращается к невыровненным данным производительность ситемы может заметно снизиться Для наблюдения за частотой появлония таких ошибок можно использокать Perfmon или wperf
Эта утилита просто модифицирует нужный параметр реестра или показывает его текущее значение Изменив значение этого параметра, перезагрузие компьютер, чтобы изменения вступили в силу
Но, даже не пользуясь утилитой AXPAhgn, Вы все равно можете заставить систему молча исправлять ошибки обращения к невыровненным данным во всех потоках Вашего процесса Для этого один из потоков должен вызвать функцию SetErrorMode
UINT SetErrorMode(UINT fuErrorMode);
В данном случае Вам нужен флаг SEM_NOAUGNMENTFAULTEXCEPT Когда он установлен, система автоматически исправляет ошибки обращения к невыровненным данным, а когда он сброшен, система вместо этого генерирует соответствующие исключения Заметье, чю изменение этого флага влияет на потоки только того про цесса, из которого была вызвана функция SetErrorMode Иначе говоря, его модификация не отражается на потоках других процессов Также учтите, что любые флаги режимов обработки ошибок наследуются всеми дочерними процессами Поэтому перед вызовом функции CreateProcess Вам может понадобиться временно сбросить этот флаг
SetErrorMode можно вызывать с флагом SEM_NOALIGNMENTFAULTEXCEPT независимо от того, на какой платформе выполняется Ваше приложение Но результаты ее вызова не всегда одинаковы На платформе x86 сбросить этот флаг просто нельзя, а на платформе Alpha его разрешается сбросить, только если параметр EnableAlignmentFaultExceptions в реестре равен 1
Для наблюдения за частотой возникновения ошибок, связанных с доступом к невыровненным данным, в Windows 2000 можно использовать Performance Monitor, подключаемый к MMC На следующей иллюстрации показано диалишвое окно Add Counters, которое позволяет добавить нужный показатель в Performance Monitor
Этот показатель сообщает, сколько раз в секунду процессор уведомляет операци онную систему о доступе к невыровненным данным. На компьютере с процессором типа x86 он всегда равен 0 Это связано с тем, что такой процессор сам справляется с проблемами обращения к невыровненным данным и не уведомляет об этом операционную систему А поскольку он обходится без помощи со стороны операционной системы, падение производительности при частом доступе к невыровненным данным не столь значительно, как на процессорах, требующих с той же целью участия операционной системы
Как видите, простого вызова SetErrorMode вполне достаточно для того, чтобы Ваше приложение работало корректно. Но это решение явно не самое эффективное Так, в AlphaArchitectureReferenceManual, опубликованном Digital Рress, утверждается, что системный код, автоматически устраняющий ошибки обращения к невыровненным данным, может снизить быстродействие в 100 раз! Издержки слишком велики К счастью, есть более эффективное решение этой проблемы.
Компилятор Microsoft С/С++ для процессоров Alpha поддерживает ключевое слово _unaligned Этот модификатор используется так же, как const или volatile, но применим лишь для переменных-указателей Когда Вы обращаетесь к данным через невыровненный указатель (unahgned pointer), компилятор генерирует код, исходя из того, что данные скорее всего не выровнены, и вставляет дополнительные машинные инструкции, необходимые для доступа к таким данным. Ниже показан тот же фрагмент кода, что и в начале раздела, но с использованием ключевого слова _unaligned
VOID SomeFunc(PVOID pvDataBuffer)
{
// первый байт в буфере содержит значение типа BYTE
char с = * (PBYTE} pvDataBuffer;
// увеличиваем указатель для перехода за этот байт
pvDataBuffer = (PVOID)((PBYTE) pvDataBuffer + 1);
// байты 2-5 в буфере содержат значение типа DWORD
DWORD dw = * (__unaligned DWORD *) pvDataBuffer;
// Предыдущая строка заставит компилятор сгенерировать дополнительные
// машинные инструкции, которые позволят считать значение типа DWORD
// в несколько приемов При этом исключение из-за попытки доступа
// к невыровненным данным не возникнет
}
При компиляции следующей строки на процессоре Alpha, генерируется 7 машинных инструкций
DWORD dw = * (__unaligned DWORD *) pvDataBuffer;
IIo если я уберу ключевое слово _unaligned, то получу всего 3 машинные инструкции Как видите, модификатор _unaligned на процессорах Alpha приводт к увеличению числа генерируемых машинных инструкций более чем в 2 раза. Но инструкции, добавляемые компилятором, все равно намного эффективнее, чем перехват процессором попыток доступа к невыровненным данным и исправление таких ошибок операционной системой.
И последнее. Ключевое слово _unaligned на процессорах типа x86 компилятором Vtsual С/С++ не поддерживается. На этих процессорах оно просто не нужно. Но это
означает, что версия компилятора для процессоров x86, встретив в исходном коде ключевое слово _unaligned, сообщит об ошибке Полому, если Вы хотите создать единую базу исходного кода приложения для обеих процессорных платформ, используйте вместо _unahgned макрос UNAUGNED Он определен в файле WmNT.h так
#if defined(_M_MRX000) defined(_M_ALPHA) defined(_M_IA64)
#define UNALIGNED _unaligned
#if defined(_WIN64)
#define UNALIGNED64 __unaligned
#else
#define UNALIGNED64
#endif
#else
#define UNALIGNED
#define UNALIGNED64
#endif
Закрытый раздел размером 64 Кб (только Windows 2000)
Этот раздел заблокирован, и любая попытка обращения к нему приводит к нарушению доступа Microsoft резервирует этот раздел специально, чтобы упростить внутреннюю реализацию операционной системы. Вспомните, когда Бы передаете Windows-функции адрес блока памяти и его размер, то она (функция), прежде чсм приступить к работе, проверяет, действителен ли данный блок. Допустим, Вы написали код:
BYTE bBuf[70000];
DWORD dwNumBytesWritTen;
WriteProcessMemory(GetCurrentProcess(), (PVOID) 0x7FFEEE90, bBuf, sizeof(bBuf), &dwNumBytesWntten);
В случае функций типа WriteProcessMemory область памяти, в которую предполагается запись, проверяется кодом, работающим в режиме ядра, — только он имеет право обращаться к памяти, выделяемой под код и данные режима ядра (в 32-разрядных системах — по адресам выше 0x80000000) Если по этому адресу есть память, вызов WriteProcessMemory, показанный выше, благополучно запишет данные в ту область памяти, которая, по идее, доступна только коду, работающему в режиме ядра. Чтобы предотвратить это и в то же время ускорить проверку таких областей памяти, Microsoft предпочла заблокировать данный раздел, и поэтому любая попытка чтения или записи в нем всегда вызывает нарушение доступа
Защита типа «копирование при записи»
Атрибуты защиты, перечисленные в предыдущей таблице, достаточно понятны, кроме двух последних: PAGE_WKITECOPY и PAGE_EXECUTE_WRITECOPY. Они предназначены специально для экономного расходования оперативной памяти и места в страничном файле. Windows поддерживает мехянизм, позволяющий двум и более процессам разделять один и тот же блок памяти. Например, если Вы запустите 10 экземпляров программы Notepad, все экземпляры будут совместно использовать одни и те же страницы с кодом и данными этой программы. И обычно никяких проблем не возникает — пока процессы ничего не записывают в общие блоки памяти. Только представьте, что творилось бы в системе, если потоки из разных процессов начали бы одновременно записывать в один и тот же блок памяти!
Чтобы предотвратить этот хаос, операционная система присваивает общему блоку памяти атрибут защиты "копирование при записи» (copy-on-write). Когда поток в одном процессе попытается что-нибудь записать в общий блок памяти, в дело тут же вступит система и проделает следующие операции
Найдет свободную страницу в оперативной памяти Заметьте, что при первом проецировании модуля на адресное пространство процесса эта страница будет скопирована на одну из страниц, выделенных в страничном файле Поскольку система выделяет нужное пространство в страничном файле еще при первом проецировании модуля, сбои на этом этапе маловероятны. Скопирует страницу с данными, которые поток пытается записать в общий блок памяти, на свободную страницу оперативной памяти, полученную па этапе 1 Последней присваивается атрибут защиты PAGE_WRITECOPY или PAGE_EXECUTE_WRITECOPY Атрибут защиты и содержимое исходной страницы нс меняются. Отобразит виртуальный адрес этой страницы в процессе на новую страницу в оперативной памяти.
Когда система выполнит эти операции, процесс получит свою копию нужной страницы памяти. Подробнее о совместном использовании памяти и о защите типа «копирование при записи" я расскажу в главе 17
Кроме того, при резервировании адресного пространства или передаче физической памяти через VirtualAlloc нельзя указывать атрибуты PAGE_WRITECOPY или PAGE_EXECUTE_WRITECOPY. Иначе вызов VirtucuAlloc даст ошибку, a GetLastError вернет код ERROR_INVALID_PARAMETER Дело в том, что эти два атрибута используются операционной системой, только когда она проецирует образы EXE- или DLL-файлов
WINDOWS 98
Windows 98 не поддерживает «копирование при записи" Обнаружив запрос на применение такой защиты, Windows 98 тут же делает копии данных, не дожидаясь попытки записи в память.