1. Особенности защищённого режима процессора i80286

Подавляющее большинство владельцев персональных компьютеров, совместимых с IBM PC, используют операционную систему Microsoft MS-DOS или аналогичную (IBM PC DOS или Digital Research DR DOS). Все эти операционные системы изначально разрабатывались для микропроцессора фирмы Intel i8086 или его более дешёвого аналога i8088. Именно такие микропроцессоры были установлены в первых персональных компьютерах фирмы IBM - IBM PC и IBM XT.

Процессоры i8086 и i8088 относятся к 16-разрядным процессорам. Максимальный объём адресуемой ими оперативной памяти составляет 1 мегабайт, что определяется использованием 20-разрядной адресации памяти.

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

Именно на такую конфигурацию адресного пространства и рассчитана операционная система MS-DOS (и её аналоги). Поэтому максимальный размер оперативной памяти, доступной программам, работающим под управлением MS-DOS, обычно составляет 500-620 килобайт, в зависимости от версии операционной системы и её настройки, от типа компьютера, от конфигурации драйверов и резидентных программ.

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

Следующая удачная модель процессора фирмы Intel - 16-разрядный процессор i80286 - принципиально отличается от i8086. Этот процессор может работать в двух режимах - реальном и защищённом.

В реальном режиме процессор i80286 является практически полным аналогом i8086, но имеет большее быстродействие. В реальный режим процессор переключается после аппаратного сброса или после включения питания компьютера.

Реальный режим обеспечивает полную совместимость процессора i80286 с программным обеспечением, подготовленным для i8086. Поэтому компьютер IBM AT (и его аналоги), в котором установлен процессор i80286, способен без труда работать с операционной системой MS-DOS и программами, разработанным ранее для процессора i8086.

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

В защищённом режиме процессор i80286 полностью преображается. Используя совершенно иной метод адресации памяти, процессор i80286 расширяет адресное пространство до 16 мегабайт. Процессор i80286 в защищённом режиме имеет встроенную поддержку мультизадачных операционных систем, значительно ускоряющую и упрощающую процесс переключения задач. Эта поддержка активно используется всеми мультизадачными операционными системами и оболочками, разработанными для компьютера IBM PC/AT.

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

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

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

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

В следующих моделях процессоров фирмы Intel - i80386 и i80486 - помимо расширения адресного пространства до умопомрачительной величины в 4 гигабайта реализована концепция страничной виртуальной памяти. Всё это возможно только в защищённом режиме.

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

Например, при работе в среде операционной системы WINDOWS версий 3.0 и 3.1 на компьютерах, оснащённых процессорами i80386 или i80486 в так называемом расширенном режиме (386 Enhanced Mode) ваша программа может распоряжаться несколькими десятками мегабайт виртуальной оперативной памяти, даже если в компьютере установлено только 3-4 мегабайта физической оперативной памяти.

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

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

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

Наличие виртуального режима значительно облегчает проектирование операционных систем, способных выполнять одновременно в мультизадачном режиме несколько программ, ориентированных на операционную систему MS-DOS. Возможность одновременной работы нескольких программ, предназначенных для MS-DOS, реализована, например, в операционных системах WINDOWS версий 3.0 и 3.1 (в расширенном режиме и только при наличии процессоров i80386, i80486), OS/2 версии 2.0, Desk View 386.

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

Изучение защищённого режима мы начнём с описания метода адресации памяти, коренным образом отличающегося от привычного для вас метода <сегмент:смещение> реального режима. Однако вначале напомним, как адресуется память в реальном режиме.

1.1. Адресация памяти в реальном режиме

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

В любом случае так называемый физический адрес передаётся из процессора в память по шине адреса. Ширина шины адреса определяет максимальный объём физической памяти, непосредственно адресуемой процессором. На рис. 1 показана схема взаимодействия процессора и памяти через шины адреса и данных.

Рис. 1. Шина адреса и шина данных

