Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MS Windows. Элементы архитектуры и системное программирование..pdf
Скачиваний:
273
Добавлен:
01.05.2014
Размер:
1.98 Mб
Скачать

Приложения

Win32/Win16/MS-DOS

Кольцо-3

Кольцо-0

Менеджер

VPICD.VXD

VDMAD.VXD

MyDevice.VXD

виртуальных

 

 

 

машин (VMM)

 

 

 

 

Программируем

Контроллер

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

 

ый контроллер

ПДП

устройство

 

прерываний

 

 

Рисунок 8. Виртуальные устройства (Virtual devices).

Обзор архитектуры MS Windows NT.

В отличие от Windows 95, операционная система Windows NT разрабатывалась изначально как 32-х разрядная ОС. Однако 16-ти разрядные приложения так же поддерживаются. Windows NT имеет модульную структуру которая придаёт гибкость этой операционной системе. Windows NT может работать на нескольких платформах

CISC (complex instruction set computing/80386/) и RISC (reduced instruction set computing/MIPS R4000 и Digital Alpha AXP/). Это свойство называется переносимостью

(Portability). Windows NT может работать в системе с одним микропроцессором или в системе с симметричной многопроцессорной архитектурой (до 32-х МП). Это свойство называется Scalability. Модульная структура операционной системы приводится на рисунке 1.

Приложения

 

 

 

 

Подсистема Win 32

Подсистемы

 

 

(subsystem)

OS/2 & POSIX

 

 

Кольцо-3

 

 

 

 

 

Кольцо-0

 

Executive services

 

 

I/O

Object

Security

Process

Local

Virtual

Manager

Manager

Reference

Manager

Procedure

Memory

 

 

Monitor

Kernel

Call Facility

Manager

 

 

 

 

 

 

 

Hardware Abstraction Level (HAL)

 

 

 

Аппаратура

 

 

Рисунок 1. Windows NT.

 

 

 

 

Hardware Abstraction Level (HAL).

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

Ядро (Kernel)

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

19

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

Эти объекты делятся на два типа:

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

Объекты управления, которые используются для управления работой модуля Kernel. К ним относятся: асинхронные вызовы процедур, прерывания и процессы.

Менеджер Объектов (Object Manager).

В документации Win32 SDK объект определяется как «внутренняя структура, которая описывает системный ресурс, например файл, поток, или графическое изображение». Программное обеспечение получает доступ к объекту через ссылку на объект (handle). Ссылка это 32-х разрядное число, которое является прямым указателем на объект (виртуальным адресом объекта) или содержит информацию косвенно указывающую на объект. Все ссылки на объекты в Windows NT создаются с помощью Менеджера Объектов.

Менеджер Процессов (Process Manager).

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

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

Менеджер виртуальной памяти (Virtual Memory Manager)

VMM (не путать с Менеджером Виртуальных Машин в Windows 95) реализует функции управления виртуальной памятью. В его задачу входит обеспечение преобразования виртуальных адресов в физические, предоставление виртуального адресного пространства остальному программному обеспечению. VMM работает непосредственно с таблицами страниц. VMM использует упомянутый ранее метод загрузки/выгрузки страниц по запросу (Demand paging). Часть страниц виртуальной памяти хранится на диске и загружается по запросу в физическую память. VMM скрывает этот процесс от остального программного обеспечения.

Концепция виртуального адресного пространства приведена на рисунке 2.

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

Физ. память (станица присутствует)

 

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

 

Диск (страница не присутствует)

 

Физ. память (станица присутствует)

Диск

Диск (страница не присутствует)

 

. . .

Физическая

память

Рисунок 2. Виртуальная память.

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

20

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

Модуль Вызова Локальных Процедур (Local Procedure Call Facility).

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

Менеджер ввода-вывода (I/O Manager).

I/O Manager контролирует все операции ввода-вывода операционной системы. Он обеспечивает единый интерфейс передачи данных между драйверами. Обработка ввода-вывода в операционной системе разделяется на несколько логических уровней. Программное обеспечение на каждом уровне не зависит от деталей реализации ПО на других уровнях. Например, только драйвер самого низкого уровня работает напрямую с аппаратурой и учитывает её особенности. Многоуровневая структура драйверов приводится на рисунке 3.

Менеджер

ввода/вывода

Менеджер кэш памяти

Файловая система

Сетевые драйверы

Драйверы аппаратуры

Рисунок 3. Многоуровневая архитектура драйверов NT.

Это структура позволяет легко заменить драйвер на любом уровне не внося изменения на других уровнях (например, в случае смены файловой системы или замены жёсткого диска). Драйверы обмениваются данными, используя специальную структуру Пакет запроса ввода-вывода (I/O Request packets/IRP/). В задачу Менеджера ввода-вывода входит доставка пакетов адресатам. Менеджер как видно из рисунка 3 обслуживает следующие основные компоненты, размещённые на разных уровнях:

Менеджер кэш памяти (Cache manager) который обеспечивает кэширование всей системы ввода-вывода. Работа Менеджера кэш памяти основана на использовании функций модуля VMM. Кэш менеджер управляет операцией опережающего чтения, подгружая в физическую память страницы памяти, обращение к которым наиболее вероятно в последующие моменты времени. Этот модуль так же поддерживает операцию отложенной записи (lazy write) когда страница физической памяти, в которую записываются данные, сохраняется на диске в файле подкачки не сразу после её модификации, а в последствии, во время, когда процессор будет менее загружен.

Драйверы файловой системы, поддерживающие различные типы файловых систем: FAT, HPFS и NTFS.

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

Сетевые драйверы.

21

Монитор защиты (Security reference monitor)

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

Подсистема Win32.

Подсистема Win32 это основной компонент пользовательского уровня операционной системы. Он содержит набор API функций необходимых для работы приложений Win32, а также реализует операции управления клавиатурой мышью и выводом на экран для всех других подсистем. Подсистема Win 32 это отдельный процесс, который получает вызовы от пользовательских приложений через Модуль Вызова Локальных процедур (LPC). Подсистема работает как сервер, а пользовательские приложения выступают в роли клиентов. Такой механизм позволяет защитить системный код третьего кольца от несанкционированного доступа со стороны пользовательских программ. Эта защита основана на том, что память, в которой располагается подсистема не глобальна. Это частная память процесса подсистемы, и следовательно она недоступна из пользовательских приложений Win32. Вызовы функций подсистемы транслируются через ядро (kernel) расположенное в нулевом кольце защиты. Ядро обеспечивает интерфейс между клиентами и сервером, переключая контексты памяти. Когда клиент вызывает функцию API, активным становится контекст памяти потока сервера. Когда сервер выполняет функцию, система снова делает текущим контекст потока клиента и возвращает ему результат и управление. В Windows NT 3.51 подсистема Win32 включает системный код 3-его кольца для управления окнами (user32.dll) и для управления графикой (gdi32.dll) вместе с графическими драйверами (Рисунок 4).

Подсистема Win32.

Консольный ввод-вывод

Графический интерфейс

Управление

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

интерфейсом

Графические драйверы

User mode

Kernel mode

Executive services

Kernel

HAL

Рисунок 4. Подсистема Win32 в NT 3.51.

В Windows NT 4.0 структура подсистемы другая. Управление окнами графический интерфейс и графические драйверы перенесены в нулевое кольцо и являются частью ядра системы. Передача управления между системным (0-кольцо) и пользовательским (3-кольцо) уровнями - это длительная по времени процедура. Поэтому при разработке NT 4.0 преследовалась цель сократить число таких операций. Архитектура NT 3.51 предусматривает следующую последовательность переходов между пользовательским и системным кодом при вызове функции подсистемы (user или gdi):

пользовательский (клиент) - системный (ядро) - пользовательский (сервер) - системный (ядро) - пользовательский (клиент).

В Windows NT 4.0 эта цепочка короче:

пользовательский (клиент) - системный(ядро) - пользовательский (клиент).

22

 

Подсистема Win32.

 

Консольный ввод-вывод

 

Некоторые служебные функции

 

 

 

User mode

 

Executive

Kernel mode

 

 

 

Executive services

 

Управление

Графический интерфейс

 

 

 

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

 

интерфейсом.

Графические драйверы

 

 

Kernel

 

 

HAL

 

Рисунок 5. Реализация функций USER и GDI в NT 4.0.

 

23

Управление памятью в операционных системах Windows 95 и NT.

Автор: Сидякин И.М.

Московский Государственный Технический Университет им. Н.Э. Баумана. Кафедра ИУ-3, (11.1998)

Email:sidiakin@iu3.bmstu.ru

Основные задачи управления памятью.

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

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

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

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

Быстрое переключение контекстов памяти при переключении задач. Windows

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

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

24

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

Защита областей памяти от доступа со стороны пользовательских приложений и запрещение модификации содержимого памяти. 32-х разрядные операционные системы Windows используют плоскую модель памяти (размер сегментов 4ГБ) при работе с приложениями Win32. Теоретически эти приложения могут использовать адреса в диапазоне от 0 до 0xFFFFFFFF. Но значительная часть этого адресного пространства зарезервирована операционной системой для собственных нужд. Эта системная память может быть легко защищена от доступа приложений расположенных в третьем кольце защиты. Кроме того, данные в нулевом или третьем кольце защиты могут быть доступны только для чтения. Это используется, например, для секций, в которых размещается код, который не следует модифицировать.

Страничная организация памяти.

Все эти полезные свойства основаны на технике трансляции страниц реализованной аппаратно в процессоре Intel 80386. Всё виртуальное адресное пространство разделяется на блоки одинаковой длины. Эти блоки называются страницами. Размер страницы равен 4K (4096 байт). Физический адрес страницы определяется соответствующей записью в таблице страниц. Таблицы страниц создаются операционной системой. Таблицы страниц составляют двухуровневую древовидную структуру. В корне этой структуры располагается каталог таблиц страниц. Каталог это таблица, записи которой указывают на таблицы страниц. В свою очередь записи таблиц страниц содержат ссылки на страницы. Структура приведена на рисунке 1.

 

 

 

