Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект лекций 2009.doc
Скачиваний:
45
Добавлен:
13.11.2019
Размер:
2.3 Mб
Скачать

2.2Виртуальная память ос Windows

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

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

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

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

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

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

Виртуальное адресное пространство каждого процесса разбивается на разделы и страницы. Размер страницы различен и во многом зависит от аппаратной платформы, для которой реализована ОС. Помимо страничного разбиения, виртуальная память в Windows разбивается на разделы. Их размер и назначение в какой-то мере зависят от конкретного ядра Windows и представлены в табл. 2.1

Таблица 2.1. Наименование разделов памяти в Windows

Раздел

32-разрядная Windows 2000/XP (на х86)

32-разрядная Windows 2000/XP (на х86 с ключом /3GB)

64-разрядная Windows XP (на x86_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

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

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

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

*pnSomeInteger = 5;

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

Раздел для совместимости с программами DOS и 16-разрядной Windows размером 4 Мб в адресном пространстве процесса необходим Windows 98 для поддержки совместимости с программами MS-DOS и 16-разрядной Windows. Обращение к нему из 32-разрядных Windows-приложений может вызвать непредвиденные последствия. Вообще говоря, процессор должен был бы генерировать нарушение доступа при обращении потока к этому участку адресного пространства, но Microsoft не смогла реализовать эту возможность.

В Windows XP программы для MS-DOS и 16-разрядной Windows выполняются в собственных адресных пространствах и 32-разрядные приложения повлиять на них не могут.

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

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

В Windows 98 основные 32-разрядные системные DLL (Kernel32.dll, AdvAPI32.dll, User32.dll и GDI32.dll) загружаются в раздел для общих MMF (memory mapped files -проецируемых в память файлов), a EXE- и остальные DLL-модули — в раздел для кода и данных пользовательского режима. Общие DLL располагаются по одному и тому же виртуальному адресу во всех процессах, но другие DLL могут загружать их (общие DLL) по разным адресам в границах раздела для кода и данных пользовательского режима (хотя это маловероятно). Проецируемые в память файлы в этот раздел никогда не помещаются

Реально используемый приложением объём виртуальной памяти несколько меньше. Остальное пространство резервируется системой. Это пространство нужно системе для кода ядра, драйверов устройств, кэш-буферов ввода-вывода, областей памяти, не сбрасываемых в файл подкачки, таблиц, используемых для контроля страниц памяти в процессе и т.д. Такой подход свойственен системам с большим монолитным ядром.

Увеличение размера ПО привело к необходимости увеличения адресного пространства выделяемого для процесса. Особенно остро данная проблема встала для серверных ОС. Как один из вариантов её решения в версиях Windows 2000 Advanced Server и Windows 2000 Data Center для процессоров x86 предусмотрена возможность увеличения этого пространства до 3 Гб. Чтобы все процессы использовали раздел для кода и данных пользовательского режима размером 3 Гб, а раздел для кода и данных режима ядра — объемом 1 Гб, необходимо добавить ключ /3GB к нужной записи в системном файле Boot.ini. Как выглядит адресное пространство процесса в этом случае, показано в графе "32-разрядная Windows 2000/XP (на x86 с ключом /3GB)" таблицы 2.1.

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

Во избежание конфликтов при выполнении таких приложений в расширенной 3 гигабайтовой среде, система в момент запуска приложения проверяет, не скомпоновано ли оно с ключом /LARGEADDRESSAWARE. Это означает, что приложение готово к использованию трехгигабайтового адресного пространства пользовательского режима. А если нет, операционная система резервирует область памяти размером 1 Гб в диапазоне адресов от 0x80000000 до 0xBFFFFFFF. Это предотвращает выделение памяти по адресам с установленным старшим битом.

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

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

Закрытый раздел размером 64 Кб заблокирован, и любая попытка обращения к нему приводит к нарушению доступа. Этот раздел резервируется для упрощения внутренней реализацию ОС. Вспомните, когда Вы передаете Windows-функции адрес блока памяти и его размер, то она (функция), прежде чем приступить к работе, проверяет, действителен ли данный блок. Например:

BYTE bBuf[70000];

DWORD dwNumBytesWritTen;

WriteProcessMemory(GetCurrentProcess(), (PVOID) 0x7FFEEE90, bBuf, sizeof(bBuf), &dwNumBytesWritten);

В случае функций типа WriteProcessMemory область памяти, в которую предполагается запись, проверяется кодом, работающим в режиме ядра, — только он имеет право обращаться к памяти, выделяемой под код и данные режима ядра (в 32-разрядных системах — по адресам выше 0x80000000). Если по этому адресу есть память, вызов WriteProcessMemory, показанный выше, благополучно запишет данные в ту область памяти, которая, по идее, доступна только коду, работающему в режиме ядра. Чтобы предотвратить это и в то же время ускорить проверку таких областей памяти, Microsoft предпочла заблокировать данный раздел, и поэтому любая попытка чтения или записи в нем всегда вызывает нарушение доступа.

Раздел для общих MMF (только Windows 98). В этом разделе размером 1 Гб система хранит данные, разделяемые всеми 32-разрядными процессами. Сюда, например, загружаются все системные DLL (Kernel32.dll, AdvAPI32 dll, User32.dll и GDI32 dll), и поэтому они доступны любому 32-разрядному процессу. Кроме того, эти DLL загружаются в каждом процессе по одному и тому же адресу памяти. На этот раздел система также отобра-жает все проецируемые в память файлы.

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

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

В Windows 98 данные, размещенные в этом разделе, увы, не защищены — любое приложение может что-то считать или записать в нем и нарушить нормальную работу операционной системы.