1. ОСОБЕННОСТИ ЗАЩИЩЁННОГО РЕЖИМА ПРОЦЕССОРА
I80286
|
Поле 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 позволяет полностью изолировать адресные пространства отдельных задач. Но об этом мы будем говорить в разделе книги, посвящённом мультизадачности.
Подведём итоги.
- Вы узнали, что в защищённом режиме применена новая схема преобразования логического адреса в физический, сильно отличающаяся от используемой в реальном режиме. Эта схема даёт, в частности, возможность адресовать непосредственно до 16 мегабайт физической памяти.
- Для преобразования адреса процессор i80286 использует таблицы дескрипторов, в которых хранятся базовые адреса сегментов, размеры сегментов и другая информация.
- Одновременно процессор может использовать две таблицы дескрипторов - локальную (LDT) и глобальную (GDT). Используемая таблица определяется полем TI селектора.
- Расположение и размер таблицы GDT должны быть загружены в специальный регистр процессора - GDTR. Это можно сделать командой LGDT.
- В таблице GDT могут находиться дескрипторы сегментов кода, данных и системные дескрипторы. В частности, там может находиться дескриптор, описывающий таблицу локальных дескрипторов LDT (если эта таблица используется).
- Перед переключением в защищённый режим программа должна подготовить таблицу GDT и загрузить её адрес в регистр GDTR.
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 - ядро операционной системы, системные драйверы.
- Кольцо 1 - программы обслуживания аппаратуры, драйверы, программы, работающие с портами ввода/вывода компьютера.
- Кольцо 2 - системы управления базами данных, расширения операционной системы.
- Кольцо 3 - прикладные программы, запускаемые пользователем.
Несложные системы могут использовать не все кольца, а только некоторые или даже одно. Например, можно расположить все программы операционной системы в кольце 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. Только эти процессоры
позволяют эффективно реализовать механизм
виртуальной памяти.