Таблица

 

 

Страница

 

...

 

...

 

Каталог таблиц

 

страниц

 

(4K)

 

 

 

страниц

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

...

 

 

Страница

 

 

 

 

 

 

 

 

 

 

(4K)

 

 

 

 

 

 

 

 

 

 

 

...

 

 

 

 

 

 

 

 

 

 

Таблица

 

 

Страница

 

 

 

...

 

 

 

 

страниц

 

(4K)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Страница

 

 

 

 

 

 

 

 

 

 

 

 

(4K)

 

 

 

 

 

 

 

Рисунок 1. Структура дерева для трансляции адресов страниц.

Запись таблицы страниц (page table entry /PTE/)может содержать физический адрес страницы, если страница присутствует в памяти, или указывать на место на диске, в котором хранится содержимое страницы. В дополнение к этой информации записи каталога и таблиц страниц содержат сведения, использующиеся для защиты страниц. Страница защищена от доступа из приложений третьего кольца защиты, если бит User/Supervisor в записи сброшен в ноль. К такой странице могут обращаться только модули, расположенные в нулевом кольце защиты. Страница доступна только для чтения, если бит R/W записи установлен в единицу.

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

На рисунке 2 показана структура адресного пространства Windows 95. Система выделяет примерно 2ГБ виртуальных адресов процессу. Это частная область памяти процесса и по умолчанию каждый процесс в системе имеет различные записи таблиц страниц для описания этой области памяти. Остальное адресное пространство общее. Верхние 2ГБ зарезервированы использования операционной системой. В верхнем гигабайте располагаются системные структуры и модули, такие как таблицы страниц, виртуальные устройства и другое системное программное обеспечение нулевого кольца. Эта область памяти защищена от доступа из пользовательских приложений. Адреса в диапазоне от 0x80000000 до 0xBFFFFFFF (2ГБ-3ГБ) используются системным

25

ПО третьего кольца. Здесь расположены системные динамические библиотеки (USER, KERNEL, GDI). Здесь так же размещаются структуры файлов отображаемых в память (Memory mapped files), которые используются для организации общей памяти процессов. Часть этого адресного пространства занято «верхней» частью 16-ти битовой кучи(heap). «Нижняя» часть кучи располагается по адресам ниже 4МБ. Это делается для совместимости с 16-ти разрядными приложениями, которые используют кучу такого типа для динамического размещения памяти. Нижний мегабайт адресного пространства зарезервирован для поддержки программ MS-DOS. Следует отметить, что весь 16-ти разрядный код размещается в общей памяти между 2-м и 3-м гигабайтом. 16-ти разрядные приложения Windows не имеют частных виртуальных адресов. Это означает, что приложения Win16 могут «видеть» друг друга в памяти. Конечно, если известны значения селекторов сегментов, в которых они располагаются.

4GB

Таблицы страниц

общая

 

и др. системные

память

 

структуры.

 

 

Виртуальные устройства

 

3GB

(VxD).

общая

 

 

Системные DLL.

память

 

Memory Mapped Files.

 

 

Верхняя часть

 

 

глобальной кучи Win16.

 

2GB

 

частная

 

 

 

Частная область

память

 

процесса.

 

4MB

 

 

 

 

общая

 

Нижняя часть глобальной

память

 

кучи Win16.

 

0

MS-DOS.

 

 

 

 

 

 

Рисунок 2. Адресное пространство Windows 95.

Типы памяти.

Как уже было отмечено, для управления виртуальной памятью система использует дополнительные структуры данных: каталоги таблиц страниц и таблицы страниц, которые так же размещаются в памяти. Подсчитаем максимальный размер памяти, который может быть занят этими структурами. Полный диапазон адресов равен 4ГБ или 1024*1024*4096 - 1 мегабайт страниц. Таблица страниц может содержать 1024 записи. Таким образом, для описания 1МБ страниц требуется 1024 таблицы страниц плюс один каталог таблиц страниц размером 4К. Заметим, что каждая таблица страниц (так же как и каталог таблиц страниц) занимает 4 килобайта. Для описания всех страниц необходимо зарезервировать 4МБ (1024*4096) памяти под таблицы страниц и дополнительно 4096 байт под каталог таблиц страниц. Конечно, это может показаться слишком высокой ценой, если к тому же принять во внимание что каждый процесс может иметь свои собственные таблицы страниц для страниц частной области адресного пространства. Однако нет необходимости размещать и инициализировать все таблицы страниц. Вместо этого таблицы создаются, только если в этом есть необходимость. На практике, большинство приложений Windows использует гораздо меньший объем памяти, чем максимально допустимые 2ГБ. Каждая страница виртуального адресного пространства с точки зрения операционной системы может

26