Например, компьютер IBM XT оснащён 20-разрядной шиной адреса и 16-разрядной шиной данных. Это означает, что имеется возможность адресоваться к 216 байтам памяти, т.е. к 1 мегабайту памяти. Причём возможно адресоваться к байтам и словам размером в 16 бит.

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

00000h <= [физический адрес] <= FFFFFh

Таким образом, для представления физического адреса в компьютерах IBM PC и IBM XT используется двадцать двоичных или пять шестнадцатеричных разрядов.

Однако все регистры процессора i8086 являются 16-разрядными. Возникает проблема представления 20-разрядного физического адреса памяти при помощи содержимого 16-разрядных регистров.

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

Логический адрес состоит из 16-разрядных компонент: компоненты сегмента памяти и компоненты смещения внутри сегмента.

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

Рис. 2. Адресация памяти в реальном режиме.

Логический адрес принято записывать в форме <сегмент:смещение>.

Например, пусть у нас есть логический адрес 1234h:0123h. Сегментная компонента равна 1234h, компонента смещения - 0123h. Вычислим физический адрес, соответствующий нашему логическому адресу:

Очевидно, что одному физическому адресу может соответствовать несколько логических. Например, физическому адресу 12453h соответствует логический адрес 1245h:0003h.

Фактически в схеме адресации памяти реального режима вся память как бы разбивается на сегменты. Физический адрес начала сегмента (базовый адрес сегмента) равен расширенной до 20 бит сегментной компоненте адреса (расширение выполняется дописыванием справа 4 нулевых бит).

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

Компонента смещения при такой схеме адресации является смещением внутри сегмента памяти. А сам сегмент памяти задаётся сегментной компонентой.

Рис.3 иллюстрирует сказанное выше. На этом рисунке показано соответствие логического адреса 0002h:0028h физическому адресу 00048h.

Рис. 3. Соответствие логического и физического адресов.

Логический адрес должен находиться в следующих пределах:

0000h:0000h <= [логический адрес] <= FFFFh:000Fh



Здесь есть одна тонкость. Логический адрес FFFFh:000Fh соответствует максимально возможному физическому адресу FFFFFh. Но используя 16-разрядные регистры процессора вы можете задать и большее значение для логического адреса, например, FFFFh:0010h. Что произойдёт в этом случае?

Если в компьютере установлены процессоры i8086 или i8088, произойдёт переполнение адреса, которое будет проигнорировано процессором. В результате логическому адресу FFFFh:0010h будет соответствовать физический адрес 00000h.

Если же используются процессоры i80286, i80386 или i80486, физическая шина адреса шире 20 бит. Для процессора i80286 шина адреса имеет ширину 24 бита, а для процессоров i80386 и i80486 - 32 бита. При работе в реальном режиме используются младшие 20 адресных линий - от A0 до A19, остальные адресные линии аппаратура компьютера блокирует.

Однако есть возможность снять блокировку с адресной линии A20. При этом в реальном режиме появляется ещё один "льготный" сегмент памяти, лежащий выше границы первого мегабайта. Этот сегмент называется областью старшей памяти (High Memory Area). Ему соответствует диапазон логических адресов от FFFFh:0010h до FFFFh:FFFFh. Размер области старшей памяти составляет 64 килобайта без 16 байт.

Операционная система MS-DOS умеет использовать старшую область памяти, располагая там своё ядро. Для этого необходимо подключить драйвер HIMEM.SYS и поместить в файл CONFIG.SYS строку:

DOS=HIGH



Архитектура процессоров серии i80XXX, работающих в реальном режиме, предполагает хранение сегментной компоненты адреса в специальных сегментных регистрах:

CS - сегмент кода;
DS - сегмент данных;
ES - дополнительный сегмент данных;
SS - сегмент стека.



Компонента смещения может находиться в регистрах BX, BP, SI, DI, IP.

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

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

Этих недостатков полностью лишена схема адресации памяти, которая используется в защищённом режиме.

1.2. Адресация памяти в защищённом режиме

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

Преобразование адресов в защищённом режиме

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

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

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

