ГЛАВА 5  

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

 

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

 

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

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

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

Функциями ОС по управлению памятью в мультипрограммной системе явля­ются:

□   отслеживание свободной и занятой памяти;

□   выделение памяти процессам и освобождение памяти по завершении процес­сов;

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

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

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

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

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

 

Типы адресов

 

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

□   Символьные имена присваивает пользователь при написании программы на алгоритмическом языке или ассемблере.

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

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

Совокупность виртуальных адресов процесса называется виртуальным адресным пространством. Диапазон возможных адресов виртуального пространства у всех процессов является одним и тем же. Например, при использовании 32-разрядных виртуальных адресов этот диапазон задается границами 0000000016 и FFFFFFFF. Тем не менее каждый процесс имеет собственное виртуальное ад­ресное пространство — транслятор присваивает виртуальные адреса переменным и кодам каждой программе независимо (рис. 5.2).

 

 

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

В разных операционных системах используются разные способы структуриза­ции виртуального адресного пространства. В одних ОС виртуальное адресное пространство процесса подобно физической памяти представлено в виде непрерывной линейной последовательности виртуальных адресов. Такую структуру адресного пространства называют также плоской (flat). При этом виртуальным адресом является единственное число, представляющее собой смещение отно­сительно начала (обычно это значение 000...000) виртуального адресного пространства (рис. 5.3, а). Адрес такого типа называют линейным виртуальным ад­ресом.

В других ОС виртуальное адресное пространство делится на части, называемые

сегментами (или секциями, или областями, или другими терминами). В этом

случае помимо линейного адреса может быть использован виртуальный адрес,

представляющий собой пару чисел (и, т), где п определяет сегмент, am — смещение внутри сегмента (рис. 5.3, б).

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

 

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

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

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

Второй способ заключается в том, что программа загружается в память в неизме­ненном виде в виртуальных адресах, то есть операнды инструкций и адреса пере­ходов имеют те значения, которые выработал транслятор. В наиболее простом случае, когда виртуальная и физическая память процесса представляют собой единые непрерывные области адресов, операционная система выполняет преоб­разование виртуальных адресов в физические по следующей схеме. При загруз­ке операционная система фиксирует смещение действительного расположения программного кода относительно виртуального адресного пространства. Во время выполнения программы при каждом обращении к оперативной памяти выполня­ется преобразование виртуального адреса в физический. Схема такого преобра­зования показана на рис. 5.4. Пусть, например, операционная система использует линейно-структурированное виртуальное адресное пространство и пусть некото­рая программа, работающая под управлением этой ОС, загружена в физическую память начиная с физического адреса S. ОС запоминает значение начального смещения S и во время выполнения программы помещает его в специальный ре­гистр процессора. При обращении к памяти виртуальные адреса данной програм­мы преобразуются в физические путем прибавления к ним смещения S. Напри­мер, при выполнении инструкции MOV пересылки данных, находящихся по адресу VA, виртуальный адрес VA заменяется физическим адресом VA+S.

 

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

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

Необходимо различать максимально возможное виртуальное адресное простран­ство процесса и назначенное (выделенное) процессу виртуальное адресное про­странство. В первом случае речь идет о максимальном размере виртуального адресного пространства, определяемом архитектурой компьютера, на котором работает ОС, и, в частности, разрядностью его схем адресации (32-битная, 64-бит­ная и т. п.). Например, при работе на компьютерах с 32-разрядными процессорами Intel Pentium операционная система может предоставить каждому процессу вир­туальное адресное пространство до 4 Гбайт (232). Однако это значение представ­ляет собой только потенциально возможный размер виртуального адресного пространства, который редко на практике бывает необходим процессу. Процесс использует только часть доступного ему виртуального адресного пространства.

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

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

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

 

 

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

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

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

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

Обычно виртуальное адресное пространство процесса делится па две непрерывные части: системную и пользовательскую. В некоторых ОС (например, Windows NT, OS/2) эти части имеют одинаковый размер — по 2 Гбайт, хотя в принципе деление может быть и другим, например 1 Гбайт — для ОС, и 2 Гбайт —для прикладных программ. Часть виртуального адресного пространства каждого процесса, отводимая под сегменты ОС, является идентичной для всех процессов. Поэтому при смене активного процесса заменяется только вторая часть виртуального адресного пространства, содержащая его индивидуальные сегменты как правило, — коды и данные прикладной программы (рис. 5.6). Архитектура современных процессоров отражает эту особенность структуры виртуального адресного пространства, например, в процессорах Intel Pentium существует два типа системных таблиц: одна — для описания сегментов, общих для всех процессов, а другая — для описания индивидуальных сегментов данного процесса. При смене процесса первая таблица остается неизменной, а вторая заменяется новой.

 

 

 

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

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

Системная часть виртуальной памяти в ОС любого типа включает область, под­вергаемую страничному вытеснению (paged), и область, на которую страничное вытеснение не распространяется {non-paged). В не вытесняемой области разме­щаются модули ОС, требующие быстрой реакции и/или постоянного присутст­вия в памяти, например диспетчер потоков или код, который управляет заменой страниц памяти. Остальные модули ОС подвергаются страничному вытеснению, как и пользовательские сегменты.

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

 

Алгоритмы распределения памяти

 

Следует ли назначать каждому процессу одну непрерывную область физической памяти или можно выделять память «кусками»? Должны ли сегменты програм­мы, загруженные в память, находиться на одном месте в течение всего периода выполнения процесса или можно ее время от времени сдвигать? Что делать, если сегменты программы не помещаются в имеющуюся память? Разные ОС по-разному отвечают на эти и другие базовые вопросы управления памятью. Далее будут рассмотрены наиболее общие подходы к распределению памяти, которые были характерны для разных периодов развития операционных систем. Некото­рые из них сохранили актуальность и широко используются в современных ОС, другие же представляют в основном только познавательный интерес, хотя их и сегодня можно встретить в специализированных системах.

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

 

 

 

 

Распределение памяти фиксированными разделами

 

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

Очередной новый процесс, поступивший на выполнение, помещается либо в об­щую очередь (рис. 5.8, а), либо в очередь к некоторому разделу (рис. 5.8, б).

 

 

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

□ Сравнивает объем памяти, требуемый для вновь поступившего процесса, с размерами

□ свободных разделов и выбирает подходящий раздел.

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

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

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

 

Распределение памяти динамическими разделами

 

В этом случае память машины не делится заранее на разделы. Сначала вся па­мять, отводимая для приложений, свободна. Каждому вновь поступающему на выполнение приложению на этапе создания процесса выделяется вся необходи­мая ему память (если достаточный объем памяти отсутствует, то приложение не принимается на выполнение и процесс для него не создается). После завершения процесса память освобождается, и на это место может быть загружен другой про­цесс. Таким образом, в произвольный момент времени оперативная память пред­ставляет собой случайную последовательность занятых и свободных участков (разделов) произвольного размера. На рис. 5.9 показано состояние памяти в раз­личные моменты времени при использовании динамического распределения. Так, в момент t0 в памяти находится только ОС, а к моменту t1 память разделена между 5 процессами, причем процесс П4, завершаясь, покидает память. На осво­бодившееся от процесса П4 место загружается процесс П6, поступивший в мо­мент t3.

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

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

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

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

□   После завершения процесса корректировка таблиц свободных и занятых об­ластей.

 

 

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

Распределение памяти динамическими разделами лежит в основе подсистем управ­ления памятью многих мультипрограммных операционных системах 60-70-х го­дов, в частности такой популярной операционной системы, как OS/360.

 

 

 

Перемещаемые разделы

 

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

 

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

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

Концепция сжатия применяется и при использовании других методов распреде­ления памяти, когда отдельному процессу выделяется не одна сплошная область памяти, а несколько несмежных участков памяти произвольного размера (сег­ментов). Такой подход был использован в ранних версиях OS/2, в которых па­мять распределялась сегментами, а возникавшая при этом фрагментация устра­нялась путем периодического перемещения сегментов.

 

Свопинг и виртуальная память

 

Необходимым условием для того, чтобы программа могла выполняться, являет­ся ее нахождение в оперативной памяти. Только в этом случае процессор может извлекать команды из памяти и интерпретировать их, выполняя заданные дейст­вия. Объем оперативной памяти, который имеется в компьютере, существенно сказывается на характере протекания вычислительного процесса. Он ограничи­вает число одновременно выполняющихся программ и размеры их виртуальных адресных пространств. В некоторых случаях, когда все задачи мультипрограмм­ной смеси являются вычислительными (то есть выполняют относительно мало операций ввода-вывода, разгружающих центральный процессор), для хорошей загрузки процессора может оказаться достаточным всего 3-5 задач. Однако если вычислительная система загружена выполнением интерактивных задач, то для эффективного использования процессора может потребоваться уже несколько де­сятков, а то и сотен задач. Эти рассуждения хорошо иллюстрирует рис. 5.11, на котором показан график зависимости коэффициента загрузки процессора от числа одновременно выполняемых процессов и доли времени, проводимого эти­ми процессами в состоянии ожидания ввода-вывода.

 

 

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

В мультипрограммном режиме помимо активного процесса, то есть процесса, коды которого в настоящий момент.  Интерпретируются процессором, имеются приостановленные процессы, находящиеся в ожидании завершения ввода-выво­да или освобождения ресурсов, а также процессы в состоянии готовности, стоя­щие в очереди к процессору. Образы таких неактивных процессов могут быть временно, до следующего цикла активности, выгружены на диск. Несмотря на то что коды и данные процесса отсутствуют в оперативной памяти, ОС «знает» о его существовании и в полной мере учитывает это при распределении процессорно­го времени и других системных ресурсов. К моменту, когда подходит очередь выполнения выгруженного процесса, его образ возвращается с диска в оператив­ную память. Если при этом обнаруживается, что свободного места в оперативной памяти не хватает, то на диск выгружается другой процесс.

Такая подмена {виртуализация) оперативной памяти дисковой памятью позво­ляет повысить уровень мультипрограммирования — объем оперативной памяти компьютера теперь не столь жестко ограничивает количество одновременно вы­полняемых процессов, поскольку суммарный объем памяти, занимаемой образа­ми этих процессов, может существенно превосходить имеющийся объем опера­тивной памяти. Виртуальным называется ресурс, который пользователю или пользовательской программе представляется обладающим свойствами, которы­ми он в действительности не обладает. В данном случае в распоряжение приклад­ного программиста предоставляется виртуальная оперативная память, размер которой намного превосходит всю имеющуюся в системе реальную оперативную память. Пользователь пишет программу, а транслятор, используя виртуальные адреса, переводит ее в машинные коды так, как будто в распоряжении програм­мы имеется однородная оперативная память большого объема. В действитель­ности же все коды и данные, используемые программой, хранятся на дисках и только при необходимости загружаются в реальную оперативную память. По­нятно, однако, что работа такой «оперативной памяти» происходит значительно медленнее.

Виртуализация оперативной памяти осуществляется совокупностью программ­ных модулей ОС и аппаратных схем процессора и включает решение следующих задач:

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

□  выбор образов процессов или их частей для перемещения из оперативной па­мяти на диск и обратно;

□ перемещение по мере необходимости данных между памятью и диском;

□   преобразование виртуальных адресов в физические.

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

ПРИМЕЧАНИЕ----------------------------------------------------------------------------------------------

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

 

Виртуализация памяти может быть осуществлена на основе двух различных под­ходов:

свопинг (swapping) — образы процессов выгружаются на диск и возвращаются в оперативную память целиком;

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

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

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

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

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