находиться в одном из следующих состояниях: доступна(available), зарезервирована(reserved), используется и присутствует в физической памяти (committed and present), используется и не присутствует в физической памяти (committed and not present). В состоянии available страница, только теоретически может быть доступна, однако работать с ней невозможно. Такая страница не имеет записи в какой либо таблице страниц. Если страница зарезервирована, её содержимое не существует ни в физической памяти, ни на диске, однако система резервирует этот участок памяти для будущего использования. Если страница находится в состоянии committed and present, её содержимое находится в физической памяти и в одной из таблиц страниц имеется запись соответствующая этой странице. Состояние committed and not present отличается от предыдущего тем, что содержимое страницы располагается на диске. Страница в состоянии сommitted and present может дополнительно иметь статус locked. Содержимое такой страницы не может быть выгружено на диск. Т.е. гарантируется что такая страница всегда будет находится в физической памяти. Попытка доступа к страницам находящимся в состоянии reserved, available или commited and not present приводит к генерации исключительной ситуации (0x0E - page fault). Windows API содержит ряд функций для управления виртуальной памятью и в частности для резервирования памяти и переключения памяти в состояние commited. Только после этой операции память становится доступной, так как для неё создаются записи в таблице страниц. Таблицы страниц так же размещаются в памяти. Каждая таблица занимает одну страницу. После размещения и инициализации таблицы страниц, страница в которой она располагается, может находится в одном из состояний committed. Если эта страница не присутствует в физической памяти, в момент обращения к ней аппаратура вырабатывает исключительную ситуацию с кодом 0х0E. Таким образом, процедура чтения/записи в виртуальную память может включать обработку двух исключений подряд, первое возникает при доступе к неприсутствующей в памяти таблице страниц, а второе при доступе к неприсутствующей в памяти странице.

Ассоциативный кэш буфер страничного преобразования.

Обработка каждого исключения занимает относительно небольшое время, но всё же снижает производительность системы. Эта проблема пока не обсуждалась, поэтому перейдём к её рассмотрению. Каждое обращение к памяти требует двух дополнительных операций чтения – каталога таблиц страниц и таблицы страниц. Процессор аппаратно поддерживает операцию кэширования адресов страниц. Пары физических и соответствующих им виртуальных адресов страниц сохраняются в ассоциативном буфере страничного преобразования (TLB). TLB может содержать до 32-х записей, которые содежат физические адреса последних обращений к памяти. TLB позволяет избежать с довольно высокой степенью вероятности, дополнительного чтения записей таблиц страниц при обращении к страницам памяти. При каждом обращении к памяти, по какому либо виртуальному адресу аппаратура проверяет TLB, и в случае если в этом буфере уже содержится запись с физическим адресом страницы берёт его из буфера. Эта ситуация называется попаданием в кэш (cache hit). В этом случае пропадает необходимость в чтении каталога и таблиц страниц. Однако, если в буфере отсутствует нужная запись происходит чтение PTE из таблиц и новая запись создаётся и помещается в буфер. Если буфер TLB уже заполнен, процессор удаляет запись из начала буфера, перед тем как поместить в буфер новую запись. В наихудшем случае каждое очередное обращение к памяти не попадает в кэш, и он становится бесполезным. Однако такая ситуация маловероятна. В многозадачной операционной системе использование буфера TLB имеет одну важную особенность. При переключении контекста памяти (т.е. при изменении записей таблиц страниц) кэш по прежнему содержит записи соответствующие предыдущему контексту. Поэтому одновременно с переключением контекста памяти операционная система должна сбросить кэш буфер - удалить все имеющиеся в нём записи. Эта операция происходит автоматически при перезаписи содержимого регистра CR3.

Общая память.

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

27

одинаковые физические. Это простое решение, однако, не лишено недостатков. Состояние страницы памяти динамически изменяется. Страница может располагаться в физической памяти или на диске, например в файле подкачки. При изменении состояния страницы (загрузки в память или выгрузки на диск), система должна модифицировать записи PTE каждого процесса который совместно с другими процессами использует эту страницу. Эта сложная операция приводит в целом к снижению производительности работы системы. Windows NT использует метод позволяющий избежать сканирования таблиц страниц процессов при загрузке общей страницы в физическую память. Однако выгрузка страницы из физической памяти производится описанным выше способом. Каждый процесс Win NT имеет свою собственную структуру таблиц страниц и корневой каталог таблиц страниц. Для общих страниц система дополнительно размещает структуры данных, которые называются прототипами таблиц страниц (prototype page table entries /PPTE). Прототипы таблиц страниц размещаются в глобальной памяти в старших адресах виртуального адресного пространства. Запись таблицы страниц процесса, которая назначается для организации общей памяти, первоначально указывает не на общую страницу памяти, а на структуру PPTE. В свою очередь PPTE содержит указатель на страницу. Система размещает прототипы таблиц страниц динамически по запросу. Детали этого механизма будут рассмотрены ниже.

Процесс 1

 

Процесс 2

Каталог

 

Каталог

таблиц

 

табиц

Запись таблицы

Прототип

Запись таблицы

страниц

записи

страниц

 

 

таблицы

 

 

 

 

страниц

 

 

Станица

Страница

Общая

Страница

Страница

 

 

страница

 

 

Рисунок 3. Организация общей памяти в Windows NT.

 

 