Рис. 4. Упрощённая схема преобразования логического адреса в физический в защищённом режиме.

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

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

На самом деле не все 16 бит селектора используются для индексации по таблице базовых адресов. В качестве индекса выступают старшие 13 бит. Два младших бита (бит 0 и бит 1) используются системой защиты памяти, о чём мы подробно поговорим в следующем разделе. Бит 2 позволяет выбирать для преобразования адреса один из двух типов таблиц преобразования адресов.

Рис. 5. Формат селектора адреса.

На этом рисунке два младших бита обозначены как RPL (Requested Privilege Level). Это поле является запрошенным программой уровнем привилегий и его мы будем обсуждать позже. Поле TI (Table Indicator) состоит из одного бита. Если этот бит равен нулю, для преобразования адреса используется так называемая глобальная таблица дескрипторов GDT (Global Descriptor Table), в противном случае - локальная таблица дескрипторов LDT (Local Descriptor Table).

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

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

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

На рис. 6 показана уточнённая схема преобразования адресов в защищённом режиме. Из рисунка видно, что регистры процессора GDTR и LDTR определяют расположение в памяти таблиц GDT и LDT соответственно. Таблицы GDT и LDT содержат дескрипторы, описывающие сегменты памяти. В этих дескрипторах, помимо другой информации (заштрихованная область) содержится 24-разрядный базовый адрес сегмента.

Старшие 13 битов селектора (индекс) выбирают элемент из таблицы GDT или LDT в зависимости от состояния бита TI селектора.

Извлечённый из таблицы дескрипторов базовый адрес сегмента складывается процессором для получения 24-разрядного физического адреса.

Рис. 6. Уточнённая схема преобразования адресов.

Селектор 0000h адресует самый первый дескриптор в глобальной таблице дескрипторов GDT. Поле RPL для этого дескриптора равно 0, поле TI также равно 0. Селектор 0008h указывает на второй элемент таблицы GDT, а селектор 0014h указывает на третий дескриптор в локальной таблице дескрипторов LDT, т.к. поле TI в нём равно 1.

Детальное описание схемы преобразования адресов

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

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

Формат регистра GDTR процессора i80286 приведён на рис.7.

Рис. 7. Формат регистра GDTR процессора i80286.

Из рисунка видно, что регистр GDTR имеет длину 5 байт. Старшие 3 байта содержат 24-разрядный физический адрес таблицы GDT, младшие два байта - длину таблицы GDT, уменьшенную на 1.

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

Перед переходом в защищённый режим программа должна создать в оперативной памяти таблицу GDT и загрузить регистр GDTR при помощи специальной команды LGDT (синтаксис транслятора Turbo Assembler, режим IDEAL):

lgdt    [QWORD gdt_ptr]



Перед выдачей команды LGDT gdt_ptr необходимо подготовить область памяти с адресом gdt_ptr, записав в неё физический адрес таблицы GDT и её размер, уменьшенный на 1 (предел):

gdt_ptr dw      GDT_LIMIT       ; предел таблицы GDT
base_lo dw      ?               ; младшее слово базового адреса GDT
base_hi dw      ?               ; старшее слово базового адреса GDT



Заметьте, что несмотря на то что размер регистра GDTR составляет 5 байт, в качестве операнда для команды LGDT используется адрес области памяти размером 6 байт. Здесь нет никакой ошибки, процессор i80286 использует только 5 байт из этой области, так как физический адрес содержит 24 разряда. Процессоры i80386 и i80486 в 32-разрядном режиме используют все 6 байтов, загружая в 6-байтный регистр GDTR 32-битовый физический адрес таблицы GDT и её 16-битовый предел.

Однако мы ещё не знаем точную структуру таблицы GDT. Сначала мы познакомим вас со структурой таблицы GDT (и соответственно, с идентичной ей структурой таблицы LDT), а затем на примере фрагмента программы покажем, как создать таблицу GDT и загрузить регистр GDTR.

