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

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

Каждому процессу выделяется собственное виртуальное адресное пространство. Для 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, потоки, выполняемые в процессе А, получают доступ к структуре данных процесса А, Но, когда по тому же адресу

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

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

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

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

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

формирует другие разделы. Давайте рассмотрим, как система использует каждый из этих разделов.

Раздел

32-разрядная

32-разрядная Windows 2000

64-разрядная Windows 2000 (на

Windows 98

 

Windows 2000 (на

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

Alpha и А-64)

 

 

х86 и Alpha)

 

 

 

 

 

 

 

 

 

 

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

0x00000000

0x00000000

0x00000000

00000000

0x00000000

 

 

 

 

 

 

нулевых указателей

0x0000FFFF

0x0000FFFF

0x00000000

0000FFFF

0x00000FFF

 

 

 

 

 

 

Для совместимости с

Hет

Нет

Нет

 

0x00001000

программами DOS и

 

 

 

 

0x003FFFFF

16-разрядной Windows

 

 

 

 

 

Для кода и данных

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).

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

Windows 98)

Этот раздел адресного пространства резервируется для того, чтобы программисты могли выявлять нулевые указатели. Любая попытка чтения или записи в память по этим адресам вызывает нарушение доступа.

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

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

При нехватке памяти malloc вернет NULL. Ho код не учитывает эту возможность и при ошибке обратится к памяти по адресу 0x00000000 А поскольку этот раздел адресного пространства заблокирован, возникнет нарушение доступа и данный процесс завершится Эта особенность помогает программистам находить «жучков* в своих приложениях.

Раздел для совместимости с программами 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)

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

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 Гб