Программно общую память можно организовать несколькими различными методами. В конце этой главы подробно рассматривается метод, основанный на использовании Файлов отображаемых в память (Memory mapped files). В ряде случаев можно ограничиться более простым с точки зрения программной реализации способом. Он менее универсален и применяется для организации общих данных для всех загруженных экземпляров одного приложения или динамической библиотеки. Общие данные должны размещаться в секции исполняемого файла с атрибутом SHARED. Такая секция объявляется в MSVC следующим образом:

#pragma data_seg(«MYSHARE»)

,где «MYSHARE» выбранное произвольно имя секции

Все переменные, размещаемые в секции MYSHARE, объявляются директивой

__declspec(allocate(«MYSHARE»)). Например:

__declspec(allocate(«MYSHARE»)) int sharedvar;

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

28

LINK < ... > /SECTION:MYSHARE, RWS <...>

Параметр RWS указывает, что секция доступна для чтения и записи и располагается в памяти общей для всех экземпляров исполняемого файла (read-write-shareable).

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

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

Технология копирования при записи.

Совместное использование кода приложения несколькими его экземплярами приводит к экономии памяти, но вместе с тем вызывает проблемы при отладке приложения. Отладчик размещает точки останова и модифицирует код приложения. Одновременно могут выполняться несколько экземпляров приложения. Если один из них работает под управлением отладчика это не должно отражаться на остальных. Следовательно, в данном случае необходимо иметь в памяти, по крайней мере, две копии кода приложения: одну для экземпляра приложения, который отлаживается и другую для всех остальных. Операционная система Windows NT использует для решения этой проблемы технологию копирования при записи. Эта технология используется и для кода и для данных приложения. Основная идея состоит в том, что система создаёт копии только тех страниц, которые модифицируются в процессе работы приложения. Остальные страницы всё равно кода или данных остаются общими для всех экземпляров приложения и в единственном числе. Первоначально этому условию удовлетворяют все страницы приложения. Кроме этого все страницы имеют атрибут доступа только для чтения. Попытка записи в такую страницу приводит к исключительной ситуации. Системный обработчик исключения снимает атрибут страницы «только чтение» создаёт копию страницы и отображает её (корректирует PTE) в адресное пространство процесса, который произвёл операцию записи. Процесс в данном случае это один из запущенных экземпляров приложения. При загрузке очередного одном экземпляра приложения для него создаются отдельные копии страниц, которые на данный момент были модифицированы ранее запущенными экземплярами. Таким образом, в Windows NT страницы дублируются по запросу, что существенно экономит память. Windows 95 не поддерживает это механизм, однако, имеет специальные средства для поддержки процедуры отладки программ. Windows API включают функции WriteProcessMemory и ReadProcessMemory которые используются отладчиками для чтения и модификации памяти отлаживаемых процессов. Эти функции не позволяют работать с адресами выше 2ГБ. Поэтому для трассировки кода системных динамических библиотек Windows 95 размещённого выше 2ГБ необходимо использовать системные отладчики, такие как SoftIce, которые не полагаются в своей работе на эти функции.

Переключение контекстов памяти.

Основными элементами мультизадачной структуры Windows являются процессы и потоки. Каждому процессу соответствует отдельный контекст памяти, включающий частные и общие страницы. Каждый процесс должен содержать, по крайней мере, один поток, который определяет последовательность исполнения инструкций программы. Операционная система переключает контексты памяти всякий раз при передаче управления потоку, процесс которого отличен от текущего. Контексты памяти организованы по разному в различных операционных системах. В Windows NT каждый процесс имеет собственную структуру таблиц страниц и собственный каталог таблиц страниц. Регистр CR3 микропроцессора 80386 содержит указатель на каталог таблиц страниц текущего процесса. Таким образом, для того чтобы переключить контекст достаточно записать в регистр CR3 указатель на каталог таблиц очередного процесса. При этом также автоматически очищается ассоциативный буфер страничного преобразования. В Windows 95 каталог таблиц страниц совместно используется всеми

29

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

Управление физической памятью в Windows NT. Рабочие наборы страниц

(Working Sets).

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