□   Сегментная виртуальная память предусматривает перемещение данных сег­ментами — частями виртуального адресного пространства произвольного раз­мера, полученными с учетом смыслового значения данных.

□   Сегментно-страничная виртуальная память использует двухуровневое деле­ние: виртуальное адресное пространство делится на сегменты, а затем сегмен­ты делятся на страницы. Единицей перемещения данных здесь является стра­ница. Этот способ управления памятью объединяет в себе элементы обоих предыдущих подходов.

Для временного хранения сегментов и страниц на диске отводится либо специ­альная область, либо специальный файл, которые во многих ОС по традиции продолжают называть областью, или файлом свопинга, хотя перемещение ин­формации между оперативной памятью и диском осуществляется уже не в фор­ме полного замещения одного процесса другим, а частями. Другое популярное название этой области — страничный файл (page file, или paging file). Текущий размер страничного файла является важным параметром, оказывающим влияние на возможности операционной системы: чем больше страничный файл, тем боль­ше приложений может одновременно выполнять ОС (при фиксированном раз­мере оперативной памяти). Однако необходимо понимать, что увеличение числа одновременно работающих приложений за счет увеличения размера страничного файла замедляет их работу, так как значительная часть времени при этом тра­тится на перекачку кодов и данных из оперативной памяти на диск и обратно. Размер страничного файла в современных ОС является настраиваемым парамет­ром, который выбирается администратором системы для достижения компро­мисса между уровнем мультипрограммирования и быстродействием системы.

 

Страничное распределение

 

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

Вся оперативная память машины также делится на части такого же размера, на­зываемые физическими страницами (или блоками, или кадрами). Размер страницы выбирается равным степени двойки: 512, 1024, 4096 байт и т. д. Это позволяет упростить механизм преобразования адресов.

 

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

Запись Таблицы, называемая дескриптором страницы, включает следующую ин­формацию:

номер физической страницы, в которую загружена данная виртуальная стра­ница;

признак присутствия, устанавливаемый в единицу, если виртуальная страни­ца находится в оперативной памяти;

признак модификации страницы, который устанавливается в единицу вся­кий раз, когда производится запись по адресу, относящемуся к данной стра­нице;

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

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

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

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

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

Виртуальный адрес при страничном распределении может быть представлен в виде пары (р, sv), где р — порядковый номер виртуальной страницы процесса (нумерация страниц начинается с 0), a sv — смещение в пределах виртуальной страни1$ы. Физический адрес также может быть представлен в виде пары (n, sf), где n — номер физической страницы, a sf — смещение в пределах физической страницы. Задача подсистемы виртуальной памяти состоит в отображении (р, sv) в (n, sf).

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

Первое из них состоит в том, что объем страницы выбирается равным степени двойки — 2к. Из этого следует, что смещение s может быть получено простым от­делением к младших разрядов в двоичной записи адреса, а оставшиеся старшие разряды адреса представляют собой двоичную запись номера страницы (при этом неважно, является страница виртуальной или физической). Например, если размер страницы 1 Кбайт (210), то из двоичной записи адреса 50718 = = 101 000 111 0012 можно определить, что он принадлежит странице, номер ко­торой в двоичном выражении равен 102 и смещен относительно ее начала на 1000 111 0012 байт (рис. 5.13).

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


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

Второе свойство заключается в том, что в пределах страницы непрерывная по­следовательность виртуальных адресов однозначно отображается в непрерыв­ную последовательность физических адресов, а значит, смещения в виртуальном и физическом адресах sv и Sf равны между собой (рис. 5.14).

 

 

 

 

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

Итак, пусть произошло обращение к памяти по некоторому виртуальному адре­су. Аппаратными схемами процессора выполняются следующие действия:

1.   Из специального регистра процессора извлекается адрес AT таблицы страниц активного процесса. На основании начального адреса таблицы страниц, номе­ра виртуальной страницы р (старшие разряды виртуального адреса) и длины отдельной записи в таблице страниц L (системная константа) определяется адрес нужного дескриптора в таблице страниц: a=AT+(pxL).

2.   Из этого дескриптора извлекается номер соответствующей физической стра­ницы — n.

3.   К номеру физической страницы присоединяется смещение s (младшие разря­ды виртуального адреса).

Типичная машинная инструкция требует 3-4 обращений к памяти (выборка команды, извлечение операндов, запись результата). И при каждом обращении

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

 

 

 

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

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

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

Наиболее популярным критерием выбора страницы на выгрузку является число обращений к ней за последний период времени. Вычисление этого критерия про­исходит следующим образом. Операционная система ведет для каждой страни­цы программный счетчик. Значения счетчиков определяются значениями при­знаков доступа. Всякий раз, когда происходит обращение к какой-либо странице, процессор устанавливает в единицу признак доступа в относящейся к данной странице записи таблицы страниц. ОС периодически просматривает признаки доступа всех страниц во всех существующих в данный момент записях таблицы страниц. Если какой-либо признак оказывается равным 1 (было обращение), то система сбрасывает его в 0, увеличивая при этом на единицу значение связанно­го с этой страницей счетчика обращений. Когда возникает необходимость уда­лить какую-либо страницу из памяти, ОС находит страницу, счетчик обращений которой имеет наименьшее значение. Для того чтобы критерий учитывал интен­сивность обращений за последний период, ОС с соответствующей периодично­стью обнуляет все счетчики.

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

Другим важным резервом повышения производительности системы является пра­вильный выбор размера страницы. Каким же должен быть оптимальный размер страницы? С одной стороны, чтобы уменьшить частоту страничных прерываний, следовало бы увеличивать размер страницы. С другой стороны, если страница велика, то велика и фиктивная область в последней виртуальной странице каж­дого процесса. Если учесть, что в среднем в каждом процессе фиктивная область составляет половину страницы, то в сумме при большом объеме страницы поте­ри могут составить существенную величину. Из приведенных соображений еле  дует, что выбор размера страницы является сложной оптимизационной задачей, требующей учета многих факторов. На практике же разработчики ОС и процессоров ограничиваются неким рациональным решением, пригодным для широкого класса вычислительных систем. Типичный размер страницы составляет несколько килобайт, например, наиболее распространенные процессоры х86 и

Pentium компании Intel, а также операционные системы, устанавливаемые на этих процессорах, поддерживают страницы размером 4096 байт (4 Кбайт).

Размер Страницы влияет также на количество записей в таблицах страниц. Чем меньше страница, тем более объемными являются таблицы страниц процессов и тем больше места они занимают в памяти. Учитывая, что в современных процес­сорах максимальный объем виртуального адресного пространства процесса, как правило, не меньше 4 Гбайт (232), то при размере страницы 4 Кбайт (212) и длине записи 4 байта для хранения таблицы страниц может потребоваться 4 Мбайт памяти! Выходом в такой ситуации является хранение в памяти только той час­ти таблицы страниц, которая активно используется в данный период времени — так как сама таблица страниц хранится в таких же страницах физической памя­ти, что и описываемые ею страницы, то принципиально возможно временно вы­теснять часть таблицы страниц из оперативной памяти.

Именно такой результат может быть достигнут путем более сложной структури­зации виртуального адресного пространства, при котором все множество вирту­альных адресов процесса делится на разделы, а разделы делятся на страницы (рис. 5.16). Все страницы имеют одинаковый размер, а разделы содержат одина­ковое количество страниц. Если размер страницы и количество страниц в разделе выбрать равными степени двойки (2к и 2к соответственно), то принадлежность виртуального адреса к разделу и странице, а также смещение внутри страницы можно определить очень просто: младшие к двоичных разрядов дают смещение, следующие  разрядов представляют собой номер виртуальной страницы, а ос­тавшиеся старшие разряды (обозначим их количество т) содержат номер раздела.

Для каждого раздела строится собственная таблица страниц. Количество дескрип­торов в таблице и их размер подбираются такими, чтобы объем таблицы оказался равным объему страницы. Например, в процессоре Pentium при размере страницы 4 Кбайт длина дескриптора страницы составляет 4 байта и количество записей в таблице страниц, помещающейся на страницу, равняется соответственно 1024. Каж­дая таблица страниц описывается дескриптором, структура которого полностью совпадает со структурой дескриптора обычной страницы. Эти дескрипторы сведены в таблицу разделов, называемую также каталогом страниц. Физический адрес таб­лицы разделов активного процесса содержится в специальном регистре процессора и поэтому всегда известен операционной системе. Страница, содержащая таблицу разделов, никогда не выгружается из памяти, в противном случае работа виртуаль­ной памяти была бы невозможна.

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Проследим более подробно схему преобразования адресов для случая двухуровне­вой структуризации виртуального адресного пространства (рис. 5.17).:

1.  Путем отбрасывания k+n младших разрядов в виртуальном адресе определя­ется номер раздела, к которому принадлежит данный виртуальный адрес.

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

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

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

 

 

 

Страничное распределение памяти может быть реализовано в упрощенном вариан­те, без выгрузки страниц на диск. В этом случае все виртуальные страницы всех процессов постоянно находятся в оперативной памяти. Такой вариант страничной организации хотя и не предоставляет пользователю преимуществ работы с вирту­альной памятью большого объема, но сохраняет другое достоинство страничной организации — позволяет успешно бороться с фрагментацией физической памяти. Действительно, во-первых, программу можно разбить на части и загрузить в разроз­ненные участки свободной памяти, во-вторых, при загрузке виртуальных страниц
никогда не образуется неиспользуемых остатков, так как размеры виртуальных и физических страниц совпадают. Такой режим работы системы управления памятью используется в некоторых специализированных ОС, когда требуется высокая реак­тивность системы и способность выполнять переменный набор приложений (при­мер — ОС семейства Novell NetWare 3.x и 4.x).

 

Сегментное распределение

 

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

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

Итак, виртуальное адресное пространство процесса делится на части — сегмен­ты, размер которых определяется с учетом смыслового значения содержащейся в них информации. Отдельный сегмент может представлять собой подпрограм­му, массив данных и т. и. Деление виртуального адресного пространства на сег­менты осуществляется компилятором на основе указаний программиста или по умолчанию, в соответствии с принятыми в системе соглашениями. Максималь­ный размер сегмента определяется разрядностью виртуального адреса, напри­мер при 32-разрядной организации процессора он равен 4 Гбайт. При этом мак­симально возможное виртуальное адресное пространство процесса представляет собой набор из N виртуальных сегментов, каждый размером по 4 Гбайт. В каж­дом сегменте виртуальные адреса находятся в диапазоне от 00000000 до FFFFFFFF. Сегменты не упорядочиваются друг относительно друга, так что общего для сегментов линейного виртуального адреса не существует, виртуаль­ный адрес задается парой чисел: номером сегмента и линейным виртуальным ад­ресом внутри сегмента.

 

 

 

 

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

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

□  базовый физический адрес сегмента в оперативной памяти;

□  размер сегмента;

□  правила доступа к сегменту;

□  признаки модификации, присутствия и обращения к данному сегменту, а также некоторая другая информация.

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

Как видно, сегментное распределение памяти имеет очень много общего со стра­ничным распределением.

Механизмы преобразования адресов этих двух способов управления памятью тоже весьма схожи, однако в них имеются и существенные отличия, которые яв­ляются следствием того, что сегменты в отличие от страниц имеют произволь­ный размер. Виртуальный адрес при сегментной организации памяти может быть представлен парой (g, s), где g — номер сегмента, as — смещение в сегменте. Фи­зический адрес получается путем сложения базового адреса сегмента, который определяется по номеру сегмента g из таблицы сегментов и смещения s (рис. 5.19).

 

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

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

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

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