Как мы уже говорили, таблицы GDT и LDT представляют собой массивы дескрипторов - описателей сегментов. Кроме дескрипторов, описывающих сегменты памяти, таблица GDT может содержать специальные типы дескрипторов - вентили вызова (call gate), задач (task gate) и ловушек (trap gate).

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

На рис. 8 приведён формат дескриптора сегмента для процессора i80286:

Рис. 8. Дескриптор сегмента для процессора i80286.

Длина дескриптора составляет 8 байт. Он состоит из следующих полей:

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

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

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

Рис. 9. Форматы поля доступа дескриптора.

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

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

Бит C называется битом подчинения, он будет рассмотрен в следующем разделе.

Биты P и A предназначены для организации виртуальной памяти. Их назначение будет описано в разделе, посвящённом виртуальной памяти. Сейчас отметим, что бит P называется битом присутствия сегмента в памяти. Для тех сегментов, которые находятся в физической памяти (мы будем иметь дело в основном с такими сегментами) этот бит должен быть установлен в 1.

Любая попытка программы обратиться к сегменту памяти, в дескрипторе которого бит P установлен в 0, приведёт к прерыванию.

Бит A называется битом обращения к сегменту и для всех наших программ должен быть установлен в 0.

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

Поле D задаёт направление расширения сегмента. Обычный сегмент данных расширяется в область старших адресов (расширение вверх). Если же в сегменте расположен стек, расширение происходит в обратном направлении - в область младших адресов (расширение вниз). Для сегментов, в которых организуются стеки, необходимо устанавливать поле D равным 1.

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

Таблица 1. Типы системных сегментов.

Поле TYPE Тип сегмента
0 Запрещённое значение
1 Доступный TSS для процессора i80286
2 Сегмент LDT
3 Занятый TSS для процессора i80286
4 Вентиль вызова для процессора i80286
5 Вентиль задачи для процессоров i80286 и i80386
6 Вентиль прерывания для процессора i80286
7 Вентиль исключения для процессора i80286
8 Запрещённое значение
9 Доступный TSS для процессора i80386
A Зарезервировано
B Занятый TSS для процессора i80386
C Вентиль вызова для процессора i80386
D Зарезервировано
E Вентиль прерывания для процессора i80386
F Вентиль ловушки для процессора i80386

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

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

В терминах языка ассемблера структура дескриптора может быть описана следующим образом:

STRUC desc_struc
        limit   dw      0        ; предел сегмента
        base_lo dw      0 ; младшее слово 24-битового
                                  ;      физического адреса сегмента
        base_hi db      0 ; старший байт 24-битового
                                  ;      физического адреса сегмента
        access  db      0 ; поле доступа
        reserved        dw      0 ; зарезервировано, для сегментов
                                  ;      процессора i80286 должно быть
                                  ;      равно нулю
ENDS    desc_struc



Тогда мы можем определить таблицу GDT как набор дескрипторов со структурой desc_struc:

GDT_BEG         = $ ; отмечаем начало GDT

LABEL   gdtadr          WORD

 gdt_0  desc_struc <0,0,0,0,0> ;первый элемент не используется
 gdt_gdt        desc_struc <GDT_SIZE-1  ,,,DATA_ACC, 0>
 gdt_ds desc_struc <DSEG_SIZE-1 ,,,DATA_ACC,0>
 gdt_cs desc_struc <CSEG_SIZE-1 ,,,CODE_ACC,0>
 gdt_ss desc_struc <STACK_SIZE-1,,,DATA_ACC,0>

GDT_SIZE        = ($ - GDT_BEG) ; вычисляем размер GDT



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

Второй дескриптор описывает саму таблицу GDT, в поле предела стоит значение GDT_SIZE-1. Это предел таблицы GDT. В поле доступа стоит значение, соответствующее сегменту данных.

Следующие три дескриптора описывают сегменты, адресуемые регистрами ds, cs и ss соответственно (сегменты данных, кода и стека). В них заполнены поля предела и доступа. Эти поля могут быть определены, например, следующим образом:

