Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6264
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

450 Часть III. Управление памятью

Табл. 13-2. (окончание)

Базовый

Тип

Размер

Блоки

Атрибут(ы)

Описание

адрес

защиты

 

 

 

 

7FFB0000

Mapped

143360

1

-R--

 

7FFD3000

Free

4096

 

 

 

7FFD4000

Private

4096

1

-RW-

 

7FFD5000

Free

40960

 

 

 

7FFDF000

Private

4096

1

-RW-

 

7FFE0000

Private

65536

2

-R--

 

Карта в таблице 13-4 показывает регионы, расположенные в адресном пространстве процесса. Каждому региону соответствует своя строка в таблице, а каждая строка состоит из шести полей.

В первом (крайнем слева) поле проставляется базовый адрес региона. Наверное, вы заметили, что просмотр адресного пространства мы начали с региона по адресу 0x00000000 и закончили последним регионом используемого адресного пространства, который начинается по адресу 0x7FFE0000. Все регионы непрерывны. Почти все базовые адреса занятых регионов начинаются со значений, кратных 64 Кб. Это связано с гранулярностью выделения памяти в адресном пространстве. А если вы увидите какой-нибудь регион, начало которого не выровнено по значению, кратному 64 Кб, значит, он выделен кодом операционной системы для управления вашим процессом.

Во втором поле показывается тип региона: Free (свободный), Private (закрытый), Image (образ) или Mapped (проецируемый). Эти типы описаны в следующей таблице.

Табл. 13-5. Типы регионов памяти

Тип

Описание

Free

Этот диапазон виртуальных адресов не сопоставлен ни с каким типом

 

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

 

приложение может зарезервировать регион по указанному базовому ад-

 

ресу или в любом месте в границах свободного региона

Private

Этот диапазон виртуальных адресов сопоставлен со страничным файлом

Image

Этот диапазон виртуальных адресов изначально был сопоставлен с обра-

 

зом EXEили DLL-файла, проецируемого в память, но теперь, возмож-

 

но, уже нет. Например, при записи в глобальную переменную из образа

 

модуля механизм поддержки «копирования при записи» выделяет соот-

 

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

 

образа файла

Mapped

Этот диапазон виртуальных адресов изначально был сопоставлен с фай-

 

лом данных, проецируемым в память, но теперь, возможно, уже нет. На-

 

пример, файл данных мог быть спроецирован с использованием меха-

 

низма поддержки «копирования при записи». Любые операции записи в

 

этот файл приведут к тому, что соответствующие страницы памяти бу-

 

дут выделены из страничного файла, а не из исходного файла данных

Глава 13. Архитектура памяти в Windows.docx 451

Способ вычисления этого ноля моей программой VMMap может давать неправильные результаты. Поясню почему. Когда регион занят, VMMap пытается «прикинуть», к какому из трех оставшихся типов он может относиться, — в Windows нет функций, способных подсказать точное предназначение региона. Я определяю это сканированием всех блоков в границах исследуемого региона, но результатам которого программа делает обоснованное предположение. Но предположение есть предположение. Если вы хотите получше разобраться в том, как это делается, изучите исходный код VMMap, приведенный в главе 14.

Втретьем поле сообщается размер региона в байтах. Например, система спроецировала образ User32.dll по адресу 0x767F0000. Когда она резервировала адресное пространство для этого образа, ей понадобилось 647 168 байтов. Не забудьте, что в третьем поле всегда содержатся значения, кратные размеру страницы, характерному для данного процессора (4096 байтов для x86). Возможно, вы заметили, что размер дискового файла и число байтов, необходимое для его проецирования в память, различаются. Генерируемый компоновщиком РЕ-файл сжимается по максимуму в целях экономии места на диске. Однако при проецировании РЕ-файла в виртуальное адресное пространство процесса и адреса и размеры всех разделы выравниваются по размеру страницы. В этом и заключается причина «разбухания» РЕ-файла в виртуальной памяти по сравнению с дисковым файлом.

Вчетвертом поле показано количество блоков в зарезервированном регионе. Блок — это неразрывная группа страниц с одинаковыми атрибутами защиты, связанная с одним и тем же типом физической памяти (подробнее об этом мы поговорим в следующем разделе). Для свободных регионов это значение всегда равно 0, так как им не передается физическая память. (Поэтому в четвертой графе никаких данных для свободных регионов не приводится.) Но для занятых регионов это значение может колебаться в пределах от 1 до максимума (его вычисляют делением размера региона на размер страницы). Скажем, у региона, начинающегося с адреса 0x767F0000, размер — 647 168 байтов. Поскольку процесс выполняется на процессоре x86 (страницы памяти по 4096 байтов), максимальное количество блоков в этом регионе равно 158 (647 168/4096); ну а, судя по карте, в нем содержится 5 блоков.