Одним из существенных отличий сегментной организации памяти от странич­ной является возможность задания дифференцированных прав доступа процесса к его сегментам. Например, один сегмент данных, содержащий исходную инфор­мацию для приложения, может иметь права доступа «только чтение», а сегмент данных, представляющий результаты, — «чтение и запись». Это свойство дает принципиальное преимущество сегментной модели памяти над страничной.

 

Сегментно-страничное распределение

 

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

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

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

В большинстве современных реализаций сегментно-страничной организации па­мяти в отличие от набора виртуальных диапазонов адресов при сегментной орга­низации памяти (рис. 5.20, а) все виртуальные сегменты образуют одно непре­рывное линейное виртуальное адресное пространство (рис. 5.20, б).

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

 

 

Для каждого процесса операционная система создает отдельную таблицу сегмен­тов, в которой содержатся описатели (дескрипторы) всех сегментов процесса. Описание сегмента включает назначенные ему права доступа и другие характе­ристики, подобные тем, которые содержатся в дескрипторах сегментов при сегментной организации памяти. Однако имеется и принципиальное отличие. В поле базового адреса указывается не начальный физический адрес сегмента, отведен­ный ему в результате загрузки в оперативную память, а начальный линейный виртуальный адрес сегмента в пространстве виртуальных адресов (на рис. 5.20 ба­зовые физические адреса обозначены S1, S2, S3, а базовые виртуальные адреса — fl, £2, f3).

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

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

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

Преобразование виртуального адреса в физический происходит в два этапа (рис. 5.21):

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

2.   На втором этапе работает страничный механизм. Полученный линейный вирту­альный адрес преобразуется в искомый физический адрес. В результате пре­образования линейный виртуальный адрес представляется в том виде, в кото­ром он используется при страничной организации памяти, а именно в виде пары (номер страницы, смещение в странице). Благодаря тому что размер страницы выбран равным степени двойки, эта задача решается простым отде­лением некоторого количества младших двоичных разрядов. При этом в стар­ших разрядах содержится номер виртуальной страницы, а в младших — смещение искомого элемента относительно начала страницы. Так, если раз­мер страницы равен 2к, то смещением является содержимое младших к разрядов, а остальные, старшие разряды содержат номер виртуальной страницы, которой принадлежит искомый адрес. Далее преобразование адреса проис­ходит так же, как при страничной организации: старшие разряды линейного виртуального адреса, содержащие номер виртуальной страницы, заменяются номером физической страницы; взятым из таблицы страниц, а младшие раз­ряды виртуального адреса, содержащие смещение, остаются без изменения.

 

 

                       

 

Как видно, механизм сегментации и страничный механизм действуют достаточ­но независимо друг от друга. Поэтому нетрудно представить себе реализацию сегментно-страничного управления памятью, в которой механизм сегментации работает по вышеописанной схеме, а страничный механизм изменен. Он реали­зует двухуровневую схему, в которой виртуальное адресное пространство делит­ся сначала на разделы, а уж потом на страницы. В таком случае преобразование виртуального адреса в физический происходит в несколько этапов. Сначала ме­ханизм сегментации обычным образом, используя таблицу сегментов, вычисляет линейный виртуальный адрес. Затем из данного виртуального адреса вычленя­ются номер раздела, номер страницы и смещение. И далее по номеру раздела из таблицы разделов определяется адрес таблицы страниц, а затем по номеру виртуальной страницы из таблицы страниц определяется номер физической страницы, к которому пристыковывается смещение. Именно такой подход реа­лизован компанией Intel в процессорах i386, i486 и Pentium.

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

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

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

 

 

 

 

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

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

2.   По номеру виртуальной страницы, заданному в виртуальном адресе, из таб­лицы страниц извлекается дескриптор, в котором указан номер физической страницы.                                    3.   К номеру физической страницы пристыковывается младшая часть виртуаль­ного адреса — смещение.

 

Разделяемые сегменты памяти

 

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

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

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

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

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

 

 

 

 

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

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

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

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

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

 

Кэширование данных

 

Иерархия запоминающих устройств

 

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

На следующем уровне располагается более быстродействующая (время доступа равно примерно 10-20 наносекундам) и менее объемная (от десятков мегабайт до нескольких гигабайт) оперативная память, реализуемая на относительно мед­ленной динамической памяти DRAM.

Для хранения данных, к которым необходимо обеспечить быстрый доступ, ис­пользуются компактные быстродействующие запоминающие устройства на ос­нове статической памяти SRAM, объем которых составляет от нескольких де­сятков до нескольких сотен килобайт, а время доступа к данным обычно не превышает 8 не.

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

 

 

 

Таким образом, можно констатировать печальную закономерность — чем больше объем устройства, тем менее быстродействующим оно является. Более того, стои­мость хранения данных в расчете на один бит также увеличивается с ростом бы­стродействия устройств. Однако пользователю хотелось бы иметь и недорогую, и быструю память. Кэш-память представляет некоторое компромиссное решение этой проблемы.

 

Кэш-память

 

Кэш-память, или просто кэш (cache), — это способ совместного функционирова­ния двух типов запоминающих устройств, отличающихся временем доступа и стоимостью хранения данных, который за счет динамического копирования в «быстрое» ЗУ наиболее часто используемой информации из «медленного» ЗУ позволяет, с одной стороны, уменьшить среднее время доступа к данным, а с дру­гой стороны, экономить более дорогую быстродействующую память.

Неотъемлемым свойством кэш-памяти является ее прозрачность для программ и пользователей. Система не требует никакой внешней информации об интенсив­ности использования данных; ни пользователи, ни программы не принимают ни­какого участия в перемещении данных из ЗУ одного типа в ЗУ другого типа, все это делается автоматически системными средствами.

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

Оно стоит дороже и, как правило, имеет сравнительно небольшой объем. «Мед­ленное» ЗУ далее будем называть основной памятью, противопоставляя ее вспо­могательной кэш-памяти.

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

 

Принцип действия кэш-памяти

 

Рассмотрим одну из возможных схем кэширования (рис. 5.25). Содержимое кэш-памяти представляет собой совокупность записей обо всех загруженных в нее элементах данных из основной памяти. Каждая запись об элементе данных включает в себя:

□  значение элемента данных;

□   адрес, который этот элемент данных имеет в основной памяти;

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

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

□   если данные обнаруживаются в кэш-памяти, то есть произошло кэш-попада­ние (cache-hit), они считываются из нее и результат передается источнику за­проса;

□  если нужные данные отсутствуют в кэш-памяти, то есть произошел кэш-про­мах (cache-miss), они считываются из основной памяти, передаются источни­ку запроса и одновременно с этим копируются в кэш-память.

 

Интуитивно понятно, что эффективность кэширования зависит от вероятности попадания в кэш. Покажем это путем нахождения зависимости среднего време­ни доступа к основной памяти от вероятности кэш-попаданий. Пусть имеется ос­новное запоминающее устройство со средним временем доступа к данным tl и кэш-память, имеющая время доступа t2, очевидно, что t2<tl. Пусть t — среднее время доступа к данным в системе с кэш-памятью, ар — вероятность кэш-попа­дания. По формуле полной вероятности имеем:

 

           

 

Среднее время доступа к данным в системе с кэш-памятью линейно зависит от вероятности попадания в кэш и изменяется от среднего времени доступа в ос­новное запоминающее устройство tl при р=0 до среднего времени доступа непо­средственно в кэш-память t2 при р=1. Отсюда видно, что использование кэш-па­мяти имеет смысл только при высокой вероятности кэш-попадания.

Вероятность обнаружения данных в кэше зависит от разных факторов, таких, на­пример, как объем кэша, объем кэшируемой памяти, алгоритм замещения дан­ных в кэше, особенности выполняемой программы, время ее работы, уровень мультипрограммирования и других особенностей вычислительного процесса. Тем не менее в большинстве реализаций кэш-памяти процент кэш-попаданий оказывается весьма высоким — свыше 90 %. Такое высокое значение вероятно­сти нахождения данных в кэш-памяти объясняется наличием у данных объек­тивных свойств: пространственной и временной локальности.

□  Временная локальность. Если произошло обращение по некоторому адресу, то следующее обращение по тому же адресу с большой вероятностью про­изойдет в ближайшее время.

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

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

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

 

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

 

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

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

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

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

В некоторых алгоритмах замещения предусматривается первоочередная выгруз­ка модифицированных, или, как еще говорят, «грязных» данных. Модифициро­ванные данные могут выгружаться не только при освобождении места в кэш-памяти для новых данных, но и в «фоновом режиме», когда система не очень загружена.

 

Способы отображения основной памяти на кэш

 

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

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

Для кэшей со случайным отображением используется так называемый ассоциа­тивный поиск, при котором сравнение выполняется не последовательно с каж­дой записью кэша, а параллельно со всеми его записями (рис. 5.26). Признак, по которому выполняется сравнение, называется тегом (tag). В данном случае те­гом является адрес данных в оперативной памяти. Электронная реализация такой схемы приводит к удорожанию памяти, причем стоимость существенно возрас­тает с увеличением объема запоминающего устройства. Поэтому ассоциативная кэш-память используется в тех случаях, когда для обеспечения высокого про­цента попадания достаточно небольшого объема памяти.

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

 

                       

 

Второй, детерминированный способ отображения предполагает, что любой эле­мент основной памяти всегда отображается в одно и то же место кэш-памяти. В этом случае кэш-память разделена на строки, каждая из которых предназначе­на для хранения одной записи об одном элементе данных и имеет свой номер. Между номерами строк кэш-памяти и адресами оперативной памяти устанавли­вается соответствие «один ко многим»: одному номеру строки соответствует не­сколько (обычно достаточно много) адресов оперативной памяти.

В качестве отображающей функции может использоваться простое выделение нескольких разрядов из адреса оперативной памяти, которые интерпретируются как номер строки кэш-памяти (такое отображение называется прямым). Напри­мер, пусть в кэш-памяти может храниться 1024 записи, то есть кэш имеет 1024 строки, пронумерованные от 0 до 1023. Тогда любой адрес оперативной памяти может быть отображен на адрес кэш-памяти простым отделением 10 двоичных разрядов (рис.5.27)

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

 

 

 

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

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

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

 

Схемы выполнения запросов в системах с кэш-памятью

 

На рис. 5.29 приведена обобщенная схема работы кэш-памяти. Большая часть ветвей этой схемы уже была подробно рассмотрена выше, поэтому остановимся здесь только на некоторых особых случаях.

 

 

 

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

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

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

 

 

 

На рис. 5.31 показана схема выполнения запроса на чтение в системе с двухуров­невым кэшем. Сначала делается попытка обнаружить данные в кэше первого уровня. Если произошел промах, поиск продолжается в кэше второго уровня. Если же нужные данные отсутствуют и здесь, тогда происходит считывание дан­ных из основной памяти. Понятно, что время доступа к данным оказывается ми­нимальным, когда кэш-попадание происходит уже на первом уровне, несколько большим — при обнаружении данных на втором уровне и обычным временем доступа к оперативной памяти, если нужных данных нет ни в том, ни в другом кэше. При считывании данных из оперативной памяти происходит их копирова­ние в кэш второго уровня, а если данные считываются из кэша второго уровня, то они копируются в кэш первого уровня.

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

 