CODE_ACC        equ     10011000b
DATA_ACC        equ     10010000b



В нашем примере мы заполнили не все поля дескрипторов в таблице GDT. Остались незаполненными поля base_lo и base_hi, т.е. физический адрес сегмента данных.

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

; Загружаем в ax адрес сегмента данных DGROUP

        mov     ax,DGROUP

; Формируем в dl:ax физический адрес, соответствующий
; сегментному адресу DGROUP

        mov     dl,ah
        shr     dl,4    
        shl     ax,4

; Складываем со смещением

        add     ax,OFFSET gdtadr
        adc     dl,0

; Записываем физический адрес GDT в элемент GDT,
; описывающий саму GDT

        mov     bx,OFFSET gdt_gdt
        mov     [(desc_struc bx).base_l],ax
        mov     [(desc_struc bx).base_h],dl




Аналогично заполняются и другие элементы таблицы GDT.

Так как дескриптор с адресом gdt_gdt описывает саму таблицу GDT (и формат этого дескриптора подходит для команды LGDT), его можно использовать для загрузки регистра GDTR:

lgdt    [QWORD gdt_gdt]




Если вы создаёте программу на языке Си, глобальная таблица дескрипторов GDT может быть определена с помощью типа descriptor следующим образом:

descriptor gdt[5];




В этом примере создаётся таблица GDT, содержащая пять дескрипторов. Тип descriptor определяется так:

typedef struct descriptor {
        word limit;
        word base_lo;
        unsigned char base_hi;
        unsigned char access;
        unsigned reserved;
} descriptor;




Инициализацию дескрипторов в таблице GDT можно выполнить, например, с помощью следующей функции:

void init_gdt_descriptor(descriptor *descr, // указатель
                                                // на инициализируемый
                                                // дескриптор

                unsigned long base, // базовый адрес сегмента
                word limit,             // предел сегмента
                unsigned char acc_byte) // поле доступа
{
        descr->base_lo  = (word)base;
        descr->base_hi  = (unsigned char)(base >> 16);
        descr->access   = acc_byte;
        descr->limit    = limit;
        descr->reserved = 0;
}




Приведём пример использования этой функции для записи в третий по счёту элемент GDT информации о сегменте данных с сегментным адресом _DS и пределом 0xffff:

        init_gdt_descriptor(&gdt[2], MK_LIN_ADDR(_DS, 0),       0xffffL,
                TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE);




Преобразовать логический адрес реального режима (сегмент:смещение) в физический адрес можно с помощью следующей макрокоманды:

#define MK_LIN_ADDR(seg,off) (((unsigned long)(seg))<<4)+(word)(off)




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

#define TYPE_CODE_DESCR     0x18
#define TYPE_DATA_DESCR     0x10
#define SEG_WRITABLE        0x02
#define SEG_READABLE        0x02
#define SEG_PRESENT_BIT     0x80




К сожалению, встроенный в Borland C 3.0 Inline-ассемблер не позволяет использовать команду LGDT в программе, составленной на языке Си. Аналогичное ограничение имеется и в Microsoft Quick C. Поэтому для загрузки этого и некоторых других регистров приходится использовать отдельные модули, составленные полностью на языке ассемблера.

В отличие от регистра GDTR, регистр LDTR имеет только 16 разрядов. Он содержит не адрес и размер таблицы LDT, а селектор дескриптора, описывающего таблицу LDT. Это системный дескриптор, который должен находиться в таблице GDT и иметь в поле TYPE значение 2.

Дескриптор сегмента LDT содержит базовый адрес и предел таблицы LDT.

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

Подведём итоги.

1.3. Защита в процессоре i80286

Мы уже говорили вам, что операционная система MS-DOS, использующая реальный режим работы процессора, не защищена от прикладных программ. Подсистема управления памятью MS-DOS основана на разбиении всей памяти на участки, в начале которых помещаются блоки управления памятью MCB (Memory Control Block). При запуске программы MS-DOS выделяет ей необходимое количество блоков и загружает соответствующим образом сегментные регистры.

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

