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

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

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

Оглавление

 

Г Л А В А 1 3 Архитектура памяти в Windows.....................................................................

432

Виртуальное адресное пространство процесса ................................................................

432

Как адресное пространство разбивается на разделы.......................................................

433

Раздел для выявления нулевых указателей............................................................

434

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

434

Раздел для кода и данных режима ядра ....................................................................

437

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

437

Передача региону физической памяти .......................................................................

438

Физическая память и страничный файл .............................................................................

440

Физическая память в страничном файле не хранится ..........................................

442

Атрибуты защиты ....................................................................................................................

443

Защита типа «копирование при записи» ....................................................................

445

Специальные флаги атрибутов защиты ....................................................................

446

Подводя итоги..........................................................................................................................

446

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

452

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

455

Г Л А В А 1 3

Архитектура памяти в Windows

Архитектура памяти, используемая в операционной системе, — ключ к пониманию того, как система делает то, что она делает. Когда начинаешь работать с новой операционной системой, всегда возникает масса вопросов. Как разделить данные между двумя приложениями? Где хранится та или иная информация? Как оптимизировать свою программу? Список вопросов можно продолжить.

Обычно знание того, как система управляет памятью, упрощает и ускоряет поиск ответов на эти вопросы. Поэтому здесь мы рассмотрим архитектуру памя-

ти, применяемую в Microsoft Windows.

Виртуальное адресное пространство процесса

Каждому процессу выделяется собственное виртуальное адресное пространство. Для 32-разрядных процессов его размер составляет 4 Гб. Соответственно 32битный указатель может быть любым числом от 0x00000000 до 0xFFFFFFFF. Всего, таким образом, указатель может принимать 4 294 967 296 значений, что как раз и перекрывает четырехгигабайтовый диапазон. Для 64-разрядных процессов размер адресного пространства равен 16 экзабайтам, поскольку 64-битный указатель может быть любым числом от 0x00000000 00000000 до 0xFFFFFFFF FFFFFFFF и принимать 18 446 744 073 709 551 616 значений, охватывая диапазон в 16 экзабайтов. Весьма впечатляюще!

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

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

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

Итак, как я уже говорил, адресное пространство процесса закрыто. Отсюда вытекает, что процесс Л в своем адресном пространстве может хранить какую-то структуру данных по адресу 0x12345678, и одновременно у процесса В по тому же адресу — но уже в его адресном пространстве — может находиться совершенно иная структура данных. Обращаясь к памяти по адресу 0x12345678, потоки, выполняемые в процессе А, получают доступ к структуре данных процесса А. Но, когда по тому же адресу обращаются потоки, выполняемые в процессе В, они получают доступ к структуре данных процесса В. Иначе говоря, потоки процесса А не могут обратиться к структуре данных в адресном пространстве процесса В, и наоборот.

А теперь, пока вы не перевозбудились от колоссального объема адресного пространства, предоставляемого вашей программе, вспомните, что оно — виртуальное, а не физическое. Другими словами, адресное пространство — всего лишь диапазон адресов памяти. И, прежде чем вы сможете обратиться к каким-либо данным, не вызвав нарушения доступа, придется спроецировать нужную часть адресного пространства на конкретный участок физической памяти (об этом мы поговорим чуть позже).

Как адресное пространство разбивается на разделы

Виртуальное адресное пространство каждого процесса разбивается на разделы. Их размер и назначение в какой-то мере зависят от конкретного ядра Windows (таблица 13-1).

Как видите, ядра 32- и 64-разрядной Windows создают разделы, почти одинаковые по назначению, но отличающиеся по размеру и расположению. Давайте рассмотрим, как система использует каждый из этих разделов.

Табл. 13-1. Так адресное пространство процесса разбивается на разделы

 

32-разрядная

32-разрядная

64-разрядная

64-разрядная

Раздел

Windows

Windows (на х86

Windows

 

Windows

 

 

(на х86)

с ключом /3GB)

(на х64)

 

(на IA-64)

 

Для выявления нулевых

0x00000000

0x00000000

0x00000000

00000000

0x00000000

00000000

указателей