На рис. 5.32 приведена схема выполнения запроса на запись в такой системе. При модификации данных необходимо убедиться, что они отсутствуют в кэшах. В этом случае выполняется запись только в оперативную память. Если данные обнаружены в кэше первого уровня, то вступает в силу алгоритм сквозной записи: выполняется запись в кэш первого уровня и передается запрос на запись в кэш второго уровня, играющий в данном случае роль основной памя­ти. Запись в кэш второго уровня в соответствии с алгоритмом обратной записи, принятом на данном уровне, сопровождается установкой признака модификации, при этом никакой записи в оперативную память не производится. Если данные найдены в кэше второго уровня, то, так же как и в предыдущем слу­чае, выполняется запись в этот кэш и устанавливается признак модификации. Рассмотренные в данном разделе проблемы кэширования охватывают только та­кой класс систем организации памяти, в котором на каждом уровне имеется одно кэширующее устройство. Существует и другой класс систем памяти, глав­ной отличительной особенностью которого является наличие нескольких кэшей одного уровня. Этот вариант характерен для распределенных систем обработки информации — мультипроцессорных компьютерах и компьютерных сетях.

 

 

 

 

ГЛАВА 6

Аппаратная поддержка мульти­программирования на примере процессора Pentium

 

Аппаратные средства поддержки мультипрограммирования имеются во всех со­временных процессорах. Несмотря на различия в реализации, для большинства типов процессоров эти средства имеют общие черты. Это в полной мере относит­ся и к рассматриваемому ниже популярному семейству 32-разрядных процессо­ров Intel: 80386, 80486, Pentium, Pentium Pro, Pentium II, Celeron и Pentium III. Более того, средства поддержки операционной системы во всех этих процессорах построены почти идентично, поэтому далее в тексте для их обозначения исполь­зуется обобщенный термин «процессоры Pentium».

Основным режимом работы процессора Pentium является защищенный режим (pro­tected mode). Для совместимости с программным обеспечением, разработанным для предшествующих моделей процессоров Intel (главным образом, модели 8086), в процессорах Pentium предусмотрен так называемый реальный режим (real mode). В реальном режиме процессор Pentium выполняет 16-разрядные инструкции и ад­ресует 1 Мбайт памяти. В этом разделе рассматривается защищенный режим рабо­ты процессора, поскольку это основной режим, используемый современными муль­типрограммными операционными системами.

 

Регистры процессора

 

В организации вычислительного процесса важную роль играют регистры про­цессора. В процессорах Pentium эти регистры делятся на несколько групп:

□   регистры общего назначения;

□   регистры сегментов;

□   указатель инструкций;

□   регистр флагов;

□  управляющие регистры;

□   регистры системных адресов;

□   регистры отладки и тестирования, а также регистры математического сопро­цессора, выполняющего операции с плавающей точкой.

В процессоре Pentium имеется восемь  32-разрядных регистров общего назначе­ния. Четыре из них, которые можно условно назвать А, В, С и D, используются для временного хранения операндов арифметических, логических и других ко­манд. Программист может обращаться к этим регистрам как к единому целому, используя обозначения ЕАХ, ЕВХ, ЕСХ, EDX, а также к некоторым их частям, как это показано на рис. 6.1. Здесь обозначение AL (L Low) относится к перво­му, самому младшему байту регистра ЕАХ, АН (Н — High) — к следующему по старшинству байту, а АХ обозначает оба младших байта регистра. Приставка Е в обозначении этих регистров (а также некоторых других) образована от слова extended (расширенный), что указывает на то, что в прежних моделях процессо­ров Intel эти регистры были 16-разрядными, а затем их разрядность была увели­чена до 32 бит.

 

 

Остальные четыре регистра общего назначения — ESI, EDI, ЕВР и ESP — пред­назначены для задания смещения адреса относительно начала некоторого сег­мента данных. Эти регистры используются совместно с регистрами сегментов в системе адресации процессора Pentium для задания виртуального адреса, кото­рый затем с помощью таблиц страниц отображается на физический адрес.

Регистры сегментов CS, SS, DS, ES, FS и GS в защищенном режиме ссылаются на дескрипторы сегментов памяти — описатели, в которых содержатся такие па­раметры сегментов, как базовый адрес, размер сегмента, атрибуты защиты и не­которые другие. Регистры сегментов хранят 16-разрядное число, называемое се­лектором, в котором 12 старших разрядов представляют собой индекс в таблице дескрипторов сегментов, 1 разряд указывает, в какой из двух таблиц, GDT или LDT, находится дескриптор, а три разряда поля RPL хранят значение уровня привилегий запроса к данному сегменту. Регистр CS (Code Segment) предназна­чен для хранения индекса дескриптора кодового сегмента, регистр SS (Stack Segment) — дескриптора сегмента стека, а остальные регистры используются для указания на дескрипторы сегментов данных. Все регистры сегментов, кроме CS, программно доступны, то есть в них можно загрузить новое значение селектора соответствующей командой (например, LDS). Значение регистра CS изменяется при выполнении команд межсегментных вызовов CALL и переходов JMP, а также при переключении задач.

Указатель инструкций EIP содержит смещение адреса текущей инструкции, ко­торое используется совместно с регистром CS для получения соответствующего виртуального адреса.

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

В процессоре Pentium имеется пять управляющих регистров — CRO, CR1, CR2, CR3 и CR4, которые хранят признаки и данные, характеризующие общее состоя­ния процессора (рис. 6.2).

Регистр CR0 содержит все основные признаки, существенно влияющие на ра­боту процессора, такие как реальный/защищенный режим работы, включение/ выключение страничного механизма системы виртуальной памяти, а также при­знаки, влияющие на работу кэша и выполнение команд с плавающей точкой. Младшие два байта регистра CR0 имеют название Mashine State Word, MSW — «слово состояния машины». Это название использовалось в процессоре 80286 для обозначения управляющего регистра, имевшего аналогичное назначение.

Регистр CR1 в настоящее время не используется (зарезервирован).

Регистры CR2 и CR3 предназначены для поддержки работы системы виртуаль­ной памяти. Регистр CR2 содержит линейный виртуальный адрес, который вызвал так называемый страничный отказ (отсутствие страницы в оперативной памяти или отказ из-за нарушения прав доступа). Регистр CR3 содержит физи­ческий адрес таблицы разделов, используемой страничным механизмом процес­сора.

 

 

В регистре CR4 хранятся признаки, разрешающие работу так называемых архи­тектурных расширений, например возможности использования страниц разме­ром 4 Мбайт и т. п.

Регистры системных адресов содержат адреса важных системных таблиц и структур, используемых при управлении процессами и памятью. Регистр GDTR {Global Descriptor Table Register) содержит физический 32-разрядный адрес гло­бальной таблицы дескрипторов GDT сегментов памяти, образующих общую часть виртуального адресного пространства всех процессов. Регистр IDTR {Interrupt Descriptor Table Register) хранит физический 32-разрядный адрес таблицы деск­рипторов прерываний IDT, используемой для вызова процедур обработки пре­рываний в защищенном режиме работы процессора. Кроме этих адресов в ре­гистрах GDTR и IDTR хранятся 16-битные лимиты, задающие ограничения на размер соответствующих таблиц.

Два 16-битных регистра хранят не физические адреса системных структур, а зна­чения индексов дескрипторов этих структур в таблице GDT, что позволяет кос­венно получить соответствующие физические адреса. Регистр TR {Task Register) содержит индекс дескриптора сегмента состояния задачи TSS. Регистр LDTR {Local Descriptor Table Register) содержит индекс дескриптора сегмента локаль­ной таблицы дескрипторов LDT сегментов памяти, образующих индивидуаль­ную часть виртуального адресного пространства процесса.

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

 

Привилегированные команды

 

Привилегированные команды — это команды, которые могут быть выполнены только при определенном уровне привилегий текущего кода CPL (Current Privi­lege Level). В процессорах Pentium поддерживается четыре уровня привилегий, от самого привилегированного нулевого, до наименее привилегированного третьего. С помощью привилегированных команд осуществляется защита структур опера­ционной системы от некорректного поведения пользовательских процессов, а так­же взаимная защита ресурсов этих процессов. (Механизм задания привилегий более детально рассмотрен ниже, в разделах, посвященных средствам управле­ния памятью, так как уровни привилегий используются для определения не только допустимости выполнения той или иной команды, но и возможности до­ступа исполняемого кода к сегментам данных и кодов, включенным в виртуаль­ное адресное пространство процесса.)

В процессоре Pentium к привилегированным командам относятся:

□команды для работы с управляющими регистрами CR n, а также для загрузки регистров системных адресов GDTR, LDTR, IDTR и TR;

□   команда останова процессора HALT;

□ команды запрета/разрешения маскируемых аппаратных прерываний CLI/SLI;

□   команды ввода-вывода IN, INS, OUT, OUTS.

Первые две группы команд могут выполняться только при самом высшем уровне привилегий кода, то есть при CPL=O. Для двух последних групп команд, иногда называемых чувствительными, условия выполнения не требуют высшего уровня привилегий кода, а связаны с соотношением уровня привилегий ввода-вывода IOPL и уровня привилегий кода CPL — выполнение этих команд разрешено в том случае, если CPL <= IOPL.

 

Средства поддержки сегментации памяти

 

Средства поддержки механизмов виртуальной памяти в процессоре Pentium по­зволяют отображать виртуальное адресное пространство на физическую память размером максимум в 4 Гбайт (этот максимум определяется использованием 32-разрядных адресов при работе с оперативной памятью).

Процессор может поддерживать как сегментную модель распределения памяти, так и сегментно-страничную. Средства сегментации образуют верхний уровень средств управления виртуальной памятью процессора Pentium, а средства стра­ничной организации — нижний уровень. Это означает, что сегментные средства работают всегда, а средства страничной организации могут быть как включены, так и выключены путем установки однобитного признака РЕ (Paging Enable) в регистре CR0 процессора. В зависимости от того, включены ли средства стра­ничной организации, изменяется смысл процедуры преобразования адресов, которая выполняется средствами сегментации. Сначала рассмотрим случай работы средств сегментации при отключенном механизме управления страницами.

 

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

 

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

 

 

Структура дескриптора, которая поддерживается в процессоре Pentium, сложилась исторически. Многое в ней связано с обеспечением совместимости с предыдущими процессорами семейства х86. Именно этим объясняется то, что базовый адрес сег­мента представлен в дескрипторе в виде трех частей, а размер сегмента занимает два поля.

Ниже перечислены основные поля дескриптора.

□   База — базовый адрес сегмента (32 бита).

□   Размер — размер сегмента (24 бита).

□   G (Granularity) — единица измерения размера сегмента, один бит. Если G=0, то размер задан в байтах и тогда сегмент не может быть больше 64 Кбайт, если G=l, то размер сегмента измеряется в страницах по 4 Кбайт.

□   Байт доступа (5-й байт дескриптора) содержит информацию, которая исполь­зуется для принятия решения о возможности или невозможности обращения к данному сегменту. Бит Р (Present) определяет, находится ли соответствующий сегмент в данный момент в памяти (Р=1) или он выгружен на диск (Р=0). Поле DPL (Descriptor Privilege Level) содержит данные об уровне привиле­гий, необходимом для доступа к сегменту. Остальные пять битов байта досту­па зависят от типа сегмента и определяют способ, которым можно использо­вать данный сегмент (то есть читать, писать, выполнять). Различаются три основных типа сегментов:

О сегмент данных;

О кодовый сегмент;

О системный сегмент (GDT, TSS и т. п.),