Операционная система может дополнять список новыми страницами или удалять страницы из списка (отнимать их у процесса) в зависимости от текущего размера рабочего набора страниц и текущего объёма свободной физической памяти. Менеджер Памяти добавляет страницы к списку при их загрузке в физическую память. Загрузка, как правило, происходит при попытке обращения к странице не присутствующей в физической памяти. NT использует метод кэширования страниц (clustered demand paging) для оптимизации механизма загрузки страниц по запросу (demand paging). Если какой либо процесс обращается к странице не присутствующей в физической памяти, имеется высокая вероятность того, что следующие обращения к памяти будут происходить в окрестности этой страницы. Менеджер памяти NT поэтому загружает вместе со страницей, к которой было произведено обращение ещё несколько страниц расположенных вокруг неё. Эти страницы составляют кластер, размер которого зависит от типа содержимого страниц (код или данные) и от объёма свободной физической памяти. В кластер может входить от 1 до 8 страниц. Если физическая память полностью занята, Менеджер Памяти должен выгрузить одну из страниц из памяти в файл подкачки. Для определения такой страницы используется алгоритм Least Recently Used (LRU). Алгоритм находит страницу, которая использовалась «наиболее давно». Считается, что вероятность обращения к странице памяти тем ниже, чем дольше к ней не обращаются. Алгоритм использует свойство аппаратуры помечать страницы, к которым производится обращение. Когда какая либо программа читает или записывает данные в страницу, микропроцессор устанавливает в единицу бит доступа A (Accessed) в записи таблицы страниц этой страницы. Менеджер памяти сканирует таблицы страниц. Если бит A очередной записи установлен, Менеджер сбрасывает этот бит в ноль и продолжает сканирование. При обнаружении записи PTE со сброшенным битом A процесс поиска останавливается и страница, на которую указывает запись становиться кандидатом на выгрузку из памяти. Если при первом проходе бит A всех записей был установлен, что само по себе маловероятно, сканирование таблиц повторяется. Так как биты A были сброшены во время первого прохода, вероятность успешного завершения поиска на втором проходе практически равна единице. Однако, принятие решения о том какая именно страница должна быть выгружена из памяти делается не только на основе изложенного алгоритма. Операционная система NT руководствуется правилами так называемых локальной и глобальной политики удаления страниц из физической памяти. Когда какой либо процесс обращается к не присутствующей в памяти странице система может освободить память для загрузки этой страницы удалив наиболее «старую» страницу из рабочего набора страниц этого процесса или из рабочего набора другого процесса или просто выделить этой странице свободную физическую память. В первом случае применяется локальная политика, а в двух последних глобальная. Решение о том какая политика должна использоваться в каждом конкретном случае зависит от текущего размера рабочего набора страниц процесса и объёма свободной физической памяти. Система задаёт минимальный и максимальный размеры рабочих наборов страниц. Если текущий размер превышает максимальный, и свободные страницы отсутствуют, используется локальная политика. В системе в фоновом режиме работает специальный модуль - Менеджер баланса наборов страниц (Balance Set Manager). Он периодически проверят размеры Рабочих наборов страниц процессов, и удаляет из них «лишние» страницы. Основная задача этого модуля поддерживать баланс использования памяти между различными процессами. Размер рабочего набора страниц процесса может увеличиваться, по мере того как процесс обращается к неприсутствующим в физической памяти страницам. С другой стороны его размер уменьшается Менеджером баланса или в результате применения глобальной политики по мере роста рабочих наборов других процессов, которые более активно работают с памятью.

База данных страниц (Page Frame Database).

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

30

этой таблице соответствует одной из физических страниц. Первая запись первой странице вторая запись второй странице и т.д.. (См. рисунок 7).

Процесс 1

 

Valid

Рабочий

 

 

Standby

набор

 

 

Modified

страниц

 

 

Modified nw

 

 

 

 

Free

...

 

Zeroed

 

In Transition

 

 

 

 

Bad

 

 

Modified nw

Процесс 2

 

In Transition

ОБЩАЯ

Standby

Рабочий

Free

 

Modified

набор

 

 

Valid

страниц

 

 

Zeroed

 

 

 

 

Standby

...

 

Valid

 

Free

 

 

 

 

Valid

 

 

Valid

 

 

Free

 

 

...

 

 

База данных

 

 

физических

 

 

страниц

Таблица страниц Процесса 1

...

Прототип записи таблицы страниц

(PPTE)

Таблица страниц Процесса 2

...

Рисунок 7. База Данных Физических Страниц (Page Frame Database)

Каждая запись в PFD содержит информацию о текущем состоянии физической страницы памяти. Если страница принадлежит рабочему набору какого либо процесса, она считается Активной (Active или Valid). Если Менеджер Памяти удаляет страницу из рабочего набора, страница переходит в одно из следующих состояний:

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

-Modified. Страница модифицирована. Если процесс изменил содержимое страницы, и эти изменения не были сохранены на диске, пока страница была активной. Такая страница не может быть использована до тех пор, пока Менеджер Памяти не сохранит её содержимое на диске. Менеджер Памяти периодически сканирует базу данных страниц. При этом страницы Modified сохраняются на диске.

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

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

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

31

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

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

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

Вместе с информацией о состоянии записи PFD содержат указатели на следующую запись таблицы, имеющую то же состояние. Например, запись страницы Standby указывает на следующую запись Standby в базе данных. Страницы, находящиеся в одинаковых состояниях объединены в связные списки. Это упрощает работу Менеджера Памяти. Исключение составляют активные страницы и страницы находящиеся в процессе передачи.

Когда требуется найти свободную страницу памяти Менеджер Памяти начинает поиск со списка Zeroed, если этот список пуст поиск продолжается в списке Free и наконец в списке Standby. Номер найденной свободной страницы добавляется к связному списку рабочего набора страниц процесса, а страница переходит в состояние Valid.

Детали организации общей памяти в Windows NT.