Поэтому, строго говоря, MS-DOS не имеет сколько-нибудь надёжного механизма управления памятью - всё основано на выполнении программами "джентльменского соглашения" о неразрушении операционной системы.

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

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

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

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

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

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

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

Кольца защиты

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

В этой схеме используются привилегии четырёх уровней - от 0 до 3. Самые большие привилегии соответствуют уровню 0. Обычно такими привилегиями обладает ядро операционной системы. Минимальные привилегии у пользовательских программ - уровень 3.

Уровни привилегий часто называют кольцами защиты (см. рис. 10).

Рис. 10. Кольца защиты.

Как распределить привилегии программ в операционной системе? Можно использовать, например, такое распределение:

Несложные системы могут использовать не все кольца, а только некоторые или даже одно. Например, можно расположить все программы операционной системы в кольце 0, а пользовательские программы - в кольце 3. Это вариант описанной выше схемы "супервизор-пользователь".

Простейшие системы можно полностью реализовать в нулевом кольце. Именно так мы и сделали в примерах программ, приведённых в этой книге.

Как практически используются кольца защиты?

Вспомним, что любой селектор имеет поле RPL (биты 0 и 1). В регистре CS хранится селектор текущего выполняемого сегмента кода. Этому селектору соответствует дескриптор в таблице GDT или LDT. В дескрипторе, в байте доступа располагается поле DPL (биты 5 и 6). Указанные поля участвуют в механизме защиты памяти.

Когда операционная система подготавливает программу для запуска, она формирует в GDT или LDT дескриптор, описывающий сегмент кода программы. В этом дескрипторе в поле DPL байта доступа проставляется номер кольца, в котором будет работать данная программа - текущий уровень привилегий CPL (Current Privilege Level). То есть возможности программы определяются содержимым поля DPL в байте доступа.

Текущий уровень привилегий копируется в поле RPL селектора сегмента кода, загруженного в регистре CS. Программа всегда может проанализировать свой текущий уровень привилегий исходя из значения поле RPL в регистре CS. Однако она не может изменить свой уровень привилегий простой заменой содержимого поля RPL в сегменте CS.

Итак, программа получает от операционной системы текущий уровень привилегий CPL, который она может проанализировать на основании содержимого регистра CS.

Дескрипторы, описывающие сегменты данных, содержат поле уровня привилегий дескриптора DPL (Descriptor Privilege Level). Поле DPL содержит минимальные привилегии, которые нужны для доступа к сегменту данных.

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

Программе будет предоставлен доступ к сегменту только в том случае, когда уровень привилегий дескриптора запрашиваемого сегмента DPL больше или равен значению max(CPL,RPL), т.е. наибольшему из значений текущего уровня привилегий CPL и уровня запрашиваемых привилегий RPL:

DPL >= max(CPL,RPL)



Если программа попытается получить доступ к более привилегированному, чем она сама сегменту памяти, её выполнение будет прервано.

В наших примерах, реализованных для простоты в кольце 0, все уровни привилегий равны 0, т.е. DPL=CPL=RPL=0.

Тип сегментов

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

Сегмент кода может быть закрыт для чтения установкой бита R в байте доступа. Такие сегменты можно только выполнять, но нельзя читать. В сегмент кода нельзя записывать какие-либо данные. В регистр CS можно загружать только такие селекторы, которые относятся к сегментам кода.

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

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

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

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

Процессор i80286 позволяет создавать сегменты любого размера в пределах 64 килобайт (процессоры i80386 и i80486 могут работать с сегментами размером 4 гигабайта). Кроме того, он следит за тем, чтобы при адресации памяти не происходил выход за границы сегмента.

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

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

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

Привилегированные и чувствительные команды

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

В таблице 2 приведён список привелигерованных команд, которые могут выполняться только в нулевом приоритетном кольце, т.е. только теми программами, которые имеют наибольшие привилегии (CPL=0).