0x0000FFFF

0x0000FFFF

0x00000000

0000FFFF

0x00000000

0000FFFF

Для кода и данных поль-

0x00010000

0x00010000

0x00000000

00010000

0x00000000

00010000

зовательского режима

0x7FFEFFFF

0xBFFEFFFF

0x000007FF FFFEFFFF

0x000006FB FFFEFFFF

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

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

 

32-разрядная

32-разрядная

64-разрядная

64-разрядная

Раздел

Windows

Windows (на х86

Windows

Windows

 

(на х86)

с ключом /3GB)

(на х64)

(на IA-64)

Закрытый, размером 64 Кб

0x7FFF0000

0xBFFF0000

0x000007FF FFFF0000

0x000006FB FFFF0000

 

0x7FFFFFFF

0xBFFFFFFF

0x000007FF FFFFFFFF

0x000006FB FFFFFFFF

Для кода и данных ре жи-

0x800000000

0xC0000000

0x00000800 00000000

0x000006FC 00000000

ма ядра

0xFFFFFFFF

0xFFFFFFFF

0xFFFFFFFF FFFFFFFF

 

Раздел для выявления нулевых указателей

Раздел адресного пространства, охватывающий адреса от 0x00000000 до 0x0000FFFF, резервируется для того, чтобы программисты могли выявлять нулевые указатели. Любая попытка чтения или записи в память по этим адресам вызывает нарушение доступа.

Довольно часто в программах, написанных на С/С++, отсутствует скрупулезная обработка ошибок. Например, в следующем фрагменте кода такой обработки вообще нет:

int* pnSomeInteger = (int*) malloc(sizeof(int)); *pnSomeInteger = 5;

При нехватке памяти malloc вернет NULL. Но код не учитывает эту возможность и при ошибке обратится к памяти по адресу 0x00000000. А поскольку этот раздел адресного пространства заблокирован, возникнет нарушение доступа и данный процесс завершится. Эта особенность помогает программистам находить «жучков» в своих приложениях. Заметьте, что в этом разделе запрещено даже резервировать память с помощью функций Win32 API.

Раздел для кода и данных пользовательского режима

В этом разделе находятся адресные пространства процессов. Доступный для использования диапазон адресов и примерный размер раздела пользовательского режима зависит от архитектуры процессора (13-2).

Табл. 13-2. Размер и адреса раздела пользовательского режима для процессоров с разной архитектурой

Архитектура ЦП

Диапазон адресов размера

Размер раздела

 

пользовательского режима

пользователь-

 

 

ского режима

х86

0x00010000-0x7FFEFFFF

-2 Гб

х86 (с ключом /3GB)

0x00010000-0xBFFFFFFF

-3 Гб

х64

0x00000000’00010000-0x000007FF’FFFEFFFF

-8192 Гб

IA-64

0x00000000’00010000-0x000006FB’FFFEFFFF

-7152 Гб

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

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

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

Впервые увидев адресное пространство своего 32-разрядного процесса, я был удивлен тем, что его полезный объем чуть ли не вдвое меньше. Неужели раздел для кода и данных режима ядра должен занимать столько места? Оказывается — да. Это пространство нужно системе для кода ядра, драйверов устройств, кэшбуферов ввода-вывода, областей памяти, не сбрасываемых в файл подкачки, таблиц, используемых для контроля страниц памяти в процессе и т. д. По сути, Майкрософт едва-едва втиснула ядро в эти виртуальные два гигабайта. В 64разрядной Windows ядро наконец получит то пространство, которое ему нужно на самом деле.

Увеличение раздела для кода и данных пользовательского режима до 3 Гб на процессорах x86