Запись PFD активной страницы также содержит указатель на запись таблицы страниц, которая в свою очередь содержит «аппаратные» атрибуты и ссылку на физическое месторасположение страницы. В частном адресном пространстве процессов одной физической странице и, следовательно, одной записи в базе данных физических страниц соответствует одна запись PTE в таблицах страниц. Но в случае, если одна и та же физическая страница совместно используется несколькими процессами, для неё должны быть созданы отдельные записи PTE в таблицах страниц каждого процесса (рисунок 7). Однако, запись в PFD не может одновременно указывать на несколько записей PTE. В этом случае она указывает на структуру Прототипа Записи Таблицы Страниц (PPTE). Структура PPTE размещается в памяти и инициализируется операционной системой. Она используется для организации общей памяти. PPTE содержит ссылку на страницу - физический адрес или смещение в файле подкачки. Записи PTE процессов, которые совместно используют страницу, могут содержать в зависимости от ситуации адрес структуры PPTE или собственно адрес страницы. Поясним это на примере:

Пусть страница совместно используется двумя процессами. Каждый процесс имеет в одной из своих таблиц страниц запись PTE этой страницы. Когда страница не присутствует в памяти, в записях PTE она помечена как не присутствующая (notpresent). При этом записи PTE содержат адреса структуры PPTE. PPTE в свою очередь содержит ссылку на месторасположение страницы в файле подкачки. Обращение одного из процессов (далее Процесс1) к странице приводит к исключительной ситуации (page fault). Обработчик исключения находит в PTE страницы адрес PPTE. Далее по ссылке в PPTE находит и загружает из файла страницу. Ссылка на страницу в PPTE, а так же адрес PPTE в записи PTE Процесса 1 заменяются на физический адрес загруженной страницы. Атрибут not-present в записи PTE процесса сбрасывается. После этой операции Процесс1 может свободно обращаться к странице. Когда второй процесс (Процесс 2) пытается обратиться к странице, процессор снова вырабатывает исключение. Это происходит потому, что запись PTE этого процесса по прежнему имеет атрибут not-present и указывает на PPTE. Система обращается к PPTE и определяет, что страница уже загружена в физическую память. В этом случае система просто переписывает в PTE Процесса 2 физический адрес страницы из PPTE. Таким образом, при загрузке страницы в память отпадает необходимость в сканировании и коррекции записей таблиц страниц всех процессов. Однако при выгрузке страницы все структуры должны быть приведены в исходное состояние. В записи PTE процессов заносится атрибут not-present и адрес PPTE. А в структуру PPTE помещается ссылка на место страницы в файле подкачки.

32

Функции для управления памятью.

Операционная система предлагает несколько способов работы с памятью. Функции управления памятью разделяются на три основные группы: функции управления виртуальной памятью, функции управления кучей (heap) и функции для организации отображения файлов в память (memory mapped files). Приведём краткое описание каждой группы функций:

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

Эти функции используются для резервирования и управления состоянием страниц виртуальной памяти. Функции, как правило, используются для работы с большими блоками памяти, выровненными по границе страниц. Страница виртуальной памяти может быть свободна (free), зарезервирована (reserved) и используема (committed). Свободные страницы потенциально доступны, однако не используются в данный момент и не зарезервированы для использования в будущем. Такие страницы могут быть преобразованы в зарезервированные или используемые. Зарезервированные страницы не занимают физического пространства на диске или в памяти. Резервирование диапазона виртуальных адресов означает лишь только то, что этот диапазон не будет участвовать в последующих операциях резервирования памяти. Содержимое страниц используемой памяти хранится в физической памяти и на диске в файле подкачки. Такие страницы могут быть защёлкнуты (locked). Если страница защелкнута, её содержимое всегда находится в физической памяти.

Для резервирования и/или перевода в используемое состояние страниц памяти используется функция VirtuallAlloc.

LPVOID

VirtualAlloc(

// базовый адрес области памяти

LPVOID lpAddress,

DWORD

dwSize,

// размер области памяти в байтах

DWORD

flAllocationType, // тип операции

DWORD

flProtect

// атрибуты защиты памяти

);

 

 

Параметр flAllocationType задаёт тип операции - резервирование памяти и/или перевод в используемое состояние. Параметр flProtect определяет атрибуты защиты области памяти. LpAddress - начальный адрес - всегда выравнивается по границе 64K для резервируемой памяти и по границе 4k для используемой памяти. Размер всегда округляется вверх до границы страницы.

Пример использования функции:

/* Резервируем область размером 10 МБ */

lpBase = VirtualAlloc (NULL, 10485760, MEM_RESERVE, PAGE_NOACCESS);

/* Переводим в ипользумое состояние 3-ю страницу этой области. */ lpPage3 = VirtualAlloc (lpBase + (2 * 4096),

4096, MEM_COMMIT, PAGE_READWRITE);

Функция VirtualFree используется для освобождения зарезервированной или используемой памяти.

BOOL VirtualFree(

LPVOID lpAddress, // базовый адрес области памяти DWORD dwSize, // размер области памяти в байтах DWORD dwFreeType // тип операции

);

Функция может перевести память из используемого состояния в зарезервированное или свободное. Тип операции задаётся четвёртым параметром dwFreeType. Примеры:

/* Переводим из используемого в зарезервированное состояние 3-ю страницу ранее размещённого диапазона. */