Дескрипторы сегментов объединяются в таблицы. Процессор Pentium для управ­ления памятью поддерживает два типа таблиц дескрипторов сегментов!:

□  глобальная таблица дескрипторов (Global Descriptor Table, GDT), которая пред­назначена для описания сегментов операционной системы и общих сегментов для всех прикладных процессов, например сегментов межпроцессного взаи­модействия;

□  локальная таблица дескрипторов (Local Descriptor Table, LDT), которая содер­жит дескрипторы сегментов отдельного пользовательского процесса.

Таблица GDT одна, а таблиц LDT столько, сколько в системе выполняется задач (процессов). При этом в каждый момент времени операционной системой и аппа­ратными средствами процессора используется только одна из таблиц LDT, а имен­но та, которая соответствует выполняемому в данный момент пользователь­скому процессу. Таблица GDT описывает общую часть виртуального адресного пространства процессов, a LDT — индивидуальную часть для каждого процесса. Таблицы GDT и LDT размещены в оперативной, памяти в виде отдельных сег­ментов. Сегменты LDT и GDT содержат системные данные, поэтому их дескрип­торы хранятся в таблице GDT. Таким образом, таблица GDT наряду с записями о других сегментах содержит запись о самой себе, а также обо всех таблицах LDT. В каждый момент времени в специальных регистрах GDTR и LDTR хранится информация о местоположении и размерах глобальной таблицы GDT и актив­ной таблицы LDT соответственно. Регистр GDTR содержит 32-разрядный физи­ческий адрес начала сегмента GDT в памяти, а также 16-битный размер этого сегмента (рис. 6.4). Регистр LDTR указывает на расположение сегмента LDT в оперативной памяти косвенно — он содержит индекс дескриптора в таблице GDT, в котором содержится адрес таблицы LDT и ее размер.

      

Процесс обращается к физической памяти по виртуальному адресу, представляю­щему собой пару (селектор, смещение). Селектор однозначно определяет вирту­альный сегмент, к которому относится искомый адрес, то есть он может интер­претироваться как номер сегмента, а смещение, как это и следует из его названия, фиксирует положение искомого адреса относительно начала сегмента. Смеще­ние задается в машинной инструкции, а селектор помещается в один из сегмент­ных регистров процессора. Под смещение отводится 32 бита, что обеспечивает максимальный размер сегмента 4 Гбайт (232).                                                     

Селектор извлекается из одного из шести 16-разрядных сегментных регистров процессора (CS, SS, DS, ES, FS или GS) в зависимости от типа команды и стадии ее выполнения — выборки кода команды или данных. Например, при обращении к памяти во время выборки следующей команды используется селектор из сег­ментного регистра кода CS, а при записи результатов в сегмент данных процесса селекторы извлекаются из сегментных регистров данных DS или ES, если же данные записываются в стек по команде PUSH, то механизм виртуальной памяти извлекает селектор из сегментного регистра стека SS, и т. п.

 

 

Селектор состоит из трех полей (рис. 6.5):

□   индекса, который задает последовательный номер дескриптора в таблице GDT или LDT (13 бит);

□  указателя типа используемой таблицы дескрипторов: GDT или LDT (1 бит);

□   требуемого уровня привилегий — RPL (2 бита), это поле используется меха­низмом защиты данных, который будет рассмотрен ниже.

Виртуальное адресное пространство процесса складывается из всех сегментов, опи­санных в общей для всех процессов таблице GDT, и сегментов, описанных в его собственной таблице LDT. Разрядность поля индекса определяет максимальное число глобальных и локальных сегментов процесса — по 8 Кбайт (213) сегментов каждого типа, всего 16 Кбайт сегментов. С учетом максимального размера сег­мента — 4 Гбайт — каждый процесс при чисто сегментной организации вирту­альной памяти (без включения страничного механизма) может работать в вирту­альном адресном пространстве в 64 Тбайт.

 

Каждый дескриптор в таблицах GDT и LDT имеет размер 8 байт, поэтому мак­симальный размер каждой из этих таблиц — 64 Кбайт (8 байт х 8 Кбайт дескрип­торов).

Из приведенного описания видно, что процессор Pentium обеспечивает поддерж­ку работы ОС в двух отношениях:

□  поддерживает работу виртуальной памяти за счет быстрого аппаратного спо­соба преобразования виртуального адреса в физический;

□  обеспечивает защиту данных и кодов различных приложений.

 

Преобразование адресов

 

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

 

 

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

  1. Значение селектора указывает механизму преобразования адресов, что вирту­альный адрес относится к сегменту, описываемому в таблице, GDT. Местонахождение таблицы GDT система определяет из регистра GDTR, в котором хранится полный 32-битный базовый физический адрес таблицы. Процессор складывает базовый адрес таблицы, взятый из регистра GDTR, со сдвинутым на 3 разряда влево (умножение на 8 в соответствии с числом байтов в одном дескрипторе сегмента) значением поля индекса из селектора. Результатом яв­ляется физический адрес дескриптора сегмента, к которому относится задан­ный виртуальный адрес.

2.   По вычисленному адресу процессу извлекает из памяти дескриптор нужного сегмента.

3.   Выполняется проверка возможности выполнения заданной операции доступа по заданному виртуальному адресу:

О сначала процессор определяет правильность адреса, сравнивая смещение, заданное в виртуальном адресе, с размером сегмента, извлеченным из ре­гистра LDTR (в случае выхода адреса за границы сегмента происходит прерывание);

О затем процессор проверяет права доступа задачи к данному сегменту па­мяти;

О далее проверяется наличие сегмента в физической памяти (если бит Р де­скриптора равен 0, то есть сегмент отсутствует в физической памяти, то происходит прерывание).

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

В случае когда селектор в виртуальном адресе указывает не на таблицу GDT, a на таблицу LDT, процедура вычисления физического адреса несколько услож­няется. Это связано с тем, что регистр LDTR в отличие от GDTR указывает на размещение таблицы сегментов не прямо, а косвенно. В LDTR содержится ин­декс дескриптора сегмента LDT. Поэтому в процедуре преобразования адресов появляется дополнительный этап — определение базового адреса таблицы LDT. На основании базового адреса таблицы GDT, взятого из регистра GDTR, и се­лектора, взятого из регистра LDTR, вычисляется смещение в таблице GDT, ко­торое и является адресом дескриптора сегмента LDT.

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

Таким образом, для использования сегментного механизма процессора Pentium опе­рационной системе необходимо сформировать таблицы GDT и LDT, загрузить их в

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

Операционная система может отказаться от использования средств сегментации про­цессора Pentium, в таком случае ей достаточно назначить каждому процессу только по одному сегменту и занести в соответствующие таблицы LDT по одному дескрип­тору. Виртуальное адресное пространство задачи будет состоять из одного сегмента длиной максимум в 4 Гбайт. Поскольку выгрузка процессов на диск будет осуществ­ляться целиком, виртуальная память вырождается в таком частном случае в свопинг.

 

Защита данных при сегментной организации памяти

 

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

□ Процессор Pentium поддерживает для каждого процесса отдельную таблицу дескрипторов сегментов LDT. Эта таблица формируется операционной систе­мой на этапе создания процесса. После активизации процесса в регистр LDTR заносится адрес (селектор) его таблицы LDT. Тем самым система делает не­доступным для процесса локальные сегменты других процессов, описанные в их таблицах LDT.

□ Вместе с тем таблица GDT, в которой хранятся дескрипторы сегментов опе­рационной системы, а также сегменты, используемые несколькими процесса­ми совместно, доступна всем процессам, а следовательно, не защищена от их несанкционированного вмешательства. Для решения этой проблемы в процес­соре Pentium предусмотрена поддержка системы безопасности на основе при­вилегий. Каждый сегмент памяти наделяется атрибутами безопасности, кото­рые характеризуют степень защищенности данного сегмента. Процессы также наделяются атрибутами безопасности, которые в этом случае характеризуют степень привилегированности процесса. Уровень привилегий процесса опре­деляется уровнем привилегий его кодового сегмента. Доступ к сегменту регу­лируется в зависимости от его защищенности и уровня привилегированности запроса. Это позволяет защитить от несанкционированного доступа сегменты GDT, а также обеспечить дифференцированный доступ к локальным сегмен­там процесса.

□ Само по себе наличие отдельных таблиц дескрипторов для каждого процесса еще не обеспечивает надежной защиты процессов друг от друга. Действитель­но, что мешает одному пользовательскому процессу изменить содержимое регистра LDTR так, чтобы он указывал на таблицу другого процесса? Анало­гично никакие значения атрибутов безопасности не смогут защитить от не­санкционированного доступа сегменты, описанные в таблице GDT, если про­цесс имеет возможность по своей инициативе устанавливать желаемый уровень привилегий для себя. Поэтому необходимым элементом защиты является на­личие аппаратных ограничений в наборе инструкций, разрешенных для выпол­нения процессу. Некоторые инструкции, имеющие критически важное значение

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

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

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

 

Система защиты манипулирует несколькими переменными, характеризующими уровень привилегий:

□   DPL {Descriptor Privilege Level) — уровень привилегий дескриптора, задается полем DPL в дескрипторе сегмента;

□   RPL {Requested Privilege Level) — запрашиваемый уровень привилегий, зада­ется полем RPL селектора сегмента;

CPL {Current Privilege Level) — текущий уровень привилегий выполняемого кода, задается полем RPL селектора кодового сегмента;

EPL {Effective Privilege Level) — эффективный уровень привилегий запроса.

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

Поле уровня привилегий является частью двух информационных структур — де­скриптора и селектора. В том и другом случае оно задается двумя битами: 00, 01, 10 и 11, характеризуя четыре степени привилегированности, от самой низкой 11 до самой высокой 00.

Уровни привилегий назначаются дескрипторам и селекторам. Во время загрузки операционной системы в память, а также при создании новых процессов опера­ционная система назначает процессу сегменты кода и данных, генерирует деск­рипторы этих сегментов и помещает их в таблицы GDT или LDT. Конкретные значения уровней привилегий DPL и RPL задаются операционной системой и транслятором либо по умолчанию, либо на основании указаний программиста. Значения DPL и RPL определяют возможности создаваемого процесса.

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

Уровень привилегий кодового сегмента определяет не только степень защищен­ности этого сегмента, но и текущий уровень привилегий CPL всех запросов к па­мяти (на чтение, запись или выполнение), которые возникнут при выполнении этого кодового сегмента. Другими словами, уровень привилегий кодового сег­мента DPL характеризует текущий уровень привилегий CPL выполняемого ко­да. При запуске кода на выполнение значение DPL из дескриптора копируется в поле RPL селектора кодового сегмента, загружаемого в регистр сегмента ко­манд CS. Значение поля RPL кодового сегмента, собственно, и является теку­щим уровнем привилегий выполняемого кода, то есть уровнем CPL.

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

Во время приостановки процесса его текущий уровень привилегий сохраняется в контексте, роль которого в процессоре Pentium играет системный сегмент со­стояния задачи {Task State Segment, TSS). Если какой-либо процесс имеет несколь­ко кодовых сегментов с разными уровнями привилегий, то поле RPL регистра CS позволяет узнать значение текущего уровня привилегий процесса. Заметим, что пользовательский процесс не может изменить значение поля привилегий в дескрипторе, так как необходимые для этого инструкции ему недоступны. В даль­нейшем уровень привилегий процесса может измениться только, в случае переда­чи управления другому кодовому сегменту путем использования особого деск­риптора — шлюза.