Некоторые приложения, такие как Microsoft SQL Server, быстрее работают и лучше масштабируются, если расширить доступное им адресное пространство за границы 2 Гб. С этой целью в версии Windows для х86-компьютеров можно увеличивать до 3 Гб. Чтобы отвести процессам раздел пользовательского режима, размер которого больше 2 Гб, и раздел режима ядра размером меньше 1 Гб, следует настроить загрузочную конфигурацию (boot configuration data, BCD) Windows и перезагрузить компьютер (подробнее о BCD см. в официальной статье по ссылке http://www.microsoft.com/whdc/system/platform/firmware/bcd.mspx).

Для настройки BCD следует запустить BCDEdit.exe с ключом /set IncreaseUserVA. Так, команда bcdedit /set IncreaseUserVa 3072 заставляет Windows зарезер-

вировать для процессов раздел пользовательского режима размером 3 Гб и 1-Гб раздел режима ядра (см. табл. 13-2). Минимальное значение параметра IncreaseUserVa — 2048, что соответствует установленному по умолчанию размеру 2 Гб. Явно сбросить этот параметр позволяет команда bcdedit /deletevalue IncreaseUserVa.

Совет. Чтобы узнать текущие параметры BCD, просто выполните в командной строке команду bcdedit /enum (о параметрах BCDEdit см. по ссылке http://msdn2.microsoft.com/en-us/library/aa906211.aspx).

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

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

Майкрософт пришлось придумать решение, которое позволило бы подобным приложениям работать в трехгигабайтовой среде. Теперь система в момент запуска приложения проверяет, не скомпоновано ли оно с ключом /LARGEADDRESSAWARE. Если да, приложение как бы заявляет, что обязуется корректно обращаться с этими адресами памяти и действительно готово к использованию трехгигабайтового адресного пространства пользовательского режима. А если нет, операционная система резервирует область памяти размером 1 Гб между 2-Гб регионом пользовательского режима и регионом режима ядра. Это предотвращает выделение памяти по адресам с установленным старшим битом.

Заметьте, что ядро и так с трудом умещается в двухгигабайтовом разделе. Но при использовании ключа /3GB ядру остается всего 1 Гб. Тем самым уменьшается количество потоков, стеков и других ресурсов, которые система могла бы предоставить приложению. Кроме того, система в этом случае способна задействовать максимум 64 Гб оперативной памяти против 128 Гб в нормальных условиях — изза нехватки виртуального адресного пространства для кода и данных режима ядра, необходимого для управления дополнительной оперативной памятью.

Примечание. Флаг LARGEADDRESSAWARE в исполняемом файле проверяется в тот момент, когда операционная система создает адресное пространство процесса. Для DLL этот флаг игнорируется. При написании DLL вы должны сами позаботиться об их корректном поведении в трехгигабайтовом разделе пользовательского режима.

Уменьшение раздела для кода и данных пользовательского режима до 2 Гб в 64-разрядной Windows

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

Однако, если бы система как-то гарантировала, что память никогда не будет выделяться по адресам выше 0x00000000 7FFFFFFF, приложение работало бы нормально. И усечение 64-разрядного адреса до 32-разрядного,

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

когда старшие 33 бита равны 0, не создало бы никаких проблем. Так вот, система дает такую гарантию при запуске приложения в «адресной песочнице» (address space sandbox), которая ограничивает полезное адресное пространство процесса до нижних 2 Гб.

По умолчанию, когда вы запускаете 64-разрядиое приложение, система резервирует все адресное пространство пользовательского режима, начиная с 0x0000000 80000000, что обеспечивает выделение памяти исключительно в нижних 2 Гб 64-разрядного адресного пространства. Это и есть «адресная песочница». Большинству приложений этого пространства более чем достаточно. А чтобы 64разрядиое приложение могло адресоваться ко всему разделу пользовательского режима (объемом 4 Тб), его следует скомпоновать с ключом

/LARGEADDRESSAWARE.

Примечание. Флаг LARGEADDRESSAWARE в исполняемом файле проверяется в тот момент, когда операционная система создает адресное пространство 64-разрядного процесса. Для DLL этот флаг игнорируется. При написании DLL вы должны сами позаботиться об их корректном поведении в четырехтерабайтовом разделе пользовательского режима.

Раздел для кода и данных режима ядра

В этот раздел помещается код операционной системы, в том числе драйверы устройств и код низкоуровневого управления потоками, памятью, файловой системой, сетевой поддержкой. Все, что находится здесь, доступно любому процессу. В Windows 2000 эти компоненты полностью защищены. Поток, который попытается обратиться по одному из адресов памяти в этом разделе, вызовет нарушение доступа, а это приведет к тому, что система в конечном счете просто закроет его приложение. (Подробнее на эту тему см. главы 23,24 и 25.)

Примечание. В 64-разрядной Windows раздел пользовательского режима (4 Тб) выглядит непропорционально малым по сравнению с 16 777 212 Тб, отведенными под раздел для кода и данных режима ядра. Дело не в том, что ядру так уж необходимо все это виртуальное пространство, а просто 64разрядное адресное пространство настолько огромно , что его бо́льшая часть не задействована. Система разрешает нашим программам использовать 4 Тб, а ядру — столько, сколько ему нужно. К счастью, какие-либо внутренние структуры данных для управления незадействованными частями раздела для кода и данных режима ядра не требуются.

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

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

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

рез функцию VirtualAlloc (о ней — в главе 15). Операция выделения региона на-

зывается резервированием (reserving).

При резервировании система обязательно выравнивает начало региона с уче-

том так называемой гранулярности выделения памяти (allocation granularity). По-

следняя величина в принципе зависит от типа процессора, но для процессоров, рассматриваемых в книге, она одинакова и составляет 64 Кб.

Резервируя регион в адресном пространстве, система обеспечивает еще и кратность размера региона размеру страницы (page). Так называется единица объема памяти, используемая системой при управлении памятью. Как и гранулярность выделения ресурсов, размер страницы зависит от типа процессора. В частности, для процессоров x86 и x64 (x86 с поддержкой EMT) он равен 4 Кб, а для 64-разрядных IA-64 — 8 Кб.

Примечание. Иногда система сама резервирует некоторые регионы адресного пространства в интересах вашего процесса, например, для хранения блока переменных окружения процесса (process environment block, РЕВ). Этот блок — небольшая структура данных, создаваемая, контролируемая и разрушаемая исключительно операционной системой. Выделение региона под РЕВ-блок осуществляется в момент создания процесса.

Кроме того, для управления потоками, существующими на данный момент в процессе, система создает блоки переменных окружения потоков (thread environment blocks, TEBs). Регионы под эти блоки резервируются и освобождаются по мере создания и разрушения потоков в процессе.

Но, требуя от вас резервировать регионы с учетом гранулярности выделения памяти (а эта гранулярность на сегодняшний день составляет 64 Кб), сама система этих правил не придерживается. Поэтому вполне вероятно, что границы региона, зарезервированного под РЕВ- и ТЕВ-блоки, не будут кратны 64 Кб. Тем не менее размер такого региона обязательно кратен размеру страниц, характерному для данного типа процессора.

Если вы попытаетесь зарезервировать регион размером 10 Кб, система автоматически округлит заданное вами значение до большей кратной величины. А это значит, что на x86 и x64 будет выделен регион размером 12 Кб, а на IА-64 —

16Кб.

И последнее в этой связи. Когда зарезервированный регион адресного пространства становится не нужным, его следует вернуть в общие ресурсы системы. Эта операция — освобождение (releasing) региона — осуществляется вызовом функции VirtualFree.

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

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

Такая операция называется передачей физической памяти (committing physical storage). Чтобы передать физическую память зарезервированному региону, вы обращаетесь все к той же функции VirtualAlloc.

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

Передавая физическую память регионам, нет нужды отводить ее целому региону. Можно, скажем, зарезервировать регион размером 64 Кб и передать физическую намять только его второй и четвертой страницам. На рис. 13-1 представлен пример того, как может выглядеть адресное пространство процесса. Как видите, структура адресного пространства зависит от архитектуры процессора. Слева показано, что происходит с адресным пространством на процессоре x86/x64 (страницы по 4 Кб), а справа — на процессоре IA64 (страницы по 8 Кб).

Рис. 13-1. Примеры адресных пространств процессов для разных типов процессоров

Когда физическая память, переданная зарезервированному региону, больше не нужна, ее освобождают. Эта операция — возврат физической памяти (decommitting physical storage) — выполняется вызовом функции VirtualFree.

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