Вчетвертом поле показано количество блоков в зарезервированном регионе. Блок — это неразрывная группа страниц с одинаковыми атрибутами защиты, связанная с одним и тем же типом физической памяти (подробнее об этом мы поговорим в следующем разделе). Для свободных регионов это значение всегда равно 0, так как им не передается физическая память. (Поэтому в четвертой графе никаких данных для свободных регионов не приводится.) Но для занятых регионов это значение может колебаться в пределах от 1 до максимума (его вычисляют делением размера региона на размер страницы). Скажем, у региона, начинающегося с адреса 0x77E20000, размер — 401408 байтов. Поскольку процесс выполняется на процессоре x86 (страницы памяти по 4096 байтов), максимальное количество блоков в этом регионе равно 98 (401 408/4096); ну а, судя по карте, в нем содержится 4 блока.

452Часть III. Управление памятью

Впятом поле — атрибуты защиты региона. Здесь используются следующие сокращения: E = execute (исполнение), R = read (чтение), W = write (запись), С = copy-on-write (копирование при записи). Если ни один из атрибутов в этой графе не указан, регион доступен без ограничений. Атрибуты защиты не присваиваются и свободным регионам. Кроме того, здесь вы никогда не увидите флагов атрибутов защиты PAGE_GUARD или PAGE_NOCACHE — они имеют смысл только для физической памяти, а не для зарезервированного адресного пространства. Атрибуты защиты присваиваются регионам только эффективности ради и всегда замещаются атрибутами защиты, присвоенными физической памяти.

Вшестом (и последнем) поле кратко описывается содержимое текущего региона. Для свободных регионов оно всегда пустое, а для закрытых — обычно пустое, так как у VMMap нет возможности выяснить, зачем приложение зарезервировало данный закрытый регион. Однако VMMap все же распознает назначение тех закрытых регионов, в которых содержатся стеки потоков. Стеки потоков выдают себя тем, что содержат блок физической памяти с флагом атрибутов защиты PAGE_GUARD. Если же стек полностью заполнен, такого блока у него нет, и тогда VMMap не в состоянии распознать стек потока.

Для регионов, содержащих образы, VMMap отображает полный путь к файлу, спроецированному в этот регион. VMMap получает эту информацию с помощью функций PSAPI (см. главу 4). VMMap отображает сведения о регионах, с которыми сопоставлены файлы данных, вызывая функцию GetMappedFileName, а сведения о регионах, с которыми сопоставлены файлы образов исполняемых файлов, помогают получить функции ToolHelp API (они также описаны в главе 4).

Блоки внутри регионов

Попробуем увеличить детализацию адресного пространства (по сравнению с тем, что показано в таблице 13-4). Например, таблица 13-6 показывает ту же карту адресного пространства, но в другом «масштабе»: по ней можно узнать, из каких блоков состоит каждый регион.

Табл. 13-6. Образец карты адресного пространства процесса (с указанием блоков внутри регионов) в Windows на 32-разрядном процессоре типа x86

Базовый

Тип

Размер

Блоки

Атрибут(ы)

Описание

адрес

защиты

 

 

 

 

00000000

Free

65536

 

 

 

00010000

Mapped

65536

1

-RW-

 

00010000

Mapped

65536

 

-RW- ---

 

00020000

Private

4096

1

-RW-

 

00020000

Private

4096

 

-RW- ---

 

 

 

 

 

Глава 13. Архитектура памяти в Windows.docx 453

Табл. 13-6. (продолжение)

 

 

 

 

Базовый

Тип

Размер

Блоки

Атрибут(ы)

Описание

 

адрес

защиты

 

 

 

 

 

 

00021000

Free

61440

 

 

 

 

00030000

Private

1048576

3

-RW-

Стек потока

 

00030000

Reserve

774144

 

-RW- ---

 

 

 

00330000

Private

4096

1

-RW-

 

 

00330000

Private

4096

 

-RW- ---

 

 

00331000

Free

61440

 

 

 

 

00340000

Mapped

20480

1