Контроль доступа процесса к сегментам данных осуществляется на основе со­поставления эффективного уровня привилегий EPL запроса и уровня приви­легий DPL дескриптора сегмента данных. Эффективный уровень привилегий учитывает не только значение CPL, но и значение запрашиваемого уровня при­вилегий для конкретного сегмента, к которому выполняется обращение. Перед тем как обратиться к сегменту данных, выполняемый код загружает селектор, указывающий на этот сегмент, в один из регистров сегментов данных: DS, ES, FS или GS. Значение поля RPL данного селектора задает уровень запрашиваемых привилегий. Эффективный уровень привилегий выполняемого кода EPL опре­деляется как максимальное (то есть худшее) из значений текущего и запраши­ваемого уровней привилегий:

 

 

Выполняемый код может получить доступ к сегменту данных для операций чте­ния или записи, если его эффективный уровень привилегий не ниже (а в ариф­метическом смысле «не больше») уровня привилегий дескриптора этого сегмента:

 

Уровень привилегий дескриптора DPL определяет степень защищенности сег­мента. Значение DPL говорит о том, каким эффективным уровнем привилегий должен обладать запрос, чтобы получить доступ к данному сегменту. Например, если дескриптор имеет DPL=2, то к нему разрешено обращаться всем процессам, имеющим уровень привилегий EPL, равный 0, 1 или 2, а для процессов с EPL, равным 3, доступ запрещен.

 

ПРИМЕЧАНИЕ-----------------------------------------------------------------------------------------------

Существует принципиальное различие в использовании полей RPL в селекторах, указывающих на кодовые сегменты и сегменты данных. Содержимое поля RPL из селектора, за­груженного в регистр CS кодового сегмента, характеризует текущий уровень привилегий CPL выполняемого из этого сегмента кода; значение его никак не связано с сегментами данных, к которым может происходить обращение из этого кодового сегмента. Содержи­мое поля RPL из селектора, загруженного в один из регистров — DS, ES, FS или GS — сег­ментов данных, определяет некую «поправку» к текущему уровню привилегий выполняе­мого кода, вносимую при доступе к сегменту данных, на который указывает этот селектор.

 

Правило вычисления эффективного уровня привилегий показывает, что он не может быть выше уровня привилегий кодового сегмента. Поэтому загрузка в ре­гистр DS (или любой другой сегментный регистр данных) селектора с высоким уровнем привилегий (например, с RPL = 0) при низком значении уровня CPL (например, 3) ничего нового не даст — если сегмент Данных, на который указы­вает DS, имеет высокий уровень привилегий (например, 1), то в доступе к нему будет отказано (так как EPL будет равен max {3,0}, то есть 3). По этой причине команды загрузки сегментных регистров не являются привилегированными.

Если запрос обращен к сегменту данных, обладающему более высоким, чем EPL, уровнем привилегий, то происходит прерывание.

Задавая в поле RPL селекторов, ссылающихся на сегменты данных, различные значения, программист может управлять доступом выполняемого кода к этим сегментам. Так, при RPL=0 эффективный уровень привилегий будет всецело определяться только текущим уровнем привилегий CPL. А если поместить в се­лектор RPL=3, то это означает снижение эффективного уровня привилегий до самого низкого значения, при котором процессу разрешен доступ только к наи­менее защищенным сегментам с DPL=3.

Контроль доступа процесса к сегменту стека позволяет предотвратить доступ низкоуровневого кода к данным, выработанным высокоуровневым кодом, и по­мещенным в стек, например к локальным переменным процедуры. Доступ к сег­менту стека разрешается только в том случае, когда уровень EPL кода совпадает с уровнем DPL сегмента стека, то есть коду разрешается работать только со сте­ком своего уровня привилегий. Использование одного и того же стека для про­цедур разного уровня привилегий может привести к тому, что низкоуровневая процедура, получив управление после возврата из вызванной ею высокоуров­невой процедуры (обратная последовательность вызова процедур в процессоре Pentium запрещена, что рассматривается ниже), может прочитать из стекового сегмента записываемые туда во время работы высокоуровневой процедуры дан­ные. Так как в ходе выполнения процесса уровень привилегий его кода может измениться, то для каждого уровня привилегий используется отдельный сегмент стека.

Контроль доступа Процесса к кодовому сегменту производится путем сопостав­ления уровня привилегий дескриптора этого кодового сегмента DPL с текущим уровнем привилегий выполняемого кода CPL. В зависимости от того, какой спо­соб обращения к кодовому сегменту используется, выполняется внутрисегмент­ная или межсегментная передача управления, вызывается подчиненный или не­подчиненный сегмент — по-разному формулируются правила контроля доступа. Эти вопросы будут рассмотрены в разделе «Средства вызова процедур и задач».

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

Способ, с помощью которого разрешено осуществлять доступ к сегменту того или иного типа, определяется несколькими битами байта доступа дескриптора сегмента. Как уже было сказано, старший, седьмой бит байта доступа является признаком Р присутствия сегмента в памяти, а шестой и пятый биты отведены для хранения уровня привилегий DPL. На рис. 6.8 показаны оставшиеся .пять битов байта доступа, смысл которых зависит от типа сегмента.

 

 

Поле S занимает 1 бит и определяет, является ли сегмент системным (S=0) или сегментом кода или данных (S=l).

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

Признак Е позволяет отличить сегмент данных (Е=0) от сегмента кода (Е=1). Для сегмента данных определяются следующие поля:

□   ED (Expand Down) — направления распространения сегмента (ED=O для обыч­ного сегмента данных, распространяющегося в сторону увеличения адресов, ED=1 для стекового сегмента данных, распространяющегося в сторону умень­шения адресов);

□   W (Writeable) — бит разрешения записи в сегмент (при W=l запись разрешена, при W=0 — запрещена);

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

Если признак W запрещает запись в сегмент данных, то разрешается только чте­ние сегмента. Выполнение сегмента данных запрещено всегда, независимо от значения признака W.

Для сегмента кода используются следующие признаки:

□   С (Conforming) — бит подчинения, определяет возможность вызова кода на выполнение из других сегментов; при С=1 (подчиненный сегмент) сегмент может выполняться в том случае, если текущий уровень привилегий вызываю­щего процесса CPL не выше уровня привилегий DPL данного кодового сегмен­та, то есть в арифметическом смысле CPL> DPL; после передачи управления новый кодовый сегмент начинает выполняться с уровнем привилегий вызвав­шего сегмента, то есть с более низкими или теми же привилегиями; при С=0 (неподчиненный сегмент) код может быть выполнен только при CPL=DPL;

□  R (Readable) — бит разрешения (R=l) или запрета (R=0) чтения из кодового септета;

□  A (Accessed) — признак обращения, имеет смысл, аналогичный полю А сегмен­та данных.

Если признак R запрещает чтение кодового сегмента, то разрешается только его выполнение. Запись в кодовый сегмент запрещена всегда, независимо от значе­ния признака R.

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

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

□  делается попытка записать данные по адресу, принадлежащему кодовому сегменту;

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

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

 

Сегментно-страничный механизм

 

Включение страничного механизма происходит, если в регистре управления CR0 самый старший бит PG установлен в единицу. При включенной системе управ­ления страницами параллельно продолжает работать и описанный выше сег­ментный механизм, однако, как будет показано ниже, смысл его работы меняется. Виртуальное адресное пространство процесса при сегментно-страничном режи­ме работы процессора ограничивается размером 4 Гбайт. В этом пространстве определены виртуальные сегменты процесса (рис. 6.9). Так как теперь все вирту­альные сегменты разделяют одно виртуальное адресное пространство, то воз­можно их наложение, поскольку процессор не контролирует такие ситуации, ос­тавляя эту проблему операционной системе.

 

 

Для реализации механизма управления страницами как физическое, так и вир­туальное адресные пространства разбиты на страницы размером 4 Кбайт (начи­ная с модели Pentium в процессорах Intel существует возможность использования страниц и по 4 Мбайт, но дальнейшее изложение ориентируется на традицион­ный размер страницы в 4 Кбайт). Всего в виртуальном адресном пространстве в сегментно-страничном режиме насчитывается 1 Мбайт (220) страниц. Несмотря на наличие нескольких виртуальных сегментов, все виртуальное адресное про­странство задачи имеет общее разбиение на страницы, так что нумерация вирту­альных страниц сквозная.

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

Работа сегментного механизма в данном случае во многом повторяет его работу при отключенном страничном механизме. На основании значения индекса в се­лекторе выбирается нужный дескриптор из таблицы GDT или LDT. Из дескриптора извлекается базовый адрес сегмента и складывается со смещением. Деск­рипторы и таблицы имеют ту же структуру. Однако имеется и принципиальное отличие, оно состоит в интерпретации содержимого поля базового адреса в деск­рипторах сегментов. Если раньше дескриптор сегмента содержал базовый адрес сегмента в физической памяти и при сложении этого адреса со смещением из виртуального адреса получался физический адрес, то теперь дескриптор содер­жит базовый адрес сегмента в виртуальном адресном пространстве, и в результа­те его уложения со смещением получается линейный виртуальный адрес.

Результирующий линейный 32-разрядный виртуальный адрес передается стра­ничному механизму для дальнейшего преобразования. Исходя из того что раз­мер страницы равен 4 Кбайт (212), в адресе можно легко выделить номер вирту­альной страницы (старшие 20 разрядов) и смещение в странице (младшие 12 разрядов). Как известно, для отображения виртуальной страницы в физическую достаточно построить таблицу страниц, каждый элемент которой — дескриптор виртуальной страницы — содержал бы номер соответствующей ей физической страницы и ее атрибуты. В процессоре Pentium так и сделано, и структура деск­риптора страницы показана на рис. 6.10. Двадцать разрядов, в которых находит­ся номер страницы, могут интерпретироваться и как базовый адрес страницы в памяти, который необходимо дополнить 12 нулями, так как младшие 12 разря­дов базового адреса страницы всегда равны нулю. Кроме номера страницы деск­риптор страницы содержит также следующие поля, близкие по смыслу соответ­ствующим полям дескриптора сегмента:

□ Р — бит присутствия страницы в физической памяти;

□  W — бит разрешения записи в страницу;

U — бит пользователь/супервизор;

□  А — признак имевшего место доступа к странице;

D — признак модификации содержимого страницы;

□   PWT и PCD — управляют механизмом кэширования страниц (введены начиная с процессора i486);

□  AVL — резерв для нужд операционной системы (AVaiLable for use).

 

При небольшом размере страницы процессора Pentium относительно размеров адресных пространств таблица страниц должна занимать в памяти весьма значи­тельное место — 4 байт х 1 Мбайт = 4 Мбайт. Это слишком много для нынешних моделей персональных компьютеров, поэтому в процессоре Pentium использует­ся деление всей таблицы страниц на части — разделы по 1024 дескриптора. Раз­мер раздела выбран так, чтобы один раздел занимал одну физическую страницу (1024 х 4 байт = 4 Кбайт). Таким образом таблица страниц делится на 1024 раз­дела.

Чтобы постоянно не хранить в памяти все разделы, создается таблица разделов {каталог страниц),  состоящая из дескрипторов разделов, которые имеют такую же структуру, что и дескрипторы страниц. Максимальный размер таблицы раз­делов составляет 4 Кбайт, то есть одна страница. Виртуальные страницы, содер­жащие разделы, как и все остальные страницы, могут выгружаться на диск. Вир­туальная страница, хранящая таблицу разделов, всегда находится в физической памяти, и номер ее физической страницы указан в специальном управляющем регистре CR3 процессора. 

 Преобразование линейного виртуального адреса в физический происходит сле­дующим образом (рис. 6.11).

 

 

