Реализация виртуальной памяти в windows nt

Организация виртуальной памяти Страничное преобразование Отложенное копирование Свопинг Адресное пространство процесса Восьмидесятые годы благодаря появлению персональных компьютеров породили иллюзию простоты системного программирования. Нет вины инженеров компании Intel в том, что им удалось разместить на кристалле только то, что удалось разместить.

Организация виртуальной памяти

Страничное преобразование
Отложенное копирование
Свопинг
Адресное пространство процесса

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


Организация виртуальной памяти
    Страничное преобразование
    Отложенное копирование
    Свопинг
    Адресное пространство процесса


Восьмидесятые годы благодаря появлению персональных компьютеров породили иллюзию простоты системного программирования. Нет вины инженеров компании Intel в том, что им удалось разместить на кристалле только то, что удалось разместить. Наоборот, их продукт следует считать величайшим техническим достижением, изменившим наш мир. Но факт остается фактом — процессор 8086 не предоставлял операционным системам тот сервис, который требовался согласно разработанным теориям создания операционных систем. В первую очередь это касалось систем управления памятью, планировщиков и систем защиты. Достаточно обратиться к классическим книгам того времени [1, 2], чтобы убедиться в том, что теория организации вычислительного процесса была хорошо разработана и реализована.

Но время диктует свои законы. Изменились микропроцессоры, и появились новые операционные системы. Однако у многих профессионалов осталась иллюзия того, что ОС от Microsoft — это лишь красивые картинки на экране и простейшее ядро внутри, а для серьезных задач требуются «серьезные» системы. На примере системы управления памятью покажем, что Windows NT — серьезная операционная система, построенная в соответствии с классическими принципами.

Организация виртуальной памяти

Страничное преобразование

Виртуальная память в Windows NT имеет страничную организацию, принятую во многих современных операционных системах. В общем виде схема страничной организации описывается следующим образом: линейный адрес разбивается на несколько частей. Старшая часть адреса содержит в себе номер элемента в корневой таблице. Этот элемент содержит адрес таблицы следующего уровня. Следующая часть линейного адреса содержит номер элемента уже в этой таблице и так далее, до последней таблицы, которая содержит номер физической страницы. А самая младшая часть адреса уже является номером байта в этой физической странице.

Процессоры Intel начиная с Pentium Pro позволяют операционным системам применять одно-, двух- и трехступенчатые схемы. И даже разрешается одновременное использование страниц различного размера. Эта возможность, конечно, повысила бы эффективность страничного преобразования, будь она внедрена в Windows NT. Увы, эта ОС возникла раньше и поддерживает только двухступенчатую схему преобразования с фиксированным размером страниц. Размер страниц для платформы Intel составляет 4 Кбайт, а для DEC Alpha — 8 Кбайт. Схема страничного преобразования (рис. 1) выглядит так:

Picture 1.
Рисунок 1.
Схема страничного преобразования адреса для платформы Intel

32-разрядный линейный адрес разбивается на три части. Старшие 10 разрядов адреса определяют номер одного из 1024 элементов в каталоге страниц, адрес которого находится в регистре процессора CR3. Этот элемент содержит физический адрес таблицы страниц. Следующие 10 разрядов линейного адреса определяют номер элемента таблицы. Элемент, в свою очередь, содержит физический адрес страницы виртуальной памяти. Размер страницы — 4 Кбайт, и младших 12 разрядов линейного адреса как раз хватает (212 = 4096), чтобы определить точный физический номер адресуемой ячейки памяти внутри этой страницы.

Рассмотрим отдельный элемент таблицы страниц (PTE — Page Table Element) более подробно, так как он содержит массу полезной информации (рис 2).

Picture 2.
Рисунок 2.
Элемент таблицы страниц в Windows NT

Старшие пять бит определяют тип страницы с точки зрения допустимых операций. Win32 API поддерживает три допустимых значения этого поля: PAGE_NOACCESS, PAGE_READONLY и PAGE_READWRITE. Следующие 20 бит определяют базовый физический адрес страницы в памяти. Если дополнить их 12 младшими разрядами линейного адреса, они образуют физический адрес ячейки памяти, к которой производится обращение. Следующие четыре бита PTE описывают используемый файл подкачки. Комбинацией этих битов ссылаются на один из 16 возможных в системе файлов. Последние три бита определяют состояние страницы в системе. Старший из них (T-Transition) отмечает страницу как переходную, следующий (D-Dirty) — как страницу, в которую была произведена запись. Информация об изменениях в странице необходима системе для того, чтобы принять решение о сохранении страницы в файле подкачки при ее вытеснении (принудительном освобождении занятой памяти). Действительно, если страница не изменялась в памяти после загрузки, то ее можно просто стереть, ведь в файле подкачки сохранилась ее копия. И наконец, младший бит (P-Present) определяет, присутствует ли страница в оперативной памяти или же она находится в файле подкачки. Конечно, 16 файлов подкачки явно недостаточно. В дальнейшем мы увидим, что в качестве файлов подкачки могут выступать исполняемые файлы, а также файлы, отображаемые в память. Официальная документация Microsoft весьма скупа на комментарии по этой ситуации. Отмечается лишь, что при отсутствии страницы в памяти 28 бит рассмотренного элемента таблицы страниц не несут никакой полезной информации и используются для определения местонахождения выгруженной страницы.

Для ускорения страничного преобразования в процессоре имеется специальная кэш-память, называемая TLB (Translation Lookaside Buffer). В ней хранятся наиболее часто используемые элементы каталога и таблиц страниц. Конечно, переключение процессов и даже потоков приводит к тому, что данные внутри этого буфера становятся неактуальными, т. е. недействительными. Это влечет за собой дополнительные потери производительности при переключении. К счастью, временной промежуток, выделяемый системой процессу, составляет 17 мс. Даже на машине с производительностью всего 20 MIPS за это время успеет выполниться непрерывный участок программы длиной (17 мс Ё 20 MIPS) 340 тыс. команд. Этого более чем достаточно, чтобы пренебречь потерями производительности при выполнении начального участка процесса.

Каждый процесс Windows NT имеет свой отдельный каталог страниц и свое собственное независимое адресное пространство, что очень хорошо с точки зрения защиты процессов друг от друга. Но за это удовольствие приходится платить ресурсами. Независимым процессам почти всегда приходится обмениваться информацией друг с другом. Windows NT предоставляет большое количество способов обмена, в том числе и при OLE. Но все они имеют в основе одно и то же действие: один процесс пишет нечто в некоторую ячейку памяти, а другой из нее читает. Это означает, что должны быть участки памяти, доступные разным процессам. На первый взгляд, это не проблема — могут же различные PTE ссылаться на одну и ту же страницу. Однако в каждом PTE хранятся атрибуты страницы, а ссылка на страницу со стороны PTE делается только в одну сторону. Это означает, что при изменении какого-либо атрибута в одном из PTE невозможно сделать то же самое для всех других PTE, ссылающихся на ту же страницу. Именно поэтому для совместно используемых страниц применяется механизм прототипов элементов таблицы страниц (Prototype Page Table Entry). Для совместно используемых страниц создается прототип PTE, который и содержит актуальные атрибуты страницы. Все PTE, принадлежащие различным процессам, ссылаются не на саму страницу, а на этот прототип, атрибуты которого и являются актуальными (рис. 3).

Picture 3.
Рисунок 3.
Совместное использование страниц процессами

Сам прототип хранится в старших адресах каждого процесса. Общий размер памяти, отводимый для хранения прототипов, не превышает 8 Мбайт.

Отложенное копирование

Все гениальное — просто. Многие программисты получили истинное наслаждение, впервые ознакомившись с тем, как изящно в Unix реализовано порождение процессов. В результате исполнения системного вызова fork(), призванного создавать новые процессы, два независимых процесса начинают совместно использовать одну и ту же область кода. Прошло немало времени, прежде чем пытливая инженерная мысль нашла возможность совместно использовать одну и ту же область данных. Действительно, нет необходимости каждому процессу иметь отдельный экземпляр данных, если эти данные только читаются. Проблема состоит в том, как определить, будут ли данные читаться в будущем или нет. Неважно, области кода или данных принадлежит совместно используемая память. Существенно лишь то, производится или нет изменение данных процессами. Если процесс производит запись, он должен иметь свою отдельную копию изменяемых данных. Если же в записи нет необходимости, то он может совместно использовать данные с остальными процессами. Подобный подход называется отложенным копированием (lazy evaluation): решение о необходимости копирования страницы откладывается до того момента, когда процесс попытается что-либо записать в нее. Все страницы порожденного процесса защищаются от записи. При попытке изменения защищенной страницы возникает процессорное исключение, страница копируется системой, и только после этого в нее разрешается запись.

Данный механизм, едва появившись, сразу же стал классическим и сегодня является составной частью многих систем Unix. Нет ничего удивительного, что он изначально присутствует в архитектуре и более молодой системы Microsoft Windows NT. Наиболее активно он используется при работе различных процессов с совместно используемыми динамически загружаемыми библиотеками (DLL), а также при запуске программ.

Свопинг

Конечно, компьютер может и не иметь 4 Гбайт оперативной памяти, адресуемых процессорами Intel для того, чтобы обеспечить все линейное адресное пространство процесса физическими ячейками памяти. Windows NT, как и все другие операционные системы, применяет свопинг (swapping). Не используемые в конкретный момент страницы памяти могут быть вытеснены на диск в так называемый файл подкачки. В соответствующем элементе таблицы страниц эта страница помечается как отсутствующая, и при попытке обращения к ней возникает исключительная ситуация — «сбой» страницы. Обрабатывая ее, операционная система находит страницу на диске и переписывает ее в память, соответствующим образом подстраивая элемент таблицы страниц. После этого попытка выполнить команду, вызвавшую исключение, повторяется.

С понятием свопинга неразрывно связаны три стратегии: выборка (fetch), размещение (placement) и замещение (replacement).

  • Выборка определяет, в какой момент необходимо переписать страницу с диска в память. В Windows NT используется классическая схема выборки с упреждением: система переписывает в память не только выбранную страницу, но и несколько следующих по принципу пространственной локальности, гласящему: наиболее вероятным является обращение к тем ячейкам памяти, которые находятся в непосредственной близости от ячейки, к которой производится обращение в настоящий момент. Поэтому вероятность того, что будут востребованы последовательные страницы, достаточна высока. Их упреждающая подкачка позволяет снизить накладные расходы, связанные с обработкой прерываний.
  • Размещение определяет, в какое место оперативной памяти необходимо поместить подгружаемую страницу. Для систем со страничной организацией данная стратегия практически не имеет никакого значения, и поэтому Windows NT выбирает первую попавшуюся свободную страницу.
  • Замещение начинает действовать с того момента, когда в оперативной памяти компьютера не остается свободного места для размещения подгружаемой страницы. В этом случае необходимо решить, какую страницу вытеснить из физической памяти в файл подкачки. Можно было бы отделаться общими словами [6], сказав, что в данном случае Windows NT использует алгоритм FIFO: вытесняется страница, загруженная раньше всех, т. е. самая «старая». Однако механизм замещения настолько интересен, что заслуживает более пристального внимания, и мы еще расскажем о нем.

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

Часть ядра Windows NT, которая занимается управлением виртуальной памятью, называется VMM — Virtual Memory Manager. Это независимый привилегированный процесс, постоянно находящийся в оперативной памяти компьютера. VMM поддерживает специальную структуру, называемую базой данных страниц (page-frame database). В ней содержатся элементы для каждой страницы. Состояние каждой страницы описывается с помощью следующих категорий:

Valid — страница используется процессом. Она реально существует в памяти и помечена в PTE как присутствующая.

Modified — содержимое страницы было изменено. В PTE страница помечена как отсутствующая (P=0) и переходная (T=1).

Standby — содержимое страницы не изменялось. В PTE страница помечена как отсутствующая (P=0) и переходная (T=1).

Free — страница, на которую не ссылается ни один PTE. Страница свободна, но подлежит обнулению, прежде чем будет использована.

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

Bad — страница, которая вызывает аппаратные ошибки и не может быть использована ни одним процессом.

Все состояния, за исключением Modified и Standby, можно обнаружить в большинстве операционных систем. Страница, находящаяся в одном из этих состояний, реально располагается в памяти, в PTE содержится ее реальный адрес, но сама страница помечена как отсутствующая. При обращении к этой странице возникает аппаратное исключение, однако его обработка не вызывает больших временных затрат. Необходимо просто пометить страницу как присутствующую (Valid, P=1) и еще раз запустить последнюю команду.

Сама база данных страниц организована как шесть независимых связных списков, каждый из которых объединяет элементы, соответствующие страницам одного типа.

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

Важнейшее понятие при организации свопинга — рабочее множество процесса (working set). В соответствии с принципом локальности процесс не использует всю память одновременно. Состав и количество страниц, с которыми процесс работает, меняется на протяжении его жизни. Страницы, с которыми процесс работает в течение фиксированного интервала времени, есть его рабочее множество на этом интервале. Переход от одного рабочего множества к другому осуществляется не равномерно, а скачками. Это вызвано семантикой процесса: переходами от обработки одного массива к обработке другого, от подпрограммы к подпрограмме, от цикла к циклу и т. д. Не нужно, чтобы в оперативной памяти размещались все страницы процесса, однако необходимо, чтобы в ней помещалось его рабочее множество. В противном случае возникает режим интенсивной подкачки, называемый трешингом (trashing), с которым сталкивается любой пользователь PC, запустивший одновременно слишком много приложений. В таком режиме затраты системы на переписывание страниц с диска в память и обратно значительно превышают время, выделяемое на полезную деятельность. Фактически система перестает заниматься чем-либо, кроме перемещения головок жесткого диска.

Теперь настало время разрешить интригу, которая возникла несколько абзацев назад, и подробно рассмотреть стратегию замещения страниц Windows NT. В классической теории операционных систем идеальной считается следующая стратегия: замещению подлежит страница, которая не будет использоваться в будущем дольше других. В той же классической теории эта стратегия признается неосуществимой, поскольку нет никакого разумного способа определить, какая из страниц подпадает под этот критерий. К счастью, в мире еще есть инженеры, готовые ломать голову над неосуществимыми идеями. Программистам Microsoft удалось приблизиться к этой стратегии на основе анализа рабочего множества процесса. В Windows NT используется весьма близкая стратегия: из памяти вытесняются страницы, вышедшие из рабочего множества процесса. Как же VMM узнает, какие страницы больше не принадлежат рабочему множеству? А очень просто! Периодически VMM просматривает список страниц с атрибутом Valid и пытается похитить их у процесса. Он помечает их как отсутствующие (P=0), но на самом деле оставляет их на месте, только переводит в разряд Modified или Standby в зависимости от значения бита D из PTE. Если похищенная страница принадлежит рабочему множеству, то к ней в ближайшее время произойдет обращение. Это, конечно, вызовет исключение — ведь страница-то помечена как отсутствующая. Но VMM очень быстро сделает эту страницу вновь доступной процессу, поскольку она реально находится в памяти. Если страница находится вне рабочего множества, то обращений к ней не будет и она со временем перейдет в разряд Free, а затем Zeroed и станет доступна другим процессам системы.

Адресное пространство процесса

В Windows NT используется плоская (flat) модель памяти. Каждому процессу выделяется «личное» изолированное адресное пространство. На 32-разрядных компьютерах размер этого пространства составляет 4 Гбайт и может быть расширен до 32 Гбайт при работе Windows NT 5.0 на процессоре Alpha. Это пространство разбивается на регионы, различные для разных версий системы (рис. 5).

В Windows NT 4.0 младшие 2 Гбайт памяти выделяются процессу для произвольного использования, а старшие 2 Гбайт резервируются и используются операционной системой. В младшую часть адресного пространства помещаются и некоторые системные динамически связываемые библиотеки (DLL). Желание расширить доступное процессу адресное пространство привело к тому, что Windows NT 4.0 Enterprise процессу выделяется дополнительный 1 Гбайт за счет сокращения системной области (рис. 5).

Разработчики Windows NT 5.0 для платформы Alpha пошли дальше. Alpha — 64-разрядный процессор, но под управлением Windows NT версии до 4.0 включительно в его адресном пространстве используются только 2 Гбайт старших и 2 Гбайт младших адресов (рис. 5).

Дело в том, что в Win32 API для хранения адреса используются 32-разрядные переменные. Windows NT расширяет их до 64-разрядных с учетом знака. Спецификация VLM (Very Large Memory), которая будет реализована в Windows NT 5.0 для процессора Alpha, подразумевает использование 64-разрядных переменных для хранения адресов (рис. 5).

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

Windows NT 4.0 / Windows NT 4.0 Enterprise

Windows NT на процессоре Alpha

Windows NT 5.0 при использовании спецификации VLM

Рисунок 5.
Адресное пространство процесса Windows NT

Windows NT поддерживает сегментностраничную
модель виртуальной памяти и использует
для этих целей аппаратную поддержку
таких процессоров как Intel 80386 и выше,
MIPS R4000, DEC Alpha и Power PC. Для этого в NT executive
имеется специальный компонент — менеджер
виртуальной памяти.

Менеджер ВП обеспечивает для процессов
следующие наборы функций:

  • управление виртуальным адресным
    пространством процесса;

  • разделение памяти между процессами;

  • защита виртуальной памяти одного
    процесса от других процессов.

Средства защиты памяти в Windows NT существуют
в четырех формах.

  • Отдельное адресное пространство для
    каждого процесса. Аппаратура запрещает
    нити доступ к физическим адресам другого
    процесса.

  • Два режима работы: режим ядра, в котором
    нитям разрешен доступ к системным
    данным, и пользовательский режим, в
    котором это запрещено.

  • Страничный механизм защиты. Каждая
    виртуальная страница имеет набор
    признаков, который определяет разрешенные
    типы доступа в пользовательском режиме
    и в режиме ядра.

  • Объектно-ориентированная защита памяти.
    Каждый раз, когда процесс открывает
    указатель на секцию, монитор ссылок
    безопасности проверяет, разрешен ли
    доступ процесса к данному объекту.

Каждый раз, когда нить использует адрес,
менеджер ВП вместе с аппаратными
средствами транслирует виртуальный
адрес в физический. Подсистема виртуальной
памяти, управляя процессом трансляции
виртуальных адресов, гарантирует, что
нить одного процесса не сможет получить
доступ к физической странице памяти,
относящейся к другому процессу.

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

Каждый процесс NT executive имеет большое
виртуальное адресное пространство
размером в 4Гб, из которых 2 Гб резервируются
для системных нужд. (Процессор MIPS R4000
требует, чтобы 2 Гб адресного пространства
были зарезервированы для системы. Хотя
другие процессоры требуют меньше, для
переносимости системы Windows NT всегда
резервирует 2 Гб.) Младшие адреса
виртуального адресного пространства
доступны для нитей, работающих и в
пользовательском, и в привилегированном
режимах, они указывают на области памяти,
уникальные для каждого процесса. Старшая
часть адресов доступна для нитей только
тогда, когда они выполняются в
привилегированном режиме. Виртуальное
адресное пространство процесса :

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

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

В составе менеджера виртуальной памяти
имеется такой компонент, как пейджер
(pager). Этот код перемещает страницы между
диском и памятью, представляя собой
промежуточное звено между аппаратными
механизмами и программно-реализуемыми
стратегиями. В его функции входит:

  • загрузка страницы в память при
    возникновении страничного прерывания,

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

  • обновление структур данных подсистемы
    управления памятью.

Процесс принятия решения о замене
страниц системой виртуальной памяти
обычно включает три фазы: извлечение,
размещение, замена.

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

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

Этап размещения. Набор правил,
используемых для определения места
размещения новой страницы в памяти,
называется стратегией размещения. В
Windows NT менеджер виртуальной памяти
просто выбирает первую страницу из
списка свободных физических страниц.
База данных физических страниц — это
массив записей, пронумерованных от 0 до
максимального номера страницы, зависящего
от объема памяти. Каждая запись содержит
информацию о соответствующей физической
странице. Менеджер виртуальной памяти
использует прямые связи в случае, когда
процесс запрашивает доступ к виртуальному
адресу в действительной виртуальной
странице.

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

Менеджер виртуальной памяти Windows NT
использует локальный алгоритм FIFO (First
Input First Output). В соответствии с алгоритмом
FIFO из памяти удаляется та страница,
которая дольше всего там находится.
Локальность в данном случае означает,
что поиск страницы-кандидата на выгрузку
осуществляется только среди страниц
того процесса, который требует загрузки
новой страницы. Существуют и глобальные
стратегии, в соответствии с которыми
поиск замещаемой страницы выполняется
на множестве страниц всех процессов.
Локальный вариант стратегии не дает
одному процессу возможность захватить
всю имеющуюся память.

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

В Windows NT реализована сегментно-страничная
модель распределения памяти. Для хранения
информации о состоянии виртуальных
сегментов используется набор структур,
называемых дескрипторами виртуальных
адресов
. Когда процессу назначается
новая область памяти, менеджер виртуальной
памяти создает дескриптор, в котором
хранится вся информация, связанная с
этой областью, такая как диапазон
адресов, признаки того, является ли
память разделяемой или частной, будет
ли процесс-потомок наследовать содержимое
этой области, признаки защиты. Затем
дескриптор встраивается в двоичное
дерево дескрипторов данного процесса,
используемое для ускорения поиска.

Для снижения объема вычислений,
затрачиваемых на работу менеджера
виртуальной памяти, в Windows NT минимизируется
количество страничных прерываний. Для
этого предпринимаются следующие меры:

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

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