Таблица 2. Привилегированные команды процессора i80286.

Команда Выполняемые функции
LGDT Загрузка регистра глобальной таблицы дескрипторов GDTR
LLDT Загрузка регистра локальной таблицы дескрипторов LDTR
LIDT Загрузка регистра таблицы дескрипторов прерываний IDTR
LTR Загрузка регистра задачи TR
LMSW Загрузка слова состояния машины MSW
CLTS Сброс флага переключения задачи
HLT Останов процессора

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

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

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

Также не следует разрешать обычным программам использовать команды, которые могут заблокировать прерывания: CLI, STI, LOCK. В мультизадачной среде обычные программы не должны иметь возможность отключать механизм разделения времени между задачами.

Регистр FLAGS флагов процессора i80286 в разрядах 12-13 содержит двухбитовое поле IOPL (Input/Output Privilege Level). Это поле определяет наименее привилегированное кольцо, в котором разрешено использовать команды ввода/вывода.

Программы, работающие не в нулевом кольце, не могут сами модифицировать поле IOPL регистра FLAGS. При выполнении команд, загружающих регистр флажков (IRET и POPF) не в нулевом кольце, поле IOPL не модифицируется. Аналогично, без изменения остаётся флаг разрешения прерываний IF.

Межсегментная передача управления

В реальном режиме передача управления выполняется с помощью команд JMP, CALL, INT, RET, IRET, а также при возникновении прерываний. Внутрисегментная передача управления выполняется командами JMP, CALL, RET, а межсегментная передача управления - командами JMP, CALL, INT, RET, IRET и в случае возникновения прерываний.

В защищённом режиме всё происходит аналогично. При внутрисегментной передаче управления в регистр IP заносится новое значение, а регистр CS не модифицируется. Межсегментная передача подразумевает одновременное изменение регистров CS и IP, а также в некоторых случаях и регистра флагов FLAGS (прерывания и команды RET, IRET).

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

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

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

Команды CALL и JMP

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

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

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

Первый способ называется прямым вызовом, второй - вызовом через вентиль вызова.

Рассмотрим сначала прямой вызов сегмента.

В этом случае операнд команды вызова - селектор дескриптора вызываемого или, другими словами, целевого сегмента.

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

Если бит C установлен в 0, целевой сегмент называется несогласованным. Несогласованный сегмент может быть вызван только такой программой, которая имеет большие или такие же привилегии, что и целевой сегмент. Т.е. должно выполняться условие CPL <= DPL.

Обычная программа, выполняющаяся в кольце 3, не может вызывать несогласованный сегмент (или передавать управление несогласованному сегменту), находящемуся в кольцах 0, 1 или 2. Этот механизм блокирует несанкционированный вызов модулей операционной системы программами пользователя.

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

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

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

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

В этом случае команды CALL или JMP адресуются к дескриптору с типом вентиля вызова. Формат этого дескриптора показан на рис. 11.

Рис. 11. Дескриптор вентиля вызова.

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

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

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

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

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

Команды RET и IRET

Команды RET и IRET предназначены для возврата из подпрограмм и процедур обработки прерываний, соответственно.

Команда RET может возвращать управление в пределах одного сегмента (внутрисегментная форма) или в другой сегмент (межсегментная форма).

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

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

1.4. Виртуальная память в процессоре i80286

Процессор i80286 может непосредственно адресовать до 16 мегабайт физической памяти, однако реально компьютеры редко имеют оперативную память такого размера. Обычный размер оперативной памяти для IBM AT составляет 2-4 мегабайта (здесь имеется в виду расширенная память - Extended Memory).

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

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

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

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

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

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

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

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

Реально описанная выше схема реализации виртуальной памяти со свопингом сегментов используется в операционной системе OS/2 версий от 1.0 до 1.3 включительно.

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

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

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

Все эти возможности имеются только в процессорах i80386 и i80486. Только эти процессоры позволяют эффективно реализовать механизм виртуальной памяти.