Поле номера виртуальной страницы (старшие 20 разрядов) делится на две рав­ные части по 10 разрядов — поле номера раздела и поле номера страницы в разделе. На основании заданного в регистре CR3 номера физической страницы, хранящей таблицу разделов, и смещения в этой странице, задаваемого полем номера раздела, процессор находит дескриптор виртуальной страницы раздела. В соответствии с атрибутами этого дескриптора определяются права доступа к странице, а также наличие ее в физической памяти. Если страницы нет в опера­тивной памяти, то происходит прерывание, в результате которого операционная система должна выполнить загрузку требуемой страницы в память. После того как страница (содержащая нужный раздел) загружена, из нее извлекается деск­риптор страницы данных, номер которой указан в линейном виртуальном ад­ресе. И наконец, на основании базового адреса страницы, полученного из деск­риптора, и смещения, заданного в линейном виртуальном адресе, вычисляется искомый физический адрес.

Таким образом, при доступе к странице в процессоре используется двухуров­невая схема адресации страниц, которая хотя и замедляет преобразование, но по­зволяет использовать страничный механизм для таблицы страниц, что сущест­венно уменьшает объем физической памяти, требуемой для ее хранения. Для ускорения преобразования адресов в блоке управления страницами используется ассоциативная память, в которой кэшируются 32 дескриптора активно исполь­зуемых страниц, что позволяет по номеру виртуальной страницы быстро извле­кать номер физической страницы без обращения к таблицам разделов и страниц.

 

Средства вызова процедур и задач

 

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

 

Вызов процедур

 

Вызов процедуры без смены кодового сегмента в защищенном режиме процессо­ра Pentium производится обычным образом с помощью команд JMP и CALL.

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

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

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

При С=0 вызываемый сегмент не считается подчиненным, и вызов разрешается, только если уровень привилегий вызывающего кода совпадает с уровнем приви­легий вызываемого сегмента (CPL=DPL). Случаи, когда вызываемый код имеет

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

 

 

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

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

ПРИМЕЧАНИЕ----------------------------------------------------------------------------------------------

При обсуждении вопросов доступа к сегментам данных с уровнем привилегий сегмента DPL сравнивалось значение эффективного уровня привилегий EPL. При доступе же к ко­довому сегменту с его DPL сравнивается значение CPL. Внимательный читатель мог заме­тить, что здесь нет никакого противоречия или особого случая, потому что при доступе к кодовому сегменту EPL всегда равно CPL: EPL=max (RPL, CPL). А так как CPL — это и есть RPL кодового сегмента, то EPL=max (CPL, CPL)=CPL.

 

Прямой вызов процедуры из подчиненного сегмента. Процессор должен поддер­живать способ безопасного вызова модулей ОС, чтобы пользовательские программы могли получать доступ к службам ОС, например выполнять ввод-вывод с помощью соответствующих системных вызовов. Для реализации этой возмож­ности существует несколько способов, и одним из них является размещение про­цедур ОС в подчиненном сегменте (С=1). Подчиненный сегмент можно вызывать с помощью указания его селектора в командах CALL или JMP из кода программ с равным или более низким уровнем привилегий (CPL>DPL). Но нужно иметь в виду, что вызываемый код будет в этом случае выполняться с привилегиями вызывающей программы. Например, если код ОС, хранящийся в сегменте с уровнем привилегий 0, будет вызван из пользовательского приложения с уров­нем привилегий 3, то процедура ОС будет наследовать привилегии пользова­тельской программы и возможности этой процедуры по доступу к системным данным будут весьма ограничены. Тем не менее выполнить действия над пользо­вательскими данными вызванная таким способом процедура ОС сможет.

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

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

Схема вызова процедуры через шлюз приведена на рис. 6.14. Селектор из поля команды CALL указывает на дескриптор шлюза в таблицах GDT или LDT. Для того чтобы получить доступ к процедуре через шлюз, описываемый данным де­скриптором, вызывающий код должен иметь не меньший уровень прав, чем де­скриптор шлюза (то есть CPL<DPL). При этом вызываемый код может иметь любой уровень привилегий (в том числе и более высокий, чем у шлюза), кото­рый сохраняется при его выполнении. Это позволяет из пользовательской про­граммы вызывать процедуры ОС, работающие с высоким уровнем привилегий. При определении адреса входа в вызываемом сегменте смещение из поля коман­ды CALL не используется, а используется смещение из дескриптора шлюза, что не дает возможности задаче самой определять точку входа в защищенный кодовый сегмент.

 

 

 

 

При вызове кодов, обладающих различными уровнями привилегий, возникает проблема передачи параметров между вызывающей и вызываемой процедурами. Для ее решения в процессоре предусмотрено существование стеков разных уров­ней, по одному стеку на каждый уровень привилегий. Используемый кодовым сегментом стек всегда соответствует текущему уровню привилегий кодового сег­мента, то есть значению CPL. В сегменте контекста задачи. TSS (более детально он описан ниже) хранятся значения селекторов стека SS для уровней привиле­гий 0, 1 и 2. Если вызывается процедура, имеющая уровень привилегий, отлич­ный от текущего, то при выполнении команды CALL создается новый стек. Для этого из сегмента TSS извлекается новое значение селектора стека, соответст­вующее новому уровню привилегий, которое загружается в регистр SS, из теку­щего стека в новый стек копируется столько 32-разрядных слов, сколько указано в поле счетчика слов дескриптора шлюза. В новом стеке также запоминается селектор старого стека, который используется при возврате в вызывающую про­цедуру.

 

Вызов задачи

 

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

 

 

 

 

Процессор Pentium производит аппаратное переключение контекстов задач, ис­пользуя для этого сегменты специального типа TSS. Структура сегмента TSS задачи приведена на рис. 6.15. Как видно из рисунка, сегмент TSS имеет фикси­рованные поля, отведенные для содержимого регистров процессора, как универ­сальных, так и некоторых управляющих (например, LDTR и CR3). Для описа­ния возможностей доступа задачи к портам ввода-вывода процессор использует в защищенном режиме поле IOPL (Input/Output Privilege Level) в своем регист­ре EFLAGS и карту битовых полей доступа к портам в сегменте TSS. Для полу­чения возможности безусловно выполнять команды ввода-вывода текущий код должен иметь уровень прав CPL не ниже, чем уровень привилегий операций ввода-вывода; задаваемый значением поля IOPL в регистре EFLAGS. Если же это условие не соблюдается, то возможность доступа к порту с конкретным адресом определяется значением соответствующего бита в карте ввода-вывода сегмента TSS (карта состоит из 64 Кбит для описания доступа к 65 536 портам) — значе­ние 0 разрешает операцию ввода-ввода с данным номером порта.

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

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

Как и в случае вызова процедуры, имеются два способа вызова задачи — непо­средственный вызов путем указания селектора дескриптора сегмента TSS нуж­ной задачи в поле команды CALL и косвенный вызов через шлюз вызова задачи.

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

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

Непосредственный вызов задачи показан на рис. 6.16. При переключении задач процессор выполняет следующие действия:

1.   Выполняется команда CALL, селектор которой указывает на дескриптор сег­мента типа TSS. Происходит проверка прав доступа, успешная при CPL<DPL.

2.   В TSS текущей задачи сохраняются значения регистров процессора. На теку­щий сегмент TSS указывает регистр процессора TR, содержащий селектор сегмента.

3.   В TR загружается селектор сегмента TSS задачи, на которую переключается процессор.

4.   Из нового TSS в регистр LDTR переносится значение селектора таблицы LDT в таблице GDT задачи.

5.  Восстанавливаются значения регистров процессора (из соответствующих по­лей нового сегмента TSS).

6.  В поле селектора возврата нового сегмента TSS заносится селектор сегмента TSS снимаемой с выполнения задачи для организации возврата к ней в бу­дущем.

 

 

Вызов задачи через шлюз происходит аналогично, добавляется только этап по­иска дескриптора сегмента TSS по значению селектора дескриптора шлюза вы­зова.

Использование всех возможностей, предоставляемых процессорами Intel 80386, 80486 и Pentium, позволяет организовать операционной системе высоконадеж­ную многозадачную среду.

 

Механизм прерываний

 

Процессор Pentium поддерживает векторную схему прерываний, с помощью ко­торой может быть вызвано 256 процедур обработки прерываний (вектор имеет длину в один байт). Соответственно таблица процедур обработки прерываний имеет 256 элементов, которые в реальном режиме работы процессора состоят из дальних адресов (CS:IP) этих процедур, а в защищенном режиме — из дескрип­торов. Контроллер прерываний в большинстве аппаратных платформ на основе процессоров Pentium реализует механизм опрашиваемых прерываний, поэтому общий механизм компьютера носит смешанный векторно-опрашиваемый характер.

Прерывания, которые обрабатывает Pentium, делятся на следующие классы:

□  аппаратные (внешние) прерывания — источником таких прерываний являет­ся сигнал на входе процессора;

исключения — внутренние прерывания процессора;

□  программные прерывания, происходящие по команде INT.

Аппаратные прерывания бывают маскируемыми и немаскируемыми. Маскируе­мые прерывания вызываются сигналом INTR на одном из входов микросхемы процессора. При его возникновении процессор завершает выполнение очередной инструкции, сохраняет в стеке значение регистра признаков программы EFLAGS и адреса возврата, а затем считывает с входов шины данных байт вектора преры­ваний и в соответствии с его значением передает управление одной из 256 про­цедур обработки прерываний.

Маскируемость прерываний управляется флагом разрешения прерываний IF (Interrupt Flag), находящимся в регистре EFLAGS процессора. При IF=1 маски­руемые прерывания разрешены, a при IF=0 — запрещены. Для явного управления флагом IF в процессоре имеются чувствительные к уровню привилегий инструк­ции разрешения маскируемых прерываний STI (SeT Interrupt flag) и запрета мас­кируемых прерываний CLI (CLear Interrupt flag). Эти инструкции разрешается выполнять при CPL<IOPL. Кроме того, состояние флага изменяется неявным образом в некоторых ситуациях, например он сбрасывается процессором при рас­познавании сигнала INTR, чтобы процессор не входил во вложенные циклы про­цедуры обработки одного и того же прерывания. Процедура обработки прерыва­ния завершается инструкцией IRET, по которой происходит извлечение из стека признаков EFLAGS, адреса возврата, установка флага разрешения прерываний IF и передача управления по адресу возврата. Для маскируемых прерываний в процессоре отведены процедуры обработки прерываний с номерами 32-255. Со­ответствие между сигналом запроса прерывания на шине ввода-вывода (напри­мер, сигналом IRQn на шине PCI) и значением вектора задается внешним по от­ношению к процессору блоком компьютера — контроллером прерываний.

Немаскируемое аппаратное прерывание происходит при появлении сигнала NIVtl (Non Maskable Interrupt) на входе процессора. Этот сигнал всегда прерывает работу процессора, вне зависимости от значения флата IF. При обработке немас­кируемого прерывания вектор не считывается, а управление всегда передается процедуре с номером 2, описываемой третьим элементом таблицы процедур обра­ботки прерываний (нумерация в этой таблице начинается с нуля). Немаскируемые прерывания предназначаются для реакции на «сверхважные» для компьютерной системы события, например сбой по питанию. В ходе процедуры обслуживания немаскируемого прерывания процессор не реагирует на другие запросы немас­кируемых и маскируемых прерываний до тех пор, пока не будет выполнена ко­манда IRET. Если при обработке немаскируемого прерывания возникает новый сигнал NMI, то он фиксируется и обрабатывается после завершения обработки текущего прерывания, то есть после выполнения команды IRET.