-RWC

\Device\

 

 

 

 

 

 

HarddiskVolume1\

 

 

 

 

 

 

Windows\System32\en-

 

 

 

 

 

 

US\user32.dll.mui

 

00340000

Mapped

20480

 

-RWC ---

 

 

 

003F0000

Free

65536

 

 

 

 

00400000

Image

126976

7

ERWC

C:\Apps\14 VMMap.exe

 

00400000

Image

4096

 

-R-- ---

 

 

00401000

Image

8192

 

ERW- ---

 

 

00403000

Image

57344

 

ERWC ---

 

 

00411000

Image

32768

 

ER-- ---

 

 

00419000

Image

8192

 

-R-- ---

 

 

0041B000

Image

8192

 

-RW- ---

 

 

0041D000

Image

8192

 

-R-- ---

 

 

0041F000

Free

4096

 

 

 

 

 

739B0000

Image

634880

9

ERWC

C:\Windows\WinSxS\

 

 

 

 

 

 

x86_microsoft.vc80.

 

 

 

 

 

 

crt_lfc8b3b9alel8e3b_

 

 

 

 

 

 

8.0.50727.312_none_

 

 

 

 

 

 

10b2ee7b9bffc2c7\

 

 

 

 

 

 

MSVCR80.dll

 

739B0006

Image

4096

 

-R-- ---

 

 

739B1000

Image

405504

 

ER-- ---

 

 

73A14000

Image

176128

 

-R-- ---

 

 

73A3F000

Image

4096

 

-RW- ---

 

 

73A40000

Image

4096

 

-RWC ---

 

 

73A41000

Image

4096

 

-RW- ---

 

 

73A42000

Image

4096

 

-RWC ---

 

 

73A43000

Image

12288

 

-RW- ---

 

 

73A46000

Image

20480

 

-R-- ---

 

 

454 Часть III. Управление памятью

Табл. 13-6. (продолжение)

Базовый

Тип

Размер

Блоки

Атрибут(ы)

Описание

адрес

защиты

 

 

 

 

73A4B000

Free

24072192

 

 

 

75140000

Image

1654784

7

ERWC

C:\Windows\WinSxS\

 

 

 

 

 

x86_microsoft.windows.

 

 

 

 

 

common-controls_6595b

 

 

 

 

 

64144ccf1df_6.0.6000.

 

 

 

 

 

16386_none_5d07289e0

 

 

 

 

 

7e

 

 

 

 

 

1d100\comctl32.dll

75140000

Image

4096

 

-R-- ---

 

75141000

Image

1273856

 

ER-- ---

 

75278000

Image

4096

 

-RW- ---

 

75279000

Image

4096

 

-RWC ---

 

7527A000

Image

8192

 

-RW- ---

 

7527C000

Image

40960

 

-RWC ---

 

75286000

Image

319488

 

-R-- ---

 

752D4000

Free

1490944

 

 

 

767F0000

Image

647168

5

ERWC

C:\Windows\system32\

 

 

 

 

 

USER32.dll

767F0000

Image

4096

 

-R-- ---

 

767F1000

Image

430080

 

ER-- ---

 

7685A000

Image

4096

 

-RW- ---

 

7685B000

Image

4096

 

-RWC ---

 

7685C000

Image

204800

 

-R-- ---

 

7688E000

Free

8192

 

 

 

76DA0000

Image

884736

5

ERWC

C:\Windows\system32\

 

 

 

 

 

kernel32.dll

76DA0000

Image

4096

 

-R-- ---

 

76DA1000

Image

823296

 

ER-- ---

 

76E6A000

Image

8192

 

-RW- ---

 

76E6C000

Image

4096

 

-RWC ---

 

76E6D000

Image

45056

 

-R-- ---

 

76E78000

Free

32768

 

 

 

7FFDF000

Private

4096

1

-RW-

 

7FFDF000

Private

4096

 

-RW- ---

 

7FFE0000

Private

65536

2

-R--

 

7FFE0000

Private

4096

 

-R-- ---

 

7FFE1000

Reserve

61440

 

-R-- ---

 

Глава 13. Архитектура памяти в Windows.docx 455

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

Впервом поле показывается адрес группы страниц с одинаковыми состоянием