VirtualFree (lpBase + (2 * 4096),

33

4096, MEM_DECOMMIT, PAGE_NOACCESS);

/* Освобождаем весь диапазон размером 10 МБ. */

VirtualFree (lpBase, 10485760, MEM_RELEASE, PAGE_NOACCESS);

Атрибуты защиты заданные функцией VirtualAlloc можно изменить с помощью функции

VirtualProtect.

BOOL VirtualProtect(

// базовый адрес области памяти

LPVOID lpAddress,

DWORD dwSize,

// размер области памяти в байтах

DWORD flNewProtect,

// атрибуты защиты памяти

PDWORD lpflOldProtect

// адрес для возврата старых атрибутов

);

защиты

 

Праметр flNewProtect может содержать флаги: PAGE_READONLY, PAGE_READWRITE, PAGE_EXECUTE_READ и др.

Например:

/* Разрешаем чтение и запись в страницу */

VirtualProtect (lpStack + 4096, 4096, PAGE_READWRITE, lpdwOldProt);

Используемые страницы могут быть защёлкнуты в памяти. Защёлкнутая страница всегда находится в физической памяти и не может быть выгружена на диск в файл подкачки. Защёлкиваются обычно код и данные, принимающие участие в критичных ко времени исполнения процедурах. Функция VirtualLock защёлкивает страницы памяти, а функция VirtalUnlock отменяет защёлкивание.

BOOL VirtualLock(

LPVOID lpAddress, // базовый адрес области памяти DWORD dwSize // размер области памяти в байтах

);

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

Куча (Heap).

Куча это надстройка над функциями управления виртуальной памятью, которая позволяет более экономно использовать виртуальные адреса. Функции кучи допускают работу с блоками данных, размеры которых не кратны размеру страницы. Приложения, которые используют эти функции, могут не учитывать специфику внутреннего страничного управления памятью. Они получают динамически выделенные блоки памяти точно заданного в байтах размера. В Windows 3.x имеются кучи двух типов: одна глобальная куча и локальные кучи для каждого приложения. Глобальная куча Windows 3.х доступна для всех приложений и в частности используется для организации обмена данными между приложениями Win16. Функции для работы с кучей в этой операционной системе разделяются на функции для работы с глобальной и локальной кучей. Функции отличаются префиксами - соответственно Local и Global. В 32-х разрядных OC Windows каждый процесс имеет свою собственную кучу. Функции с префиксами Local и Global в приложениях Win32 работают одинаково. Для работы с кучей имеется несколько функций, включая :

GlobalAlloc, - размещает в куче блок памяти заданного размера; GlobalFree, - освобождает блок памяти

GlobalRealloc - изменяет размер блока памяти.

34

GlobalLock, and GobalUnLock - защёлкивает и отменяет защёлкивание блока памяти.

Отметим, что имеется такой же набор функций с префиксом Local.

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

LocalLock).

Файлы отображаемые в память (Memory Mapped Files).

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

Mov Eax, [Edi]

Идея отображения файлов в память заключается в том, что содержимое файла или фрагмента файла проецируется в заданный диапазон виртуальных адресов. Файл делится на страницы размером 4K. Операционная система создаёт записи таблиц страниц для этого диапазона. Первоначально страницы файла помечены как неприсутствующие в памяти (not present). Запись PTE каждой такой страницы содержит ссылку на файл, в котором расположена страница, и смещение страницы от начала файла. Обращение странице вызывает исключительную ситуацию (page fault) и системный обработчик исключения загружает страницу в физическую память. Этот механизм используется операционной системой для организации виртуальной памяти на диске в файле (файлах) подкачки. Однако его можно применить к любому файлу. Длина файла может быть слишком велика, чтобы его можно было целиком отобразить в память. Поэтому допускается отображать в память отдельные фрагменты, файла которые называются окна (views). В окно может входить фрагмент файла или весь файл. Операционная система отображает в память не только файл подкачки. Эта же технология применяется при загрузке исполняемых модулей: приложений и динамических библиотек. При загрузке приложения секции файла приложения содержащие код отображаются в память. Формат исполняемых файлов Windows (portable executable file format, сокращённо PE) сконструирован таким образом, что содержимое файла исполняемого модуля почти не отличается от его образа в памяти после загрузки. В общем, можно сказать, что операционная система использует файл подкачки для хранения страниц содержащих данные и файлы приложений для хранения страниц содержащих код (Рисунок 4).

 

 

 

Диск

Виртуалная

 

 

 

 

 

 

 

 

 

 

 

память

 

 

 

 

 

 

 

 

 

Процесса 1

 

 

 

 

 

 

 

 

 

Станицы кода

 

 

 

 

 

 

 

 

 

Файл программы 1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Станицы данных

 

 

 

 

 

 

 

 

 

 

 

 

Системный файл подкачки

 

 

 

 

 

 

 

 

 

 

 

 

 

Виртуалная

 

 

 

 

 

 

 

 

 

память

 

 

 

 

 

 

 

 

 

Процесса 2

 

 

 

 

 

 

 

 

 

Станицы данных

 

 

 

 

 

 

 

 

 

Файл программы 2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Станицы кода

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

35