Исключения (exeprtions) делятся в процессоре Pentium на отказы (faults), ловуш­ки (traps) и аварийные завершения (aborts).

Отказы соответствуют некорректным ситуациям, которые выявляются до выпол­нения инструкции, например, при обращении по адресу, находящемуся в отсут­ствующей в оперативной памяти странице (страничный отказ). После обработки исключения-отказа процессор повторяет выполнения команды, которую он не смог выполнить из-за отказа. Ловушки обрабатываются процессором после вы­полнения инструкции, например при возникновении переполнения. После обра­ботки процессор выполняет инструкцию, следующую за той, которая вызвала исключение. Аварийные завершения соответствуют ситуациям, когда невозмож­но точно определить команду, вызвавшую прерывание. Чаще всего это происходит во время серьезных отказов, связанных со сбоями в работе аппаратуры ком­пьютера. Для обработки исключений в таблице прерываний отводятся номера 0-31.

Программные прерывания в процессоре Pentium происходят при выполнении инструкции INT с однобайтовым аргументом, в котором указывается вектор пре­рывания. Общая длина инструкции INT — два байта, исключение составляет инструкция INT 3, которая целиком помещается в один байт — это удобно при отладке программ, когда инструкция INT заменяет первый байт любой команды, вызывая переход на процедуру отладки. Программные прерывания подобно ло­вушкам обрабатываются после выполнения соответствующей инструкции INT, а возврат происходит в следующую инструкцию. Программное прерывание мо­жет вызвать любую из 256 процедур обработки прерываний, указанных в табли­це прерываний.

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

Таблица прерываний в реальном режиме состоит из 256 элементов, каждый из которых имеет длину в 4 байта и представляет собой дальний адрес (CS:IP) про­цедуры обработки прерываний. Таблица прерываний реального режима всегда находится в фиксированном месте физической памяти — с начального адреса 00000 по адрес 003FF.

В защищенном режиме таблица прерываний носит название IDT (Interrupt De­scriptor Table) и может располагаться в любом месте физической памяти. Ее на­чало (32-разрядный физический адрес) и размер (16 бит) можно найти в регист­ре системных адресов IDTR. Каждый из 256 элементов таблицы прерываний представляет собой 8-байтный дескриптор. В таблице прерываний могут нахо­диться .только дескрипторы определенного типа — дескрипторы шлюзов преры­ваний, шлюзов ловушек и шлюзов задач.

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

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

Итак, процессор Pentium предоставляет операционной системе широкий диапа­зон возможностей для организации обработки прерываний различного типа.

 

Кэширование в процессоре Pentium

 

В процессоре Pentium кэширование используется в следующих случаях.

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

Кэширование пар номеров виртуальных и физических страниц в буфере ассо­циативной трансляции TLB (Translation Lookaside Buffer) позволяет ускорять преобразование виртуальных адресов в физические при сегментно-страничной организации памяти. TLB представляет собой ассоциативную память не­большого объема, предназначенную для хранения интенсивно используемых дескрипторов страниц. В процессоре Pentium имеются отдельные TLB для ин­струкций и данных.

□  Кэширование данных и инструкций в кэш-памяти первого уровня. Эта память, называемая также внутренней кэш-памятью, поскольку она размещена непо­средственно на кристалле микропроцессора, имеет объем 16/32 Кбайт. В про­цессоре Pentium кэш первого уровня разделен на память для хранения дан­ных и память для хранения инструкций. Согласование данных выполняется только методом сквозной записи.

□  Кэширование данных и инструкций в кэш-памяти второго уровня. Эта память называется также внешней кэш-памятью, поскольку она устанавливается в виде отдельной микросхемы на системной плате. Кэш-память второго уровня является общей для данных и инструкций и имеет объем 256/512 Кбайт. Поиск в кэше второго уровня выполняется в случае, когда констатируется промах в кэше первого уровня. Для согласования данных в кэше второго уровня может использоваться как сквозная, так и обратная запись.

Рассмотрим более подробно принципы работы буфера ассоциативной трансля­ции и кэша первого уровня.

 

Буфер ассоциативной трансляции

 

В буфере TLB кэшируются дескрипторы страниц из таблицы страниц (рис. 6.17). Для хранения дескриптора в кэше отводится одна строка. Каждая строка допол­нена тегом, в котором содержится номер соответствующей виртуальной стра­ницы. Строки объединены по четыре в группы, называемые наборами. Таблица TLB, используемая для преобразования адресов инструкций, имеет 32 строки и соответственно 8 наборов. Номер набора называют индексом (index). Таким об­разом, путем кэширования может быть получен физический адрес для доступа к 32 страницам памяти, содержащим инструкции.

 

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

При поиске данных в TLB используется линейный виртуальный адрес. Разряды 12-14 используются как индекс набора. Далее проверяются биты действитель­ности v всех строк выбранного набора. В начале работы кэш-памяти биты дейст­вительности всех строк сбрасываются в нуль. Бит действительности принимает значение 1, когда в соответствующей строке содержится достоверная информа­ция и сбрасывается в нуль, когда строка объявляется свободной, в результате ра­боты алгоритма замещения. Для всех действительных строк выполняется ассо­циативная процедура сравнения тегов со старшими разрядами (15-31 разряд) линейного виртуального адреса. Если произошло кэш-попадание, то номер фи­зической страницы быстро поступает в схему формирования физического адреса.

Если произошел промах и нужного дескриптора в TLB нет, то запускается мно­гоэтапная процедура преобразования адреса, включающая обращения к табли­цам разделов и страниц. Когда нужный дескриптор отыскивается в таблице стра­ниц, он копируется в TLB. Номер набора, в который записывается кэшируемый дескриптор, определяется тремя младшими разрядами номера виртуальной стра­ницы (разряды 12-14 линейного виртуального адреса).

Однако поскольку в наборе имеется четыре строки, необходимо определить, в ка­кую именно надо поместить кэшируемые данные. Дескриптор записывается либо в первую попавшуюся свободную строку, либо, если все строки заняты, в строку, к которой дольше всего не обращались. Признаком занятости строки служит бит действительности v, имеющийся у каждой строки. Если v=0, значит, строка сво­бодна для записи в нее нового содержимого. Для определения строки, которая не использовалась дольше всех других, в данном наборе, применяется упрощенный вариант алгоритма PseudoLRU (Pseudo Least Recently Used). Этот алгоритм осно­ван на анализе трех бит: b0, b1 b3, называемых битами обращения. Биты обра­щения приписываются набору и устанавливаются в соответствии с алгоритмом, приведенном на рис. 6.18. Здесь L0, L1, L2, L3 обозначают последовательные стро­ки набора.

На замену выбирается одна из следующих строк:

□   L0, если b0=0 и b10;

□   L1, если b0=0 и b1=1;

□   L2, если b0=1 и b2=0;

□   L3, если b0=1 и b2=1.

 

 

Можно легко показать, что данная процедура не всегда приводит к выбору дей­ствительно дольше всех не вызывавшейся строки. Пусть, например, обращения к строкам выполнялись в следующей хронологической последовательности: L0, L2, L3, L1, то есть ближайшее по времени обращение было к строке L1, дольше же все­го не было обращений к строке L0. Биты обращения в данном случае примут сле­дующие значения. Поскольку последнее по времени обращение было к строке из пары (L0, L1), значит, b0=1. А в паре (L2, L3) последнее обращение было к L3, сле­довательно, b2=0. Отсюда, по правилу, приведенному выше, на замену выбирает­ся строка L2, вместо строки L0, к которой на самом деле дольше всего не было об­ращений.

Однако в большинстве случаев этот алгоритм дает результат, совпадающий с оп­тимальным. Например, для последовательности L0, L3, LI, L2 биты обращения имеют значения b0=0, b10, отсюда точное решение — L0. Даже в случае ошиб­ки (вероятность которой составляет 33 %) решения, найденные по алгоритму PseudoLRU, близки к оптимальным. Так, в первом примере вместо строки L0, яв­ляющейся правильным решением, алгоритм дал ближайшую к ней по времени обращения строку L2.

Несмотря на то что алгоритм PseudoLRU дает в общем случае приближенные решения, он широко применяется при кэшировании, так как является быстрым и экономичным, что чрезвычайно важно для кэш-памяти.

Таким образом, в буфере TLB процессора Pentium используется комбинирован­ный способ отображения кэшируемых данных на кэш-память; прямое отображе­ние дескрипторов на наборы и случайное отображение на строки в пределах на­бора.

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

 

Кэш первого уровня

 

Кэш первого уровня используется на этапе обработки запроса к основной памя­ти по физическому адресу.

Работа кэш-памяти первого уровня имеет много общего с работой буфера TLB. В TLB единицей хранения является дескриптор, а в кэше первого уровня — байт  данных. Обновление данных в кэше происходит блоками по 16 байт. Таким образом, младшие 4 бита физического адреса байта могут интерпретироваться как смещение в блоке, а старшие разряды — как номер блока.                                 

Для хранения блоков данных в кэше отводятся строки, также имеющие объем 16 байт. Строки объединены в наборы по четыре. При объеме кэша 16 Кбайт в него входят 256 (28) наборов.

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

 

 

 

Так же как в TLB, выбор строки в наборе осуществляется на основе анализа би­тов действительности и битов обращения по алгоритму PseudoLRU. Блок данных заносится в строку кэш-памяти вместе со своим тегом — старшими разряда­ми адреса основной памяти. Бит действительности строки устанавливается в 1.

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

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

 

Совместная работа кэшей разного уровня

 

Разные виды кэш-памяти вступают «в игру» на разных этапах обработки запроса к основной памяти. В зависимости от того, насколько удачно для запроса сложи­лась ситуация с попаданиями в кэш-память разного типа, время его выполнения может измениться в десятки раз. На рис. 6.20 показана схема выполнения запро­са к памяти с сегментно-страничной организацией.

Рассмотрим операцию считывания операнда из оперативной памяти по его вир­туальному адресу — номеру виртуального сегмента и смещения в этом сегмен­те. Первое обращение к кэш-памяти происходит на этапе работы сегментного механизма, когда необходимо вычислить линейный виртуальный адрес, исполь­зуя информацию дескриптора сегмента. Все дескрипторы сегментов, входящих в виртуальное адресное пространство процесса, хранятся в оперативной памяти, в таблицах GDT и LDT. Однако реального обращения к оперативной памяти мо­жет и не быть, если нужный сегмент является одним из активных сегментов про­цесса — в этом случае его дескриптор находится в соответствующем скрытом регистре. Кэширование дескрипторов сегментов предоставляет первую возмож­ность сокращения времени доступа к оперативной памяти.

Следующую возможность предоставляет буфер ассоциативной трансляции TLB, в котором кэшируются дескрипторы страниц, что позволяет сэкономить время при вычислении физического адреса. Вероятность кэш-попадания в данном слу­чае очень велика — в среднем она составляет 98 %, и только 2 % обращений тре­буют действительного чтения таблиц разделов и страниц из оперативной памяти.

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

 

Таким образом, наличие разнообразных кэшей в процессоре Pentium позволяет во многих случаях существенно сократить время обработки запроса к оператив­ной памяти.