1 Управление памятью

  • Немного истории
  • Виртуальная память в Microsoft Windows NT
  • Функции для работы с виртуальной памятью
  • Приложение VIRTUAL
  • Работа с пулами памяти
  • Приложение HEAPMEM
  • Система управления памятью, встроенная в ядро Microsoft Windows NT, является самой сложной и самой совершенной из тех, с которыми нам приводилось встречаться до сих пор. Она наилучшим образом отвечает потребностям современных приложений, “пожирающих” оперативную память мегабайтами и десятками мегабайт.

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

    Сложность системы управления памятью обусловлена тем, что она использует все аппаратные возможности современных процессоров, в том числе механизм страничной адресации памяти. Так как операционная система Microsoft Windows NT является мультизадачной и может работать в мультипроцессорных системах, программисту приходится учитывать это при организации работы приложения с памятью.

    В распоряжении программиста имеются функции, предназначенные для работы с памятью на разных уровнях. Наиболее низкоуровневые средства обеспечивают работу с виртуальной памятью на уровне отдельных страниц. Более высокоуровневые средства позволяют получать блоки памяти практически любого размера из отдельных пулов, принадлежащих процессу. Можно также использовать классические функции стандартной библиотеки транслятора языка C, такие как malloc XE «malloc» и free XE «free» . Кроме того, вам доступны функции, которые пришли из программного интерфейса 16-разрядной операционной системы Microsoft Windows весии 3.1. Особое место занимают функции, предназначенные для отображения файлов в виртуальную оперативную память, позволяющие работать с файлами как с обычными массивами.

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

    Немного истории

    Для того чтобы вам было легче разобраться с системой управления памятью Microsoft Windows NT и ощутить, насколько она совершенна, сделаем краткий обзор принципов управления памятью, положенных в основу операционных систем MS-DOS и Microsoft Windows версии 3.1.

    Управление памятью в MS-DOS

    Принципы управления памятью в операционной системе MS-DOS и соответствующий программный интерфейс были рассмотрены нами в 19 томе “Библиотеки системного программиста”, который называется “MS-DOS для программиста”.

    Напомним, что MS-DOS использует так называемый реальный режим работы процессора XE «реальный режим работы процессора» . Такие операционные системы, как Microsoft Windows версии 3.1, Microsoft Windows 95, Microsoft Windows NT и IBM OS/2 на этапе своей загрузки переводят процессор в защищенный режим работы. Подробно эти режимы мы описали в 6 томе “Библиотеки системного программиста”, который называется “Защищенный режим работы процессоров 80286/80386/80486”.

    В реальном режиме работы процессора физический адрес XE «физический адрес» , попадающий на шину адреса системной платы компьютера составляется из двух 16-разрядных компонент, которые называются сегментом XE «сегмент» и смещением XE «смещение» (рис. 1.1).

    Рис.1.1. Получение физического адреса в реальном режиме работы процессора

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

    Задавая произвольные значения для сегмента и смещения, программа может сконструировать физический адрес для обращения к памяти размером чуть больше одного мегабайта. Точное значение с учетом наличия области старшей памяти XE «область старшей памяти» High Memory Area XE «High Memory Area» равно 1 Мбайт + 64 Кбайт — 16 байт.

    Хорошо знакомая вам из программирования для MS-DOS комбинация компонет [сегмент : смещение] называется логическим адресом XE «логический адрес» реального режима. В общем виде процедура преобразования логического адреса в физический схематически изображена на рис. 1.2.

    Рис. 1.2. Преобразование логического адреса в физический

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

    Логические адреса реального режима находятся в диапазоне от [0000h : 0000h] до [FFFFh : 000Fh]. Это соответствует диапазону физических адресов от 00000h до FFFFFh, лежащих в пределах первого мегабайта оперативной памяти. Задавая логические адреса в пределах от [FFFFh : 0010h] до [FFFFh : FFFFh], можно адресовать область старшей памяти High Memory Area, имеющей размер 64 Кбайт — 16 байт.

    Таким образом, схема преобразования адреса реального режима не позволяет адресовать больше одного мегабайта памяти, что явно недостаточно для работы современных приложений. Даже если в вашем компьютере установлено 16 Мбайт памяти, операционная система MS-DOS не сможет адресовать непосредственно старшие 15 Мбайт (рис. 1.3).

    Рис. 1.3. Адресация памяти в MS-DOS

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

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

    Что же касается программного интерфейса для управления памятью в MS-DOS, то такой существует в рамках прерывания INT 21h XE «INT 21h» . Он позволяет программам заказывать и освобождать области памяти, лежащие в границах первого мегабайта. Однако ничто не мешает программам выйти за пределы полученной области памяти, выполнив при этом самоликвидацию или уничтожение операционной системы.

    Управление памятью в Microsoft Windows версии 3.1

    Подробно схема управления памятью в Microsoft Windows версии 3.1 и соответствующие функции программного интерфейса мы описали в 13 томе “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1 для программиста. Часть третья”. Здесь же мы остановимся только на самых важных моментах, необходимых для понимания отличий этой схемы от схемы адресации памяти в Microsoft Windows NT.

    Адресация памяти

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

    В стандартном режиме Microsoft Windows селектор и смещение всегда являются 16-разрядными. Когда Microsoft Windows работает в расширенном режиме, одна из компонент этой операционной системы (виртуальные драйверы VxD) работает в режиме 32-разрядной адресации. При этом селектор XE «селектор» является 16-разрядным, а смещение XE «смещение» — 32-разрядным. Что же касается обычных приложений, то и в расширенном режиме они не используют все возможности процессора 80386. В частности, размеры сегментов обычного приложения Windows не превышают 64 Кбайт.

    Тем не менее расширенный режим работы обладает большим преимуществом: в нем используется страничная адресация и виртуальная память. Схема адресации для стандартного режима была описана в 13 томе “Библиотеки системного программиста”. Виртуальные драйверы описаны в 17 томе этой же серии книг.

    В 32-разрядном режиме адресации (который в Microsoft Windows версии 3.1 используется только виртуальными драйверами) выполняется двухступенчатое преобразование логического адреса в физический, показанное на рис. 1.4.

    Рис. 1.4. Преобразование логического адреса в физический для 32-разрядного режима

    На первом этапе логический адрес, состоящий из 16-разрядного селектора и 32-разрядного смещения, преобразуется в 32-разрядный линейный адрес XE «линейный адрес» .

    Если бы линейный адрес отображался один к одному на физический (что возможно), то с его помощью можно было бы адресовать 232 = 4294967296 байт памяти, то есть 4 Гбайт. Дальнейшие преобразования линейного адреса в 32-разрядный физический адрес с использованием механизма страничной адресации позволяют еще больше расширить размер адресуемой памяти. И хотя в Microsoft Windows версии 3.1 приложения не могут использовать больше 256 Мбайт виртуальной памяти, операционная система Microsoft Windows NT успешно преодалевает этой барьер. Забегая вперед, скажем, что каждому приложению Microsoft Windows NT доступно ни много ни мало как… 2 Гбайта виртуальной памяти! Лишь бы в компьютере были установлены диски подходящей емкости.

    Как же выполняется преобразование логического адреса в линейный в защищенном режиме работы процессора?

    Как мы говорили в 6 и 13 томах “Библиотеки системного программиста”, для данного преобразования используется одна глобальная таблица дескрипторов GDT XE «GDT «(Global Descriptor TableXE «Global Descriptor Table») и несколько локальных таблиц дескрипторов LDT XE «LDT «(Local Descroptor TableXE «Local Descroptor Table»). Это справедливо как для Microsoft Windows версии 3.1, так и для Microsoft Windows NT.

    В таблицах дескрипторов хранятся элементы, описывающие отдельные сегменты памяти. В этих элементах среди всего прочего хранится одна из компонент линейного адреса — так называемый базовый адрес XE «базовый адрес» , имеющий 32 разряда. Преобразователь логического адреса в линейный складывает базовый адрес и 32-разрядное смещение, в результате чего получается 32-разрядный линейный адрес.

    Селектор предназначен для индексации внутри одной из перечисленных выше таблиц дескрипторов. Селектор состоит из трех полей — индекса, индикатора TI и поля уровня привилегий RPL (рис. 1.5).

    Рис. 1.5. Формат селектораXE «формат селектора»

    Поле TI (Table IndicatorXE «Table Indicator») используется для выбора глобальной или локальной таблицы дескрипторов. В любой момент времени может использоваться одна глобальная таблица дескрипторов и одна локальная таблица дескрипторов. Если бит TI равен 0, для выборки базового адреса используется глобальная таблица дескрипторов GDT XE «GDT» , если 1 — локальная LDT XE «LDT» .

    Поле RPL (Requested Privilege LevelXE «Requested Privilege Level») селектора содержит уровень привилегий, запрошенный приложением при обращении к сегменту памяти, который описывается дескриптором. Программа может обращаться только к таким сегментам, которые имеют соответствующий уровень привилегий. Поэтому программа не может, например, воспользоваться глобальной таблицей дескрипторов для получения доступа к описанным в ней системным сегментам, если она не обладает достаточным уровнем привилегий. На этом основана защита системных данных от разрушения (преднамеренного или в результате программной ошибки) со стороны прикладных программ.

    Упрощенная схема преобразования логического адреса в линейный показана на рис. 1.6.

    Рис. 1.6. Преобразование логического адреса в линейный

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

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

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

    Таким образом, в защищенном режиме приложение не может делать с адресами все, что ему вздумается, как это было в реальном режиме. Более подробную информацию об этом вы можете получить из 6 тома “Библиотеки системного программиста”.

    Зачем нужно использовать дескрипторные таблицы двух типов?

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

    Заметим, что операционная система Microsoft Windows версии 3.1 создает одну общую глобальную таблицу дескрипторов, одну таблицу локальных дескрипторов для системной виртуальной машины, в рамках которой работают все приложения Windows, и по одной локальной таблице дескрипторов для каждой виртуальной машины DOS. Подробнее об этом вы можете узнать из главы “Драйверы для Windows” 17 тома “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1 для программиста. Дополнительные главы”.

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

    Займемся теперь преобразователем линейного адреса в физический. Процесс такого преобразования имеет самое непосредственное отношение к страничной адресации памяти.

    Линейный адрес разделяется на три поля:

    ·       номер таблицы в каталоге таблиц страниц (10 бит);

    ·       номер страницы в таблице страниц (10 бит);

    ·       смещение внутри страницы (12 бит)

    Вся память при этом делится на страницы XE «страницы памяти» размером 4096 байт, адресуемые с помощью специальных таблиц страниц. В системе существует один каталог таблиц страниц XE «каталог таблиц страниц» и много таблиц страниц. Таблица страниц XE «таблица страниц» содержит 32-разрядные физические адреса страниц памяти.

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

    Рис. 1.7. Преобразование линейного адреса в физический

    Отметим, что преобразование линейного адреса в физический выполняется аппаратурой процессора с помощью каталога таблиц страниц XE «каталог таблиц страниц «и таблиц страницXE «таблица страниц», подготовленных операционной системой. Приложение Windows никогда не работает с таблицами страниц или каталогом таблиц страниц. Оно пользуется логическими адресами в формате [селектор : смещение].

    Основное преимущество системы управления памятью расширенного режима Windows заключается в возможности использования виртуальной памяти.

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

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

    Пулы памяти в Microsoft Windows версии 3.1

    Напомним, что в Microsoft Windows версии 3.1 существует глобальная область памяти (глобальный пул XE «глобальный пул» Global Heap XE «Global Heap» ), доступная для всех приложений. Кроме того, для каждого приложения выделяется “в личное пользование” локальный пул XE «локальный пул» Local Heap XE «Local Heap» размером 64 Кбайт.

    Если одно приложение получает область памяти из глобального пула, оно может передать адрес этой области другому приложению и то сможет им воспользоваться (так как все приложения работают в одном адресном пространстве). В операционной системе Microsoft Windows NT этот прием работать не будет, так как каждое приложение работает в отдельном адресном пространстве с использованием отдельной локальной таблицы дескрипторов.

    Для получения памяти из глобального и локального пула в программном интерфейсе Microsoft Windows версии 3.1 были предусмотрены отдельные функции с именами GlobalAlloc XE «GlobalAlloc» и LocalAlloc XE «LocalAlloc» . Выделенную этими функциями область памяти перед использованием было необходимо зафиксировать, вызвав, соответственно, функции GlobalLock XE «GlobalLock» и LocalLock XE «LocalLock» . Для выделяемых областей памяти автоматически создавались дескрипторы в локальной таблице дескрипторов.

    При необходимости приложение могло изменять содержимое глобальной или локальной таблицы дескрипторов, для чего в программном интерфейсе Microsoft Windows версии 3.1 были предусмотрены соответствующие функции. Эти функции, описанные нами в 13 томе “Библиотеки системного программиста”, давали обычным приложениям Windows почти ничем не ограниченные права доступа к памяти. Поэтому можно утверждать, что несмотря на использование защищенного режима работы процессора, по надежности Microsoft Windows версии 3.1 недалеко ушла от операционной системы MS-DOS.

    Виртуальная память в Microsoft Windows NT

    В основе всей системы управления памятью Microsoft Windows NT лежит система виртуальной памяти, встроенная в ядро операционной системы. Эта система позволяет приложениям использовать области памяти, размер которых значительно превышает объем установленной в компьютере физической оперативной памяти.

    Насколько значительно?

    Каждое приложение, запущенное в среде Microsoft Windows NT, может адресовать до 2 Гбайт виртуальной памяти. Причем для каждого приложения выделяется отдельное пространство виртуальной памяти, так что если, например, вы запустили 10 приложений, то в сумме они могут использовать до 20 Гбайт виртуальной памяти. При всем при этом в компьютере может быть установлено всего лишь 16 Мбайт физической оперативной памяти.

    Казалось бы, зачем приложениям столько памяти?

    Заметим, однако, что требования программ к объему оперативной памяти растут очень быстро. Еще совсем недавно были в ходу компьютеры с объемом оперативной памяти всего 1 — 4 Мбайт. Однако современные приложения, особенно связанные с обработкой графической или мультимедийной информации предъявляют повышенные требования к этому ресурсу. Например, размер файла с графическим изображением True Color может достигать десятков Мбайт, а размер файла с видеоизображением — сотен Мбайт. Без использования виртуальной памяти возможность работы с такими файлами была бы проблематичной.

    Так же как и в операционной системе Microsoft Windows версии 3.1, в Microsoft Windows NT для создания виртуальной памяти используются дисковые устройства (рис. 1.8). Система виртуальной памяти Microsoft Windows NT позволяет создать до 16 отдельных файлов страниц, расположенных на разных дисковых устройствах, установленных в компьютере. Так как максимальный объем доступной виртуальной памяти определяется объемом использованных для нее дисковых устройств, то при необходимости вы можете, например, подключить к компьютеру несколько дисков большой емкости и разместить на них файлы страниц. Сегодня стали уже вполне доступными дисковые устройства с объемом 4 — 10 Гбайт, что позволяет в Microsoft Windows NT создавать виртуальную память действительно большого размера.

    Рис. 1.8. Виртуальная память в Microsoft Windows NT создается с использованием оперативной памяти и дисковых устройств

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

    Несегментированная модель памяти FLAT XE «FLAT»

    Операционная система Microsoft Windows NT использует все возможности защищенного режима процессора, в частности, переключение задач и страничную адресацию. Схема преобразования адреса похожа на ту, что была применена в Microsoft Windows версии 3.1, однако есть много отличий.

    Наиболее значительное — полный отказ от использования сегментированной модели памяти в 32-разрядных приложениях, к которой вы привыкли, создавая программы для MS-DOS и Microsoft Windows версии 3.1. В самом деле, используя 32-разрядное смещение, приложение может работать с памятью объемом 4 Гбайт без использования сегментных регистров процессора.

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

    Несегментированная модель памяти называется сплошной моделью памяти FLAT XE «FLAT» . При программировании в этой модели памяти вам не потребуются ключевые слова near и far, так как все объекты, расположенные в памяти, адресуются с использованием только одного смещения. В этом модель памяти FLAT напоминает модель памяти TINY XE «TINY» , с тем исключением, однако, что в последней размер адресуемой памяти не может превышать 64 Кбайт (из-за того что в модели TINY используется один сегмент и 16-разрядное смещение).

    Таким образом, в распоряжении каждого приложения (или точнее говоря, каждого процесса) Microsoft Windows NT предоставляется линейное адресное пространство размером 4 Гбайта. Область размером 2 Гбайта с диапазоном адресов от 00000000h до 7FFFFFFFh предоставлена в распоряжение приложению, другие же 2 Гбайта адресуемого пространства зарезервированы для использования операционной системой (рис. 1.9).

    Рис. 1.9. Адресное пространство приложения Microsoft Windows NT

    Как это показано на рис. 1.9, небольшая часть адресного пространства в пределах первых 2 Гбайт также зарезервирована для операционной системы. Это области размером 64 Кбайта, расположенные в самом начале адресного пространства, а также в конце первых 2 Гбайт, и предназначенные для обнаружения попыток использования неправильных указателей. Таким образом, приложения не могут адресовать память в диапазонах адресов 00000000h — 0000FFFFh и 7FFF0000h — 7FFFFFFFh.

    Изолированные адресные пространства

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

    Заметим, что приложение не имеет физической возможности выполнить адресацию памяти в пространстве, принадлежащем другому приложению. Какой бы линейный адрес ни задавало приложение, этот адрес всегда будет соответствовать одной из страниц, принадлежащих самому приложению. Такое положение дел обеспечивается системой управления виртуальной памятью Microsoft Windows NT и значительно повышает устойчивость операционной системы.

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

    В операционной системе Microsoft Windows версии 3.1 все приложения работали в одном адресном пространстве. Поэтому для передачи данных между приложениями можно было заказать область памяти в глобальном пуле с помощью функции GlobalAlloc XE «GlobalAlloc» и затем передать адрес этой области другому приложению. В Microsoft Windows NT этот метод работать не будет.

    Выход, тем не менее, есть. Ничто не мешает операционной системе создать в каталоге страниц нескольких приложений специальный дескриптор, указывающий на страницы виртуальной памяти общего пользования. Такие дескрипторы называются дескрипторами прототипа PTE XE «дескриптор прототипа PTE» (Prototype Page Table Entry XE «Prototype Page Table Entry» ) и используются для совместного использования страниц памяти в операционной системе Microsoft Windows NT.

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

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

    Дескрипторы страниц памяти

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

    На рис. 1.10 показан формат дескриптора страницы XE «дескриптор страницы памяти» .

    Рис. 1.10. Формат дескриптора страницы

    Физический адрес страницы имеет 20 разрядов. Для получения 32-разрядного физического адреса байта внутри страницы к нему добавляются 12 байт смещения, взятые из линейного адреса, как это было показано на рис. 1.7.

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

    Биты с 3 по 6 содержат номер файла страниц, в котором находится страница, соответствующая данному дескриптору. Напомним, что в отличие от Microsoft Windows версии 3.1, операционная система Microsoft Windows NT позволяет создать до 16 файлов страниц.

    Биты с 0 по 2 описывают состояние страницы памяти. Станица может быть отмечена флагами T (находится в переходном состоянии), D (обновленная, но не сохраненная в файле страниц), и P (присутствующая в памяти). Если приложение выполняет попытку обращения к странице памяти, которой нет в памяти, возникает аппаратное прерывание и нужная страница автоматически читается из соответствующего файла страниц в физическую оперативную память. После этого работа приложения продолжается.

    Состояние страниц памяти

    В дополнение к трем битам состояния страниц, хранящихся в дескрипторах страниц, система управления виртуальной памятью хранит состояние страниц XE «состояние страниц памяти» в специальной базе данных страниц. В этой базе данных страница может быть отмечена как имеющая одно из следующих состояний:

     Состояние

     Описание

     Свободная

     Страница доступна для использования после ее заполнения нулями

     Заполненная нулями

     Свободная страница, заполненная нулями и доступная для использования приложениями

     Правильная

     Страница используется активным процессом

     Измененная

     Содержимое страницы было изменено, однако она не быле еще сохранена на диске в файле страниц

     Запасная

     Страница удалена из рабочего набора страниц процесса

     Плохая

     При обращении к этой странице возникла аппаратная ошибка

    Обратите внимание, что если часть оперативной памяти неисправна, есть вероятность, что операционная система Microsoft Windows NT сможет продолжить работу. Неисправные страницы будут отмечены в базе данных страниц как плохие и к ним не будет выполняться обращение.

    Функции для работы с виртуальной памятью

    В программном интерфейсе Microsoft Windows NT имеются средства, предназначенные для работы с виртуальной памятью. Они работают на уровне страниц памяти, имеющих размер 4096 байт, поэтому обычно нет смыла использовать эти функции только для того чтобы заказать в программе буфер размером в несколько десятков байт.

    Получение виртуальной памяти

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

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

    Для чего может потребоваться резервирование диапазона адресов?

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

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

    Для того чтобы зарезервировать или получить в свое распоряжение некоторое количество страниц виртуальной памяти, приложение должно воспользоваться функцией VirtualAlloc XE «VirtualAlloc» , прототип которой представлен ниже:

    
    LPVOID VirtualAlloc(
      LPVOID lpvAddress,        // адрес области
      DWORD  cbSize,            // размер области
      DWORD  fdwAllocationType, // способ получения памяти
      DWORD  fdwProtect);       // тип доступа
    

    Параметры lpvAddress и cbSize задают, соответственно, начальный адрес и размер резервируемой либо получаемой в пользование области памяти. При резервировании адрес округляется до ближайшей границы блока размером 64 Кбайт. В остальных случаях адрес округляется до границы ближайшей страницы памяти.

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

    Что же касается параметра cbSize, то он округляется до целого числа страниц. Поэтому если вы пытаетесь с помощью функции VirtualAlloc XE «VirtualAlloc» получить область памяти размером в один байт, вам будет выделена страница размером 4096 байт. Аналогично, при попытке получить блок памяти размером 4097 байт вы получите две страницы памяти общим размером 8192 байта. Как мы уже говорили, программный интерфейс системы управления виртуальной памятью не предназначен для работы с областями малого размера.

    Для параметра fdwAllocationType вы можете использовать одно из следующих значений:

     Значение

     Описание

     MEM_RESERVE XE «MEM_RESERVE»

     Функция VirtualAlloc XE «VirtualAlloc» выполняет резервирование диапазона адресов в адресном пространстве приложения

     MEM_COMMIT XE «MEM_COMMIT»

     Выполняется выделение страниц памяти для непосредственной работы с ними. Выделенные страницы заполняются нулями

     MEM_TOP_DOWN XE «MEM_TOP_DOWN»

     Память выделяется в области верхних адресов адресного пространства приложения

    С помощью параметра fdwProtect приложение может установить желаемй тип доступа для заказанных страниц. Можно использвать одно из следующих значений:

    Значение

    Разрешенный доступ

    PAGE_READWRITE XE «PAGE_READWRITE»

    Чтение и запись

    PAGE_READONLY XE «PAGE_READONLY»

    Только чтение

    PAGE_EXECUTE XE «PAGE_EXECUTE»

    Только исполнение программного кода

    PAGE_EXECUTE_READ XE «PAGE_EXECUTE_READ»

    Исполнение и чтение

    PAGE_EXECUTE_READWRITE XE «PAGE_EXECUTE_READWRITE»

    Исполнение, чтение и запись

    PAGE_NOACCESS XE «PAGE_NOACCESS»

    Запрещен любой вид доступа

    PAGE_GUARD XE «PAGE_GUARD»

    Сигнализация доступа к старнице. Это значение можно использовать вместе с любыми другими, кроме PAGE_NOACCESS XE «PAGE_NOACCESS»

    PAGE_NOCACHE XE «PAGE_NOCACHE»

    Отмена кэширования для страницы памяти. Используется драйверами устройств. Это значение можно использовать вместе с любыми другими, кроме PAGE_NOACCESS XE «PAGE_NOACCESS»

    Если страница отмечена как PAGE_READONLY XE «PAGE_READONLY» , при попытке записи в нее возникает аппаратное прерывание защиты доступа (access violation). Эта страница также не может содержать исполнимый код. Попытка выполнения такого кода приведет к возникновению прерывания.

    С другой стороны, у вас есть возможность получения страниц, предназначенных только для хранения исполнимого кода. Если такие страницы отмечены как PAGE_EXECUTE, для них не разрешаются операции чтения и записи.

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

    В случае успешного завершения функция VirtualAlloc XE «VirtualAlloc» возвратит адрес зарезервированной или полученной области страниц. При ошибке будет возвращено значение NULL.

    Приложение может вначале зарезервировать страницы, вызвав функцию VirtualAlloc XE «VirtualAlloc» с параметром MEM_RESERVE XE «MEM_RESERVE» , а затем получить их в пользование, вызвав эту же функцию еще раз для полученной области памяти, но уже с параметром MEM_COMMIT XE «MEM_COMMIT» .

    Освобождение виртуальной памяти

    После использования вы должны освободить полученную ранее виртуальную память, вызвав функцию VirtualFree XE «VirtualFree» :

    
    BOOL VirtualFree(
      LPVOID lpvAddress,   // адрес области
      DWORD  cbSize,       // размер области
      DWORD  fdwFreeType); // выполняемая операция
    

    Через параметры lpvAddress и cbSize передаются, соответственно, адрес и размер освобождаемой области.

    Если вы зарезервировали область виртуальной памяти функцией VirtualAlloc XE «VirtualAlloc» с параметром MEM_RESERVE XE «MEM_RESERVE» для последующего получения страниц в пользование и затем вызвали эту функцию еще раз с параметром MEM_COMMIT XE «MEM_COMMIT» , вы можете либо совсем освободить область памяти, обозначив соответствующие страницы как свободные, либо оставить их зарезервированными, но не используемыми.

    В первом случае вы должны вызвать функцию VirtualFree XE «VirtualFree» с параметром fdwFreeType, равным MEM_RELEASE XE «MEM_RELEASE» , во втором — с параметром MEM_DECOMMIT XE «MEM_DECOMMIT» .

    Три состояния страниц виртуальной памяти

    Страницы виртуальной памяти, принадлежащие адресному пространству процесса в Microsoft Windows NT, могут находиться в одном из трех состояний. Они могут быть свободными (free XE «free» ), зарезервированными (reserved) или выделенными для использования (committed). В адресном пространстве приложения есть также относительно небольшое количество страниц, зарезервированных для себя операционной системой. Эти страницы недоступны приложению.

    Функция VirtualAlloc XE «VirtualAlloc» может либо зарезервировать свободные страницы памяти (для чего ее нужно вызвать с параметром MEM_RESERVE XE «MEM_RESERVE» ), либо выделить свободные или зарезервированные страницы для непосредственного использования (для этого функция вызывается с параметром MEM_COMMIT XE «MEM_COMMIT» ). Приложение может либо сразу получить страницы памяти в использование, либо предварительно зарезервировать их, обеспечив доступное сплошное адресное пространство достаточного размера.

    Для того чтобы зарезервированная или используемая область памяти стала свободной, вы должны вызвать для нее функцию VirtualFree XE «VirtualFree» с параметром MEM_RELEASE XE «MEM_RELEASE» .

    Вы можете перевести страницы используемой области памяти в зарезервированное состояние, не освобождая соответствующего адресного пространства. Это можно сделать при помощи функции VirtualFree XE «VirtualFree» с параметром MEM_DECOMMIT XE «MEM_DECOMMIT» .

    На рис. 1.11 мы показали три состояния страниц виртуальной памяти и способы перевода страниц из одного состояния в другое.

    Рис. 1.11. Три состояния страниц виртуальной памяти

    Фиксирование страниц виртуальной памяти

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

    В программном интерфейсе Microsoft Windows NT есть функция VirtualLock XE «VirtualLock» , с помощью которой нетрудно зафиксировать нужное вам количество страниц в физической памяти.

    Прототип функции VirtualLock XE «VirtualLock» представлен ниже:

    
    BOOL VirtualLock(
      LPVOID lpvAddress, // адрес начала фиксируемой
                         // области памяти
      DWORD  cbSize);    // размер области в байтах
    

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

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

    Для расфиксирования страниц памяти следует вызвать функцию VirtualUnlock, имеющую аналогичное назначение параметров:

    
    BOOL VirtualUnlock(
      LPVOID lpvAddress, // адрес начала расфиксируемой
                         // области памяти
      DWORD  cbSize);    // размер области в байтах
    

    Сколько страниц памяти можно зафиксировать функцией VirtualLock XE «VirtualLock» ?

    Не очень много. По умолчанию приложение может зафиксировать не более 30 страниц виртуальной памяти. И это сделано не зря — фиксирование большого количества страниц одним приложением уменьшает объем физической памяти, доступной для других приложений и может отрицательно сказаться на производительности всей системы в целом. Однако при необходимости вы можете увеличить это значение при помощи функции SetProcessWorkingSetSize XE «SetProcessWorkingSetSize» , описанной в SDK.

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

    Изменение типа разрешенного доступа для страниц
    памяти

    При получении страниц памяти в пользование функцией VirtualAlloc XE «VirtualAlloc» вы можете в последнем параметре указать тип доступа, разрешенного для этих страниц. В процессе работы приложение может изменять тип доступа для полученных им страниц при помощи функции VirtualProtect, прототип которой представлен ниже:

    
    BOOL VirtualProtect(
      LPVOID lpvAddress,      // адрес области памяти
      DWORD  cbSize,          // размер области памяти в байтах
      DWORD  fdwNewProtect,   // новый тип разрешенного доступа
      PDWORD pfdwOldProtect); // указатель на переменную,
                    // в которую будет записан прежний код доступа
    

    Через параметр lpvAddress вы должны передать адрес области памяти, расположенной в готовых для использования страницах (а не в зарезервированных страницах).

    Новый тип доступа передается через параметр fdwNewProtect. Здесь вы можете использовать все константы, что и для последнего параметра функции VirtualAlloc XE «VirtualAlloc» , например, PAGE_READWRITE XE «PAGE_READWRITE» или PAGE_READONLY XE «PAGE_READONLY» .

    Зачем вам может пригодиться изменение кода доступа?

    Например, вы можете получить страницы страницы памяти, доступные на запись, а затем, записав туда данные, разрешить доступ только на чтение или на исполнение. Устанавливая тип доступа PAGE_NOACCESS XE «PAGE_NOACCESS» для страниц, которые в данный момент не используются приложением, вы можете, например, обнаружить во время исполнения кода ошибки, связанные с использованием неправильных указателей. Для решения аналогичных задач можно также устанавливать тип доступа PAGE_GUARD XE «PAGE_GUARD» .

    Заметим, что функция VirtualProtect позволяет изменить код доступа только для тех страниц, которые созданы вызывающим ее процессом. При необходимости приложение может изменить код доступа страниц другого процесса, имеющего код доступа PROCESS_VM_OPERATION XE «PROCESS_VM_OPERATION» (например, процесса, созданного приложением). Это можно сделать при помощи функции VirtualProtectEx XE «VirtualProtectEx» , прототип которой представлен ниже:

    
    BOOL VirtualProtectEx(
      HANDLE hProcess,        // идентификатор процесса
      LPVOID lpvAddress,      // адрес области памяти
      DWORD  cbSize,          // размер области памяти в байтах
      DWORD  fdwNewProtect,   // новый тип разрешенного доступа
      PDWORD pfdwOldProtect); // указатель на переменную,
                    // в которую будет записан прежний код доступа
    

    Через параметр hProcess функции VirtualProtectEx XE «VirtualProtectEx» следует передать идентификатор процесса. Подробнее об этом идентификаторе вы узнаете из главы, посвященной мультизадачности в операционной системе Microsoft Windows NT.

    Получение информации об использовании виртуальной
    памяти

    В программном интерфейсе Microsoft Windows NT есть средства для получения справочной информации об использовании процессами виртуальной памяти. Это функции VirtualQuery XE «VirtualQuery» и VirtualQueryEx XE «VirtualQueryEx» . С помощью них процесс может исследовать, соответственно, собственное адресное пространство и адресное пространство других процессов, определяя такие характеристики областей виртуальной памяти, как размеры, тип доступа, состояние и так далее. Из-за ограниченного объема книги мы не будем рассматривать эти функции. При необходимости вы сможете найти их подробное описание в справочной системе, поставляющейся вместе с SDK или системой разработки Microsoft Visual C++.

    С помощью приложения Process Walker XE «приложение Process Walker» , которое поставляется в составе SDK, вы сможете визуально исследовать распределение виртуальной памяти для процессов. На рис. 1.12 показан фрагмент такого распределения для приложения CALC.EXE. Приложение вызывает функции VirtualQuery XE «VirtualQuery» и VirtualQueryEx XE «VirtualQueryEx» (исходные тексты приложения поставляется в составе SDK; вы найдете их в каталоге win32sdkmstoolssamplessdktoolswinntpviewer).

    Рис. 1.12. Исследование распределения виртуальной памяти при помощи приложения Process Walker

    Для загрузки и исследования процесса вы должны выбрать из меню Process строку Load Process. Затем при помощи диалоговой панели Open executable image следует выбрать загрузочный файл нужного вам приложения. В окне появится распределение памяти в виде таблицы.

    В столбце Address отобажается логический адрес областей памяти. Обратите внимание, что в модели памяти FLAT XE «FLAT» он состоит только из смещения. Базовый адрес отображается в столбце BaseAddr и имеет смысл только для зарезервированных (Reserve) или готовых к использованию (Commit) страниц. Для свободных страниц (Free) он равен нулю.

    В столбце Prot в виде двухбуквенного сокращения имен сответствующих констант отображается тип доступа, разрешенный для страниц. Например, страницы, доступные только на чтение, отмечены в этом столбце как RO (PAGE_READONLY XE «PAGE_READONLY» ).

    В линейном адресном пространстве процесса находятся не только код и данные самого процесса. В него также отображаются страницы системных библиотек динамической загрузки DLL (как это видно из рис. 1.12). При этом в столбце Object может отображаться тип объекта (библиотека DLL или исполняемый EXE-модуль), а в столбцах Section и Name, соответственно, имя секции и имя библиотеки DLL.

    Если сделать двойной щелчок левой клавишей мыши по строке, соответствующей страницам памяти, отмеченным как Commit, на экране появится окно с дампом содержимого этих страниц (рис. 1.13).

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

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

    Приложение VIRTUAL

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

    При помощи меню Set protection (рис. 1.14) вы можете установить для полученной страницы памяти тип доступа PAGE_NOACCES, PAGE_READONLY XE «PAGE_READONLY» , PAGE_READWRITE XE «PAGE_READWRITE» , а также комбинацию типов доступа PAGE_READWRITE и PAGE_GUARD XE «PAGE_GUARD» .

    Рис. 1.14. Меню Set protection, предназначенное для установки типа доступа

    При помощи строк меню Memory (рис. 1.15) вы можете выполнять над полученной страницей памяти операции чтения, записи, фиксирования и расфиксирования (соответственно, строки Read, Write, Lock и Unlock).

    Рис. 1.15. Меню Memory, предназначенное для выполнения различных операций над страницей памяти

    Первоначально для страницы устанавливается код доступа PAGE_NOACCES, запрещающий любой доступ, поэтому при выборе из меню Memory любой строки вы получите сообщение об ошибке.

    Если установить код доступа PAGE_READONLY XE «PAGE_READONLY» , то, как и следовало ожидать, вы сможете выполнить только операции чтения, фиксирования и расфиксирования страницы. Тип доступа PAGE_READWRITE XE «PAGE_READWRITE» дополнительно разешает выполнение операции записи.

    В том случае, когда для страницы устанволена комбинация типов доступа PAGE_READWRITE XE «PAGE_READWRITE» и PAGE_GUARD XE «PAGE_GUARD» , при первой попытке выполнения над этой страницей любой операции появляется сообщение об ошибке, так как возникает исключение. Во второй раз эта же операция выполнится без ошибки, так как после возникновения исключения тип доступа PAGE_GUARD сбрасывается автоматически.

    Когда любое исключение возникает в 16-разрядном приложении Microsoft Windows версии 3.1, оно завершает свою работу с сообщением о фатальной ошибке. С этим не может ничего сделать ни пользователь, ни программист, создающий такие приложения. Что же касается операционной системы Microsoft Windows NT, то приложение может самостоятельно выполнять обработку исключений.

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

    Рис. 1.16. Сообщение, возникающее на экране при попытке обращения к странице с типом доступа PAGE_NOACCES

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

    Аналогично, при попытке приложения обратиться к странице с типом доступа PAGE_GUARD XE «PAGE_GUARD» , на экране появится сообщение, показанное на рис. 1.17.

    Рис. 1.17. Сообщение, возникающее при попытке обращения к странице с типом доступа PAGE_GUARD XE «PAGE_GUARD»

    Наше приложение VIRTUAL способно обрабатывать исключения, поэтому даже при попытках выполнения неразрешенного вида доступа все ограничивается выводом соответствующего сообщения, после чего работа приложения может быть продолжена. На рис. 1.18 показано такое сообщение, возникающее при обращении к памяти с типом доступа PAGE_GUARD XE «PAGE_GUARD» .

    Рис. 1.18. Сообщение об исключении, отображаемое приложением VIRTUAL

    Обработку исключений мы рассмотрим в отдельной главе одной из следующих наших книг, посвященных операционной системе Microsoft Windows NT, а сейчас только скажем, что при ее использовании вы можете значительно повысить устойчивость работы приложения, встроив в него мощную систему проверки (и, в некоторых случаях, даже исправления) различных ошибок.

    Исходные тексты приложения

    Основной файл исходных текстов приложения VIRTUAL представлен в листинге 1.1. Заметим, что вы можете приобрести дискету, которая продается вместе с нашей книгой, избавив себя и от необходимости набирать исходные тексты приложений вручную, и от ошибок, неизбежно возникающих при этом.

    Листинг 1.1. Файл virtual/virtual.c

    
    #define STRICT
    #include <windows.h>
    #include <windowsx.h>
    #include <stdio.h>
    #include "resource.h"
    #include "afxres.h"
    #include "virtual.h"
    
    // Размер блока вируальной памяти, над которым будут
    // выполняться операции
    #define MEMBLOCK_SIZE 4096
    
    HINSTANCE hInst;
    char szAppName[]  = "VirtualApp";
    char szAppTitle[] = "Working with Virtual Memory";
    
    // Указатель на блок памяти
    LPVOID lpMemoryBuffer;
    
    // Идентификатор меню Set protection
    HMENU hSetMenu;
    
    // -----------------------------------------------------
    // Функция WinMain
    // -----------------------------------------------------
    int APIENTRY
    WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
            LPSTR lpCmdLine, int nCmdShow)
    {
      WNDCLASSEX wc;
      HWND hWnd;
      MSG msg;
    
      // Сохраняем идентификатор приложения
      hInst = hInstance;
    
      // Преверяем, не было ли это приложение запущено ранее
      hWnd = FindWindow(szAppName, NULL);
      if(hWnd)
      {
        // Если было, выдвигаем окно приложения на
        // передний план
        if(IsIconic(hWnd))
          ShowWindow(hWnd, SW_RESTORE);
        SetForegroundWindow(hWnd);
        return FALSE;
      }
    
      // Регистрируем класс окна
      memset(&wc, 0, sizeof(wc));
      wc.cbSize = sizeof(WNDCLASSEX);
      wc.hIconSm = LoadImage(hInst,
        MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0);
      wc.style = 0;
      wc.lpfnWndProc = (WNDPROC)WndProc;
      wc.cbClsExtra  = 0;
      wc.cbWndExtra  = 0;
      wc.hInstance = hInst;
      wc.hIcon = LoadImage(hInst,
        MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0);
      wc.hCursor = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
      wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU);
      wc.lpszClassName = szAppName;
      if(!RegisterClassEx(&wc))
        if(!RegisterClass((LPWNDCLASS)&wc.style))
              return FALSE;
    
      // Создаем главное окно приложения
      hWnd = CreateWindow(szAppName, szAppTitle,
         WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
         NULL, NULL, hInst, NULL);
      if(!hWnd) return(FALSE);
    
      // Отображаем окно и запускаем цикл
      // обработки сообщений
      ShowWindow(hWnd, nCmdShow);
      UpdateWindow(hWnd);
      while(GetMessage(&msg, NULL, 0, 0))
      {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
      return msg.wParam;
    }
    
    // -----------------------------------------------------
    // Функция WndProc
    // -----------------------------------------------------
    LRESULT WINAPI
    WndProc(HWND hWnd, UINT msg, WPARAM wParam,
            LPARAM lParam)
    {
      switch(msg)
      {
        HANDLE_MSG(hWnd, WM_CREATE,  WndProc_OnCreate);
        HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy);
        HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand);
    
        default:
          return(DefWindowProc(hWnd, msg, wParam, lParam));
      }
    }
    
    // -----------------------------------------------------
    // Функция WndProc_OnCreate
    // -----------------------------------------------------
    BOOL WndProc_OnCreate(HWND hWnd,
                          LPCREATESTRUCT lpCreateStruct)
    {
      // Временный указатель
      LPVOID lpReserved;
    
      // Резервируем страницы виртуальной памяти
      lpReserved = VirtualAlloc(NULL, MEMBLOCK_SIZE,
        MEM_RESERVE, PAGE_NOACCESS);
    
      // При ошибке завершаем работу приложения
      if(lpReserved == NULL)
      {
        MessageBox(hWnd,
          "Ошибка при резервировании памяти",
           szAppTitle, MB_OK | MB_ICONEXCLAMATION);
    
        return FALSE;
      }
    
      // Получаем память в пользование
      lpMemoryBuffer = VirtualAlloc(lpReserved,
        MEMBLOCK_SIZE, MEM_COMMIT, PAGE_NOACCESS);
    
      // При ошибке освобождаем зарезервированную ранее
      // память и завершаем работу приложения
      if(lpMemoryBuffer == NULL)
      {
        MessageBox(hWnd,
          "Ошибка при получении памяти для использования",
           szAppTitle, MB_OK | MB_ICONEXCLAMATION);
    
        VirtualFree(lpReserved, 0, MEM_RELEASE);
        return FALSE;
      }
    
      // Получаем идентификатор меню Set protection
      hSetMenu = GetSubMenu(GetMenu(hWnd), 1);
    
      // Отмечаем строку PAGE_NOACCESS этого меню
      CheckMenuItem(hSetMenu,
        ID_SETPROTECTION_PAGENOACCESS, MF_CHECKED);
    
      return TRUE;
    }
    
    // -----------------------------------------------------
    // Функция WndProc_OnDestroy
    // -----------------------------------------------------
    #pragma warning(disable: 4098)
    void WndProc_OnDestroy(HWND hWnd)
    {
    
      // Перед завершением работы приложения освобождаем
      // полученную ранее память
      if(lpMemoryBuffer != NULL)
        VirtualFree(lpMemoryBuffer, 0, MEM_RELEASE);
    
      PostQuitMessage(0);
      return 0L;
    }
    
    // -----------------------------------------------------
    // Функция WndProc_OnCommand
    // -----------------------------------------------------
    #pragma warning(disable: 4098)
    void WndProc_OnCommand(HWND hWnd, int id,
      HWND hwndCtl, UINT codeNotify)
    {
      int   test;
      DWORD dwOldProtect;
      char  chBuff[256];
    
      switch (id)
      {
        // Устанавливаем тип доступа PAGE_NOACCESS
        case ID_SETPROTECTION_PAGENOACCESS:
        {
          VirtualProtect(lpMemoryBuffer, MEMBLOCK_SIZE,
            PAGE_NOACCESS, &dwOldProtect);
    
          // Отмечаем строку PAGE_NOACCESS меню Set protection
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGENOACCESS, MF_CHECKED);
    
          // Убираем отметку с других строк меню Set protection
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGEREADONLY, MF_UNCHECKED);
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGEREADWRITE, MF_UNCHECKED);
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGEGUARD, MF_UNCHECKED);
    
          break;
        }
    
        // Устанавливаем тип доступа PAGE_READONLY
        case ID_SETPROTECTION_PAGEREADONLY:
        {
          VirtualProtect(lpMemoryBuffer, MEMBLOCK_SIZE,
            PAGE_READONLY, &dwOldProtect);
    
          // Отмечаем строку PAGE_READONLY меню Set protection
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGEREADONLY, MF_CHECKED);
    
          // Убираем отметку с других строк меню Set protection
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGENOACCESS, MF_UNCHECKED);
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGEREADWRITE, MF_UNCHECKED);
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGEGUARD, MF_UNCHECKED);
          break;
        }
    
        // Устанавливаем тип доступа PAGE_READWRITE
        case ID_SETPROTECTION_PAGEREADWRITE:
        {
          VirtualProtect(lpMemoryBuffer, MEMBLOCK_SIZE,
            PAGE_READWRITE, &dwOldProtect);
    
          // Отмечаем строку PAGE_READWRITE меню Set protection
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGEREADWRITE, MF_CHECKED);
    
          // Убираем отметку с других строк меню Set protection
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGENOACCESS, MF_UNCHECKED);
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGEREADONLY, MF_UNCHECKED);
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGEGUARD, MF_UNCHECKED);
          break;
        }
    
        // Устанавливаем тип доступа
        //   PAGE_READWRITE | PAGE_GUARD
        case ID_SETPROTECTION_PAGEGUARD:
        {
          VirtualProtect(lpMemoryBuffer, MEMBLOCK_SIZE,
            PAGE_READWRITE | PAGE_GUARD, &dwOldProtect);
    
          // Отмечаем строку PAGE_READWRITE & PAGE_GUARD
          // меню Set protection
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGEGUARD, MF_CHECKED);
    
          // Убираем отметку с других строк меню Set protection
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGENOACCESS, MF_UNCHECKED);
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGEREADONLY, MF_UNCHECKED);
          CheckMenuItem(hSetMenu,
            ID_SETPROTECTION_PAGEREADWRITE, MF_UNCHECKED);
          break;
        }
    
        // Выполняем попытку чтения
        case ID_MEMORY_READ:
        {
          __try
          {
            test = *((int *)lpMemoryBuffer);
          }
    
          // Если возникло исключение, получаем и
          // отображаем его код
          __except (EXCEPTION_EXECUTE_HANDLER)
          {
            sprintf(chBuff, "Исключение с кодомn"
              "%lXnпри чтении блока памяти",
              GetExceptionCode());
    
            MessageBox(hWnd, chBuff,
              szAppTitle, MB_OK | MB_ICONEXCLAMATION);
            break;
          }
    
          // Если операция завершилась успешно,
          // сообщаем об этом пользователю
          MessageBox(hWnd, "Чтение выполнено",
            szAppTitle, MB_OK | MB_ICONEXCLAMATION);
          break;
        }
    
        // Выполняем попытку записи
        case ID_MEMORY_WRITE:
        {
          __try
          {
            *((int *)lpMemoryBuffer) = 1;
          }
    
          // Если возникло исключение, получаем и
          // отображаем его код
          __except (EXCEPTION_EXECUTE_HANDLER)
          {
            sprintf(chBuff, "Исключение с кодомn"
              "%lXnпри записи в блок памяти",
              GetExceptionCode());
    
            MessageBox(hWnd, chBuff,
              szAppTitle, MB_OK | MB_ICONEXCLAMATION);
            break;
          }
    
          // Если операция завершилась успешно,
          // сообщаем об этом пользователю
          MessageBox(hWnd, "Запись выполнена",
            szAppTitle, MB_OK | MB_ICONEXCLAMATION);
          break;
        }
    
        // Выполняем попытку фиксирования блока памяти
        case ID_MEMORY_LOCK:
        {
          if(VirtualLock(lpMemoryBuffer,
            MEMBLOCK_SIZE) == FALSE)
          {
            MessageBox(hWnd,
              "Ошибка при фиксировании страниц в памяти",
              szAppTitle, MB_OK | MB_ICONEXCLAMATION);
            break;
          }
    
          MessageBox(hWnd, "Фиксирование выполнено",
            szAppTitle, MB_OK | MB_ICONEXCLAMATION);
          break;
        }
    
        // Выполняем попытку расфиксирования блока памяти
        case ID_MEMORY_UNLOCK:
        {
          if(VirtualUnlock(lpMemoryBuffer,
            MEMBLOCK_SIZE) == FALSE)
          {
            MessageBox(hWnd,
              "Ошибка при расфиксировании страниц в памяти",
              szAppTitle, MB_OK | MB_ICONEXCLAMATION);
            break;
          }
    
          MessageBox(hWnd, "Расфиксирование выполнено",
            szAppTitle, MB_OK | MB_ICONEXCLAMATION);
          break;
        }
    
        case ID_FILE_EXIT:
        {
          // Завершаем работу приложения
          PostQuitMessage(0);
          return 0L;
          break;
        }
    
        case ID_HELP_ABOUT:
        {
          MessageBox(hWnd,
            "Working with Virtual Memoryn"
            "(C) Alexandr Frolov, 1996n"
            "Email: frolov@glas.apc.org",
            szAppTitle, MB_OK | MB_ICONINFORMATION);
          return 0L;
          break;
        }
        default:
          break;
      }
      return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify,
        DefWindowProc);
    }
    

    В файле virtual.h (листинг 1.2) представлены прототипы функций, определенных в нашем приложении.

    Листинг 1.2. Файл virtual/virtual.h

    
    LRESULT WINAPI
      WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct);
    void WndProc_OnDestroy(HWND hWnd);
    void WndProc_OnCommand(HWND hWnd, int id,
      HWND hwndCtl, UINT codeNotify);
    

    Файл resource.h (листинг 1.3) создается автоматически системой разработки Microsoft Visual C++ и содержит определения констант, использованных в файле описания ресурсов приложения.

    Листинг 1.3. Файл virtual/resource.h

    
    //{{NO_DEPENDENCIES}}
    // Microsoft Developer Studio generated include file.
    // Used by Virtual.RC
    //
    #define IDR_APPMENU                     102
    #define IDI_APPICON                     103
    #define IDI_APPICONSM                   104
    #define ID_FILE_EXIT                    40001
    #define ID_HELP_ABOUT                   40003
    #define ID_SETPROTECTION_PAGENOACCESS   40035
    #define ID_SETPROTECTION_PAGEREADONLY   40036
    #define ID_SETPROTECTION_PAGEREADWRITE  40037
    #define ID_SETPROTECTION_PAGEGUARD      40038
    #define ID_MEMORY_READ                  40039
    #define ID_MEMORY_WRITE                 40040
    #define ID_MEMORY_LOCK                  40041
    #define ID_MEMORY_UNLOCK                40042
    
    // Next default values for new objects
    //
    #ifdef APSTUDIO_INVOKED
    #ifndef APSTUDIO_READONLY_SYMBOLS
    #define _APS_NEXT_RESOURCE_VALUE        121
    #define _APS_NEXT_COMMAND_VALUE         40043
    #define _APS_NEXT_CONTROL_VALUE         1000
    #define _APS_NEXT_SYMED_VALUE           101
    #endif
    #endif
    

    Файл описания ресурсов приложения virtual.rc (листинг 1.4) также автоматически создается системой разработки Microsoft Visual C++. В нем определены пиктограммы приложения, меню и текстовые строки (которые мы в данном случае не используем).

    Листинг 1.4. Файл virtual/virtual.rc

    
    //Microsoft Developer Studio generated resource script.
    //
    #include "resource.h"
    
    #define APSTUDIO_READONLY_SYMBOLS
    //////////////////////////////////////////////////////////////
    // Generated from the TEXTINCLUDE 2 resource.
    //
    #include "afxres.h"
    
    //////////////////////////////////////////////////////////////
    #undef APSTUDIO_READONLY_SYMBOLS
    
    //////////////////////////////////////////////////////////////
    // English (U.S.) resources
    
    #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
    #ifdef _WIN32
    LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
    #pragma code_page(1252)
    #endif //_WIN32
    
    //////////////////////////////////////////////////////////////
    // Menu
    //
    IDR_APPMENU MENU DISCARDABLE
    BEGIN
        POPUP "&File"
        BEGIN
            MENUITEM "E&xit", ID_FILE_EXIT
        END
        POPUP "&Set protection"
        BEGIN
          MENUITEM "PAGE_NOACCESS", ID_SETPROTECTION_PAGENOACCESS
          MENUITEM "PAGE_READONLY", ID_SETPROTECTION_PAGEREADONLY
          MENUITEM "PAGE_READWRITE",ID_SETPROTECTION_PAGEREADWRITE
          MENUITEM "PAGE_GUARD && PAGE_READWRITE",
                ID_SETPROTECTION_PAGEGUARD
        END
        POPUP "&Memory"
        BEGIN
            MENUITEM "&Read",               ID_MEMORY_READ
            MENUITEM "&Write",              ID_MEMORY_WRITE
            MENUITEM "&Lock",               ID_MEMORY_LOCK
            MENUITEM "&Unlock",                     ID_MEMORY_UNLOCK
        END
        POPUP "&Help"
        BEGIN
            MENUITEM "&About...",           ID_HELP_ABOUT
        END
    END
    
    #ifdef APSTUDIO_INVOKED
    //////////////////////////////////////////////////////////////
    // TEXTINCLUDE
    //
    1 TEXTINCLUDE DISCARDABLE
    BEGIN
        "resource.h"
    END
    
    2 TEXTINCLUDE DISCARDABLE
    BEGIN
        "#include ""afxres.h""rn"
        ""
    END
    
    3 TEXTINCLUDE DISCARDABLE
    BEGIN
        "rn"
        ""
    END
    
    #endif    // APSTUDIO_INVOKED
    
    //////////////////////////////////////////////////////////////
    // Icon
    
    // Icon with lowest ID value placed first to ensure application icon
    // remains consistent on all systems.
    IDI_APPICON             ICON    DISCARDABLE     "virtual.ico"
    IDI_APPICONSM           ICON    DISCARDABLE     "virtsm.ico"
    
    //////////////////////////////////////////////////////////////
    // String Table
    //
    STRINGTABLE DISCARDABLE
    BEGIN
        ID_FILE_EXIT            "Quits the application"
    END
    
    #endif    // English (U.S.) resources
    //////////////////////////////////////////////////////////////
    
    #ifndef APSTUDIO_INVOKED
    //////////////////////////////////////////////////////////////
    // Generated from the TEXTINCLUDE 3 resource.
    //////////////////////////////////////////////////////////////
    #endif    // not APSTUDIO_INVOKED
    

    Описание исходных текстов приложения

    Если вы читали нашу книгу “Операционная система Microsoft Windows 95 для программиста” (22 том “Библиотеки системного программиста”), то вы уже сталкивались с 32-разрядными приложениями, работающими в сплошной модели памяти. При создании приложения VIRTUAL мы использовали те же инструментальные средства, что и в упомянутой книге, а именно Microsoft Visual C++ версии 4.0.

    Определения и глобальные переменные

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

    В глобальных переменных hInst, szAppName и szAppTitle хранится, соответственно, идентификатор приложения, полученный при запуске через параметр hInstance функции WinMain, имя и заголовок главного окна приложения.

    Глобальная переменная lpMemoryBuffer содержит указатель на блок виртуальной памяти размером MEMBLOCK_SIZE байт. Это именно тот блок памяти, с которым в нашем приложении выполняются все операции.

    И, наконец, в глобальной переменной hSetMenu хранится идентификатор меню Set protection, который необходим для выполнения операции отметки строк этого меню.

    Функция WinMain

    Изучая исходный текст функции WinMain, вы по большому счету не найдете никаких отличий от исходных текстов этой функции, приведенных в приложениях Microsoft Windows 95 из 22 тома “Библиотеки системного программиста”. В этом нет ничего удивительного, так как и в Microsoft Windows 95, и в Microsoft Windows NT реализован 32-разрядный интерфейс WIN32, которым мы должны пользоваться. Более того, разрабатывая приложения для Microsoft Windows 95, вы должны проверять их работоспособность в среде Microsoft Windows NT, так как в противном случае вы не сможете стать соискателем логотипа “Designed for Microsoft Windows 95”.

    Итак, вернемся к нашей функции WinMain.

    Прежде всего эта функция сохраняет идентификатор приложения hInstance в глобальной переменной hInst для дальнейшего использования.

    Затем выполняется поиск нашего приложения среди уже запущенных. При этом используется функция FindWindow. Если эта функция среди активных приложений найдет приложение с именем szAppName, она возвратит идентификатор его окна, если нет — значение NULL.

    Если приложение найдено и его окно не свернуто в пиктограмму (что проверяется при помощи функции IsIconic), оно выдвигается на передний план при помощи функции SetForegroundWindow. После этого функция WinMain завершает свою работу.

    В среде Microsoft Windows NT, так же как и в среде Microsoft Windows 95, мы не можем проверить, было ли наше приложение запущенно ранее, анализируя значение параметра hPrevInstance функции WinMain, так как в отличие от Microsoft Windows версии 3.1, в указанных операционных системах через этот параметр всегда передается значение NULL. Об этом мы подробно говорили в 22 томе “Библиотеки системного программиста”.

    Если приложение не найдено, выполняется регистрация класса окна. В отличие от операционной системы Microsoft Windows версии 3.1, в Microsoft Windows 95 и Microsoft Windows NT следует использовать регистрацию при помощи функции RegisterClassEx XE «RegisterClassEx» , подготовив для нее структуру WNDCLASSEX XE «WNDCLASSEX» .

    По сравнению со знакомой вам структурой WNDCLASS операционной системы Microsoft Windows версии 3.1 в структуре WNDCLASSEX имеются два дополнительных поля с именами cbSize и hIconSm. В первое из них необходимо записать размер структуры WNDCLASSEX, а во второе — идентификатор пиктограммы малого размера. Эта пиктограмма в Microsoft Windows NT версии 4.0 будет отображаться в левом верхнем углу главного окна приложения, играя роль пиктограммы системного меню.

    Для загрузки пиктограмм из ресуросв приложения мы воспользовались функцией LoadImage XE «LoadImage» , описанной нами в 22 томе “Библиотеки системного программиста”.

    Заметим, что если вызов функции регистрации класса окна RegisterClassEx XE «RegisterClassEx» закончился неудачно (это может произойти, например, если вы запустите приложение в среде Microsoft Windows NT версии 3.5 или более ранней версии), мы выполняем регистрацию старым способом, который тоже работает, — при помощи функции RegisterClass XE «RegisterClass» .

    После регистрации функция WinMain создает главное окно приложения, отображает и обновляет его, а затем запускает цикл обработки сообщений. Все эти процедуры мы описывали в 11 томе “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1 для программиста. Часть первая”.

    Функция WndProc

    Функция WndProc обрабатывает сообщения WM_CREATE XE «WM_CREATE» , WM_DESTROY XE «WM_DESTROY» и WM_COMMAND XE «WM_COMMAND» . Соответствующие функции обработки WndProc_OnCreate, WndProc_OnDestroy и WndProc_OnCommand назначаются для этих сообщений при помощи макрокоманды HANDLE_MSG XE «HANDLE_MSG» . Этот метод обработки сообщений был нами подробно рассмотрен в 22 томе “Библиотеки системного программиста”, поэтому сейчас мы не будем его описывать.

    Функция WndProc_OnCreate

    Напомним, что при создании окна его функции окна передается сообщение WM_CREATE XE «WM_CREATE» . Функция WndProc_OnCreate, определенная в нашем приложении, выполняет обработку этого сообщения.

    Прежде всего, функция резервирует область виртуальной памяти размером MEMBLOCK_SIZE байт, вызывая функцию VirtualAlloc XE «VirtualAlloc» с параметром MEM_RESERVE XE «MEM_RESERVE» :

    
    lpReserved = VirtualAlloc(NULL, MEMBLOCK_SIZE,
      MEM_RESERVE, PAGE_NOACCESS);
    

    Через первый параметр мы передаем функции VirtualAlloc XE «VirtualAlloc» значение NULL, поэтому операционная система сама определит для нас начальный адрес резервируемой области. Этот адрес мы сохраняем во временной локальной переменной lpReserved.

    В случае ошибки выводится соответствующее сообщение. Если же резервирование адресного пространства выполнено успешно, функция получает память в использование, вызывая для этого функцию VirtualAlloc XE «VirtualAlloc» еще раз, но уже с параметром MEM_COMMIT XE «MEM_COMMIT» :

    
    lpMemoryBuffer = VirtualAlloc(lpReserved,
      MEMBLOCK_SIZE, MEM_COMMIT, PAGE_NOACCESS);
    

    Так как в качестве первого параметра функции VirtualAlloc XE «VirtualAlloc» передается значение lpReserved, выделение страниц памяти выполняется в зарезервированной ранее области адресов.

    При невозможности получения памяти в диапазоне зарезервированных адресов мы отдаем зарезервированные адреса системе и завершаем работу приложения, запрещая создание его главного окна:

    
    VirtualFree(lpReserved, 0, MEM_RELEASE);
    return FALSE;
    

    Заметим, что мы могли бы и не вызывать функцию VirtualFree XE «VirtualFree» , так как после завершения процесса операционная система Microsoft Windows NT автоматически освобождает все распределенные для него ранее страницы виртуальной памяти.

    Последнее, что делает обработчик сообщения WM_CREATE XE «WM_CREATE» , это получение идентификатора меню Set protection и отметку в этом меню строки PAGE_NOACCESS XE «PAGE_NOACCESS» :

    
    hSetMenu = GetSubMenu(GetMenu(hWnd), 1);
    CheckMenuItem(hSetMenu,
      ID_SETPROTECTION_PAGENOACCESS, MF_CHECKED);
    

    Использованные при этом функции были описаны в главе “Меню” 13 тома “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1 для программиста. Часть третья”.

    Функция WndProc_OnDestroy

    Обработчик сообщения WM_DESTROY XE «WM_DESTROY» проверяет содрежимое указателя lpMemoryBuffer и, если оно не равно NULL, освобождает память при помощи функции VirtualFree XE «VirtualFree» :

    
    if(lpMemoryBuffer != NULL)
      VirtualFree(lpMemoryBuffer, 0, MEM_RELEASE);
    

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

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

    Функция WndProc_OnCommand

    Функция WndProc_OnCommand обрабатывает сообщение WM_COMMAND XE «WM_COMMAND» , поступающее от главного меню приложения. Выбирая строки меню Set protection, пользователь может изменять тип доступа, разрешенного для блока памяти, заказанного приложением при обработке сообщения WM_CREATE XE «WM_CREATE» . Меню Memory позволяет пользователю выполнять над этим блоком операции чтения, записи, фиксирования и расфиксирования.

    Изменение типа доступа выполняется при помощи функции VirtualProtect. Например, установка типа доступа PAGE_NOACCESS XE «PAGE_NOACCESS» выполняется следующим образом:

    
    VirtualProtect(lpMemoryBuffer, MEMBLOCK_SIZE,
      PAGE_NOACCESS, &dwOldProtect);
    

    При этом старый тип доступа записывается в переменную dwOldProtect, но никак не используется нашим приложением.

    После изменения типа доступа обработчик сообщения WM_COMMAND XE «WM_COMMAND» изменяет соответствующим образом отметку строк меню Set protection, для чего используется макрокоманда CheckMenuItem.

    Теперь рассмотрим обработку сообщения WM_COMMAND XE «WM_COMMAND» в том случае, когда оно приходит от меню Memory.

    Если пользователь выполняет попытку чтения блока памяти, выбирая из меню Memory строку Read, выполняется следующий фрагмент кода:

    
    case ID_MEMORY_READ:
    {
      __try
      {
        test = *((int *)lpMemoryBuffer);
      }
      __except (EXCEPTION_EXECUTE_HANDLER)
      {
        sprintf(chBuff, "Исключение с кодомn"
          "%lXnпри чтении блока памяти", GetExceptionCode());
        MessageBox(hWnd, chBuff,
          szAppTitle, MB_OK | MB_ICONEXCLAMATION);
        break;
      }
      MessageBox(hWnd, "Чтение выполнено",
        szAppTitle, MB_OK | MB_ICONEXCLAMATION);
      break;
    }
    

    Здесь в области действия оператора __try XE «__try» , ограниченной фигурными скобками, содержимое первого слова буфера lpMemoryBuffer читается во временную переменную test. Эта, безопасная на первый взгляд операция может привести в приложении Microsoft Windows NT к возникновению исключения, так как соответствующая страница памяти может оказаться недоступной для чтения. Если не предусмотреть обработку исключения, при его возникновении работа приложения завершится аварийно.

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

    Таким образом, если исключение возникнет, пользователь увидит сообщение с кодом исключения, а если нет — на экране появится сообщение о том, что чтение выполнено.

    Попытка записи выполняется при выборе из меню Memory строки Write. Вот фрагмент кода, выполняющий запись:

    
    __try
    {
      *((int *)lpMemoryBuffer) = 1;
    }
    

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

    Когда пользователь выбирает из меню Memory строку Lock, выполняется попытка зафиксировать страницы блока памяти при помощи функции VirtualLock XE «VirtualLock» , как это показано ниже:

    
    case ID_MEMORY_LOCK:
    {
      if(VirtualLock(lpMemoryBuffer, MEMBLOCK_SIZE) == FALSE)
      {
        MessageBox(hWnd,
           "Ошибка при фиксировании страниц в памяти",
           szAppTitle, MB_OK | MB_ICONEXCLAMATION);
        break;
      }
      MessageBox(hWnd, "Фиксирование выполнено",
        szAppTitle, MB_OK | MB_ICONEXCLAMATION);
      break;
    }
    

    Обратите внимание, что в данном случае мы не выполняем обработку исключений, но проверяем код возврата функции VirtualLock XE «VirtualLock» . При попытке зафиксировать страницу с кодом доступа PAGE_NOACCESS XE «PAGE_NOACCESS» не произойдет аварийного завершения работы приложения. В этом случае функция VirtualLock просто вернет значение FALSE, означающее ошибку.

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

    Работа с пулами памяти

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

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

    Пулы памяти в Microsoft Windows NT

    Как мы уже говорили, приложения Microsoft Windows версии 3.1 могли заказывать память из двух областей или двух пулов — из глобального пула, доступного всем приложениям, и локального, создаваемого для каждого приложения.

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

    По умолчанию для стандартного пула резервируется 1 Мбайт сплошного адресного пространства, причем 4 Кбайта памяти выделяются приложению для непосредственного использования. Если приложению требуется больше памяти, в адресном пространстве резервируется еще один или несколько Мбайт памяти.

    Если ваше приложение работает с большими объемами данных, для загрузки этих данных в непрерывное адресное пространство можно увеличить размер стандартного пула двумя способами.

    Во-первых, параметры стандартного пула можно задать в параметре /HEAP редактора связи:

    
    /HEAP: 0x2000000, 0x10000
    

    В данном случае для стандартного пула будет зарезервировано 2 Мбайта памяти, причем сразу после загрузки приложения 10 Кбайт памяти будет получено в пользование.

    Во-вторых, параметры стандартного пула можно указать в файле определения модуля (который является необязательным). Например, так:

    
    HEAPSIZE 0x2000000 0x10000
    

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

    Функции для работы с пулами памяти

    Итак, в распоряжении приложения Microsoft Windows NT имеется один стандартный пул и произвольное количество динамических пулов памяти.

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

    Получение идентификатора стандартного пула

    Идентификатор стандартного пула получить очень просто. Этот идентификатор возвращает функция GetProcessHeap XE «GetProcessHeap» , не имеющая параметров:

    
    HANDLE GetProcessHeap XE GetProcessHeap (VOID);
    

    Создание динамического пула

    Если вам нужен динамический пул, вы можете его создать при помощи функции HeapCreate XE «HeapCreate» :

    
    HANDLE HeapCreate(
      DWORD  flOptions,     // флаг создания пула
      DWORD  dwInitialSize, // первоначальный размер пула в байтах
      DWORD  dwMaximumSize);// максимальный размер пула в байтах
    

    Параметры dwMaximumSize и dwInitialSize определяют, соответственно, размер зарезервированной для пула памяти и размер памяти, полученной для использования.

    Через параметр flOptions вы можете передать нулевое значение, а также значения HEAP_NO_SERIALIZE XE «HEAP_NO_SERIALIZE» и HEAP_GENERATE_EXCEPTIONS XE «HEAP_GENERATE_EXCEPTIONS» .

    Параметр HEAP_NO_SERIALIZE XE «HEAP_NO_SERIALIZE» имеет отношение к мультизадачности, которая будет рассмотрена в отдельной главе нашей книги. Если этот параметр не указан, работающие параллельно задачи одного процесса не могут одновременно получать доступ к такому пулу. Вы можете использовать флаг HEAP_NO_SERIALIZE для повышения производительности, если создаваемым вами пулом будет пользоваться только одна задача процесса.

    При выделении памяти из пула могут возникать ошибочные ситуации. Если не указан флаг HEAP_GENERATE_EXCEPTIONS XE «HEAP_GENERATE_EXCEPTIONS» , при ошибках соотвтетвующий функции будут возвращать значение NULL. В противном случае в приложении будут генерироваться исключения. Флаг HEAP_GENERATE_EXCEPTIONS удобен в тех случаях, когда в вашем приложении предусмотрена обработка исключений, позволяющая исправлять возникающие ошибки.

    В случае удачи функция HeapCreate XE «HeapCreate» возвращает идентификатор созданного динамического пула памяти. При ошибке возвращается значение NULL (либо возникает исключение, если указан флаг HEAP_GENERATE_EXCEPTIONS).

    Удаление динамического пула

    Для удаления динамического пула памяти, созданного функцией HeapCreate XE «HeapCreate» , вы должны использовать функцию HeapDestroy XE «HeapDestroy» :

    
    BOOL HeapDestroy XE HeapDestroy (HANDLE hHeap);
    

    Через единственный параметр этой функции передается идентификатор удаляемого динамического пула. Заметим, что вам не следует удалять стандартный пул, передавая этой функции значение, полученное от функции GetProcessHeap XE «GetProcessHeap» .

    Функция HeapDestroy XE «HeapDestroy» выполняет безусловное удаление пула памяти, даже если из него были получены блоки памяти и на момент удаления пула они не были возвращены системе.

    Получение блока памяти из пула

    Для получения памяти из стандартного или динамического пула приложение должно воспользоваться функцией HeapAlloc XE «HeapAlloc» , прототип которой мы привели ниже:

    
    LPVOID HeapAlloc(
      HANDLE hHeap,    // идентификатор пула
      DWORD  dwFlags,  // управляющие флаги
      DWORD  dwBytes); // объем получаемой памяти в байтах
    

    Что касается параметра hHeap, то для него вы можете использовать либо идентификатор страндартного пула памяти, полученного от функции GetProcessHeap XE «GetProcessHeap» , либо идентификатор динамического пула, созданного приложением при помощи функции HeapCreate XE «HeapCreate» .

    Параметр dwBytes определяет нужный приложению объем памяти в байтах.

    Параметр dwFlags может быть комбинацией следующих значений:

     Значение

     Описание

     HEAP_GENERATE_EXCEPTIONS XE «HEAP_GENERATE_EXCEPTIONS»

     Если при выполнении функции произойдет ошибка, возникнет исключение

     HEAP_NO_SERIALIZE XE «HEAP_NO_SERIALIZE»

     Если указан этот флаг, не выполняется блокировака одновременного обращения к блоку памяти нескольких задач одного процесса

     HEAP_ZERO_MEMORY XE «HEAP_ZERO_MEMORY»

     Выделенная память заполняется нулями

    Изменение размера блока памяти

    С помощью функции HeapReAlloc XE «HeapReAlloc» приложение может изменить размер блока памяти, выделенного ранее функцией HeapAlloc XE «HeapAlloc» , уменьшив или увеличив его. Прототип функции HeapReAlloc приведен ниже:

    
    LPVOID HeapReAlloc(
      HANDLE hHeap,    // идентификатор пула
      DWORD  dwFlags,  // флаг изменения размера блока памяти
      LPVOID lpMem,    // адрес блока памяти
      DWORD  dwBytes); // новый размер блока памяти в байтах
    

    Для пула hHeap эта функция изменяет размер блока памяти, расположенного по адресу lpMem. Новый размер составит dwBytes байт.

    В случае удачи функция HeapReAlloc XE «HeapReAlloc» возвратит адрес нового блока памяти, который не обязательно будет совпадать с адресом, полученным этой функцией через параметр lpMem.

    Через параметр dwFlags вы можете передавать те же параметры, что и через аналогичный параметр для функции HeapAlloc XE «HeapAlloc» . Дополнительно можно указать параметр HEAP_REALLOC_IN_PLACE_ONLY XE «HEAP_REALLOC_IN_PLACE_ONLY» , определяющий, что при изменении размера блока памяти его нужно оставить на прежнем месте адресного пространства. Очевидно, что если указан этот параметр, в случае успешного завершения функция HeapReAlloc XE «HeapReAlloc» вернет то же значение, что было передано ей через параметр lpMem.

    Определение размера блока памяти

    Зная адрес блока памяти, полученного из пула, вы можете определить его размер при помощи функции HeapSize XE «HeapSize» :

    
    DWORD HeapSize(
      HANDLE  hHeap,   // идентификатор пула
      DWORD   dwFlags, // управляющие флаги
      LPCVOID lpMem);  // адрес проверяемого блока памяти
    

    В случае ошибки эта функция возвращает значение 0xFFFFFFFF.

    Если блоком памяти пользуется только одна задача процесса, вы можете передать через параметр dwFlags значение HEAP_NO_SERIALIZE XE «HEAP_NO_SERIALIZE» .

    Освобождение памяти

    Память, выделенную с помощью функции HeapAlloc XE «HeapAlloc» , следует освободить, как только в ней отпадет надобность. Это нужно сделать при помощи функции HeapFree XE «HeapFree» :

    
    BOOL HeapFree(
      HANDLE hHeap,   // идентификатор пула
      DWORD  dwFlags, // флаги освобождения памяти
      LPVOID lpMem);  // адрес освобождаемого блока памяти
    

    Если блоком памяти пользуется только одна задача процесса, вы можете передать через параметр dwFlags значение HEAP_NO_SERIALIZE XE «HEAP_NO_SERIALIZE» .

    Если размер блока памяти, выделенного функцией HeapAlloc XE «HeapAlloc» , был изменен функцией HeapReAlloc XE «HeapReAlloc» , для освобождения такого блока памяти вы все равно должны использовать функцию HeapFree XE «HeapFree» .

    Использование функций malloc XE «malloc» и
    free
    XE «free»

    В библиотеке Microsoft Visual C++ имеются стандартные функции, предназначенные для динамического получения и освобождения памяти, такие как malloc XE «malloc» и free XE «free» .

    У нас есть хорошая новость для вас — в среде Microsoft Windows NT вы можете использовать эти функции с той же эффективностью, что и функции, предназначенные для работы с пулами — HeapAlloc XE «HeapAlloc» , HeapFree XE «HeapFree» и так далее. Правда, эти функции получают память только из стандартного пула.

    Одно из преимуществ функций malloc XE «malloc» и free XE «free» заключается в возможности их использования на других платформах, отличных от Microsoft Windows NT.

    Старые функции управления памятью

    В 32-разрядных приложениях Microsoft Windows NT вы можете пользоваться многими функциями управления памятью операционной системы Microsoft Windows версии 3.1, которые оставлены в новой операционной системе для совместимости. Мы подробно рассмотрели эти функции в главе “Управление памятью” 13 тома “Библиотеки системного программиста”.

    Напомним, что в 16-разрядном программном интерфейсе Microsoft Windows версии 3.1 существует два набора функций (глобальные и локальные), предназначенных для работы с глобальным и локальным пулом памяти. Это такие функции, как GlobalAlloc XE «GlobalAlloc» , LocalAlloc XE «LocalAlloc» , GlobalFree XE «GlobalFree» , LocalFree XE «LocalFree» и так далее. В 32-разрядных приложениях Microsoft Windows NT вы можете пользоваться как глобальными, так и локальными функциями, причем результат будет совершенно одинаковый. Причина этого заключается в том, что все эти функции пользуются функциями программного интерфейса Microsoft Windows NT, предназначенными для работы со стандартным пулом памяти: HeapAlloc XE «HeapAlloc» , HeapReAlloc XE «HeapReAlloc» , HeapFree XE «HeapFree» и так далее.

    Вот список функций старого программного интерфейса, доступных приложениям Microsoft Windows NT:

     Имя функции

     Описание

     GlobalAlloc XE «GlobalAlloc» , LocalAlloc XE «LocalAlloc»

     Получение глобального (локального для функции LocalAlloc XE «LocalAlloc» ) блока памяти

     GlobalReAlloc XE «GlobalReAlloc» , LocalReAlloc XE «LocalReAlloc»

     Изменение размера глобального (локального) блока памяти

     GlobalFree XE «GlobalFree» , LocalFree XE «LocalFree»

     Освобождение глобального (локального) блока памяти

     GlobalLock XE «GlobalLock» , LocalLock XE «LocalLock»

     Фиксирование глобального (локального) блока памяти

     GlobalUnlock XE «GlobalUnlock» , LocalUnlock

     Расфиксирование глобального (локального) блока памяти

     GlobalSize XE «GlobalSize» , LocalSize XE «LocalSize»

     Определение размера глобального (локального) блока памяти

     GlobalDiscard XE «GlobalDiscard» , LocalDiscard XE «LocalDiscard»

     Принудительное удаление глобального (локального) блока памяти

     GlobalFlags XE «GlobalFlags» , LocalFlags XE «LocalFlags»

     Определение состояния глобального (локального) блока памяти

     GlobalHandle XE «GlobalHandle» , LocalHandle XE «LocalHandle»

     Определение идентификатора глобального (локального) блока памяти

    Заметим, что хотя при получении памяти с помощью функции GlobalAlloc XE «GlobalAlloc» вы по-прежнему можете указывать флаг GMEM_DDESHARE XE «GMEM_DDESHARE» , другие приложения, запущенные в среде Microsoft Windows NT, не будут иметь к этой памяти доступ. Причина очевидна — адресные пространства приложений изолированы. Однако в документации SDK сказано, что этот флаг можно использовать для увеличения производительности приложений, использующих механизм динамической передачи сообщений DDE XE «механизм динамической передачи сообщений DDE» . Этот механизм мы подробно описали в главе “Обмен данными через DDE XE «DDE» ” в 17 томе “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1. Дополнительные главы”.

    Обратим ваше внимание также на то, что в среде Microsoft Windows версии 3.1 вы могли получать фиксированную (fixed), перемещаемую (moveable) и удаляемую (discardable) память.

    В среде Microsoft Windows NT вы по-прежнему можете пользоваться различными типами памяти, если для получения блоков памяти используете функции GlobalAlloc XE «GlobalAlloc» или LocalAlloc XE «LocalAlloc» . Однако теперь вам едва ли потребуется перемещаемая память, так как новая система управления памятью выполняет операцию перемещения с помощью механизма страничной адресации, не изменяя значение логического адреса.

    В том случае, если вы все же решили получить блок перемещаемой памяти, перед использованием его необходимо зафиксировать функцией GlobalLock XE «GlobalLock» или LocalLock XE «LocalLock» (соответственно, для блоков памяти, полученных функциями GlobalAlloc XE «GlobalAlloc» и LocalAlloc XE «LocalAlloc» ). Это нужно сделать потому что если вы заказываете перемещаемый блок памяти, функции GlobalAlloc и LocalAlloc возвращают не адрес блока памяти, а его идентификатор.

    Если же вы получаете фиксированный блок памяти, то функции GlobalAlloc XE «GlobalAlloc» и LocalAlloc XE «LocalAlloc» вернут вам его адрес, который можно немедленно использовать. При этом надо иметь в виду, что операционная система сможет перемещать этот блок памяти без изменения его логического адреса.

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

    Приложение HEAPMEM

    На примере приложения HEAPMEM мы покажем вам, как можно использовать функции, предназначенные для работы с пулами памяти.

    В отличие от предыдущего приложения, приложение HEAPMEM работает в так называемом консольном (или текстовом) режиме. Такое приложение может пользоваться стандартными функциями консольного ввода/вывода из библиотеки C++. Для него система создает отдельное окно (рис. 1.19).

    Рис. 1.19. Окно консольного приложения HEAPMEM

    Что делает наше приложение?

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

    Первый способ предполагает использование динамического пула памяти и обработку исключений. В приложении намеренно создаются две ситуации, в которых происходят исключения с кодами C0000017 и C0000005. Во второй раз приложение работает со стандартным пулом памяти и не обрабатыает исключения, проверяя код завершения функций. И, наконец, третий способ связан с использованием функций malloc XE «malloc» и free XE «free» .

    Исходный текст приложения

    Исходный текст приложения HEAPMEM представлен в листинге 1.5. Файлы описания ресурсов и определения модуля не используются.

    Листинг 1.5. Файл heapmem/heapmem.c

    
    #include <windows.h>
    #include <stdio.h>
    #include <conio.h>
    
    int main()
    {
      // Идентификатор динамического пула
      HANDLE hHeap;
    
      // Указатель, в который будет записан адрес
      // полученного блока памяти
      char *lpszBuff;
    
      // =================================================
      // Работа с динамическим пулом
      // Используем структурную обработку исключений
      // =================================================
    
      // Создаем динамический пул
      hHeap = HeapCreate(0, 0x1000, 0x2000);
    
      // Если произошла ошибка, выводим ее код и
      // завершаем работу приложения
      if(hHeap == NULL)
      {
        fprintf(stdout,"HeapCreate: Error %ldn",
          GetLastError());
        getch();
        return 0;
      }
    
      // Пытаемся получить из пула блок памяти
      __try
      {
        lpszBuff = (char*)HeapAlloc(hHeap,
          HEAP_GENERATE_EXCEPTIONS, 0x1500);
      }
    
      // Если память недоступна, происходит исключение,
      // которое мы обрабатываем
      __except (EXCEPTION_EXECUTE_HANDLER)
      {
        fprintf(stdout,"1. HeapAlloc: Exception %lXn",
          GetExceptionCode());
      }
    
      // Пытаемся записать в буфер текстовую строку
      __try
      {
        strcpy(lpszBuff, "Строка для проверки");
      }
    
      // Если содержимое указателя lpszBuff равно NULL,
      // произойдет исключение
      __except (EXCEPTION_EXECUTE_HANDLER)
      {
        fprintf(stdout,"1. strcpy: Exception %lX n",
          GetExceptionCode());
      }
    
      // Выполняем повторную попытку, указывая меньший
      // размер блока памяти
      __try
      {
        lpszBuff = (char*)HeapAlloc(hHeap,
          HEAP_GENERATE_EXCEPTIONS, 0x100);
      }
      __except (EXCEPTION_EXECUTE_HANDLER)
      {
        fprintf(stdout,"2. HeapAlloc: Exception %lXn",
          GetExceptionCode());
      }
    
      __try
      {
        strcpy(lpszBuff, "Test string");
      }
      __except (EXCEPTION_EXECUTE_HANDLER)
      {
        fprintf(stdout,"2. strcpy: Exception %lX n",
          GetExceptionCode());
      }
    
      // Отображаем записанную строку
      if(lpszBuff != NULL)
        printf("String:>%s<n", lpszBuff);
    
      // Изменяем размер блока памяти
      __try
      {
        HeapReAlloc(hHeap, HEAP_GENERATE_EXCEPTIONS |
          HEAP_REALLOC_IN_PLACE_ONLY, lpszBuff, 150);
      }
      __except (EXCEPTION_EXECUTE_HANDLER)
      {
        fprintf(stdout,"HeapReAlloc: Exception %lX n",
          GetExceptionCode());
      }
    
      // Освобождаем блок памяти
      if(lpszBuff != NULL)
        HeapFree(hHeap, HEAP_NO_SERIALIZE, lpszBuff);
    
      // Удаляем пул памяти
      if(!HeapDestroy(hHeap))
        fprintf(stdout,"Ошибка %ld при удалении пулаn",
          GetLastError());
    
      // =================================================
      // Работа со стандартным пулом
      // Исключения не обрабатываем
      // =================================================
    
      // Получаем блок памяти из стандартного пула
      lpszBuff = (char*)HeapAlloc(GetProcessHeap(),
        HEAP_ZERO_MEMORY, 0x1000);
    
      // Если памяти нет, выводим сообщение об ошибке
      // и завершаем работу программы
      if(lpszBuff == NULL)
      {
        fprintf(stdout,"3. HeapAlloc: Error %ldn",
          GetLastError());
    
        getch();
        return 0;
      }
    
      // Выполняем копирование строки
      strcpy(lpszBuff, "Test string");
    
      // Отображаем скопированную строку
      printf("String:>%s<n", lpszBuff);
    
      // Освобождаем блок памяти
      if(lpszBuff != NULL)
        HeapFree(GetProcessHeap(), HEAP_NO_SERIALIZE, lpszBuff);
    
      // =================================================
      // Работа со стандартными функциями
      // Исключения не обрабатываем
      // =================================================
    
      lpszBuff = malloc(1000);
    
      if(lpszBuff != NULL)
      {
        strcpy(lpszBuff, "Test string");
        printf("String:>%s<n", lpszBuff);
        free(lpszBuff);
      }
    
      printf("Press any key...");
      getch();
      return 0;
    }
    

    В отличие от обычного приложения Microsoft Windows NT, исходный текст консольного приложения должен содержать функцию main (аналогично программе MS-DOS). В теле этой функции мы определили переменные hHeap и lpszBuff. Первая из них используется для хранения идентификатора динамического пула памяти, вторая — для хранения указателя на полученный блок памяти.

    Работа с динамическим пулом памяти

    Вначале наше приложение создает динамический пул памяти, вызывая для этого функцию HeapCreate XE «HeapCreate» . Для пула резервируется 2 Кбайта памяти, причем для непосредственного использования выделяется только 1 Кбайт.

    При возникновении ошибки ее код определяется с помощью функции GetLastError XE «GetLastError» и отображается в консольном окне хорошо знакомой вам из MS-DOS функцией fprintf. Затем работа приложения завершается.

    Заметим, что многие (но не все) функции программного интерфейса Microsoft Windows NT в случае возникновения ошибки перед возвращением управления устанавливают код ошибки, вызывая для этого функцию SetLastError XE «SetLastError» . При необходимости приложение может извлечь этот код сразу после вызова функции, как это показано в нашем приложении.

    Далее приложение пытается получить блок памяти размером 0x1500 байт, вызывая функцию HeapAlloc XE «HeapAlloc» :

    
    __try
    {
      lpszBuff = (char*)HeapAlloc(hHeap,
        HEAP_GENERATE_EXCEPTIONS, 0x1500);
    }
    

    Так как во втором параметре мы передали этой функции значение HEAP_GENERATE_EXCEPTIONS XE «HEAP_GENERATE_EXCEPTIONS» , в случае ошибки возникнет исключение. Поэтому вызов функции HeapAlloc XE «HeapAlloc» выполняется с обработкой исключений. Соответствующий обработчик получает код исключения при помощи функции GetExceptionCode XE «GetExceptionCode» и отображает его в консольном окне.

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

    На следующем шаге, невзирая на исключение, наше приложение пытается записать в блок памяти, указатель на который находится в переменной lpszBuff, текстовую строку:

    
    __try
    {
      strcpy(lpszBuff, "Строка для проверки");
    }
    

    Так как при получении блока памяти произошло исключение, в указателе lpszBuff находится неправильный адрес. Это, в свою очередь, приведет к возникновению исключения при попытке записи строки. Поэтому на рис. 1.19 в верхней части консольного окна находятся два сообщения об исключениях.

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

    Затем приложение пытается изменить размер полученного блока памяти, вызывая функцию HeapReAlloc XE «HeapReAlloc» :

    
    __try
    {
      HeapReAlloc(hHeap, HEAP_GENERATE_EXCEPTIONS |
        HEAP_REALLOC_IN_PLACE_ONLY, lpszBuff, 150);
    }
    

    Так как указан флаг HEAP_REALLOC_IN_PLACE_ONLY XE «HEAP_REALLOC_IN_PLACE_ONLY» , при изменении размера блок не будет перемещен, поэтому мы игнорируем значение, возвращаемое функцией HeapReAlloc XE «HeapReAlloc» .

    А что произойдет, если размер блока увеличится настолько, что он не поместится в адресном пространстве, отведенном для него ранее?

    Мы указали флаг HEAP_GENERATE_EXCEPTIONS XE «HEAP_GENERATE_EXCEPTIONS» , поэтому в этом случае произойдет исключение, которое наше приложение обработает.

    После изменения размера блока памяти приложение освобождает его функцией HeapFree XE «HeapFree» , а затем удаляет динамический пул памяти, так как мы больше не будем с ним работать.

    Работа со стандартным пулом памяти

    Второй способ выделения блока памяти основан на использовании стандартного пула. Для получения памяти из стандартного пула мы пользуемся функцией HeapAlloc XE «HeapAlloc» , передавая ей в качестве первого параметра значение идентификатора стандартного пула памяти, полученное от функции GetProcessHeap XE «GetProcessHeap» :

    
    lpszBuff = (char*)HeapAlloc(GetProcessHeap(),
      HEAP_ZERO_MEMORY, 0x1000);
    

    Так как мы указали флаг HEAP_ZERO_MEMORY XE «HEAP_ZERO_MEMORY» , полученный блок памяти будет расписан нулями. Флаг HEAP_GENERATE_EXCEPTIONS XE «HEAP_GENERATE_EXCEPTIONS» не указан, поэтому после вызова функции мы должны проверить значение, полученное от нее.

    На следующем этапе приложение выполняет копирование строки в блок памяти и отображение ее в консольном окне:

    
    strcpy(lpszBuff, "Test string");
    printf("String:>%s<n", lpszBuff);
    

    Так как исключения не обрабатываются, при их возникновении работа приложения завершится аварийно.

    После использования приложение освобождает блок памяти, полученный из стандартного пула, для чего вызывается функция HeapFree XE «HeapFree» :

    
    HeapFree(GetProcessHeap(), HEAP_NO_SERIALIZE, lpszBuff);
    

    Последний фрагмент приложения демонстрирует использование функций malloc XE «malloc» и free XE «free» для работы со стандартным пулом памяти и в комментариях не нуждается.

    Аннотация: Виртуальная память. Реализация виртуальной памяти в Windows. Структура виртуального адресного пространства. Выделение памяти процессам. Дескрипторы виртуальных адресов. Трансляция адресов. Ошибки страниц. Пределы памяти.

    Виртуальная память

    Всем процессам в операционной системе Windows предоставляется важнейший ресурсвиртуальная память (virtual memory). Все данные, с которыми процессы непосредственно работают, хранятся именно в виртуальной памяти.

    Название «виртуальная» произошло из-за того что процессу неизвестно реальное (физическое) расположение памяти – она может находиться как в оперативной памяти (ОЗУ), так и на диске. Операционная система предоставляет процессу виртуальное адресное пространство (ВАП, virtual address space) определенного размера и процесс может работать с ячейками памяти по любым виртуальным адресам этого пространства, не «задумываясь» о том, где реально хранятся данные.

    Размер виртуальной памяти теоретически ограничивается разрядностью операционной системы. На практике в конкретной реализации операционной системы устанавливаются ограничения ниже теоретического предела. Например, для 32-разрядных систем (x86), которые используют для адресации 32 разрядные регистры и переменные, теоретический максимум составляет 4 ГБ (232 байт = 4 294 967 296 байт = 4 ГБ). Однако для процессов доступна только половина этой памяти – 2 ГБ, другая половина отдается системным компонентам. В 64 разрядных системах (x64) теоретический предел равен 16 экзабайт (264 байт = 16 777 216 ТБ = 16 ЭБ). При этом процессам выделяется 8 ТБ, ещё столько же отдается системе, остальное адресное пространство в нынешних версиях Windows не используется.

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

    Реализация виртуальной памяти в Windows

    Схема реализации виртуальной памяти в 32-разрядной операционной системе Windows представлена на рис.11.1. Как уже отмечалось, процессу предоставляется виртуальное адресное пространство размером 4 ГБ, из которых 2 ГБ, расположенных по младшим адресам (0000 0000 – 7FFF FFFF), процесс может использовать по своему усмотрению (пользовательское ВАП), а оставшиеся два гигабайта (8000 0000 – FFFF FFFF) выделяются под системные структуры данных и компоненты (системное ВАП)1Специальный ключ /3GB в файле boot.ini увеличивает пользовательское ВАП до 3 ГБ, соответственно, уменьшая системное ВАП до 1 ГБ. Начиная с Windows Vista вместо файла boot.ini используется утилита BCDEDIT. Чтобы увеличить пользовательское ВАП, нужно выполнить следующую команду: bcdedit /Set IncreaseUserVa 3072. При этом, чтобы приложение могло использовать увеличенное ВАП, оно должно компилироваться с ключом /LARGEADDRESSAWARE.. Отметим, что каждый процесс имеет свое собственное пользовательское ВАП, а системное ВАП для всех процессов одно и то же.

    Реализация виртуальной памяти в 32-разрядных Windows

    Рис.
    11.1.
    Реализация виртуальной памяти в 32-разрядных Windows

    Виртуальная память делится на блоки одинакового размера – виртуальные страницы. В Windows страницы бывают большие (x86 – 4 МБ, x64 – 2 МБ) и малые (4 КБ). Физическая память (ОЗУ) также делится на страницы точно такого же размера, как и виртуальная память. Общее количество малых виртуальных страниц процесса в 32 разрядных системах равно 1 048 576 (4 ГБ / 4 КБ = 1 048 576).

    Обычно процессы задействуют не весь объем виртуальной памяти, а только небольшую его часть. Соответственно, не имеет смысла (и, часто, возможности) выделять страницу в физической памяти для каждой виртуальной страницы всех процессов. Вместо этого в ОЗУ (говорят, «резидентно») находится ограниченное количество страниц, которые непосредственно необходимы процессу. Такое подмножество виртуальных страниц процесса, расположенных в физической памяти, называется рабочим набором процесса (working set).

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

    Каким образом процесс узнает, где в данный момент находится требуемая страница? Для этого служат специальные структуры данных – таблицы страниц (page table).

    Структура виртуального адресного пространства

    Рассмотрим, из каких элементов состоит виртуальное адресное пространство процесса в 32 разрядных Windows (рис.11.2).

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

    При запуске программы создается процесс (см. лекцию 6 «Процессы и потоки»), при этом в память загружаются код и данные программы (исполняемый образ, executable image), а также необходимые программе динамически подключаемые библиотеки (DLL). Формируется куча (heap) – область, в которой процесс может выделять память динамическим структурам данных (т. е. структурам, размер которых заранее неизвестен, а определяется в ходе выполнения программы). По умолчанию размер кучи составляет 1 МБ, но при компиляции приложения или в ходе выполнения процесса может быть изменен. Кроме того, каждому потоку предоставляется стек (stack) для хранения локальных переменных и параметров функций, также по умолчанию размером 1 МБ.

    Структура виртуального адресного пространства

    Рис.
    11.2.
    Структура виртуального адресного пространства

    В системном ВАП расположены:

    • образы ядра (ntoskrnl.exe), исполнительной системы, HAL (hal.dll), драйверов устройств, требуемых при загрузке системы;
    • таблицы страниц процесса;
    • системный кэш;
    • пул подкачиваемой памяти (paged pool) – системная куча подкачиваемой памяти;
    • пул подкачиваемой памяти (nonpaged pool) – системная куча неподкачиваемой памяти;
    • другие элементы (см. [5]).

    Переменные, в которых хранятся границы разделов в системном ВАП, приведены в [5, стр. 442]. Вычисляются эти переменные в функции MmInitSystem (файл basentosmmmminit.c, строка 373), отвечающей за инициализацию подсистемы памяти. В файле basentosmmi386mi386.h приведена структура ВАП и определены константы, связанные с управлением памятью (например, стартовый адрес системного кэша MM_SYSTEM_CACHE_START, строка 199).

    Выделение памяти процессам

    Существует несколько способов выделения виртуальной памяти процессам при помощи Windows API2См. обзор в MSDN «Comparing Memory Allocation Methods» (http://msdn.microsoft.com/en-us/library/windows/desktop/aa366533(v=vs.85).aspx).. Рассмотрим два основных способа – с помощью функции VirtualAlloc и с использованием кучи.

    1. WinAPI функция VirtualAlloc позволяет резервировать и передавать виртуальную память процессу. При резервировании запрошенный диапазон виртуального адресного пространства закрепляется за процессом (при условии наличия достаточного количества свободных страниц в пользовательском ВАП), соответствующие виртуальные страницы становятся зарезервированными (reserved), но доступа к этой памяти у процесса нет – при попытке чтения или записи возникнет исключение. Чтобы получить доступ, процесс должен передать память зарезервированным страницам, которые в этом случае становятся переданными (commit).

    Отметим, что резервируются участки виртуальной памяти по адресам, кратным значению константы гранулярности выделения памяти MM_ALLOCATION_GRANULARITY (файл basentosincmm.h, строка 54). Это значение равно 64 КБ. Кроме того, размер резервируемой области должен быть кратен размеру страницы (4 КБ).

    WinAPI функция VirtualAlloc для выделения памяти использует функцию ядра NtAllocateVirtualMemory (файл basentosmmallocvm.c, строка 173).

    2. Для более гибкого распределения памяти существует куча процесса, которая управляется диспетчером кучи (heap manager). Кучу используют WinAPI функция HeapAlloc, а также оператор языка C malloc и оператор C++ new. Диспетчер кучи предоставляет возможность процессу выделять память с гранулярностью 8 байтов (в 32-разрядных системах), а для обслуживания этих запросов использует те же функции ядра, что и VirtualAlloc.

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

    Для хранения информации о зарезервированных страницах памяти используются дескрипторы виртуальных адресов (Virtual Address Descriptors, VAD). Каждый дескриптор содержит данные об одной зарезервированной области памяти и описывается структурой MMVAD (файл basentosmmmi.h, строка 3976).

    Границы области определяются двумя полями – StartingVpn (начальный VPN) и EndingVpn (конечный VPN). VPN (Virtual Page Number) – это номер виртуальной страницы; страницы просто нумеруются, начиная с нулевой. Если размер страницы 4 КБ (212 байт), то VPN получается из виртуального адреса начала страницы отбрасыванием младших 12 бит (или 3 шестнадцатеричных цифр). Например, если виртуальная страница начинается с адреса 0x340000, то VPN такой страницы равен 0x340.

    Дескрипторы виртуальных адресов для каждого процесса организованы в сбалансированное двоичное АВЛ дерево3АВЛ дерево – структура данных для организации эффективного поиска; двоичное дерево, сбалансированное по высоте. Названо в честь разработчиков – советских ученых Г. М. Адельсон Вельского и Е. М. Ландиса. (AVL tree). Для этого в структуре MMVAD имеются поля указатели на левого и правого потомков: LeftChild и RightChild.

    Для хранения информации о состоянии области памяти, за которую отвечает дескриптор, в структуре MMVAD содержится поле флагов VadFlags.

    1. Концепции Windows NT

    Операционная система Windows NT реализована в двух вариантах: Windows NT Server и Windows NT Workstation. Windows NT Server 4.0 — сетевая операционная система с приложениями для Internet, сервисами файлов и печати, службой удаленного доступа, встроенным маршрутизатором, индексированием файлов и управлением сетью. Второй вариант Windows NT — Windows NT Workstation 4.0 во многом напоминает NT Server, но она оптимизирована в качестве операционной системы для рабочей станции. С точки зрения архитектуры и возможностей Windows NT Server является надмножеством Windows NT Workstation и включает в себя все возможности последней. Далее, в случаях когда не указывается, какая из ОС имеется в виду, комментарии относятся к обеим.

    1.1. История создания, основные версии, перспективы развития

    1.1.1. Истоки Windows NT

    Начало работ по созданию Windows NT приходится на конец 88го года. Microsoft поручила Дэвиду Катлеру (David Cutler) возглавить новый проект в области программного обеспечения: разработку ОС новой технологии (New Technology — NT). Дэвид Катлер был главным консультантом фирмы DEC, он проработал в этой фирме 17 лет, разрабатывая ОС и компиляторы: VAX/VMS, ОС для MicroVAX I, OS RSX-11M, компиляторы VAX PL/1, VAX C.

    Сначала Windows NT развивалась как облегченный вариант OS/2 (OS/2 Lite), который за счет усечения некоторых функций мог бы работать на менее мощных машинах. Однако со временем, увидев как успешно принимается потребителями Windows 3.0, Microsoft переориентировалась и стала разрабатывать улучшенный вариант Windows 3.1. Новая стратегия Microsoft состояла в создании единого семейства базирующихся на Windows операционных систем, которые охватывали бы множество типов компьютеров, от самых маленьких ноутбуков до самых больших мультипроцессорных рабочих станций.

    Windows NT, как было названо следующее поколение Windowsсистем, относится к самому высокому уровню в иерархии семейства Windows. Эта операционная система, первоначально поддерживавшая привычный графический интерфейс (GUI) пользователя Windows, явилась первой полностью 32-разрядной ОС фирмы Microsoft. Win32 API — программный интерфейс для разработки новых приложений — сделал доступными для приложений улучшенные свойства ОС, такие как многонитевые процессы, средства синхронизации, безопасности, ввода-вывода, управление объектами.

    Концептуальные преимущества Windows NT по сравнению с парой MS-DOS/Windows 3.1 были очевидны. Ее 32-битная основа вместе с истинными многозадачностью и многонитевостью существенно повышали потенциал системы.

    Первые ОС семейства NT — Windows NT 3.1 и Windows NT Advanced Server 3.1 появились в июле 1993 года. Кодовое название следующей версии Windows NT 3.5 — Daytona, — совпадающее с названием скоростной трассы во Флориде, возможно, говорило о том, что ее главным достоинством является скорость. Действительно, производительность версии 3.5 возросла в 1,5 раза по сравнению в версией 3.1, и после ее появления многие корпоративные пользователи, которые отвергли версию 3.1 по тем или иным причинам, пересмотрели свое отношение к линии NT: в 1995 году доля Windows NT в своем секторе рынка возросла в 2 раза и составила 15%.

    1.1.2. Особенности версии Windows NT 4.0

    В августе 1996 года вышла очередная версия Windows NT 4.0. Сначала предполагалось, что эта очередная версия Windows NT получит номер 3.52, однако ей был присвоен номер 4.0, который раньше упоминался в компьютерной прессе в связи с другой ожидаемой версией Windows NT, имеющей кодовое название Cairo. Возможно это говорит о том, что в этой последней версии (Windows NT 4.0) появилось так много новых важных свойств, которые требуют более значимых изменений в кодировке. Новшества, внесенные в Windows NT Server 4.0, в основном связаны с улучшением интерфейса пользователя, расширением поддержки Internet, появлением новых и модернизацией существующих инструментов администрирования и повышением производительности системы.

    В Windows NT 4.0 было внесено много существенных изменений, среди которых наиболее значительными являются следующие:

    • реализация интерфейса в стиле Windows 95;
    • ориентировка в сторону Internet и intranet;
    • архитектурные изменения, позволившие резко повысить производительность графических операций;
    • модификация средств взаимодействия с NetWare — Gateway и клиент NCP поддерживают теперь NDS;
    • поддержка многопротокольной маршрутизации;
    • появление в Windows NT 4.0 эмулятора Intel’овских процессоров для RISC-платформ.

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

    Производительность Windows NT Server 4.0 при работе в качестве сервера файлов также значительно возросла и превысила производительность Windows NT Server 3.51 по некоторым данным более чем в 2 раза.

    Новые административные средства Windows NT могут работать удаленно на клиентах Windows 95. Кроме того, Windows NT Server обеспечивает сервис удаленной загрузки для клиентов Windows 95. (Это полезно для бездисковых рабочих станций.)

    В Windows NT 4.0 использован новый графический интерфейс с пользователем в стиле Windows 95. Хотя некоторым пользователям такая перемена не всегда нравится, но этим Microsoft восстанавливает принцип «единого интерфейса для всех платформ», который изначально считался одной из сильных сторон Windows NT. В сети с клиентскими станциями, работающими под управлением Windows 95 или Windows NT (а также в смешанной сети, включающей такие станции), администраторы Windows NT Server могут выполнять свои функции, применяя тот же интерфейс, что и пользователи рабочих станций.

    Помимо внешних изменений, модернизация графического интерфейса не сильно отразилась на методах управления сетью. Базовый инструментарий администратора Windows NT Server остался прежним. Программы User Manager for Domains, Server Manager, Disk Administrator, Event Viewer, Performance Monitor, DHCP Manager, WINS Manager, Network Client Administrator, License Manager и Migration Tool for NetWare не претерпели существенных изменений. Remote Access Administrator также не изменился, но теперь он перенесен из отдельной папки в меню Administrative Tools. Новый редактор системной политики System Policy Editor, совместимый как с Windows NT, так и с Windows 95, заменил редактор профилей пользователей User Profile Editor, знакомый вам по версиям Windows NT Server 3.x. В версию 4.0 вошли четыре дополнения: административные программы-мастера Administrative Wizards, уже упоминавшийся System Policy Editor, а также расширенное средство Windows NT Diagnostics и программа Network Monitor (программа мониторинга работы сети, ранее поставлявшаяся только в составе продукта Microsoft Systems Management Server).

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

    Windows NT 4.0 имеет несколько полезных системных компонентов для мониторинга, заимствованных у Systems Management Server компании Microsoft. Основное приложение — инструмент Performance Monitor, графически отслеживающий выбранные системные события. В частности Performance Monitor может быть использован для получения диаграмм загруженности ЦПУ, общего ввода/вывода сетевых плат и количества переданных байт по HTTP.

    Другое приложение, Monitor Tool, также способно захватывать и отслеживать приходящий и уходящий с компьютера под NT сетевой трафик. Сценарий мониторинга (например, описание того, кадры каких протоколов требуется отследить, какие характеристики трафика измерить, от каких рабочих станций или пользователей) может быть сохранен и при необходимости использован заново.

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

    Другие новшества в Windows NT Server 4.0 связаны, в основном, с Internet и intranet. Важное место среди них занимают следующие, вошедшие в комплект поставки, программные компоненты:

    • Internet Information Server (IIS) версии 2.0 — продукт Microsoft, предоставляющий услуги Web-, ftp- и gopher-сервера, возможности Internet Information Server сравнимы, а по ряду тестов и превосходят аналогичный популярный продукт Server Netscape. Microsoft Internet Information Server 2.0, является самым быстрым сервером Web для Windows NT Server — он на 40 процентов превосходит производительность своего предшественника — версию 1.0;
    • Объектная модель распределенных компонентов — Distributed Component Object Model (DCOM), которая обеспечивает безопасную связь между компонентами через Internet;
    • DNS/WINS Server, который позволяет легко находить в Internet или intranet-сетях нужные Web-узлы;
    • технология PPTP (point-to-point tunneling protocol), которая расширяет функциональность сервера удаленного доступа Windows NT Server (RAS) и обеспечивает возможности создания частных сетей в Internet;
    • программа FrontPage, которая позволяет создавать Web-страницы на основе разнообразных шаблонов, проверять правильность ссылок и осуществлять общее управление создаваемыми Web-узлами;
    • индексный сервер Microsoft Search Server, который позволяет легко находить информацию на распределенных серверах intranet-сети в рамках любых документов, в том числе и созданных в Microsoft Office.

    Два средства новой системы, предназначенные для работы в Internet, представляют особый интерес для администраторов. Во-первых, это служба имен DNS. Она позволяет использовать DNS-имена, но поддерживает только статическую адресацию. Для снятия этого ограничения Microsoft предлагает интеграцию служб DNS и WINS, назвав это сочетание «истинно динамической DNS». Теперь, когда клиенту WINS нужно определить IP-адрес, соответствующий символьному NetBIOS-имени, он обращается сначала к базе данных WINS, а затем — собственно к DNS. Таким образом, в системе на равных можно применять и динамически распознаваемые имена WINS, и статические имена DNS.

    Кроме того, в состав Windows NT 4.0 вошла Web-ориентированная утилита администрирования, открывающая доступ к средствам администрирования Windows NT из любого Web-броузера. Из соображений безопасности для удаленного администрирования следует использовать Web-броузеры, способные регистрировать пользователя непосредственно на сервере Windows NT (т. е. такие, как Internet Explorer) или поддерживать протокол SSL.

    Одно из усовершенствований связано с тем, что повышающаяся роль Internet’а и клиент-серверных систем ведет к росту числа мобильных пользователей. Microsoft в связи с этим улучшила RAS (улучшила поддержку ISDN) и предоставила средства безопасной работы с RAS через Internet. В RAS реализованы протоколы PPTP (создает зашифрованный трафик через Internet) и Multilink PPP (позволяет объединять несколько каналов в один). Клиентами могут быть Windows NT 4.0 Workstation или Windows 95.

    Распределенная модель объектной компоновки (Distributed Component Object Model) — еще одно ключевое дополнение к Windows NT Server 4.0. Модель объектной компоновки (COM) позволяет разработчикам программ создавать приложения, состоящие из отдельных компонент. Распределенная модель (DCOM) в Windows NT Server 4.0 расширяет COM таким образом, что позволяет отдельным компонентам взаимодействовать через Internet. DCOM является растущим стандартом Internet, опубликованным в соответствии с форматом, определенным в спецификациях RFC 1543.

    При разработке Windows NT 4.0 Microsoft решила пожертвовать стабильностью ради производительности. С этой целью были внесены изменения в архитектуру: библиотеки менеджера окон и GDI, а также драйверы графических адаптеров были перенесены из пользовательского режима в режим ядра. Это изменение означает отход от принятой в предыдущих версиях Windows NT 3.х концепции микроядра.

    Перенос графической библиотеки и драйверов в область ядра повышает скорость выполнения графического ввода-вывода. Эти изменения особенно сказались на скорости выполнения приложений Win32, в то время как приложения Windows-16 и графические приложения DOS работают примерно также, как и в версии 3.5.

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

    1.1.3. Новые свойства Windows NT 5.0

    В конце 1997 ожидается появление Windows NT 5.0 — усовершенствованной версии Windows NT. Это будет не только полностью 32-разрядная, но также и полностью объектно-ориентированная система. Основу Windows NT 5.0 составляет объектно-ориентированная файловая система, реализованная на базе стандарта OLE 2.0, которая позволяет хранить не файлы, а объекты. Объектно-ориентированный подход позволяет с наименьшими затратами обеспечивать корректность многочисленных копий данных, таких как документы, электронные таблицы, приложения и других видов информации, хранящихся на разных машинах сети.

    Версия Windows NT 5.0 обещает много, и нововведения прежде всего коснутся следующих подсистем:

    • Active Directory — глобальная справочная служба, которая развивает 2-х уровневый подход к разрешению имен DNS. Active Directory содержит информацию не только о файлах, но и об объектах других типов, например:

      Имя компьютера , IP-адрес
      Имя пользователя, пароль, почтовый адрес, …
      Имя приложения, компьютер, версия, права доступа…

    • Distributed File System (Dfs) — распределенная файловая система (свободно доступна на www.microsoft.com). Каталоги этой файловой системы, находящиеся на разных серверах, монтируются в общее дерево, начинающееся на корневом сервере с корневым share-именем. Различные поддеревья одного share могут состоять из файловых систем не только Microsoft, но и Novell NCP и Sun NFS. Некоторые ветви распределенной файловой системы могут по желанию администратора реплицироваться прозрачным образом.
    • Distributed Component Object Model (DCOM) — программные объекты (ActiveX или другие) могут распределяться по серверам сети и вызываться приложениями с любого компьютера. Информация о месте расположения объектов регистрируется в Active Directory.
    • Средства обеспечения безопасности: проверка прав доступа к документам в системе Windows NT 5.0 будет осуществляться по методу Kerberos или с помощью электронной подписи, а передача документов по сети будет реализована с использованием шифрования.

    1.1.4. Требования к аппаратуре

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

    Для работы Windows NT Workstation 4.0 компьютер должен иметь процессор не ниже i486 (в этой версии системы корпорация Microsoft отказалась от какой-либо поддержки процессоров i386), ОЗУ емкостью не менее 12 Мбайт и 108 Мбайт дискового пространства. И хотя эта ОС вполне работоспособна на компьютерах, имеющих оперативную память менее 16 Мбайт, однако рекомендуется устанавливать ее при наличии ОЗУ, емкость которого вдвое превышает допустимый минимум, т. е. составляет 24 Мбайт, а свободное дисковое пространство равно, по крайней мере, 216 Мбайт. Запустить Windows NT Workstation 4.0 можно и на системе, обладающей меньшими ресурсами, но тогда вряд ли пользователь останется доволен ее производительностью.

    Для Windows NT Server 4.0 Microsoft определяет следующие аппаратные требования: процессор не ниже i486, ОЗУ емкостью 16 Мбайт и не менее 148 Мбайт непрерывного свободного дискового пространства. Для ознакомления с функциональными возможностями системы это, возможно, и достаточно, но для «промышленного» использования этих минимальных требований явно недостаточно. Для сервера с низкой или средней загруженностью (определяется числом обслуживаемых пользователей) необходимо наличие 32 Мбайт оперативной памяти и жесткого диска емкостью не менее 1 Гбайт.

    Полный список аппаратуры, прошедшей тестирование на совместимость с Windows NT, содержится в документации к системе и на сервере www.microsoft.com.

    1.1.5. Области использования Windows NT

    Обладая высокой производительностью, стабильностью, развитыми средствами безопасности, и имея в своем арсенале широкий набор базовых системных функций, Windows NT Server может найти применения в различных областях, и прежде всего он может быть использован в качестве сервера в корпоративной сети. Здесь весьма полезной оказывается его способность выполнять функции контроллера доменов, что позволяет структурировать сеть и тем самым упрощать задачи администрирования и управления. Он используется также в качестве файл-сервера, принт-сервера, сервера приложений, сервера удаленного доступа и сервера связи (программного маршрутизатора).

    Клиентами в сети с Windows NT Server могут являться компьютеры с установленными на них различными операционными системами. Стандартно поддерживаются: Windows NT Workstation, MS-DOS, OS/2, Windows for Workgroups, Windows 95, клоны UNIX, Macintosh. Основные клиенты входят в стандартную поставку Windows NT Server.

    Windows NT Server является мощной платформой для сложных сетевых приложений, особенно тех, которые построены с использованием технологии клиент-сервер. В сочетании с серверами BackOffice он может удовлетворить очень широкий круг потребностей корпоративных пользователей. Так, под управлением Windows NT Server может работать сервер баз данных SQL Server фирмы Microsoft, а также серверы баз данных других известных фирм, такие как серверы баз данных Oracle и Sybase, Adabas и InterBase.

    На платформе Windows NT Server может быть установлена мощная система администрирования Microsoft System Management Server, функциями которой является инвентаризация аппаратной и программной конфигурации компьютеров сети, автоматическая установка программных продуктов на рабочие станции, удаленное управление любым компьютером и мониторинг сети.

    Windows NT Server может использоваться как сервер связи с мэйнфреймами IBM и системами IBM AS400. Для этого создан специальный продукт Microsoft SNA Server, позволяющий легко объединить в одной сети IBM PC-совместимые рабочие станции и мощные мейнфреймы. SNA Sever является шлюзом, позволяющим осуществлять доступ к рабочей станции как к серверам локальной сети, так и к мэйнфреймам без необходимости использования двух сетевых карт или нескольких стеков сетевых протоколов. Это приводит к снижению стоимости оборудования и уменьшению объема требуемой оперативной памяти. Обеспечивая прозрачный доступ к мэйнфреймам, SNA Server, будучи интегрированным с системой безопасности NT Server, обеспечивает авторизацию доступа к хосту. SNA Server может работать с любым из протоколов, поддерживаемых в NT Server: IPX/SPX, TCP/IP или NetBEUI.

    Windows NT Server является платформой для Microsoft Exchange — нового высоко производительного пакета для коллективной работы, построенного на основе почтового сервера.

    Наконец, последняя версия Windows NT 4.0 является надежной платформой для приложений, ориентированных на Internet: Web-серверов, Web-броузеров, информационно-поисковых систем, систем электронной коммерции в сети Internet.

    Операционная система Windows NT Workstation позиционируется прежде всего как клиент в сетях Windows NT Server, а также в сетях NetWare, Unix, Vines. В сетях NetWare рабочие станции Windows NT восполняют известный пробел — отсутствие хорошего сервера приложений. Компьютер с установленной на нем Windows NT может быть рабочей станцией и в одноранговых сетях, выполняя одновременно функции и клиента, и сервера. Windows NT Workstation может применяться в качестве ОС автономного компьютера, если необходимы повышенная производительность или секретность, а также при реализации сложных графических приложений, например, в системах автоматизированного проектирования.

    1.2. Микроядерная структура — основа стабильности системы

    При разработке структуры Windows NT была в значительной степени использована концепция микроядра. В соответствии с этой идеей ОС делится на несколько подсистем-серверов, каждая из которых выполняет отдельный набор сервисных функций — например, сервис памяти, сервис по созданию процессов или сервис по планированию процессов. Каждый сервер выполняется в пользовательском режиме, выполняя циклическую проверку, не появился ли запрос от клиента на одну из его сервисных функций. Клиент, которым может быть либо другая компонента ОС, либо прикладная программа, запрашивает сервис, посылая сообщение на сервер. Этот запрос перехватывается ядром, которое из-за ограниченности выполняемых функций в случае такой организации называется микроядром. Ядро ОС, работая в привилегированном режиме, доставляет сообщение нужному серверу. Сервер выполняет операцию, после чего ядро возвращает результаты клиенту с помощью другого сообщения (рисунок 1.1). Микроядро играет роль регулировщика — оно проверяет сообщения, пересылает их между серверами и клиентами и предоставляет доступ к аппаратуре.

    Рис. 1.1. Структура ОС на базе микроядра

    Использование концепции микроядра способствует переносимости операционных систем, поскольку весь машинно-зависимый код изолирован в микроядре, а значит для переноса системы на новый процессор требуется меньше изменений, и все они логически сгруппированы вместе. Операционная система Windows NT может работать на компьютерах, построенных на базе процессоров Intel, PowerPC, DEC Alpha, MIPS.

    Технология микроядер является основой построения множественных прикладных сред, которые обеспечивают совместимость программ, написанных для разных ОС. Абстрагируя интерфейсы прикладных программ от расположенных ниже операционных систем, микроядра позволяют гарантировать, что вложения в прикладные программы не пропадут в течение нескольких лет, даже если будут сменяться операционные системы и процессоры. В среде Windows NT, кроме «родных» 32-битовых приложений, могут выполняться приложения MS-DOS, 16-битовые Windows-приложения, Posix- и OS/2-приложения.

    Однако, такая гибкость не дается даром. Пересылка сообщений не так быстра, как обычные вызовы функций, и ее оптимизация является критическим фактором успеха операционной системы на основе микроядра. Поэтому разработчики Windows NT отказались от модели микроядра в ее чистом виде. Кроме собственно микроядра, в привилегированном режиме работает часть Windows NT, называемая executive — исполнительная подсистема. Она включает ряд компонентов, которые управляют виртуальной памятью, объектами, вводом-выводом и файловой системой (включая сетевые драйверы), взаимодействием процессов и, частично, системой безопасности. Часть Windows NT, работающая в пользовательском режиме состоит из серверов Windows NT, называемых также защищенными подсистемами (рисунок 1.2).

    Рис. 1.2. Структура Windows NT

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

    Поддержку защищенных подсистем обеспечивает исполнительная часть Windows NT — executive, которая работает в пространстве ядра и никогда не сбрасывается на диск. Ее составными частями являются:

    • Менеджер объектов. Создает, удаляет и управляет объектами NT executive — абстрактными типами данных, используемыми для представления ресурсов системы.
    • Монитор безопасности. Устанавливает правила защиты на локальном компьютере. Охраняет ресурсы операционной системы, выполняет защиту и регистрацию исполняемых объектов.
    • Менеджер процессов. Создает и завершает, приостанавливает и возобновляет процессы и нити, а также хранит о них информацию.
    • Менеджер виртуальной памяти.
    • Средства локального вызова процедур. Передают сообщения между клиентскими и серверными процессами одного и того же компьютера.
    • Подсистема ввода-вывода. Включает в себя следующие компоненты: 1) менеджер ввода-вывода предоставляет средства ввода-вывода, независимые от устройств; 2) файловые системы, NT-драйверы, выполняющие файл-ориентированные запросы на ввод-вывод, транслирующие их в вызовы обычных устройств; 3) сетевой редиректор и сетевой сервер, драйверы файловых систем, передающие удаленные запросы на ввод-вывод на машины сети и получающие запросы от них; 4) драйверы устройств NT executive, низкоуровневые драйверы, которые непосредственно управляют устройством; 5) менеджер кэша, реализующий кэширование диска.

    Исполнительная часть в свою очередь основывается на службах нижнего уровня, предоставляемых ядром (его можно назвать и микроядром) NT. В функции ядра входит:

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

    Обратиться к ядру можно только посредством прерывания. Ядро расположено над уровнем аппаратных абстракций (Hardware Abstraction Level HAL), который концентрирует в одном месте основную часть машинно-зависимых процедур. Располагается HAL между NT executive и аппаратным обеспечением и скрывает от системы такие детали, как контроллеры прерываний, интерфейсы ввода/вывода и механизмы взаимодействия между процессорами. Такое решение позволяет легко переносить Windows NT с одной платформы на другую путем замены только слоя HAL.

    Среди всех защищенных подсистем можно выделить так называемые подсистемы окружения — Win32, 16-битный Windows, DOS, OS/2, Posix. Каждая из этих подсистем реализует соответствующий прикладной программный интерфейс. Windows NT использует подсистемы окружения со следующими целями:

    • Обеспечить несколько программных интерфейсов (APIs), сохраняя как можно более простым базовый программный код (NT executive).
    • Экранировать базовую операционную систему от изменений или расширений в поддерживаемых API.
    • Объединить часть глобальных данных, требуемых всем API, и в то же время отделить данные, требуемые одному API от данных, требуемых другим API.
    • Защитить окружение каждого API от приложений, а также от окружений других API и защитить базовую операционную систему от различных окружений.
    • Позволить операционной системе расширяться в будущем за счет новых API.

    1.3. Планирование процессов и нитей

    В отличие от Windows, в которой реализована многозадачность без вытеснения (non-preemptive multitasking), в Windows NT используется механизм многозадачности с вытеснением (preemptive multitasking).

    Windows NT поддерживает симметричную мультипроцессорную организацию вычислительного процесса, в соответствии с которой ОС может выполняться на любом свободном процессоре или на всех процессорах одновременно, разделяя память между ними. Так как многозадачность реализуется на уровне нитей, разные части одного и того же процесса могут действительно выполняться параллельно. Следовательно, многонитевые серверы могут одновременно обслуживать более одного клиента.

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

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

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

    Windows NT поддерживает 32 уровня приоритетов, разделенных на два класса — класс реального времени и класс переменных приоритетов (рисунок 1.3). Нити реального времени, приоритеты которых находятся в диапазоне от 16 до 31, являются более приоритетными процессами и используются для выполнения задач, критичных ко времени.

    В Windows NT определено 4 класса приоритетов процессов:

    • IDLE_PRIORITY_CLASS — уровень 4
    • NORMAL_PRIORITY_CLASS — уровень 9 при интерактивной работе процесса (forground) и уровень 7 при работе в фоновом режиме (background)
    • HIGH_PRIORITY_CLASS — уровень 13
    • REALTIME_PRIORITY_CLASS — уровень 24

    Большинство приложений либо не определяет класс приоритета процесса при его создании, либо устанавливает его в значение NORMAL_PRIORITY_CLASS. Класс IDLE — самый низкоприоритетный — хорошо использовать для работ, некритичных к скорости их выполнения, например, при наблюдении за состоянием системы или же при резервном копировании на ленту. Высокий приоритет (HIGH_PRIORITY_CLASS) следует использовать только тогда, когда это абсолютно необходимо, так как нити такого процесса будут выполняться всегда перед нитями процесса с нормальным приоритетом. В Windows NT с приоритетом HIGH работает процесс Task Manager. Обычно он находится в состоянии ожидания, но при нажатии комбинации клавиш Ctrl+Esc нить Task Manager пробуждается и немедленно вытесняет любые нити обычных приложений. Приоритеты реального времени системными процессами Windows NT (и тем более офисными приложениями) не используются. Этот класс приоритетов нужно использовать только для систем реального времени, например, сбора данных от промышленных установок, управления движущимися объектами и т.п.

    Рис. 1.3. Система очередей готовых нитей

    Все нити, созданные процессом определенного класса, имеют сначала приоритет процесса. Но в ходе своего выполнения нить может изменить свой приоритет относительного базового приоритета процесса с помощью системного вызова SetThreadPriority. Этот вызов имеет параметр, который может принимать 5 относительных значений, понижая приоритет относительно базового на 2 или 1 единицу, повышая его на 2 или 1 единицу или делая его равным базовому. Имеется еще 2 абсолютных значения этого параметра — IDLE и CRITICAL. Значение IDLE делает приоритет нити равным 1 независимо от его базового приоритета (для процессов REALTIME приоритет становится равным 16), а значение CRITICAL повышает приоритет до 15 для всех процессов (для процессов REALTIME приоритет повышается до 31).

    На выполнение всегда выбирается нить с самым высоким приоритетом. Каждый раз, когда необходимо выбрать нить для выполнения, диспетчер прежде всего просматривает очередь готовых нитей реального времени и обращается к другим нитям, только когда очередь нитей реального времени пуста. Обычно большинство нитей в системе попадают в класс нитей с переменными приоритетами, диапазон приоритетов которых от 0 до 15. Этот класс имеет на-звание «переменные приоритеты» потому, что диспетчер настраивает систему, выбирая (понижая или повышая) приоритеты нитей этого класса.

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

    Итак, нить освобождает процессор, если:

    • нить блокируется, уходя в ожидание,
    • нить завершается,
    • нить исчерпала квант,
    • нить вытеснена более приоритетной нитью.

    При планировании нитей в Windows NT используются концепции квантования, абсолютных приоритетов и динамических приоритетов.

    • Квантование — нитям отводится квант времени, по истечении которого выполнение нити прекращается.
    • Абсолютные приоритеты — при появлении в очереди нити с более высоким приоритетом, чем у активной в данный момент, выполнение последней немедленно прерывается.
    • Динамические приоритеты — приоритеты нитей могут изменяться системой: понижаются у нитей, исчерпавших квант, повышаются у нитей, недоиспользовавших квант из-за перехода в состояние ожидания.

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

    1.4. Управление памятью

    Windows NT поддерживает сегментностраничную модель виртуальной памяти и использует для этих целей аппаратную поддержку таких процессоров как Intel 80386 и выше, MIPS R4000, DEC Alpha и Power PC. Для этого в NT executive имеется специальный компонент — менеджер виртуальной памяти.

    Менеджер ВП обеспечивает для процессов следующие наборы функций:

    • управление виртуальным адресным пространством процесса;
    • разделение памяти между процессами;
    • защита виртуальной памяти одного процесса от других процессов.

    Средства защиты памяти в Windows NT существуют в четырех формах.

    • Отдельное адресное пространство для каждого процесса. Аппаратура запрещает нити доступ к физическим адресам другого процесса.
    • Два режима работы: режим ядра, в котором нитям разрешен доступ к системным данным, и пользовательский режим, в котором это запрещено.
    • Страничный механизм защиты. Каждая виртуальная страница имеет набор признаков, который определяет разрешенные типы доступа в пользовательском режиме и в режиме ядра.
    • Объектно-ориентированная защита памяти. Каждый раз, когда процесс открывает указатель на секцию, монитор ссылок безопасности проверяет, разрешен ли доступ процесса к данному объекту.

    Каждый раз, когда нить использует адрес, менеджер ВП вместе с аппаратными средствами транслирует виртуальный адрес в физический. Подсистема виртуальной памяти, управляя процессом трансляции виртуальных адресов, гарантирует, что нить одного процесса не сможет получить доступ к физической странице памяти, относящейся к другому процессу.

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

    Каждый процесс NT executive имеет большое виртуальное адресное пространство размером в 4Гб, из которых 2 Гб резервируются для системных нужд. (Процессор MIPS R4000 требует, чтобы 2 Гб адресного пространства были зарезервированы для системы. Хотя другие процессоры требуют меньше, для переносимости системы Windows NT всегда резервирует 2 Гб.) Младшие адреса виртуального адресного пространства доступны для нитей, работающих и в пользовательском, и в привилегированном режимах, они указывают на области памяти, уникальные для каждого процесса. Старшая часть адресов доступна для нитей только тогда, когда они выполняются в привилегированном режиме. Виртуальное адресное пространство процесса показано на рисунке 1.4.

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

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

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

    В составе менеджера виртуальной памяти имеется такой компонент, как пейджер (pager). Этот код перемещает страницы между диском и памятью, представляя собой промежуточное звено между аппаратными механизмами и программно-реализуемыми стратегиями. В его функции входит:

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

    Процесс принятия решения о замене страниц системой виртуальной памяти обычно включает три фазы: извлечение, размещение, замена.

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

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

    Этап размещения. Набор правил, используемых для определения места размещения новой страницы в памяти, называется стратегией размещения. В Windows NT менеджер виртуальной памяти просто выбирает первую страницу из списка свободных физических страниц. База данных физических страниц — это массив записей, пронумерованных от 0 до максимального номера страницы, зависящего от объема памяти. Каждая запись содержит информацию о соответствующей физической странице. Менеджер виртуальной памяти использует прямые связи в случае, когда процесс запрашивает доступ к виртуальному адресу в действительной виртуальной странице.

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

    Менеджер виртуальной памяти Windows NT использует локальный алгоритм FIFO (First Input First Output). В соответствии с алгоритмом FIFO из памяти удаляется та страница, которая дольше всего там находится. Локальность в данном случае означает, что поиск страницы-кандидата на выгрузку осуществляется только среди страниц того процесса, который требует загрузки новой страницы. Существуют и глобальные стратегии, в соответствии с которыми поиск замещаемой страницы выполняется на множестве страниц всех процессов. Локальный вариант стратегии не дает одному процессу возможность захватить всю имеющуюся память.

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

    В Windows NT реализована сегментно-страничная модель распределения памяти. Для хранения информации о состоянии виртуальных сегментов используется набор структур, называемых дескрипторами виртуальных адресов. Когда процессу назначается новая область памяти, менеджер виртуальной памяти создает дескриптор, в котором хранится вся информация, связанная с этой областью, такая как диапазон адресов, признаки того, является ли память разделяемой или частной, будет ли процесс-потомок наследовать содержимое этой области, признаки защиты. Затем дескриптор встраивается в двоичное дерево дескрипторов данного процесса, используемое для ускорения поиска.

    Для снижения объема вычислений, затрачиваемых на работу менеджера виртуальной памяти, в Windows NT минимизируется количество страничных прерываний. Для этого предпринимаются следующие меры:

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


    Содержание |
    Вперед

    Предмет: Операционные системы.
    Вопрос: №6

    ***

    —————————————————————

    Маленькое вступление к распределению оперативной памяти в MSDOS:

    Логическая структура памяти ПК обособлена особенностями системы адресации процессоров семейства х86. Процессоры 8086/88, применявшиеся в первых моделях ПК, имели доступное адресное пространство 1 Мбайт (так как использовали 20 разрядную шину адреса). Эти процессоры использовали сегментную модель памяти, унаследованную и следующими моделями в реальном режиме. Согласно этой модели линейный (физический) адрес вычисляется по формуле: Address = segment*10h+offset.

    Таким образом, обеспечивается доступ к адресному пространству от 00000h до FFFFFh при помощи пары 16 разрядных регистров.

    В реальном режиме процессора, используемом в DOS, применяется та же сегментная модель памяти и формально доступен лишь 1 Мбайт памяти, что является недостаточным для большинства современных приложений. Однако выяснилось, что процессоры 80286 в реальном режиме эмулируют 8086 с ошибкой: если задать адрес seg=FFFFh и offset = FFFFh, то данная формула дает адрес 10FFEFh, но ввиду 20-битного ограничения на шину адреса (которое было в 8086) эта комбинация в физической памяти указывала на 0FFEFh. Таким образом, адресное пространство сворачивалось в кольцо с небольшим «нахлестом». Но начиная с процессора 80286, шина адреса была расширена до 24 бит, а в последствии (в процессорах 386DX, 486 и выше) до 32 бит и даже 36 (P6). И та самая единица, которая отбрасывалась в процессорах 8086/88, теперь попадает на шину адреса, и в результате максимально доступный линейный адрес в реальном режиме достиг 10FFEFh. Дополнительные байты оперативной памяти, адресуемой в реальном режиме, позволили освободить дефицитное пространство оперативной памяти для прикладных программ. Эту область (100000h-10FFEFh), названную высокой памятью HMA (High Memory Area), стали помещать часть ОС и небольшие резидентные программы.

    Однако для обеспечения полной совместимости с процессором 8086/88 в схему ПК ввели вентиль линии А20 шины адреса – Gate A20, который либо пропускает сигнал процессора, либо принудительно обнуляет линию А20 системной шины адреса.

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

    —————————————————————

    Тест POST:

    Основную часть адресного пространства занимает оперативная память. Объем установленной памяти определяется тестом POST при начальном включении (перезагрузке) компьютера, начиная с младших адресов. Натолкнувшись на отсутствие памяти (ошибку), тест останавливается на достигнутом и сообщает системе объем реально работающей памяти.

    Распределение памяти ПК непосредствен-но адресуемой процессором:

    memory1

    Conventional memoryпри работе в среде ОС типа MS DOS стандартная память является самой дефицитной в ПК. На её небольшой объем (640 Кбайт) претендуют и BIOS, а оставшаяся часть предназначена для прикладного ПО. Стандарная память распределяется:

    00000h-003FFh – векторы прерываний;

    00400h-004FFh – область переменных BIOS;

    00500h-00xxxh – область DOS;

    00xxxh-9FFFFh – память, предоставляемая пользователю (до 638 Кбайт). Иногда верхние 128 Кбайт стандартной памяти (область 80000h-9FFFFh) называют Extended Conventional Memory.

    Верхняя память (UMA) – объемом 384 Кбайт, имеет области различного назначения, которые могут быть заполнены буферной памятью адаптеров, постоянной памятью или оставаться незаполненными. Эти области доступны DOS для размещения резидентных программ и драйверов через драйвер EMM386, который отображает в них доступную дополнительную память.

    Стандартное распределение дополнитель-ной памяти:

    A0000h-BFFFFh – Video RAM (128 Кбайт) – видеопамять (обычно используется не полностью);

    C0000h-DFFFFh – Adapter ROM, Adapter RAM (128 Кбайт) – резерв для адаптеров, использующих собственные модули ROM BIOS или/и специальное ОЗУ, совместно используемое с системной шиной;

    E0000h-EFFFFh – свободная область (64 Кбайт), иногда занятая под System BIOS;

    F0000h-FFFFFh – System BIOS (64 Кбайт) – системная BIOS;

    FD000h-FDFFFh – ESCD (Extended System Configuration Data) – область энергонеза-висимой памяти, используемая для конфигурирования устройств Plug and Play. Эта область имеется только при наличии PnP BIOS, ее положение и размер жестко не

    заданы.

    В области UMA практически всегда присутствует графический адаптер.

    Распространенным потребителем UMA являются также расширения ROM BIOS, расположенные на платах дисковых контроллеров, и микросхемы удаленной загрузки (Boot ROM) на платах адаптеров ЛВС. Обычно они занимают область C8000h –CBFFFh/C9FFFh/C8FFFh (для дисковых контроллеров), но могут и перемещаться при конфигурировании адаптеров.

    Память выше 100000h – дополнительная (расширенная)память Extended Memory, непосредственно доступная только в защищенном (и в «большом реальном») режиме для компьютеров с процессорами 286 и выше. В ней выделяется область 100000h-10FFEFh – высокая память НМА – это единственная область расширенной памяти, доступная 286+ в реальном режиме при открытом вентиле Gate A20.

    Отображаемая память EMS (Expended Memory Specification) – программная специи-фкация использования дополнительной памяти DOS-программами реального режима. Спецификация LIM EMS – продукт соглашения фирм Lotus, Intel, Microsoft на использование EMS. С помощью специальных аппаратных или программных средств любая область дополнительной памяти может быть отображена на небольшие страницы, расположенные в области UMA. В первоначальном варианте можно было использовать 4 страницы по 16 Кбайт, примыкающие друг к другу, начиная обычно с адреса D0000h (положение страниц можно менять в пределах свободных областей UMA). Обращение прикладных программ к памяти EMS осуще-ствляется через диспетчер памяти, вызываемый по прерыванию Int 67h. Программа, нуждающаяся в дополнительной памяти, должна сначала запросить выделение области, указав ее размер в 16-килобайтных страницах. В ответ на этот запрос, если имеется свободная память, диспетчер сообщает программе номер дескриптора EMS (EMS handler), по которому программа в дальнейшем будет ссылаться на выделенную ей область при управлении отображением. Далее программа через диспетчер назначает отображение требуемой логической страницы из выделенной ей области дополнительной памяти на выбранную физическую страницу, расположенную в области UMA. После этого любые программные обращения процессора к физической странице, расположенной в пределах первого мегабайта, будут в действительности работать с логической страницей дополнительной памяти, расположенной выше первого мегабайта, причем без переключения в защищенный режим. Для работы с иной логической страницей требуется вызов диспетчера для переназначения отображения. В EMS 4.0, эмулируемой на процессорах 386+, появилась возможность увеличения числа доступных физических страниц и отображения дополнительной памяти не только на фиксированные области UMA, но и на любые области памяти.

    Расширенная память XMS (Extended Memory Specification) – иная программная спецификация использования дополнитель-

    ной памяти DOS-программами, разрабо-танная компаниями Lotus, Intel, Microsoft и AST для компьютеров на процессорах 286 и выше. Эта спецификация позволяет программе получить в распоряжение одну или несколько областей дополнительной памяти, а также использовать область НМА. Распределением областей ведает драйвер HIMEM.SYS. Драйвер позволяет захваты-вать или освобождать область НМА (65520 байт, начиная с 100000h), а также управлять вентилем линии адреса А20. Функции XMS позволяют программе:

    – определить размер максимального доступного блока памяти;

    – захватить или освободить блок памяти;

    – копировать данные из одного блока в другой, причем участники копирования могут быть блоками как стандартной, так и дополнительной памяти в любых сочетаниях;

    – запереть блок памяти (запретить копирование) и отпереть его;

    – изменить размер выделенного блока.

    В ответ на запрос выделения области драйвер выдает номер дескриптора блока (16-битное число XMS handler), по которому выполняются дальнейшие манипуляции с этим блоком. Размер блока может достигать 64 Мбайт. Спецификация XMS позволяет программам реального режима устраивать «склады» данных в дополнительной памяти, которая им непосредственно недоступна, копируя в нее и из нее данные доступных областей первого мегабайта памяти. Доступ к драйверу XMS осуществляется через прерывание Int 2Fh. Заботу о переключении в защищенный режим и обратно для получения доступа к дополнительной памяти берет на себя диспетчер. По умолчанию HIMEM.SYS позволяет использовать до 32 дескрипторов блоков, но это число можно увеличить.

    Как видно, спецификации EMS и XMS отличаются по принципу действия: в EMS для доступа к дополнительной памяти выполняется отображение памяти (страничная переадресация), а в XMS – копирование блоков данных. На компьютерах с процессорами 386+ эти спецификации мирно сосуществуют при использовании драйвера HIMEM.SYS, поверх которого может быть загружен и драйвер EMM386.EXE, пользующийся памятью XMS для эмуляции EMS-памяти. Память, доступная EMS и XMS, может выделяться динамически из числа дополнительной. Ключ NOEMS в строке запуска EMM386 запрещает выделение памяти под использование по спецификации

    EMS.

    —————————————————————

    Распределение оперативной памяти в Windows 9.x

    memory-windows98

    Windows 9х являются 32-разрядными, многопотоковыми ОС с вытесняющей многозадачностью. Основной пользователь-

    ский интерфейс этих ОС – графический. Для своей загрузки они используют операционную систему MS DOS 7.Х, и если в файле MSDOS.SYS в секции [Options] прописано «BootGUI = 0», то процессор работает в обычном реальном режиме. Распределение памяти в MS DOS 7.X такое же, как и в предыдущих версиях DOS. Однако при загрузке GUI-интерфейса перед загрузкой ядра Windows 95/98 процессор переключается в защищенный режим  работы и начинает распределять память уже с помощью страничного механизма.

    Использование так называемой плоской модели памяти,при которой все возможные сегменты, используемые программистом, совпадают друг с другом и имеют максимально возможный размер, определяемый системными соглашениями данной ОС, приводит к тому, что, с точки зрения программиста, память получается неструктурированной. За счет представле-ния адреса как пары (Р, i) память можно трактовать и как двумерную, то есть «плоскую», и как линейную, что существенно облегчает создание системного программного обеспечения и прикладных программ с помощью соответствующих систем программи-рования.

    Таким образом, в системе фактически действует только страничный механизм преобразования виртуальных адресов в физические. Программы используют классическую «small» (малую) модель памяти. Каждая прикладная программа определяется 32-битными адресами, в которых сегмент кода имеет то же значение, что и сегменты данных. Единственный сегмент программы отображается непосредственно в область виртуального линейного адресного пространства, который, в свою очередь, состоит из 4 килобайтных страниц. Каждая страница может располагаться в любом месте оперативной памяти (естественно, в том месте, куда ее разместит диспетчер памяти, который сам находится в невыгружаемой области) или может быть перемещена на диск, если не запрещено использовать страничный файл.

    Младшие адреса виртуального адресного пространства совместно используются всеми процессами. Это сделано для обеспечения совместимости с драйверами устройств реального режима, резидентными программами и некоторыми 16-разрядными программами Windows. Безусловно, это плохое решение с точки зрения надежности, поскольку оно приводит к тому, что любой процесс может непреднамеренно (или же, наоборот, специально) испортить компоненты, находящиеся в этих адресах.

    В Windows 95/98 каждая 32-разрядная прикладная программа выполняется в своем собственном адресном пространстве, но все они используют совместно один и тот же 32-разрядный системный код. Доступ к чужим адресным пространствам в принципе возможен. Другими словами, виртуальные адресные пространства не используют всех аппаратных средств защиты, заложенных в микропроцессор. В результате неправильно написанная 32-разрядная прикладная программа может привести к аварийному сбою всей системы. Все 16-битовые прикладные программы Windows разделяют общее адресное пространство, поэтому они так же уязвимы друг перед другом, как и в среде Windows 3.X.

    Системный код Windows 95 размещается выше границы 2 Гбайт. В пространстве с отметками 2 и 3 Гбайт находятся системные библиотеки DLL (Dynamic Link Library – динамически загружаемый библиотечный модуль), используемый несколькими программами.

    Заметим, что в 32-битовых микропроцессорах семейства i80x86 имеются четыре уровня защиты, именуемые

    кольцами с номерами от 0 до 3.

    Кольцо с номером 0 является наиболее привилегированным, то есть максимально защищенным. Компоненты системы Windows 95, относящиеся к кольцу 0, отображаются на виртуальное адресное пространство между 3 и 4 гигабайтами. К этим компонентам относятся собственно ядро Windows, подсистема управления виртуальными машинами, модули файловой системы и виртуальные драйверы (VxD).

    Область памяти между 2 и 4 гигабайтами адресного пространства каждой 32-разрядной прикладной программы совместно используется всеми 32-разрядными прикладными программами. Такая организация позволяет обслуживать вызовы API непосредственно в адресном пространстве прикладной программы и ограничивает размер рабочего множества, однако это снижает надежность. Ничто не может помешать программе, содержащей ошибку, произвести запись в адреса, принадлежащие системным DLL, и вызвать крах всей системы.

    В области между 2 и 3 гигабайтами также находятся все запускаемые 16-разрядные прикладные программы Windows. С  целью обеспечения совместимости эти программы выполняются в совместно используемом адресном пространстве, где они могут испортить друг друга так же, как и в Windows 3.x.

    Адреса памяти ниже 4 Мбайт также отображаются в адресное пространство каждой прикладной программы и совместно

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

    Минимально допустимый объем оператив-ной памяти ОС Windows 95 равен 4 Мбайт, однако при таком объеме пробуксовка столь велика, что практически работать нельзя.

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

    —————————————————————

    Распределение оперативной памяти в Windows NT:

    memory-windows-nt

    В операционных системах Windows NT тоже используется плоская модель памяти. Заметим, что Windows NT 4,0 Server практически не отличается от Windows NT 4.0 Workstation; разница лишь в наличии у сервера некоторых дополнительных служб, дополнительных утилит для управления доменом и несколько иных значений в настройках системного реестра. Однако схема распределения возможного виртуального адресного пространства в системах Windows NT разительно отличается от модели памяти Windows 95/98.

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

    Логическое распределение адресного пространства:

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

    Ядро системы и несколько драйверов работают в нулевом кольце защиты в отдельном адресном пространстве.

    2.) Остальные программные модули самой операционной системы, которые выступают как серверные процессы по отношению к прикладным программам (клиентам), функционируют также в своем собственном системном виртуальном адресном пространстве, невидимом для прикладных процессов.

    Прикладным программам выделяется 2 Гбайт локального (собственного) линейного (неструктурированного) адресного простран-ства от границы 64 Кбайт до 2 Гбайт (первые 64 Кбайт полностью недоступны). Прикладные программы изолированы друг от друга, хотя могут общаться через буфер обмена (clipboard) и механизмы:

    1.) универсальные механизмы динамичес-кого обмена данными DDE (Dynamic Data Exchange);

    2.) технологию документно-ориентирован-ной архитектуры приложений OLE (Object Linking and Embedding).

    В верхней части каждой 2-гигабайтной области прикладной программы размещен код системных DLL кольца 3, который выполняет перенаправление вызовов в совершенно изолированное адресное пространство, где содержится уже собственно системный код, выступающий как сервер-процесс (server process), который проверяет значения параметров, исполняет запрошенную функцию и пересылает результаты назад в адресное пространство прикладной программы. Хотя сервер-процесс сам по себе остается процессом прикладного уровня, он полностью защищен от вызывающей его прикладной программы и изолирован от нее.

    Между отметками 2 и 4 Гбайт расположе-ны низкоуровневые системные компоненты Windows NT кольца 0, в том числе ядро, планировщик потоков и диспетчер виртуальной памяти. Системные страницы в этой области наделены привилегиями супервизора, которые задаются физическими схемами кольцевой защиты процессора. Это делает низкоуровневый системный код невидимым и недоступным для записи программ прикладного уровня, но приводит к падению производительности во время переходов между кольцами.

    Для 16-разрядных прикладных Windows-программ ОС Windows NT реализует сеансы Windows on Windows (WOW). В отличие от Windows 95/98 ОС Windows NT дает возможность выполнять 16-разрядные программы Windows индивидуально в собственных пространствах памяти или совместно в разделяемом адресном пространстве. Почти во всех случаях 16- и 32-разрядные прикладные программы Windows могут свободно взаимодей-ствовать, используя OLE, независимо от того, выполняются они в отдельной или общей памяти. Собственные прикладные программы и сеансы WOW выполняются в режиме вытесняющей многозадачности, основанной на управлении отдельными потоками. Множественные 16-разрядные прикладные программы Windows в одном сеансе W0W выполняются в соответствии с кооперативной моделью многозадачности. Windows NT может также выполнять в многозадачном режиме несколько сеансов DOS. Поскольку Windows NT имеет полностью 32-разрядную архитектуру, не существует теоретических ограничений на ресурсы интерфейса графических устройств GDI (Graphics Device Interface) и USER.

    Процессами выделения памяти, ее резервирования, освобождения и подкачки управляет диспетчер виртуальной памяти Windows NT VMM (Virtual Memory Manager).

    Каждая виртуальная страница памяти, отображаемая на физическую страницу, переносится в так называемый страничный фрейм (page frame). Прежде чем код или данные можно будет переместить с диска в память, диспетчер виртуальной памяти (модуль VMM) должен найти или создать свободный страничный фрейм или фрейм, заполненный нулями.

    Когда процесс использует код или данные, находящиеся в физической памяти, система резервирует место для этой страницы в файле подкачки Pagefile.sys на диске. Файл Pagefile.sys представляет собой зарезервированный блок дискового прост-ранства, который используется для выгрузки страниц, помеченных как «грязные», при необходимости освобождения физической памяти.

    В системах Windows NT 4.0 объекты, созда-

    ваемые и используемые приложениями и операционной системой, хранятся в так называемых пулах памяти (memory pools).

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

    Перемещаемый или нерезидентный пул (paged pool) содержит объекты, которые могут быть при необходимости выгружены на диск.

    Неперемещаемый или резидентный пул (nonpaged pool) содержит объекты, которые должны постоянно находиться в памяти.

    Вся виртуальная память в Windows NT подразделяется на классы:

    1.) зарезервированную (reserved)

    2.) выделенную (committed)

    3.) доступную (available).

    Зарезервированная память представляет собой набор непрерывных адресов, которые диспетчер виртуальной памяти (VMM) выделяет для процесса, но не учитывает в общей квоте памяти процесса до тех пор, пока она не будет фактически использована.  Если процессу потребуется больший объем памяти (чем для него зарезервировано), то дополнительная память может быть одновременно зарезервирована и использована, если в системе имеется доступная память.

    Память выделена, если диспетчер VMM резервирует для нее место в файле Pagefile.sys на тот случай, когда потребуется выгрузить содержимое памяти на диск. Объем выделенной памяти процесса характеризует фактически потребляемый им объем памяти. Выделенная память ограничивается размером файла подкачки. Предельный объем выделенной памяти в системе (commit limit) определяется тем, какой объем памяти можно выделить процессам без увеличения размеров файла подкачки.

    Вся память, которая не является ни выделенной, ни зарезервированной, является доступной. К доступной относятся свободная память, обнуленная память (освобожденная и заполненная нулями), а также память, находящаяся в списке ожидания (standby list), которая была удалена из рабочего набора процесса, но может быть затребована вновь.

    Загрузку программ в UMb нужно производить как и ранее, через использование команд DeviceHigh=

    (в config.sys) и LH
    .

    Этого вполне достаточно для MS-DOS Mode. Но, по умолчанию Win95 (как и Windows 3.x) использует всю свободную UMB память (на момент загрузки) для размещения ядра. Для того, чтобы этого не происходило (ядро все равно останется в UMB), необходимо задать:

    system.ini

    [386enh]

    LocalLoadHigh=true

    после чего вы можете загружать DOS-драйвера и под 32-bit_kernel/GUI.

    Note: при использовании других менеджером памяти указанные шаги могут отличаться.

    Еще одна полезная команда это:

    system.ini

    [NonWindowsApp]

    LocalTSRs=<список_без_расширений>

    Данная команда создает уникальные блоки для каждой VM и поэтому, скажем, переключатель клавиатуры не будет иметь один и тот же статус во всех DOS-окнах.

    1.7.2.1.5. XMS-память

    Эта память обслуживается (как и ранее) драйвером HIMEM.SYS, который загружается в config.sys, либо самой Win95 если оный отсутствует. В момент загрузки ядра/GUI Win95 передает управление внутреннему 32-битному менеджеру памяти и на этом работа HIMEM.SYS заканчивается.

    Вы можете выделять XMS-память для DOS-программ используя стандартный путь через Properties нужной DOS-задачи, Memory -> Extended (XMS) Memory. Можно поставить Auto и тогда Win95 будет следить за запросами из DOS- задачи и довыделять память только в случае надобности. Это экономит память во многих ситуациях (для некоторых программ все же лучше задать необходимый размер, например для DOOM — 4096Kb).

    1.7.2.1.6. EMS-память

    Expanded Memory стала довольно редка, но по-прежнему используется некоторыми играми и старыми программами. Т.к. аппаратная реализации «канула в лету», то приходится использовать алгоритмы эмуляции. Подход в установки EMS памяти для DOS-задач схож с XMS. См. пункт «XMS»

    1.7.2.1.7. Распределение оперативной памяти в Microsoft Windows 95/98

    С точки зрения базовой архитектуры ОС Windows 95/98 они обе являются 32-разрядными, многопотоковыми ОС с вытесняющей многозадачностью. Основной пользовательский интерфейс этих ОС – графический.

    Для своей загрузки они используют операционную систему MS-DOS 7.Х (MS-DOS 98), и в случае если в файле MSDOS.SYS в секции [Option] прописано BootGUI=0, то процессор работает в обычном реальном режиме. Распределение памяти в MS-DOS 7X такое же, как и в предыдущих версиях DOS. Однако при загрузке GUI-интерфейса перед загрузкой ядра Windows 95/98 процессор переключается в защищенный режим работы и начинает распределять память уже с помощью страничного механизма.

    Использование так называемой плоской модели памяти, при которой все возможные сегменты, которые может использовать программист, совпадают друг с другом и имеют максимально возможный размер, определяемый системными соглашениями данной ОС, приводит к тому, что с точки зрения программиста память получается неструктурированной. За счет представления адреса как пары (P, i) память можно трактовать и как двумерную, то есть «плоскую», но при этом ее можно трактовать и как линейную, и это существенно облегчает создание системного программного обеспечения и прикладных программ с помощью соответствующих систем программирования.

    Таким образом, в системе фактически действует только страничный механизм преобразования виртуальных адресов в физические. Программы используют классическую «small» (малую) модель памяти. Каждая прикладная программа определяется 32-битными адресами, в которых сегмент кода имеет то же значение, что и сегменты данных Единственный сегмент программы отображается непосредственно в область виртуального линейного адресного пространства, который, в свою очередь, состоит из 4 килобайтных страниц. Каждая страница может располагаться где угодно в оперативной памяти (естественно, в том месте, куда ее разместит диспетчер памяти, который сам находится в невыгружаемой области) или может быть перемещена на диск, если не запрещено использовать страничный файл.

    Младшие адреса виртуального пространства совместно используются всеми процессами. Это сделано для обеспечения совместимости с драйверами устройств реального режима, резидентными программами и некоторыми 16-разрядными программами Windows. Безусловно, плохое решение с точки зрения надежности, поскольку оно приводит к тому, что любой процесс может непреднамеренно (или же, наоборот, специально) испортить компоненты, находящиеся в этих адресах.

    В Windows 95/98 каждая 32-разрядная прикладная программа выполняется в своем собственном адресном пространстве, но все они используют совместно один и тот же 32-разрядный системный код. Доступ к чужим адресным пространствам в принципе возможен. Другими словами, виртуальные адреса пространства не используют всех аппаратных средств защиты, заложенных в микропроцессор. В результате неправильно написанная 32-разрядная прикладная программа может привести к аварийному сбою всей системы. Все 16-битовые прикладные программы Windows разделяют общее адресное пространство, поэтому они так же уязвимы друг перед другом, как и в среде Windows 3.Х.

    Системный код Windows 95 размещается выше границы 2 Гбайт. В пространстве с отметками 2 и 3 Гбайт находятся системные библиотеки DLL (dynamic link library – динамически загружаемый библиотечный модуль), используемые несколькими программами. Заметим, что в 32-битовых микропроцессорах семейства i80x86 имеются четыре уровня защиты, именуемые кольцами с номерами от 0 до 3. Кольцо с номером 0 является наиболее привилегированным, то есть максимально защищенным. Компоненты системы Windows 95, относящиеся к кольцу 0, отображаются на виртуальное адресное пространство между 3 и 4 Гбайт. К этим компонентам относятся ядро Windows, подсистема управления виртуальными машинами, модули файловой системы и виртуальные драйверы (VxD).

    Область памяти между 2 и 4 Гбайт адресного пространства каждой 32- разрядной прикладной программы совместно используется всеми 32-разрядными прикладными программами. Такая организация позволяет обслуживать вызовы API непосредственно в адресном пространстве прикладной программы и ограничивает размер рабочего множества. Однако за это приходится расплачиваться снижением надежности. Ничто не может помешать программе, содержащей ошибку, произвести запись в адреса, принадлежащие системным DLL, и вызвать крах всей системы.

    В области между 2 и 3 Гбайт также находятся все запускаемые 16-разрядные прикладные программы Windows. С целью обеспечения совместимости эти программы выполняются в совместно используемом адресном пространстве, где они могут испортить друг друга так же, как и в Windows 3.x.

    Адрес памяти ниже 4 Мбайт также отображаются в адресное пространство каждой прикладной программы и совместно используются всеми процессами. Благодаря этому становится возможной совместимость с существующими драйверами реального режима, которым необходим доступ к этим адресам. Это делает еще одну область памяти незащищенной от случайной записи. К самым нижним 64 Кбайт этого адресного пространства 32-разрядные прикладные программы обращаться не могут, что дает возможность перехватывать неверные указатели, но 16-разрядные программы, которые, возможно, содержат ошибки, могут записывать туда данные.

    Вышеизложенную модель распределения памяти можно проиллюстрировать с помощью рис.1.7.2.1.1.

    Минимально допустимый объем оперативной памяти, начиная с которого ОС Windows 95 может функционировать, равен 4 Мбайт, однако при таком объеме пробуксовка столь велика, что практически работать нельзя. Страничный файл с помощью которого реализуется механизм виртуальной памяти, по умолчанию располагается в каталоге самой Windows и имеет переменный размер. Система отслеживает его длину, увеличивая или сокращая этот файл при необходимости. Вместе с фрагментацией файла подкачки это приводит к тому, что быстродействие системы становится меньше, чем если файл был фиксированного размера и располагался в смежных кластерах (был бы дефрагментирован). Сделать файл подкачки заданного размера можно либо специально разработанный для этого апплет (Панель управления ►Система ►Быстродействие ►Файловая система), либо просто прописав в файле SYSTEM.INI в секции [386EnH) строчки с указанием диска и имени этого файла, например:

    PagingDrive=C:

    PagingFile=C:PageFile.sys

    MinPagingFileSize=65536

    MaxPagingFileSize=262144

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

    4Гб

    Системные компоненты,

    Относящиеся к 0 кольцу защиты

    Адреса между 2 4 Гб отображаются

    в адресное пространство каждой

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

    используются

    3Гб Системные DLL

    Прикладные программы Win 16

    Совместно используемые DLL

    2Гб

    Прикладные программы Win 32

    В этой области адресного

    пространства каждой прикладной

    программы располагается свое

    собственное адресное

    пространство. «Личные» адресные

    пространства других программ

    невидимы для программы, и,

    следовательно, она не может

    никак изменить их содержимое

    4Мб

    64Кб

    Компоненты реального режима

    Эта область используется

    всеми процессами

    0

    Рис.1.7.2.1.1. Модель памяти OC Windows 95/98

    1.7.2.1.8. Распределение оперативной памяти в Microsoft Windows NT

    В операционных системах Windows NT тоже используется плоская модель памяти. Заметим, что Windows NT 4.0 server практически не отличается от Windows NT 4.0 workstation; разница лишь в наличии у сервера некоторых дополнительных служб, дополнительных утилит для управления доменом и несколько иных значений в настройках системного реестра. Однако схема распределения возможного виртуального адресного пространства в системах Windows NT разительно отличается от модели памяти Windows 95/98. Прежде всего, в отличие от Windows 95/98 в гораздо большей степени используется ряд серьезных аппаратных средств защиты, имеющихся в микропроцессорах, а также применено принципиально другое логическое распределение адресного пространства.

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

    Во-вторых, остальные программные модули самой операционной системы, которые выступают как серверные процессы по отношению к прикладным программам (клиентам), функционируют также в своем собственном системном виртуальном адресном пространстве, невидимом для прикладных процессов. Логическое распределение адресных пространств приведено на рис. 1.7.2.1.2.

    Прикладным программам выделяется 2 Гбайт локального (собственного) линейного (неструктурированного) адресного пространства от границы 64 Кбайт до 2 Гбайт (первые 64 Кбайт полностью недоступны). Прикладные программы изолированы друг от друга, хотя могут общаться через буфер обмена (clipboard), механизмы DDE (Dynamic Data Exchange – механизм динамического обмена данными) и OLE (Object Linking and Embedding – механизм связи и внедрения объектов).

    4Гб

    2Гб

    64Кб

    0

    Код ядра

    (работает в кольце

    защиты с номером 0)

    Прикладные программы обращаются к DLL, которые перенаправляют обращение к системе

    Этот системный код находится в своем собственном адресном пространстве и недоступен вызывающим его процессам

    DLL Win32
    Клиентской стороны

    Процесс системного сервера

    Прикладные
    программы Win32
    (у каждой программы
    свое собственное
    виртуальное
    пространство памяти)

    Виртуальные машины

    Win16

    Рис.1.7.2.1.2. Модель распределения виртуальной памяти в Windows NT

    В верхней части каждой 2-гигабайтовой области прикладной программы размещен код системных DLL кольца 3, который выполняет перенаправление вызовов в совершенно изолированное адресное пространство, где содержится уже собственно системный код. Этот системный код, выступающий как сервер-процесс (server process), проверяет значения параметров, исполняет запрошенную функцию и пересылает результаты назад в адресное пространство прикладной программы. Хотя сервер-процесс сам по себе остается процессом прикладного уровня, он полностью защищен от вызывающей его прикладной программы и изолирован от нее.

    Между отметками 2 и 4 Гбайт расположены низкоуровневые системные компоненты Windows NT кольца 0, в том числе ядро, планировщик потоков и диспетчер виртуальной памяти. Системные страницы в этой области наделены привилегиями супервизора, которые задаются физическими схемами кольцевой защиты процессора. Это делает низкоуровневый системный код невидимым и недоступным для записи для программ прикладного уровня, но приводит к падению производительности во время переходов между кольцами.

    Для 16-разрядных прикладных Windows-программ OC Windows NT реализует сеансы Windows on Windows (WOW). В отличие от Windows 95/98 OC Windows NT дает возможность выполнять 16-разрядные программы Windows индивидуально в собственных пространствах памяти или совместно в разделяемом адресном пространстве. Почти во всех случаях 16- и 32-разрядные прикладные программы Windows могут свободно взаимодействовать, используя OLE, независимо от того, выполняются они в отдельной или общей памяти. Собственные прикладные программы и сеансы WOW выполняются в режиме вытесняющей многозадачности, основанной на управлении отдельными потоками. Множественные 16-разрядные прикладные программы Windows в одном сеансе WOW выполняются в соответствии с кооперативной моделью многозадачности. Windows NT может также выполнять в многозадачном режиме несколько сеансов DOS. Поскольку Windows NT имеет полностью 32-разрядную архитектуру, не существует теоретических ограничений на ресурсы GDI (Graphics Device Interface – интерфейс графических устройств) и USER.

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

    Процессами выделения памяти, ее резервирования, освобождения и подкачки управляет диспетчер виртуальной памяти Windows NT (Windows NT virtual memory manager, VMM). В своей работе этот компонент реализует сложную стратегию учета требований к коду и данным процесса для минимизации доступа к диску, поскольку реализация виртуальной памяти часто приводит к большому количеству дисковых операций.

    Каждая виртуальная страница памяти, отображаемая на физическую страницу, переносится в так называемый страничный фрейм (page frame). Прежде чем код или данные можно будет переместить с диска в память, диспетчер виртуальной памяти (модуль VMM) должен найти или создать свободный страничный фрейм или фрейм, заполненный нулями. Заметим, что заполнение страниц нулями представляет собой одно из требований стандарта на системы безопасности уровня С2, принятого правительством США. Страничные фреймы должны заполняться нулями для того, чтобы исключить возможность использования их предыдущего содержимого другими процессами. Чтобы фрейм можно было освободить, необходимо скопировать на диск изменения в его странице данных, и только после этого фрейм можно будет повторно использовать. Программы, как правило, не меняют страницы кода. Страницы кода, в которые программы не внесли изменений, можно удалить.

    Диспетчер виртуальной памяти может быстро и относительно легко удовлетворить программные прерывания типа «ошибка страницы» (page fault). Что касается аппаратных прерываний типа «ошибка страницы», то они приводят к подкачке, которая снижает производительность системы. Мы уже говорили о том, что в Windows NT, к большому сожалению, выбрана дисциплина FIFO для замещения страниц, а не более эффективные дисциплины LRU и LFU.

    Когда процесс использует код или данные, находящиеся в физической памяти, система резервирует место для этой страницы в файле подкачки Pagefile.sys на диске. Это делается с расчетом на тот случай, что данные потребуется выгрузить на диск. Файл Pagefile.sys представляет собой зарезервированный блок дискового пространства, который используется для выгрузки страниц, помеченных как «грязные», при необходимости освобождения физической памяти. Заметим, что этот файл может быть как непрерывным, так и фрагментированным; он может быть расположен на системном диске либо на любом другом и даже на нескольких дисках. Размер этого страничного файла ограничивает объем данных, которые могут храниться во внешней памяти при использовании механизмов виртуальной памяти. По умолчанию размер файла подкачки устанавливается равным объему физической памяти плюс 12 Мбайт, однако пользователь имеет возможность изменить его размер по своему усмотрению. Проблема нехватки виртуальной памяти часто может быть решена за счет увеличения размера файла подкачки.

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

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

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

    Вся виртуальная память в Windows NT подразделяется на классы: зарезервированную (reserved), выделенную (committed) и доступную (available).

    • Зарезервированная память представляет собой набор непрерывных адресов, которые диспетчер виртуальной памяти (VMM) выделяет для процесса, но не учитывает в общей квоте памяти процесса до тех пор, пока она не будет фактически использована. Когда процессу требуется выполнить запись в память, ему выделяется нужный объем из зарезервированной памяти. Если процессу потребуется больший объем памяти, то дополнительная память может быть одновременно зарезервирована и использована, если в системе имеется доступная память.
    • Память выделена, если диспетчер VMM резервирует для нее место в файле Pagefile.sys на тот случай, когда потребуется выгрузить содержимое памяти на диск. Объем выделенной памяти процесса характеризует фактически потребляемый им объем памяти. Выделенная память ограничивается размером файла подкачки. Предельный объем выделенной памяти в системе (commit limit) определяется тем, какой объем памяти можно выделить процесса без увеличения размеров файла подкачки. Если в системе имеется достаточный объем дискового пространства, то файл подкачки может быть увеличен и, тем самым, будет расширен предельный объем выделенной памяти.

    Вся память, которая не является ни выделенной, ни зарезервированной, является доступной. К доступной относится свободная память, обнуленная память (освобожденная и заполненная нулями), а также память, находящаяся в списке ожидания (standby list), которая была удалена из рабочего набора процесса, но может быть затребована вновь.

    1.7.2.2. Процессы и нити в Windows

    В разных ОС процессы реализуются по-разному. Эти различия заключаются в том, какими структурами данных представлены процессы, как они именуются, какими способами защищены друг от друга и какие отношения существуют между ними. Процессы Windows NT имеют следующие характерные свойства:

    Процессы Windows NT реализованы в форме объектов, и доступ к ним осуществляется посредством службы объектов.

    Процесс Windows NT имеет многонитевую организацию.

    Как объекты-процессы, так и объекты-нити имеют встроенные средства синхронизации.

    Менеджер процессов Windows NT не поддерживает между процессами отношений типа «родитель-потомок».

    В любой системе понятие «процесс» включает следующее:

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

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

    В Windows NT процесс — это просто объект, создаваемый и уничтожаемый менеджером объектов. Объект-процесс, как и другие объекты, содержит заголовок, который создает и инициализирует менеджер объектов. Менеджер процессов определяет атрибуты, хранимые в теле объекта-процесса, а также обеспечивает системный сервис, который восстанавливает и изменяет эти атрибуты.

    В число атрибутов тела объекта-процесса входят:

    • Идентификатор процесса — уникальное значение, которое идентифицирует процесс в рамках операционной системы.
    • Токен доступа — исполняемый объект, содержащий информацию о безопасности.
    • Базовый приоритет — основа для исполнительного приоритета нитей процесса.
    • Процессорная совместимость — набор процессоров, на которых могут выполняться нити процесса.
    • Предельные значения квот — максимальное количество страничной и нестраничной системной памяти, дискового пространства, предназначенного для выгрузки страниц, процессорного времени — которые могут быть использованы процессами пользователя.
    • Время исполнения — общее количество времени, в течение которого выполняются все нити процесса.

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

    Объект-нить имеет следующие атрибуты тела:

    • Идентификатор клиента — уникальное значение, которое идентифицирует нить при ее обращении к серверу.
    • Контекст нити — информация, которая необходима ОС для того, чтобы продолжить выполнение прерванной нити. Контекст нити содержит текущее состояние регистров, стеков и индивидуальной области памяти, которая используется подсистемами и библиотеками.
    • Динамический приоритет — значение приоритета нити в данный момент.
    • Базовый приоритет — нижний предел динамического приоритета нити.
    • Процессорная совместимость нитей — перечень типов процессоров, на которых может выполняться нить.
    • Время выполнения нити — суммарное время выполнения нити в пользовательском режиме и в режиме ядра, накопленное за период существования нити.
    • Состояние предупреждения — флаг, который показывает, что нить должна выполнять вызов асинхронной процедуры.
    • Счетчик приостановок — текущее количество приостановок выполнения нити.

    Кроме перечисленных, имеются и некоторые другие атрибуты.

    Как видно из перечня, многие атрибуты объекта-нити аналогичны атрибутам объекта-процесса. Весьма сходны и сервисные функции, которые могут быть выполнены над объектами-процессами и объектами-нитями: создание, открытие, завершение, приостановка, запрос и установка информации, запрос и установка контекста и другие функции.

    1.7.2.3. Алгоритм планирования процессов и нитей

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

    В ОС Windows NT нить в ходе своего существования может иметь одно из шести состояний (рис. 7.7). Жизненный цикл нити начинается в тот момент, когда программа создает новую нить. Запрос передается NT Executive, менеджер процессов выделяет память для объекта-нити и обращается к ядру, чтобы инициализировать объект-нить ядра.

    После инициализации нить проходит через следующие состояния:

    Готовность. При поиске нити на выполнение диспетчер просматривает только нити, находящиеся в состоянии готовности, у которых есть все для выполнения, но не хватает только процессора.

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

    Выполнение. Как только происходит переключение контекстов, нить переходит в состояние выполнения и находится в нем до тех пор, пока либо ядро не вытеснит ее из-за того, что появилась более приоритетная нить или закончился квант времени, выделенный этой нити, либо нить завершится вообще, либо она по собственной инициативе перейдет в состояние ожидания.

    Ожидание. Нить может входить в состояние ожидания несколькими способами: нить по своей инициативе ожидает некоторый объект для того, чтобы синхронизировать свое выполнение; операционная система (например, подсистема ввода-вывода) может ожидать в интересах нити; подсистема окружения может непосредственно заставить нить приостановить себя. Когда ожидание нити подойдет к концу, она возвращается в состояние готовности.

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

    Завершение. Когда выполнение нити закончилось, она входит в состояние завершения. Находясь в этом состоянии, нить может быть либо удалена, либо не удалена. Это зависит от алгоритма работы менеджера объектов, в соответствии с которым он и решает, когда удалять объект. Если Executive имеет указатель на объект-нить, то она может быть инициализирована и использована снова.

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

    Windows NT поддерживает 32 уровня приоритетов, разделенных на два класса — класс реального времени и класс переменных приоритетов. Нити реального времени, приоритеты которых находятся в диапазоне от 16 до 31, являются более приоритетными процессами и используются для выполнения задач, критичных ко времени.

    Каждый раз, когда необходимо выбрать нить для выполнения, диспетчер прежде всего просматривает очередь готовых нитей реального времени и обращается к другим нитям, только когда очередь нитей реального времени пуста. Большинство нитей в системе попадают в класс нитей с переменными приоритетами, диапазон приоритетов которых от 0 до 15. Этот класс имеет название «переменные приоритеты» потому, что диспетчер настраивает систему, выбирая (понижая или повышая) приоритеты нитей этого класса.

    Алгоритм планирования нитей в Windows NT объединяет в себе обе базовых концепции — квантование и приоритеты. Как и во всех других алгоритмах, основанных на квантовании, каждой нити назначается квант, в течение которого она может выполняться. Нить освобождает процессор, если:

    • блокируется, уходя в состояние ожидания;
    • завершается;
    • исчерпан квант;
    • в очереди готовых появляется более приоритетная нить.

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

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

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

    Заключение к лекции № 5

    В лекции были рассмотрены особенности реализации виртуальной памяти в операционных системах семейства Windows.

    Показано, что эффективное управление в режиме вытесняющей мультизадачности здесь обеспечивается следующими механизмами: объектным представлением процессов и их компонентов — нитей; наличием приоритетов процессов и нитей, продуманным алгоритмом управления планированием процессов и нитей.

    Контрольные вопросы

    1. Назовите основные свойства процессов в Windows NT.
    2. Перечислите атрибуты тела процесса в Windows NT.
    3. Перечислите атрибуты тела нити в Windows NT.
    4. Перечислите операции, которые можно выполнить над объектами-процессами в Windows NT.
    5. Перечислите операции, которые можно выполнить над объектами-нитями в Windows NT.
    6. Перечислите и охарактеризуйте состояния, в которых может находиться нить в Windows NT.
    7. Нарисуйте граф состояний, в которых может находиться нить в Windows NT.

    Разработал профессор кафедры

    д.т.н., проф. А.А.Безбогов

    Рис. 7.7. Граф состояний нити

    Понравилась статья? Поделить с друзьями:
  • Расширенный поиск файлов в windows 10 по дате
  • Реал тайм борд для компьютера windows
  • Расширенный поиск документов в windows 10
  • Реакция на закрытие крышки ноутбука windows 10
  • Расширенный поиск в проводнике windows 10