иатрибутами защиты. Например, no адресу 0x767F0000 передана единственная страница (4096 байтов) физической памяти с атрибутом защиты, разрешающим только чтение. А по адресу 0x767F1000 присутствует блок размером 105 страниц (430 080 байтов) переданной памяти с атрибутами, разрешающими и чтение, и исполнение. Если бы атрибуты защиты этих блоков совпадали, их можно было бы объединить, и тогда на карте памяти появился бы единый элемент размером в 106 страниц (434 176 байтов).

Во втором поле сообщается тип физической памяти, с которой связан тот или иной блок, расположенный в границах зарезервированного региона. В нем появляется одно из пяти возможных значений: Free (свободный), Private (закрытый), Mapped (проецируемый), Image (образ) или Reserve (резервный). Значения Private, Mapped и Image говорят о том, что блок поддерживается физической памятью соответственно из страничного файла, файла данных, загруженного EXEили DLLмодуля. Если же в поле указано значение Free или Reserve, блок вообще не связан с физической памятью.

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

PAGE_WRITECOPY или PAGE_EXECUTE_WRITECOPY, система подсунет ва-

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

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

Вчетвертом поле показывается количество блоков внутри зарезервированного региона.

Впятом поле выводятся атрибуты защиты и флаги атрибутов защиты текущего блока. Атрибуты защиты блока замещают атрибуты защиты региона, содержащего данный блок. Их допустимые значения идентичны применяемым для регионов; кроме того, блоку могут быть присвоены флаги PAGE_GUARD, PAGE_WRITECOMBINJE и PAGE_NOCACHE, недопустимые для региона.

Выравнивание данных

Здесь мы отвлечемся от виртуального адресного пространства процесса и обсудим такую важную тему, как выравнивание данных. Кстати, выравнивание данных — не столько часть архитектуры памяти в операционной системе, сколько часть архитектуры процессора.

456 Часть III. Управление памятью

Процессоры работают эффективнее, когда имеют дело с правильно выровненными данными. Например, значение типа WORD всегда должно начинаться с четного адреса, кратного 2, значение типа DWORD — с четного адреса, кратного 4, и т. д. При попытке считать невыровненные данные процессор сделает одно из двух: либо возбудит исключение, либо считает их в несколько приемов.

Вот фрагмент кода, обращающийся к невыровненным данным:

V0ID SomeFunc(PV0ID pvDataBuffer) {

//первый байт в буфере содержит значение типа

BYTE char с = * (PBYTE) pvDataBuffer;

//увеличиваем указатель для перехода за этот байт pvDataBuffer = (PVOID)((PBYTE) pvDataBuffer + 1);

//байты 2-5 в буфере содержат значение типа DWORD DWORD dw = * (DWORD *) pvDataBuffer;

//на некоторых процессорах предыдущая строка приведет к исключению

//из-за некорректного выравнивания данных

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

Рассмотрим, как справляется с выравниванием данных процессор типа x86. Такой процессор в регистре EFLAGS содержит специальный битовый флаг, называемый флагом AC (alignment check). По умолчанию, при первой подаче питания на процессор он сброшен. Когда этот флаг равен 0, процессор автоматически выполняет инструкции, необходимые для успешного доступа к невыровненным данным. Однако если этот флаг установлен (равен 1), то при каждой попытке доступа к невыровненным данным процессор инициирует прерывание INT 17h. Версия Windows для процессоров типа x86 никогда не изменяют этот битовый флаг процессора. Поэтому в программе, работающей на процессоре типа x86, исключения, связанные с попыткой доступа к невыровненным данным, никогда не возникают. То же верно для процессоров AMD x86-64, по умолчанию использующих аппаратное выравнивание данных.

Теперь обратим внимание на процессор IA-64. Он не умеет оперировать с невыровненными данными. Когда происходит попытка доступа к таким данным, этот процессор уведомляет операционную систему. Далее Windows решает, что делать — генерировать соответствующее исключение или самой устранить возникшую проблему, выдав процессору дополнительные инс-

Глава 13. Архитектура памяти в Windows.docx 457

трукции. По умолчанию Windows, установленная на компьютере с процессором IA-64, автоматически преобразует ошибки обращения к невыровненным данным в исключения EXCEPTION_DATATYPE_MISALIGNMENT. Однако вы можете изменить ее поведение, заставив один из потоков процесса автоматически выравнивать данные для остальных потоков этого процесса, вызывая функцию SetErrorMode:

UINT SetErrorMode(UINT fuErrorMode);

В данном случае вам нужен флаг SEM_NOALIGNMENTFAULTEXCEPT. Когда он установлен, система автоматически исправляет ошибки обращения к невыровненным данным, а когда он сброшен, система вместо этого генерирует соответствующие исключения. Заметьте, что изменение этого флага влияет на потоки только того процесса, из которого была вызвана функция SetErrorMode. Иначе говоря, его модификация не отражается на потоках других процессов. Учтите также, что после установки или сброса изменить этот флаг не удастся, пока жив процесс.

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

SetErrorMode можно вызывать с флагом SEM_NOALIGNMENTFAULTEXCEPT независимо от того, на какой платформе выполняется Ваше приложение. Но результаты ее вызова не всегда одинаковы. На платформах x86 и x64 сбросить этот флаг просто нельзя. Для наблюдения за частотой возникновения ошибок, связанных с доступом к невыровненным данным, в Windows можно использовать Performance Monitor, подключаемый к MMC. На следующей иллюстрации показано диалоговое окно Add Counters, которое позволяет добавить нуж-

ный показатель в Performance Monitor.

458 Часть III. Управление памятью

Этот показатель сообщает, сколько раз в секунду процессор уведомляет операционную систему о доступе к невыровненным данным. На компьютере с процессором типа x86 он всегда равен 0. Это связано с тем, что такой процессор сам справляется с проблемами обращения к невыровненным данным и не уведомляет об этом операционную систему. А поскольку он обходится без помощи со стороны операционной системы, падение производительности при частом доступе к невыровненным данным не столь значительно, как на процессорах, требующих с той же целью участия операционной системы.

Компилятор Microsoft С/С++ для процессоров IA-64 поддерживает ключевое слово __unaligned. Этот модификатор используется так же, как const или volatile, но применим лишь для переменных-указателей. Когда вы обращаетесь к данным через невыровненный указатель (unaligned pointer), компилятор генерирует код, исходя из того, что данные скорее всего не выровнены, и вставляет дополнительные машинные инструкции, необходимые для доступа к таким данным. Ниже показан тот же фрагмент кода, что и в начале раздела, но с использованием ключе-

вого слова __unaligned.

V0ID SomeFunc(PVOID pvDataBuffer) {

//первый байт в буфере содержит значение типа BYTE char с = * (PBYTE) pvDataBuffer;

//увеличиваем указатель для перехода за этот байт pvDataBuffer = (PVOID)((PBYTE) pvDataBuffer + 1);

//байты 2-5 в буфере содержат значение типа DW0RD DWORD dw = * (__unaligned DWORD *) pvDataBuffer;

//Предыдущая строка заставит компилятор сгенерировать дополнительные

//машинные инструкции, которые позволят считать значение типа DW0RD

//в несколько приемов. При этом исключение из-за попытки доступа

//к невыровненный данным не возникнет.

Но если я уберу ключевое слово __unaligned, то получу всего 3 машинные инструкции. Как видите, модификатор __unaligned на процессорах IA-64 приводит к увеличению числа генерируемых машинных инструкций более чем в 2 раза. Но инструкции, добавляемые компилятором, все равно намного эффективнее, чем перехват процессором попыток доступа к невыровненным данным и исправление таких ошибок операционной системой. Используя счетчик Alignment Fixups/sec counter, несложно увидеть, что обращения через невыровненные указатели почти не отражаются на показаниях счетчика. Кстати, компилятор будет генерировать дополнительные инструкции, даже если структуры будут выровнены, что снизит эффективность кода.

И последнее. Ключевое слово __unaligned на процессорах типа x86 компилятором Visual С/С++ не поддерживается. На этих процессорах оно прос-

Глава 13. Архитектура памяти в Windows.docx 459

то не нужно. Но это означает, что версия компилятора для процессоров x86 встретив в исходном коде ключевое слово __unaligned, сообщит об ошибке. Поэтому, если вы хотите создать единую базу исходного кода приложения UNALIGNED или UNALIGNED64. Он определен в файле WinNT.h так:

#if defined(_M_MRX000) || defined(_M_ALPHA) || defined(_M_PPC) || defined(_M_IA64) || defined(_M_AMD64)

#define ALIGNMENT_MACHINE #define UNALIGNED __unaligned #if defined(_WIN64)

#define UNALIGNED64 __unaligned #else

#define UNALIGNED64 #endif

#else

#undef ALIGNMENT_MACHINE #define UNALIGNED #define UNALIGNED64

#endif

Соседние файлы в предмете Программирование на C++