Защищенный режим процессоров Intel

         

Что дальше?


В 1993 году должен появиться новый процессор - i80586. Предполагается, что он по своей производительности сможет составить реальную конкуренцию RISC-процессорам, используемым в рабочих станциях типа SUN. Фирма Intel пока хранит в секрете подробности архитектуры нового процессора, поэтому мы можем только догадываться о его возможностях.

Учитывая преёмственность во всех процессорах, разрабатываемых фирмой Intel начиная с модели i8080, можно предполагать, что этот суперсовременный скоростной процессор будет способен выполнять программы, разработанные когда-то для процессора i8086, причём с необычайно высокой скоростью!



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


Вы наверное знаете, что для работы с памятью используются две шины - шина адреса и шина данных. Физически память устроена таким образом, что возможна адресация как 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. Вычислим физический адрес, соответствующий нашему логическому адресу: расширяем до 20 бит сегментную компоненту, дописывая справа 4 нулевых бита, получаем число 12340h; расширяем до 20 бит компоненту смещения, дописывая слева 4 нулевых бита, получаем число 00123h; для получения физического адреса складываем полученные числа: 12340h + 00123h = 12453h. Очевидно, что одному физическому адресу может соответствовать несколько логических. Например, физическому адресу 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 мегабайта плюс примерно 64 килобайта старшей области памяти для процессоров i80286, i80386 и i80486); свободный доступ для любых программ к любым областям данных, что представляет потенциальную опасность для целостности операционной системы. Этих недостатков полностью лишена схема адресации памяти, которая используется в защищённом режиме.


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


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



ARPL Коррекция поля привилегий инициатора запроса в селекторе


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

Первый операнд команды - 16-разрядный регистр или слово памяти, содержащие значение проверяемого селектора. Второй операнд - регистр, в который записано содержимое CS прикладной программы.

Если команда не изменяла уровень привилегий, в регистре FLAGS (EFLAGS для процессоров i80386 и i80486) устанавливается флаг нуля. В противном случае этот флаг сбрасывается.

Пример использования команды:

mov dx, cs mov ax, TESTED_SELECTOR arpl dx, ax

Каждый раз при переключении задачи


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


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


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

Регистр 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 байт. Он состоит из следующих полей: поле базового адреса длиной 24 бита содержит физический адрес сегмента, описываемого данным дескриптором; поле предела содержит размер сегмента в байтах, уменьшенный на единицу; поле доступа описывает тип сегмента (сегмент кода, сегмент данных и др.); зарезервированное поле длиной 16 бит для процессора i80286 должно содержать нули, это поле используется процессорами i80386 и i80486 (там, в частности, хранится старший байт 32-разрядного базового адреса сегмента). В реальном режиме начало сегмента в памяти определяется сегментным адресом, длина сегмента составляет 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 позволяет полностью изолировать адресные пространства отдельных задач. Но об этом мы будем говорить в разделе книги, посвящённом мультизадачности.

Подведём итоги. Вы узнали, что в защищённом режиме применена новая схема преобразования логического адреса в физический, сильно отличающаяся от используемой в реальном режиме. Эта схема даёт, в частности, возможность адресовать непосредственно до 16 мегабайт физической памяти. Для преобразования адреса процессор i80286 использует таблицы дескрипторов, в которых хранятся базовые адреса сегментов, размеры сегментов и другая информация. Одновременно процессор может использовать две таблицы дескрипторов - локальную (LDT) и глобальную (GDT). Используемая таблица определяется полем TI селектора. Расположение и размер таблицы GDT должны быть загружены в специальный регистр процессора - GDTR. Это можно сделать командой LGDT. В таблице GDT могут находиться дескрипторы сегментов кода, данных и системные дескрипторы. В частности, там может находиться дескриптор, описывающий таблицу локальных дескрипторов LDT (если эта таблица используется). Перед переключением в защищённый режим программа должна подготовить таблицу GDT и загрузить её адрес в регистр GDTR.

DOS-экстендеры


Вы уже наверное заметили, что все программы, приведённые в этой книге, не поддаются отладке с помощью обычных отладчиков, таких как Borland Turbo Debugger или Microsoft Code View. Эти отладчики зависают уже на этапе загрузки регистра дескрипторной таблицы прерываний IDTR, до перехода в защищённый режим дело так и не доходит. И это понятно - такие отладчики рассчитаны на обычные программы реального режима.

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

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

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

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

Модуль DOS-экстендера, прикомпонованный к программе, может использовать интерфейсы DPMI, VCPI, XMS (драйвер HIMEM.SYS), INT15h, или может использовать собственную схему управления памятью в защищённом режиме и собственные средства переключения режима процессора или состояния адресной линии A20. В спецификации DPMI приведены рекомендации для разработчиков DOS-экстендеров по использованию перечисленных выше интерфейсов.
DOS- экстендер должен проверять наличие интерфейсов и по возможности использовать более высокоуровневый интерфейс. Проверка должна выполняться в следующем порядке: DPMI EMS/VCPI XMS Функции прерывания INT 15h К сожалению, не все поставляющиеся DOS-экстендеры следуют этим рекомендациям. В результате могут возникнуть проблемы при попытке запустить разработанную с помощью DOS-экстендера программу на виртуальной машине WINDOWS в режиме "Enhanced 386 Mode" или в MS-DOS при установленных драйверах EMM386 или QEMM.

Необходимость обеспечения совместимости с интерфейсами DPMI, VCPI и XMS требует тщательного выбора DOS-экстендера. Так как в настоящее время операционная система WINDOWS находится в состоянии взрывообразного распространения среди пользователей персональных компьютеров, неудачный выбор DOS-экстендера может привести к тому, что огромное количество потенциальных покупателей не смогут использовать вашу программу в среде WINDOWS. То же относится и к значительному количеству пользователей дрйверов расширенной памяти EMM386 и QEMM. В документации на DOS-экстендер должно содержаться подтверждение совместимости с интерфейсами DPMI, VCPI и XMS. Такой DOS-экстендер будет совместим с современными операционными системами и драйверами расширенной памяти.

Примерами программ, созданных с использованием несовместимых с DPMI интерфейсом служат СУБД ORACLE версии 5.1 и FOXPRO версии 2.0. Эти программные продукты не будут работать на виртуальной машине WINDOWS в режиме "Enhanced 386 Mode".

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

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

Мы кратко рассмотрим возможности двух DOS-экстендеров: 386-DOS/Extender фирмы Phar Lap и виртуальную машину операционной системы WINDOWS в режиме "Enhanced 386 Mode".


Драйверы, резидентные программы и WINDOWS


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

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

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

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

Перед запуском WINDOWS и перед её завершением вызываются функции прерывания INT2Fh 1605h и 1606h соответственно. Ваша резидентная программа или драйвер могут подготовить собственные обработчики для этих прерываний и отслеживать моменты запуска WINDOWS и завершения её работы.

Функция 1605h вызывается при запуске WINDOWS:

Регистры при вызове прерывания:

AX 1605h ES:BX 0000h:0000h DS:SI 0000h:0000h CX 0000h DX Флаги: Бит 0 = 0, если выполняется инициализация WINDOWS в расширенном режиме; Бит 0 = 1, если выполняется инициализация DOS-экстендера "Microsoft 286 DOS extender" (используется в стандартном режиме работы WINDOWS); Биты 1-15 зарезервированы, их содержимое неопределено.

Регистры перед возвратом из прерывания:


CX 0000h, если WINDOWS может продолжать инициализацию; CX <> 0, если запуск WINDOWS недопустим. Функция 1606h вызывается при завершении WINDOWS расширенном или стандартном режиме:

Регистры при вызове прерывания:

AX 1606h DX Флаги: Бит 0 = 0, если выполняется завершение WINDOWS, работавшей в расширенном режиме; Бит 0 = 1, если выполняется завершение DOS-экстендера "Microsoft 286 DOS extender"; Биты 1-15 зарезервированы, их содержимое не определено. Обработчик функции 1605h может выполнить необходимые действия, связанные с модификацией алгоритма работы резидентной программы или драйвера, а также при помощи соответствующей установки регистра CX может разрешить или запретить запуск WINDOWS.

Обработчик функции 1606h получает управление при завершении работы WINDOWS и может восстановить прежний алгоритм работы критичной к WINDOWS резидентной программы или драйвера.

Приведённая ниже резидентная программа перехватывает прерывание INT 2Fh и отслеживает фунции 1605h и 1606h, вызавая сообщение и ожидая нажатия на любую клавишу при запуске и завершении работы WINDOWS:

Листинг 22. Контроль запуска WINDOWS Файл wintsr.asm ----------------------------------------------------------- .MODEL tiny .CODE .STARTUP jmp begin old_int2Fh_off dw 0 ; Адрес старого обработчика old_int2Fh_seg dw 0 ; прерывания 2Fh ; Сообщение, которое будет выдано на экран ; при запуске WINDOWS msg_win db 'WINDOWS Started. Press any key...$' msg_win_off dw offset msg_win ; Сообщение, которое будет выдано на экран ; при завершении WINDOWS msg_win1 db 'WINDOWS Ended. Press any key...$' msg_winend_off dw offset msg_win1 ; Новый обработчик прерывания 2Fh нужен ; для проверки наличия программы в памяти ; при ее запуске для предохранения ; от повторного запуска new_int2Fh proc far cmp ax,0FF00h jz installed cmp ax,1605h jz winstart cmp ax,1606h jz winend jmp dword ptr cs:old_int2Fh_off winstart: ; запуск WINDOWS push ax push bx push cx push dx push ds mov dx,cs:msg_win_off mov ah,9 push cs pop ds int 21h mov ax,0 int 16h pop ds pop dx pop cx pop bx pop ax jmp dword ptr cs:old_int2Fh_off winend: ; завершение WINDOWS push ax push bx push cx push dx push ds mov dx,cs:msg_winend_off mov ah,9 push cs pop ds int 21h mov ax,0 int 16h pop ds pop dx pop cx pop bx pop ax jmp dword ptr cs:old_int2Fh_off ; Если код функции 0FF00h, то возвращаем ; в регистре AX значение 00FFh.


Это признак ; того, что программа уже загружена в память installed: mov ax,00FFh iret new_int2Fh endp ;============================== ; Точка входа в программу ; В этом месте начинается выполнение программы begin proc ; Проверяем, не загружена ли уже программа ; в память mov ax,0FF00h int 2Fh cmp ax,00FFh jne first_start mov dx,offset msg_load1 mov ah,9 int 21h .EXIT ; Первоначальный запуск программы first_start: ; Запоминаем адрес старого обработчика прерывания 2Fh mov ax,352Fh int 21h mov cs:old_int2Fh_off,bx mov cs:old_int2Fh_seg,es push cs pop ds ; Выводим сообщение mov dx,offset msg_load mov ah,9 int 21h mov dx,OFFSET new_int2Fh mov ax,252Fh int 21h ; Завершаем программу и оставляем резидентно ; в памяти часть программы, содержащую новые ; обработчики прерываний mov dx,OFFSET begin int 27h begin endp msg_load db 'Резидентная программа WINTSR загружена$' msg_load1 db 'Резидентная программа WINTSR уже загружена$' end Следующая резидентная программа работает аналогично, но она запрещает запуск WINDOWS. Попробуйте, запустив предварительно программу NOWINTSR, запустить WINDOWS и посмотрите, что из этого получится.

Листинг 23. Запрет запуска WINDOWS Файл nowintsr.asm ----------------------------------------------------------- .MODEL tiny .CODE .STARTUP jmp begin old_int2Fh_off dw 0 ; Адрес старого обработчика old_int2Fh_seg dw 0 ; прерывания 2Fh ; Сообщение, которое выдаётся при запуске WINDOWS msg_win db 'NOWINTSR несовместима с WINDOWS. Нажмите любую клавишу...$' msg_win_off dw offset msg_win ; Сообщение, которое выдаётся при завершении WINDOWS msg_win1 db 10,13,'WINDOWS Ended. Press any key...$' msg_winend_off dw offset msg_win1 ; Новый обработчик прерывания 2Fh нужен ; для проверки наличия программы в памяти ; при ее запуске для предохранения ; от повторного запуска new_int2Fh proc far cmp ax,0FF00h jz installed cmp ax,1605h jz winstart cmp ax,1606h jz winend jmp dword ptr cs:old_int2Fh_off winstart: push ax push bx push cx push dx push ds mov dx,cs:msg_win_off mov ah,9 push cs pop ds int 21h mov ax,0 int 16h pop ds pop dx pop cx pop bx pop ax mov cx,0ffh jmp dword ptr cs:old_int2Fh_off winend: push ax push bx push cx push dx push ds mov dx,cs:msg_winend_off mov ah,9 push cs pop ds int 21h mov ax,0 int 16h pop ds pop dx pop cx pop bx pop ax jmp dword ptr cs:old_int2Fh_off ; Если код функции 0FF00h, то возвращаем ; в регистре AX значение 00FFh.


Это признак ; того, что программа уже загружена в память installed: mov ax,00FFh iret new_int2Fh endp ;============================== ; Точка входа в программу begin proc ; Проверяем, не загружена ли уже программа ; в память mov ax,0FF00h int 2Fh cmp ax,00FFh jne first_start mov dx,offset msg_load1 mov ah,9 int 21h .EXIT ; Первоначальный запуск программы first_start: ; Запоминаем адрес старого обработчика прерывания 2Fh mov ax,352Fh int 21h mov cs:old_int2Fh_off,bx mov cs:old_int2Fh_seg,es push cs pop ds ; Выводим сообщение mov dx,offset msg_load mov ah,9 int 21h mov dx,OFFSET new_int2Fh mov ax,252Fh int 21h ; Завершаем программу и оставляем резидентно ; в памяти часть программы, содержащую новые ; обработчики прерываний mov dx,OFFSET begin int 27h begin endp msg_load db 'Резидентная программа NOWINTSR загружена$' msg_load1 db 'Резидентная программа NOWINTSR уже загружена$' end

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


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

Регистры на входе AX 0603h BX:CX Начальный линейный адрес фиксируемого участка памяти. SI:DI Размер фиксируемого блока памяти в байтах. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

Включение защищённого режима работы процессора.


Номер бита Назначение
0 - PE Включение защищённого режима работы процессора.
1 - MP Присутствие сопроцессора.
2 - EM Эмуляция сопроцессора.
3 - TS Переключение задачи.
4 - ET Тип сопроцессора - i80287 или i80387.
5-14 Зарезервировано
15 - PG Включение механизма трансляции страниц


Включение защищённого режима работы процессора.


Номер бита Назначение
0 - PE Включение защищённого режима работы процессора.
1 - MP Присутствие сопроцессора.
2 - EM Эмуляция сопроцессора.
3 - TS Переключение задачи.
4 - ET Тип сопроцессора - i80287 или i80387.
5 - NE Числовая ошибка. Разрешает обработку ошибок при операциях с плавающей точкой.
6-15 Зарезервировано
16 - WP Защита записи. При установке этого бита страницы пользователя защищены от записи в режиме супервизора.
17 Зарезервировано
18 - AM Бит маски выравнивания. Этот бит разрешает или запрещает контроль выравнивания операндов команд в памяти. Контроль выравнивания разрешён только для программ, работающих в третьем кольце, при условии что установлен бит AM.
19-28 Зарезервировано
29 - NW Разрешение сквозной записи. Используется в механизме управления кэшированием.
30 - CD Запрещение кэширования. Если этот бит установлен, внутреннее кэширование запрещено.
31 - PG Включение механизма трансляции страниц


Прозрачность записи на уровне страниц.


Номер бита Назначение
0-2 Зарезервировано
3 - PWT Прозрачность записи на уровне страниц. Используется для управления записью во внешний кэш.
4 - PCD Запрещение кэширования на уровне страниц. Используется для управления работой внешнего кэша.
5-31 - PBDR Базовый регистр каталога страниц


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


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

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

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

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

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



ИЕРАРХИЯ СРЕДСТВ ДЛЯ РАБОТЫ В ЗАЩИЩЁННОМ РЕЖИМЕ


6.1.

6.2.

6.3.

6.4.

6.5.

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

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

Мы рассмотрим средства, доступные программистам в среде MS-DOS версии 5.0 и MS WINDOWS версий 3.0 и 3.1.

Существует несколько уровней программной поддержки защищённого режима и поддержки работы с расширенной памятью: интерфейс BIOS; интерфейс драйвера HIMEM.SYS; интерфейс EMS/VCPI; интерфейс DPMI; расширители DOS (DOS-экстендеры).

Интерфейсом самого низкого уровня является интерфейс BIOS, предоставляемый программам в виде нескольких функций прерывания BIOS INT15h. Интерфейс BIOS позволяет программе перевести процессор из реального режима в защищённый, переслать блок памяти из стандартной памяти в расширенную или из расширенной в стандартную. Этим все его возможности и ограничиваются. Интерфейс BIOS используется для старта мультизадачных операционных систем защищённого режима (таких, как OS/2) или в старых программах, работающих с расширенной памятью в защищённом режиме (например, СУБД ORACLE версии 5.1).

Назначение драйвера HIMEM.SYS и его возможности были подробно описаны во второй части второго тома "Библиотеки системного программиста" (глава 10).
С помощью функций, предоставляемых этим драйвером, программа может выполнять различные действия с блоками расширенной памяти, а также управлять адресной линией A20. Основное различие между способом работы с расширенной памятью драйвера HIMEM.SYS и интерфейсом прерывания BIOS INT 15h заключается в том, что первый выполняет выделение программе и внутренний учёт блоков расширенной памяти, а второй рассматривает всю расширенную память как один непрерывный участок.

Если в системе установлен драйвер HIMEM.SYS, ваша программа не должна пользоваться прерыванием INT 15h во избежание конфликта со схемой распределения расширенной памяти, используемой драйвером. Однако драйвер HIMEM.SYS не открывает для программ доступ к защищённому режиму. Он полностью работает в реальном режиме, а для обращения к расширенной памяти использует либо недокументированную машинную команду LOADALL (если используется процессор 80286), либо возможности процессора 80386, который позволяет адресовать расширенную память в реальном режиме (при соответствующей инициализации системных регистров и таблиц).

В приложении мы описали действия, выполняемы командой LOADALL. Вы убедитесь, что команда полностью оправдывает своё название! (Load All - загрузить всё).

Следующий уровень - интерфейс EMS/VCPI. Во второй части второго тома "Библиотеки системного программиста", в главе 11, мы подробно рассматривали интерфейс EMS, который используется для работы с дополнительной памятью. Там же разъясняются отличия между расширенной и дополнительной памятью. Используя трансляцию страниц, некоторые драйверы памяти (например, EMM386 или QEMM) могут эмулировать присуствие дополнительной памяти, используя расширенную память. При этом стандартный набор функций управления дополнительной памятью, реализованный в рамках прерывания INT 67h, дополнен ешё несколькими функциями для работы в защищённом режиме процессора.

Эти новые функции реализуют интерфейс виртуальной управляющей программы VCPI (Virtual Control Programm Interface).


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

Интерфейс DPMI (DOS Protected Mode Interface - интерфейс защищённого режима для DOS) реализуется модулем, называющимся сервером DPMI. Этот интерфейс доступен для тех программ, которые работают на виртуальной машине WINDOWS или OS/2 версии 2.0 (позже мы обсудим некоторые детали, связанные с использованием интерфейса DPMI в WINDOWS).

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

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

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

К формируемому с помощью DOS-экстендера загрузочному модулю добавляются процедуры, необходимые для инициализации защищённого режима.Эти процедуры первыми получают управление и выполняют начальную инициализацию таблиц GDT, LDT, IDT, содержат обработчики прерываний и исключений, систему управления виртуальной памятью и т.д.

Пример DOS-экстендера, поставляемого вместе с транслятором - 386-DOS/Extender фирмы Phar Lap.


Интерфейс BIOS


Этот интерфейс реализуется в рамках прерывания BIOS INT 15h в компьютерах моделей IBM AT на основе процессоров i80286, i80386 или i80486.



Интерфейс DPMI


Для создания программ, работающих в защищённом режиме, фирмы Microsoft, Intel, IBM, Lotus, Phar Lap, Rational Systems, Borland, Quarterdeck разработали интерфейс с защищённым режимом - DPMI (DOS Protected Mode Interface). Пользуясь этим интерфейсом, программы, стартующие как обычные DOS-программы реального режима, могут переключиться в защищённый режим и работать с расширенной или даже виртуальной памятью. Интерфейс DPMI обеспечивается специальной программой, называемой сервером DPMI. Сервером может быть отдельная резидентная программа, либо средства для поддержки DPMI могут входить в состав операционной системы (например, WINDOWS и OS/2 версии 2.0 позволяют работающим под их управлением DOS-программам использовать интерфейс DPMI).

В этом разделе мы кратко опишем функции DPMI, а в разделе, посвященном виртуальной машине WINDOWS, приведём пример программы, работающей в защищённом режиме с использованием функций DPMI спецификации 0.9. Спецификация DPMI версии 0.9 поддерживается операционной системой WINDOWS версий 3.0 и 3.1, работающей в расширенном режиме на процессорах i80386 или i80486.

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

Программа должна вызывать прерывание INT 31h после переключения в защищённый режим, в реальном режиме это прерывание вызывать нельзя.



Интерфейс EMS/VCPI


Во второй части второго тома "Библиотеки системного программиста" (глава 11) мы рассказывали вам о дополнительной памяти и об использовании для работы с ней спецификации EMS - Expanded Memory Specification.

Драйверы дополнительной памяти предоставляют программам интерфейс прерывания INT 67h, который мы тогда подробно описали. Мы также говорили о том, что для компьютеров на базе процессоров i80386 или i80486 существуют драйверы памяти, эмулирующие дополнительную память с использованием расширенной. Самые известные драйверы такого типа - EMM386.SYS и QEMM.SYS.

Эти драйверы используют защищённый (точнее, виртуальный) режим работы процессора i80386 и страничную адресацию расширенной памяти. Для прикладных программ предоставляется интерфейс, который называется VCPI - Virtual Programm Control Interface. Этот интерфейс реализован как подфункции функции DEh прерывания INT 67h:

Таблица 9. Функции интерфейса VCPI.

Подфункция Выполняемые действия
00 Проверить наличие в системе интерфейса VCPI.
01 Получить адрес точки входа для работы с интерфейсом VCPI.
02 Определить максимальный физический адрес памяти.
03 Определить количество свободных страниц памяти размером 4 килобайта.
04 Получить страницу памяти.
05 Освободить страницу памяти.
06 Получить физический адрес страницы памяти, располагающейся в пределах первого мегабайта, т.е. в стандартной памяти.
07 Прочитать содержимое системного регистра CR0.
08 Прочитать содержимое отладочных регистров.
09 Установить отладочные регистры.
0A Получить отображение векторов прерываний, используемых контроллерами прерываний 8259.
0B Установить отображение векторов прерываний, используемых контроллерами прерываний 8259.
0C Переключить процессор из реального в защищённый режим, а также из защищённого в виртуальный режим.

Перед вызовом прерывания INT 67h регистр AH должен содержать DEh, а номер требуемой подфункции должен быть загружен в регистр AL. Кроме того, прежде чем вызывать прерывание INT 67h, в самом начале работы программы необходимо убедиться в том, что в системе установлен драйвер EMS.
О том, как это сделать, мы рассказывали в главе 11 второго тома "Библиотеки системного программиста". Там же приведён соответствующий пример программы.

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

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

Драйверы EMM386 и QEMM обеспечивают для программ DOS интерфейс VCPI и сами пользуются этим интерфейсом. Вы знаете, что в пределах первого мегабайта адресного пространства имеется 640 килобайт памяти. Остальная память используется видеоадаптерами, ПЗУ BIOS и другой аппаратурой. Вся эта память называется зарезервированной памятью. Зарезервированная память задействована не полностью, в ней есть окна. Страницы памяти, соответствующие свободным окнам, с использованием механизма трансляции страниц отображаются в адресное пространство за пределами первого мегабайта памяти, т.е. на расширенную память.

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

Рассмотрим функции интерфейса VCPI более подробно.


Интерфейс HIMEM.SYS


Как мы уже говорили, назначение драйвера HIMEM.SYS и его возможности были подробно описаны во второй части второго тома "Библиотеки системного программиста" (глава 10). Поэтому здесь мы не будем подробно рассматривать эти функции и говорить об их использовании, а приведём только краткий перечень:

Таблица 8. Функции XMS.

00h Получить версию XMS (XMS - eXtended Memory Specification - спецификация расширенной памяти).
01h Запросить управление областью старшей памятью HMA.
02h Освободить область HMA.
03h Глобальное разрешение линии A20.
04h Глобальное запрещение линии A20.
05h Локальное разрешение линии A20.
06h Локальное запрещение линии A20.
07h Определение текущего состояния линии A20.
08h Определение размера свободной расширенной памяти.
09h Получить блок расширенной памяти EMB.
0Ah Освободить блок EMB.
0Bh Копирование блоков расширенной памяти EMB.
0Ch Блокирование блока EMB. Для заблокированного EMB можно определить его физический адрес.
0Dh Разблокирование EMB.
0Eh Получить информацию об индексе EMB.
0Fh Изменить размер блока EMB.
10h Запросить управление областью UMB.
11h Освободить область UMB.

Для проверки наличия в системе драйвера, поддерживающего спецификацию XMS, необходимо загрузить в регистр AX значение 4300h и вызвать прерывание INT2Fh. Если в регистре AL окажется значение 80h, драйвер установлен. В этом случае можно получить адрес управляющей программы, которую надо вызывать для выполнения функций. Если загрузить в регистр AX значение 4310h и вызвать прерывание INT 2Fh, в регистрах ES:BX будет записан искомый адрес.

Как можно заметить, функции XMS позволяют управлять линией A20 и копировать блоки расширенной памяти. Но вы не найдёте среди них функции для перехода в защищённый режим!

И это не случайно. Всё дело в том, что для работы с расширенной памятью драйвер HIMEM.SYS, реализующий спецификацию XMS, не использует защищённый режим работы процесора!

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


Секрет заключается в том, что для процессора i80286 драйвер HIMEM.SYS использует недокументированную машинную команду LOADALL. Эта команда предназначена для тестирования процессора и не приведена в документации на процессор i80286 (см. описание команды LOADALL в приложении).

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

Такой способ адресации расширенной памяти значительно ускоряет процесс копирования по сравнению с использованием функции 87h прерывания INT 15h. Кроме того, во время копирования функцией 0Bh драйвера HIMEM.SYS прерывания остаются разрешёнными.

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

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

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

В приложении описана утилита MEMOSCOP. Эта утилита выводит на экран список установленных в системе драйверов дополнительной памяти и доступных интерфейсов с защищённым режимом. В файле xmmc.asm находятся функции для работы с интерфейсом XMS.Этот файл и сами функции XMS были подробно описаны во втором томе "Библиотеки системного программиста".


Исключения в защищённом режиме


Для обработки особых ситуаций - исключений - разработчики процессора i80286 зарезервировали 31 номер прерывания. В таблице 3 приведён полный список зарезервированных прерываний защищённого режима.

Таблица 4. Зарезервированные прерывания защищённого режима.

00h Ошибка при выполнении команды деления.
01h Прерывание для пошаговой работы, используется отладчиками.
02h Немаскируемое прерывание.
03h Прерывание по точке останова для отладчиков.
04h Переполнение, генерируется командой INTO, если установлен флаг OF.
05h Генерируется при выполнении машинной команды BOUND, если проверяемое значение вышло за пределы заданного диапазона.
06h Недействительный код операции, или длина команды больше 10 байт.
07h Отсутствие арифметического сопроцессора.
08h Двойная ошибка, вырабатывается в том случае, если при обработке исключения возникло ещё одно исключение. Если во время обработки этого прерывания возникает третье исключение, процессор переходит в состояние отключения, что приводит к перезапуску процессора.
09h Превышение сегмента арифметическим сопроцессором.
0Ah Недействительный сегмент состояния задачи TSS.
0Bh Отсутствие сегмента. Вырабатывается при попытке использовать для адресации дескриптор, у которого бит присуствия сегмента в памяти P сброшен в 0. Это прерывание используется для реализации механизма виртуальной памяти. В этом случае по прерыванию 0Bh операционная система может выполнить подкачку отсутствующего сегмента в память.
0Ch Исключение при работе со стеком. Может возникать в случае отсутствия сегмента стека в памяти или в случае переполнения (антипереполнения) стека.
0Dh Исключение по защите памяти. Возникает при любых попытках получения доступа к сегментам памяти, если программа обладает недостаточным уровнем привилегий.
0Eh Отказ страницы для процессоров i80386 или i80486, зарезервировано для i80286.
0Fh Зарезервировано.
10h Исключение сопроцессора.
11h - 1Ah Зарезервированы.

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

Формат кода ошибки приведён на рис. 13.



Рис. 13. Формат кода ошибки процессора i80286.

Поле индекса содержит индекс дескриптора, при обращении к которому произошла ошибка. Поле I, равное 1, означает, что этот индекс относится к таблице IDT. В этом случае произошла ошибка при обработке прерывания или исключения.

Если бит I равен 0, поле TI выбирает таблицу дескрипторов (GDT или LDT) по аналогии с соответствующим полем селектора.

Бит EXT устанавливается в том случае, когда ошибка произошла не в результате выполнения текущей команды, а по внешним относительно выполняемой программы причинам. Например, при обработке аппаратного прерывания от устройства ввода/вывода произошло обращение к отсутствующему в памяти сегменту (у которого в дескрипторе сброшен бит присутствия P).

Как мы только что говорили, коды ошибок включаются в стек не для всех исключений. Программа сможет проанализировать этот код только для следующих исключений: 08h - двойная ошибка; 0Ah - недействительный TSS; 0Bh - отсутствие сегмента в памяти; 0Ch - исключение при работе со стеком; 0Dh - исключение по защите памяти. Заметим, что аналога коду ошибки для зарезервированных прерываний в реальном режиме нет.

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

Что такое повторная запускаемость?

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

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

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

Свойством повторной запускаемости обладает большинство зарезервированных прерываний, кроме следующих: 01h - прерывание для пошаговой работы; 08h - двойная ошибка; 09h - превышение сегмента сопроцессором; 0Dh - исключение по защите памяти; 10h - исключение сопроцессора.

Использование функций DPMI


Приведённая ниже программа демонстрирует использование функций интерфейса DPMI, описанного в предыдущей главе. Эта программа может работать только под управлением WINDOWS версий 3.0, 3.1 и только в расширенном режиме на процессорах i80386 или i80486. Такое ограничение связано с тем, что только в расширенном режиме существует понятие виртуальной DOS-машины, и только в этом режиме DOS-программа может воспользоваться сервисом DPMI.

Вы можете также попробовать запустить эту программу под управлением DOS-экстендера, входящего в состав интегрированной системы разработки программ Borland C++ 3.1. Запустите программу DPMIRES.EXE, входящую в состав Borland C++ 3.1, и затем - программу, приведённую ниже. (DOS-экстендеры, входящие в состав Borland C++ 2.0 или 3.0, не вполне совместимы с DPMI, поэтому наш пример с этими системами работать не будет).

Программа начинает свою работу с проверки доступности сервера DPMI, при этом не делается никаких предположений относительно средств, обеспечивающих присутствие DPMI. Это означает, что вы можете проверить работоспособность этой программы в различных средах, предоставляющих интерфейс DPMI, например на виртуальной DOS-машине операционной системы OS/2 версии 2.0.

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

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

Листинг 21. Использование интерфейса DPMI Файл dpmi.c ----------------------------------------------------------- #include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include <stdarg.h> typedef struct { unsigned long edi, esi, ebp, reserved, ebx, edx, ecx, eax; unsigned flags, es, ds, fs, gs, ip, cs, sp, ss; } RM_INT_CALL; #define MONO_MODE 0x07 #define BW_80_MODE 0x02 #define COLOR_80_MODE 0x03 // Макро для вычисления линейного адреса исходя из // логического адреса реального режима #define ABSADDR(seg, ofs) \ ((((unsigned long) seg) << 4) + ((ofs) & 0xFFFF)) typedef struct { // байт доступа unsigned accessed : 1; unsigned read_write : 1; unsigned conf_exp : 1; unsigned code : 1; unsigned xsystem : 1; unsigned dpl : 2; unsigned present : 1; } ACCESS; typedef struct { // дескриптор unsigned limit; unsigned addr_lo; unsigned char addr_hi; ACCESS access; unsigned reserved; } DESCRIPTOR; // Структура для записи информации о памяти typedef struct { unsigned long avail_block; unsigned long max_page; unsigned long max_locked; unsigned long linadr_space; unsigned long total_unlocked; unsigned long free_pages; unsigned long tot_phys_pages; unsigned long free_linspace; unsigned long size_fp; char reserved[12]; } PMI; void dos_exit(unsigned); void dpmi_init(void); void set_pmode(void); void cdecl pm_printf(const char *, ...); void pm_puts(char *); void pm_putch(int); int rm_int(unsigned, unsigned , RM_INT_CALL far *); int mi_show(void); unsigned get_sel(void); int set_descriptor(unsigned pm_sel, DESCRIPTOR far *desc); void vi_print(unsigned int x, unsigned int y, char *s, char attr); void vi_hello_msg(void); void main() { clrscr(); printf("DOS Protected Mode Interface Demo, © Frolov A.V., 1992\n\r" "--------------------------------------------------------\n\r\n\r"); // Определяем текущий видеорежим и // сегментный адрес видеобуфера video_init(); // Инициализируем защищённый режим dpmi_init(); printf("\n\r\n\r\n\rДля входа в защищённый режим нажмите любую клавишу..."); getch(); // Входим в защищённый режим set_pmode(); // Стираем экран и выводим сообщение, находясь в // защищённом режиме.
Пользуемся выводом через // эмулируемое прерывание реального режима DOS textcolor(BLACK); textbackground(LIGHTGRAY); clrscr(); pm_printf(" Установлен защищённый режим работы процессора!\n\r" " ----------------------------------------------\n\r\n\r"); // Выводим текущую информацию о распределении памяти mi_show(); pm_printf("\n\r\n\r\n\ r Для продолжения нажмите любую клавишу..."); getch(); clrscr(); // Получаем селектор для непосредственного доступа к видеопамяти alloc_videosel(); pm_printf("\n\r\n\r\n\r Для продолжения нажмите любую клавишу..."); getch(); clrscr(); // Выводим сообщения, пользуясь непосредственным доступом // к видеопамяти vi_hello_msg(); vi_print(0, 3, " Для возврата в реальный режим нажмите любую клавишу", 0x7f); getch(); // Освобождаем полученный селектор free_videosel(); textcolor(LIGHTGRAY); textbackground(BLACK); clrscr(); // Завершаем работу программы выходом в DOS dos_exit(0); } // ------------------------------------------------- // Процедура для завершения работы программы // ------------------------------------------------- void dos_exit(unsigned err) { asm mov ax, err asm mov ah, 04ch asm int 21h } // ------------------------------------------------- // Инициализация для работы с DPMI // ------------------------------------------------- union REGS inregs, outregs; struct SREGS segregs; void (far *pm_entry)(); unsigned hostdata_seg, hostdata_size, dpmi_flags; void dpmi_init(void) { // Проверяем доступность и параметры сервера DPMI inregs.x.ax = 0x1687; int86x(0x2F, &inregs, &outregs, &segregs); if(outregs.x.ax != 0) { printf("Сервер DPMI не активен."); exit(-1); } // Определяем версию сервера DPMI printf("Версия сервера DPMI: \t\t\t%d.%d\n", outregs.h.dh, outregs.h.dl); // Определяем тип процессора printf("Тип процессора:\t\t\t\t"); if(outregs.h.cl == 2) printf("80286"); else if(outregs.h.cl == 3) printf("80386"); else if(outregs.h.cl == 4) printf("80486"); // Определяем возможность работы с 32-разрядными // программами dpmi_flags = outregs.x.bx; printf("\nПоддержка 32-разрядных программ:\t"); if(dpmi_flags && 1) printf("ПРИСУТСТВУЕТ"); else printf("ОТСУТСТВУЕТ"); // Определяем размер области памяти для сервера DPMI hostdata_size = outregs.x.si; printf("\nРазмер памяти для сервера DPMI:\t\t%d байт", hostdata_size * 16); // Определяем адрес точки входа в защищённый режим FP_SEG(pm_entry) = segregs.es; FP_OFF(pm_entry) = outregs.x.di; printf("\nАдрес точки входа в защищённый режим: \t%Fp\n", pm_entry); // Заказываем память для сервера DPMI if(hostdata_size) { if(_dos_allocmem(hostdata_size, &hostdata_seg) != 0) { printf("Мало стандартной памяти"); exit(-1); } } } // ------------------------------------------------ // Процедура для установки защищённого режима // ------------------------------------------------ void set_pmode() { // Входим в защищённый режим asm { mov ax, hostdata_seg mov es, ax mov ax, dpmi_flags } (*pm_entry)(); } // ------------------------------------------- // Процедура вывода символа на экран в // защищённом режиме // ------------------------------------------- void pm_putch(int chr) { // Структура для вызова прерывания должна // быть определена как статическая static RM_INT_CALL regs; static RM_INT_CALL far *pregs = (void far *) 0; // В первый раз инициализируем структуру // и указатель на неё if (!pregs) { pregs = &regs; memset(pregs, 0, sizeof(RM_INT_CALL)); regs.eax = 0x0200; } regs.edx = chr; // Вызываем прерывание DOS для вывода символа rm_int(0x21, 0, pregs); } // ------------------------------------------- // Процедура вывода строки на экран в // защищённом режиме // ------------------------------------------- void pm_puts(char *str_ptr) { while (*str_ptr) { pm_putch(*str_ptr); str_ptr++; } } // ------------------------------------------- // Процедура вывода строки на экран в // защищённом режиме, аналог функции printf() // ------------------------------------------- void cdecl pm_printf(const char *fmt, ...) { char buffer[512], *sptr=buffer; va_list marker; va_start(marker, fmt); vsprintf(buffer, fmt, marker); va_end(marker); while (*sptr) pm_putch(*sptr++); } // -------------------------------------------- // Процедура вызова прерывания реального режима // -------------------------------------------- int rm_int(unsigned int_number, // номер прерывания unsigned params, // количество слов параметров, // передаваемых через стек RM_INT_CALL far *rm_call) // адрес структуры // для вызова прерывания { asm { push di push bx push cx mov ax, 0300h // функция вызова прерывания mov bx, int_number mov cx, params; les di, rm_call // запись в ES:DI адреса структуры int 31h // вызов сервера DPMI jc error mov ax, 0 // нормальное завершение jmp short rm_int_end } error: asm mov ax, 0 // завершение с ошибкой rm_int_end: asm pop cx asm pop bx asm pop di } // ----------------------------------------------------- // Процедура отображает текущее состояние памяти // ----------------------------------------------------- int mi_show(void) { PMI minfo, far *minfoptr = &minfo; unsigned long psize, far *psizeptr=&psize; unsigned sel; void far *fp; get_mi(minfoptr); pm_printf(" Информация об использовании памяти\n\r" " ----------------------------------\n\r" "\r\n Размер максимального доступного блока:\t\t%ld байт" "\r\n Доступно незафиксированных страниц:\t\t%ld", minfo.avail_block, minfo.max_page); pm_printf("\r\n Доступно зафиксированных страниц:\t\t%ld" "\r\n Размер линейного адресного пространства:\t%ld страниц" "\r\n Всего имеется незафиксированных страниц:\t%ld", minfo.max_locked, minfo.linadr_space, minfo.total_unlocked); pm_printf("\r\n Количество свободных страниц:\t\t\t%ld" "\r\n Общее количество физических страниц:\t\t%ld", minfo.free_pages, minfo.tot_phys_pages); pm_printf("\r\n Свободное линейное адресное пространство:\t%ld страниц" "\r\n Размер файла/раздела для страничного обмена:\t%ld страниц", minfo.free_linspace, minfo.size_fp); get_page_size(psizeptr); pm_printf("\r\n Размер страницы:\t\t\t\t%ld байт\r\n", psize); // Выводим текущие значения регистров CS и DS asm mov sel,cs pm_printf("\n\r CS = %04.4X, ",sel); asm mov sel,ds pm_printf("DS = %04.4X",sel); // Выводим значение текущего приоритетного кольца fp = (void far *) main; sel = FP_SEG(fp) & 3; pm_printf("\n\r Номер приоритетного кольца = %d\n\r",sel); } // ----------------------------------------------- // Процедура для получения информации об // использовании памяти // ----------------------------------------------- int get_mi(PMI far *minfo) { asm { mov ax, 0500h les di, minfo // ES:DI = адрес структуры DMI int 31h jc error mov ax, 0 jmp short get_mi_end } error: asm mov ax, 1 get_mi_end: } // ------------------------------------------------ // Процедура для получения размера страницы памяти // ------------------------------------------------ int get_page_size(long far *page_size) { asm { mov ax, 0604h int 31h jc error les di, page_size // ES:DI = адрес page_size mov es:[di], cx mov es:[di+2], bx mov ax, 0 jmp short gps_end } error: asm mov ax, 1 gps_end: } // -------------------------------------------------- // Определение сегментного адреса видеопамяти // -------------------------------------------------- unsigned crt_mode, crt_seg; int video_init(void) { union REGS r; // Определяем текущий видеорежим r.h.ah=15; int86(0x10,&r,&r); crt_mode = r.h.al; if(crt_mode == MONO_MODE) crt_seg = 0xb000; else if(crt_mode == BW_80_MODE crt_mode == COLOR_80_MODE) crt_seg = 0xb800; else { printf("\nИзвините, этот видеорежим недопустим."); exit(-1); } } // --------------------------------------------------- // Получение селектора для адресации видеопамяти // --------------------------------------------------- char far *vid_ptr; DESCRIPTOR d; unsigned ldtsel; int alloc_videosel(void) { void far *fp; unsigned long addr; FP_SEG(vid_ptr) = crt_seg; FP_OFF(vid_ptr) = 0; pm_printf(" Адрес видеопамяти реального режима:\t %Fp\r\n", vid_ptr); // Получаем свободный LDT-селектор if (! (ldtsel = get_sel())) { pm_printf(" Ошибка при получении селектора"); dos_exit(-1); } pm_printf(" Получен селектор:\t\t\t%04.4X\n\r", ldtsel); // Подготавливаем дескриптор для полученного селектора d.limit = 0x2000; addr = ABSADDR(crt_seg, 0); d.addr_lo = addr & 0xFFFF; d.addr_hi = addr >> 16; d.access.accessed = 0; // не использовался d.access.read_write = 1; // разрешены чтение/запись d.access.conf_exp = 0; // не стек d.access.code = 0; // это сегмент данных d.access.xsystem = 1; // не системный дескриптор d.access.dpl = 3; // приоритетное кольцо 3 d.access.present = 1; // сегмент присутствует в памяти d.reserved = 0; // Устанавливаем дескриптор if (!set_descriptor(ldtsel, &d)) { pm_printf(" Ошибка при установке дескриптора"); getch(); dos_exit(-1); } // Выводим на экран адрес видеопамяти FP_SEG(vid_ptr) = ldtsel; FP_OFF(vid_ptr) = 0; pm_printf(" Адрес видеопамяти защищённого режима:\t%Fp\r\n", vid_ptr); } // -------------------------------------------------- // Освобождение селектора видеопамяти // -------------------------------------------------- int free_videosel(void) { if (!sel_free(ldtsel)) { pm_printf(" Ошибка при освобождении селектора"); dos_exit(-1); } } // ---------------------------------------------- // Получить один селектор в LDT // ---------------------------------------------- unsigned get_sel(void) { asm { mov ax, 0 // получить селектор mov cx, 1 // нужен один селектор int 31h jc error jmp short gs_end // AX содержит новый LDT-селектор } error: asm mov ax, 0 // произошла ошибка gs_end: } // -------------------------------------------------- // Установить дескриптор для LDT-селектора // -------------------------------------------------- int set_descriptor(unsigned pm_sel, DESCRIPTOR far *desc) { asm { push di push bx mov ax, 000Ch mov bx, pm_sel les di, desc int 31h jc error mov ax, 1 jmp short sd_end } error: asm mov ax, 0 sd_end: asm pop bx asm pop di } // -------------------------------------------------- // Освободить LDT-селектор // -------------------------------------------------- int sel_free(unsigned pmodesel) { asm { mov ax, 0001h mov bx, pmodesel int 31h jc error mov ax, 1 jmp short done } error: asm mov ax, 0 done: } // ------------------------------------------------------- // Вывод символа непосредственной записью в видеопамять // ------------------------------------------------------- void vi_putch(unsigned int x, unsigned int y ,char c, char attr) { register unsigned int offset; char far *vid_ptr; offset=(y*160) + (x*2); vid_ptr=MK_FP(ldtsel, offset); *vid_ptr++=c; *vid_ptr=attr; } // ------------------------------------------------------- // Вывод строки непосредственной записью в видеопамять // ------------------------------------------------------- void vi_print(unsigned int x, unsigned int y, char *s, char attr) { while(*s) vi_putch(x++, y, *s++, attr); } // ------------------------------------------------------- // Вывод сообщения непосредственной записью в видеопамять // ------------------------------------------------------- void vi_hello_msg(void) { vi_print(0, 0, " Демонстрация работы с интерфейсом " "DPMI ¦ © Frolov A.V., 1992 ", 0x30); }

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


Функция изменяет размер блока памяти, полученного при помощи функции 0501h.

Регистры на входе AX 0503h BX:CX Новый размер блока памяти в байтах. SI:DI Индекс изменяемого блока памяти. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. BX:CX Новый линейный адрес полученного блока. SI:DI Новый индекс блока памяти.

Изменить размер блока памяти, полученного из пула DOS


С помощью этой функции программа может увеличить или уменьшить размер блока памяти, полученного функцией 0100h.

Регистры на входе AX 0102h BX Новый размер блока памяти в параграфах. DX Селектор модифицируемого блока. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. В случае ошибки регистр AX содержит код ошибки, полученный от DOS: 07h - разрушен блок MCB; 08h - слишком большой размер заказанного блока. В этом случае регистр BX содержит максимально возможный размер блока в параграфах. 09h - неправильное задание сегментного адреса модифицируемого блока памяти.

Эмуляция прерывания реального режима


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

Регистры на входе AX 0300h BL Номер прерывания. BH Байт флагов. Если установлен в единицу бит 0, выполняется сброс контроллера и линии A20. Остальные биты зарезервированы и должны быть сброшены в нуль. CX Количество слов, которые должны быть скопированы из стека защищённого режима в стек реального режима. ES:(E)DI Адрес управляющей структуры для вызова прерывания. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. ES:(E)DI Адрес модифицированной (в результате выполнения обработчика прерывания) управляющей структуры в формате защищённого режима.

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

Таблица 11. Формат управляющей структуры для эмуляции прерывания реального режима средствами DPMI.

Смещение Регистр
00h EDI
04h ESI
08h EBP
0Ch Зарезервировано
10h EBX
14h EDX
18h ECX
1Ch EAX
20h FLAGS
22h ES
24h DS
26h FS
28h GS
2Ah IP
2Ch CS
2Eh SP
30h SS

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

push Parametr1 push Parametr2 push Parametr3 mov cx, 3 ; Копируем три параметра mov ax, 0301h int 31h add sp, 6 ; Восстанавливаем стек

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

Parametr1 Parametr2 Parametr3 CS для возврата IP для возврата

Указатель стека реального режима SS:SP установлен на слово, содержащее IP для возврата.



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


Процессор 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.


Команды 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). Процессор может выполнить возврат только в менее привилегированный сегмент или в сегмент, обладающий такими же привилегиями, что и тот, из которого выполняется возврат.



Критическая секция


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

Про программу, захватившую процессор, говорят, что она вошла в критическую секцию.

Для выхода из критической секции и возобновления работы диспетчера задач WINDOWS программа должна вызвать функцию 1682h прерывания INT 2Fh.



LAR Загрузка байта прав доступа


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

В процессорах i80386 и i80486 команда LAR использует в качестве первого операнда 32-разрядный регистр. Кроме байта прав доступа в этот регистр заносятся биты типа сегмента (9-11), DPL (14), бит присутствия (15), бит дробности (23).



LGDT Загрузка регистра GDTR


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



LIDT Загрузка регистра IDTR


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



LLDT Загрузка регистра LDTR


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



LMSW Загрузка слова состояния процессора


С помощью этой команды можно выполнить загрузку младшего слова регистра CR0 из регистра - операнда команды.

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



LSL Загрузка предела сегмента


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



LTR Загрузка регистра задачи


Команда предназначена для загрузки регистра TR - регистра задачи. Загрузка этого регистра не приводит к переключению задачи.



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


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

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

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

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

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



MOV Загрузка системных регистров


Для процессоров i80386 и i80486 в качестве операндов обычной команды MOV допустимо (на нулевом уровне привилегий) указывать системные регистры - CR0, CR2, CR3, DR0, DR1, DR2, DR3, DR6, DR7, TR6, TR7. Команда MOV может быть использована процессорами i80386 и i80486 для возврата процессора из защищённого режима в реальный.



Мультизадачность


В процессоре i80386 получила дальнейшее развитие аппаратная поддержка мультизадачности, впервые введённая в процессоре i80286. На рис. 22 представлен формат сегмента TSS для процессора i80386.

Рис. 22. Сегмент TSS процессора i80386.

Из рисунка видно, что в TSS предусмотрены поля для хранения сегментных регистров GS, FS, DS, SS, CS, ES (процессор i80386 имеет два новых сегментных регистра - GS и FS). Имеется поле для хранения содержимого регистра LDTR, указывающего на локальную таблицу дескрипторов, распределённую данной задаче.

Для хранения содержимого 32-разрядных регистров используются поля TSS, обозначенные на рисунке как EDI, ESI, EBP, ESP, EBX, EDX, ECX, EAX, EFLAGS, EIP.

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

TSS процессора i80386 содержит указатели на стеки для второго, первого и нулевого приоритетных колец. Это поля SS2:ESP2, SS1:ESP1, SS0:ESP0.

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

Бит T используется для отладки. Если он установлен в 1, при переключении на задачу возникает отладочное исключение, которое может быть использовано системным отладчиком.

Есть ещё одно очень интересное новшество, введённое в процессор i80386 - битовая карта ввода/вывода. Что это такое?

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

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

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

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

Поле базы карты ввода/вывода указывает 16-разрядное смещение начала битовой карты ввода/вывода относительно TSS. Предел TSS должен определяться с учётом карты ввода/вывода.

Каждый бит в карте ввода/вывода соответствует адресу байта порта ввода/вывода. После битовой карты должен располагаться байт 0FFh.

При выполнении 16- или 32-разрядных операций ввода/вывода процессор проверяет все биты (2 или 4 бита), соответствующие адресу порта. Если проверяемый бит установлен в 1, происходит исключение.

Для тех программ, которые являются привилегированными, если уровень привилегий меньше или равен уровню IOPL, процессор не выполняет проверку битовой карты ввода/вывода.

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


В подавляющем большинстве программы, составленные


4.1.

4.2.

4.3.

4.4.

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

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

Резидентные программы, за редким исключением, не реализуют настоящую мультизадачность. Обычно с помощью резидентных программ вы можете только переключаться от одной запущенной программы к другой. Типичный пример "мультизадачной" резидентной программы - часы, которые работают параллельно с другими программами и постоянно показывают время в заранее определённом месте экрана. Другой пример - резидентная программа фоновой печати PRINT, входящая в состав MS-DOS.

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

Учитывая необходимость реализации переключения программ, фирма Microsoft в операционной системе MS-DOS версии 5.0 реализовала переключатель программ, встроенный в диалоговую оболочку DOSSHELL. Эта оболочка позволяет запустить на выполнение несколько программ и переключаться от одной к другой. Но активна только одна задача - та, на которую переключился пользователь. Остальные находятся в "замороженном" состоянии.

Однако часто бывает необходимо, чтобы программы работали в режиме разделения времени процессора. В этих случаях нужно использовать операционную систему, работающую в мультизадачном режиме - OS/2, UNIX, XENIX, WINDOWS, DeskView.

Мультизадачность позволяет не только задействовать все ресурсы современных персональных компьютеров, но и существенно повышает производительность труда.
Например, вы с помощью модема принимаете файл размером 1-2 мегабайта. Скорость передачи данных по телефонным линиям редко превышает 2400 бит в секунду, поэтому в худшем случае процесс получения файла может растянуться на часы. Без использования мультизадачности ваш компьютер всё это время будет занят только приёмом файла. Более того, в основном он будет находиться в состоянии ожидания, так как передача данных производится очень медленно.

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

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

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

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

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

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

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



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

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

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

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

Ситуация напоминает железнодорожный переезд - движение разрешено либо поездам, либо автомобилям, но не одновременно и тем и другим!

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

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


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

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

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

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

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


Недокументированная команда LOADALL


Оказывается, для процессора i80286 существует способ получения доступа к расширенной памяти, не переключаясь в защищённый режим. Для этого может быть использована недокументированная команда LOADALL, имеющая код 0F05h (команда не имеет операндов). Эта команда не описана в справочниках по процессору i80286, информация о ней поставляется фирмой Intel по запросу. Те сведения о команде LOADALL, которые приведены в нашей книге, получены по электронной почте из BBS и могут быть использованы только для расширения вашего кругозора и для оценки полезности этой команды в ваших разработках.

Команда LOADALL первоначально была задумана фирмой Intel как тестовая. Однако оказалось, что она пригодна и для обращения к расширенной памяти в реальном режиме. Широко известный драйвер расширенной памяти HIMEM.SYS обращается в область адресов выше первого мегабайта именно с помощью команды LOADALL (а не переключаясь в защищённый режим и возвращаясь обратно, как это можно было бы предположить).

Команда LOADALL сокращает время, требуемое драйверу HIMEM.SYS на доступ к расширенной памяти, так как время на переключение в защищённый режим и обратное переключение достаточно велико по сравнению с временем, необходимым на копирование данных из основной памяти в расширенную или обратно.

Другое применение команды - драйвер электронного диска Microsoft RAMDRIVE.SYS и блок совместимости операционной системы Microsoft OS/2 версии 1.x.

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

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

Формат буфера представлен в следующей таблице:

Таблица 16. Формат буфера для команды LOADALL.


Адрес Регистры процессора
800h-805h Не используется
806h-807h Слово состояния процессора MSW (Machine Status Word)
808h-815h Не используется
816h-817h Регистр задачи TR (Task Register)
818h-819h Регистр флагов
81Ah-81Bh Регистр IP (Instruction Pointer)
81Ch-81Dh Селектор LDT (Local Descriptor Table)
81Eh-81Fh Регистр DS (Data Segment Selector)
820h-821h Регистр SS (Stack Segment Selector)
822h-823h Регистр CS (Code Segment Selector)
824h-825h Регистр ES (Extra Segment Selector)
826h-827h Регистр DI (Destination Index)
818h-829h Регистр SI (Source Index)
82Ah-82Bh Регистр BP (Base Pointer)
82Ch-82Dh Регистр SP (Stack Pointer)
82Eh-82Fh Регистр BX (Data Register BX)
830h-831h Регистр DX (Data Register DX)
832h-833h Регистр CX (Data Register CX)
834h-835h Регистр AX (Accumulator)
836h-83Bh Кэш дескриптора ES
83Ch-841h Кэш дескриптора CS
842h-847h Кэш дескриптора SS
848h-84Dh Кеш дескриптора DS
84Eh-853h Регистр GDTR (Global Descriptor Table Register)
854h-859h Кэш дескриптора LDT
85Ah-85Fh Регистр IDTR (Interrupt Descriptor Table Register)
860h-865h Кэш дескриптора TSS (Task State Segment)
Для ускорения доступа к содержимому дескрипторных таблиц в процессоре имеются так называемые теневые регистры или регистры кэша дескрипторов. Когда процессор загружает селектор в сегментный регистр, автоматически выполняется загрузка соответствующего регистра кэша дескриптора. Не существует какого-либо иного способа загрузить кэш дескриптора явно из программы с помощью обычных команд. Однако вы можете воспользоваться для этого командой LOADALL, подготовив в описанном выше буфере необходимые значения.

Формат кэша дескриптора приведён в следующей таблице:

Таблица 17. Формат кэша дескриптора.

Смещение поля Назначение поля
0-2 24-битовый базовый адрес сегмента
3 Байт доступа, его формат полностью аналогичен формату байта доступа дескриптора, за исключением бита присутствия. На месте этого бита находится бит VALID. Если этот бит сброшен в 0, при попытке использовать дескриптор для адресации памяти произойдёт исключение 13 с кодом ошибки 0.
4-5 16-битовый предел сегмента



Можно предложить следующий алгоритм использования команды LOADALL: Запретитите прерывания. Сохраните где-нибудь в буфере программы область памяти, начинающуюся с адреса 00800h и имеющую длину 102 байта. Заполните буфер для команды LOADALL необходимыми значениями для всех загружаемых регистров. Базовый адрес в области кэша дескриптора сегмента данных должен указывать на необходимый вам участок расширенной памяти. Выполните команду LOADALL. Сегмент данных теперь будет указывать на область расширенной памяти. Выполните запись или чтение области расширенной памяти. Восстановите базовый адрес сегмента данных в кэше дескриптора данных в буфре, расположенном по адресу 00800h. Выполните команду LOADALL ещё раз. Восстановите содержимое сохранённого ранее буфера. Разрешите прерывания. При выполнении команды LOADALL не делается никаких проверок. Вам необходимо самим позаботиться о том, чтобы загружаемые в регистры процессора значения имели какой-нибудь смысл. В противном случае состояние процессора окажется непредсказуемым.

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

Процессор i80387 также имеет команду LOADALL, но её код и выполняемые функции другие.


Обработка аппаратных прерываний


Вспомните диапазон номеров прерываний, используемый в реальном режиме в компьютерах IBMPC: для обработки прерываний IRQ0-IRQ7 используются номера прерываний от 08h до 0Fh, а для IRQ8-IRQ15 - от 70h до 77h.

Но в защищённом режиме номера от 08h до 0Fh зарезервированы для обработки исключений!

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

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



Обработка прерываний


Процессор i80386 в защищённом режиме обрабатывает прерывания точно также, как и процессор i80286. Есть отличия в формате дескрипторов, располагающихся в дескрипторной таблице прерываний IDT. Эти отличия заключаются в использовании других значений в поле типа (см. таблицу 1 в главе 1) и в том, что два байта, зарезервированные в вентилях прерывания и исключения процессора i80286 используются процессором i80386 для хранения битов 16-31 32-разрядного смещения.



ОБРАБОТКА ПРЕРЫВАНИЙ В ЗАЩИЩЁННОМ РЕЖИМЕ


3.1.

3.2.

3.3.

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

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



ОБЗОР ЛИТЕРАТУРЫ


Архитектура микропроцессора 80286 С.П. Морс, Д.Д. Алберт М.: Радио и связь, 1990 ББК 32.97 М 80 УДК 681.325.5-181.4

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

Обращаем ваше внимание на ошибку, допущенную в данном издании книги в разделе, посвящённом мультизадачности. На рисунке 5.16-б (стр. 199) расположение бита занятости B показано неправильно - этот бит находится не в нулевом разряде, а в первом.

Микропроцессор 80386 П. Брамм, Д. Брамм М.: Мир, 1990 ББК 32.973-01 Б 87 УДК 681.3

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

Логическое проектирование операционных систем. А. Шоу М: Мир, 1981 ББК 32.973 Ш 81 УДК 681.142.2

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

Проектирование операционных систем для малых ЭВМ С. Кейслер М: Мир, 1986 ББК 32.973 К 33 УДК 681.142.2

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

Программирование в Microsoft WINDOWS С.А. Гладков, Г.В. Фролов М.: "ДИАЛОГ-МИФИ", 1992, в двух частях ББК 32.973.1 Г 52 УДК 681.3

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

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

Библиотека системного программиста Том 1. Операционная система MS-DOS А.В. Фролов, Г.В. Фролов М.: "ДИАЛОГ-МИФИ", 1991, в трёх книгах (книги 1-2 и книга 3) ББК 32.973.1 Ф 91 УДК 681.322 Первый том "Библиотеки системного программиста" содержит сведения о внутренней структуре MS-DOS, об использовании документированных и некоторых недокументированных возможностей этой операционной системы в программах реального режима работы процессора. Большое внимание уделено резидентным программам и драйверам.

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

Библиотека системного программиста Том 2. Аппаратное обеспечение IBM PC А.В. Фролов, Г.В. Фролов М.: "ДИАЛОГ-МИФИ", 1992, в двух книгах ББК 32.973.1 Ф 91 Во втором томе "Библиотеки системного программиста" описано практически всё оборудование персонального компьютера IBM PC и его программирование (кроме видеоадаптеров, которые описаны в 3 томе "Библиотеки системного программиста").

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

Все программы, приведённые в книге, доступны на дискете, которую можно купить вместе с книгой.

Библиотека системного программиста Том 3. Программирование видеоадаптеров CGA, EGA, VGA. А.В. Фролов, Г.В. Фролов М.: "ДИАЛОГ-МИФИ", 1992 В третьем томе "Библиотеки системного программиста" описаны видеоадаптеры CGA, EGA, VGA. Для каждого типа видеоадаптера приведена информация, необходимая для разработки драйверов, в частности, работающих в защищённом режиме.



Подробно описаны архитектура, схема организации видеопамяти, использование регистров, программирование на уровне BIOS и DOS. Рассмотрены основные возможности по программированию видеоадаптеров, предоставляемые библиотеками Microsoft Quick C.

Вместе с книгой продаётся дискета, содержащая исходные тексты всех программ.

32-Bit Microprocessors Editor: H.J. Mitchell ISBN 0-00-383067-5 Книга является сборником статей о 32-разрядных микропроцессорах. Описаны архитектура и особенности микропроцессоров с RISC-архитектурой, процессор WE32100, транспьютеры фирмы Inmos, микропроцессор i80386, Motorola 68020 и Zilog Z80000.

80386. Programmer's Reference Manual, INTEL Corp., USA, 1988. Эта и следующая книги - фирменные руководства по процессорам. они необходимы тем, кто собирается разрабатывать коммерческое программное обеспечение, работающее в защищённом режиме и использующее возможности последних моделей процессоров фирмы Intel.

INTEL i80486 Microprocessor. Hardware Reference Manual, INTEL Corp., USA, 1990. Справочное руководство по процессору i80486.

Undocumented DOS Andrew Schulman, Raymond J. Michels, Jim Kyle, Tim Paterson, David Maxey, Ralf Braun, 1991. ISBN 0-201-57064-5 Книга посвящена недокументированным возможностям MS-DOS. В плане использования защищённого режима представляет интерес глава, посвящённая виртуальной DOS-машине операционной системе WINDOWS. В ней рассказывается о способе вызова недокументированных прерываний MS-DOS из программы, работающей на виртуальной DOS-машине в защищённом режиме. Программа пользуется функциями DPMI.


Очистить CLIPBOARD


С помощью этой функции можно удалить данные из CLIPBOARD:

Регистры на входе AX 1702h Регистры на выходе: AX 0, если при выполнении операции произошла ошибка; не равно 0, если операция успешно выполнена.

ОПЕРАЦИОННАЯ СИСТЕМА MICROSOFT WINDOWS


7.1.

7.2.

7.3.

Не будет преувеличением сказать, что операционная система WINDOWS версии 3.0 открыла новый этап в жизни персонального компьютера IBM PC. Удобная, интуитивно ясная графическая оболочка, аналогичная используемой в компьютерах фирмы Apple, быстро завоевала популярность у пользователей IBM PC. В нашей стране WINDOWS также получила широкое распространение, особенно после того, как стали доступны компьютеры с памятью 2 мегабайта и более. Новая верисия WINDOWS 3.1 обладает улучшенным графическим интерфейсом и большей производительностью.

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

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

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

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

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

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

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

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

В зависимости от типа процессора и режима, в котором работает WINDOWS (стандартный или расширенный) выполнение DOS-программ организуется по-разному.

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

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

Что такое виртуальная машина, хорошо знают те, кто работал на ЕС ЭВМ в операционной системе VM (известна также под названием "СВМ" - система виртуальных машин).


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

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

Дополнительно в распоряжение DOS-программы может быть предоставлена расширенная или дополнительная память (интерфейсы EMS и XMS). Размер этой памяти можно задать в pif-файле (см. руководство по WINDOWS).

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

Для приложений WINDOWS и виртуальной DOS-машины доступен интерфейс DPMI. Это, в частности, означает, что виртуальная DOS-машина не просто допускает выполнение DOS-программ, но и предоставляет им дополнительные возможности, которые они не имели бы, если бы работали под управлением "настоящей" MS-DOS.

Описанию этих дополнительных возможностей и посвящена данная глава книги.


Определение текущего режима работы


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

Для выполнения функции необходимо вызвать прерывание INT 2Fh, предварительно загрузив регистр AX:

Регистры на входе: AX 1686h Регистры на выходе: AX 0, если программа работает в защищённом режиме под управлением DPMI, не равно 0, если программа работает в реальном или виртуальном режиме.

Определить количество свободных страниц памяти


Регистры на входе: AX 0DE03h Регистры на выходе: AH равен 00h - успешное выполнение функции, не равен 00h - ошибка. EDX Количество свободных страниц памяти, доступных для всех задач в системе.

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



Определить максимальный физический адрес памяти


Регистры на входе: AX 0DE02h Регистры на выходе: AH равен 00h - успешное выполнение функции, не равен 00h - ошибка. EDX Максимальный физический адрес страницы памяти размером 4 килобайта.



Определить размер расширенной памяти


Регистры на входе: AH 88h Регистры на выходе: AX Размер доступной расширенной памяти в килобайтах.

Эта функция предназначена для определения размера расширенной памяти, доступной для использования функциями прерывания INT15h.

Учтите, что если в системе установлен драйвер HIMEM.SYS, функция 88h может вернуть нулевой размер доступной расширенной памяти. Некоторые программы (например, СУБД Oracle версии 5.1) могут оказаться несовместимыми с драйвером HIMEM.SYS, так как они работают с расширенной памятью средствами прерывания INT 15h. Аналогичные проблемы могут возникнуть и при использовании других драйверов расширенной памяти, например, QEMM.

Как правило, драйверы расширенной памяти позволяют зарезервировать часть расширенной памяти для программ, использующих интерфейс INT 15h. Для этого необходимо задать соответствующие параметры. Например, для драйвера HIMEM.SYS размер зарезервированной расширенной памяти можно указать следующим образом:

device=c:\dos\himem.sys /int15=xxxx

В этой строке "xxxx" - размер зарезервированной памяти в килобайтах.



Появление следующей после i80286 модели


5.1.

5.2.

5.3.

5.4.

5.5.

5.6.

5.7.

5.8.

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

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

К сожалению, часто владельцы такой мощной техники, как персональные компьютеры на базе i80386 и i80486, не используют возможностей этих компьютеров даже наполовину. Работая в среде операционной системы MS-DOS с программами, подготовленными специально для этой операционной системы, вы будете использовать только режим совместимости процессоров i80386 и i80486, остальные преимущества новых процессоров (помимо более высокой скорости выполнения программ) будут вам недоступны.

Аналогичное можно сказать и об операционной системе OS/2 версий от 1.0 до 1.3. Эта операционная система рассчитана на процессор i80286. Разумеется, вы можете использовать OS/2 этих версий в компьютерах на базе i80386 или i80486, однако лучше приобрести OS/2 версии 2.0 или более старшей версии, ориентированной на использование архитектурных особенностей новых процессоров.

Какие же преимущества имеют процессоры i80386 и i80486 перед предыдущими моделями? Приведём только самые важные, на наш взгляд, особенности новых процессоров: Высокое быстродействие, определяемое, в частности, высокой тактовой частотой (25 Мгц, 33 Мгц и даже больше). Разрядность процессоров i80386 и i80486 составляет 32 бита (предыдущие модели от i8086 до i80286 были 16-разрядными).
Увеличение разрядности процессора приводит к увеличению скорости выполнения программ, так как процессор способен за один машинный такт обработать больший объём информации. Расширенный набор машинных команд и большое количество поддерживаемых процессором типов данных позволяют создавать более эффективные, более компактные программы, которые работают значительно быстрее ориентированных на 16-разрядные процессоры. Новый механизм преобразования адресов, являющийся дальнейшим развитием механизма, использованного в процессоре i80286. Теперь процессор может рассматривать память как один или несколько сегментов, причём размер сегмента может достигать 4 гигабайт (4*2*30 байт). С другой стороны, страничная адресация позволяет организовать защиту памяти для каждой страницы. Размер страницы составляет 4 килобайта. Использование страниц значительно облегчает реализацию виртуальной памяти. Помимо реального и защищённого режима работы, в процессорах i80386 и i80486 предусмотрен режим виртуального процессора 8086 (виртуальный режим), в который процессор может войти из защищённого режима. Виртуальный режим позволяет эмулировать процессор i8086, находясь в защищённом режиме. Это, в частности, даёт возможность в мультизадачной операционной системе организовать одновременное выполнение нескольких программ, ориентированных на процессор i8086. Объём книги ограничен, поэтому мы не сможем подробно рассмотреть все особенности процессоров i80836 и i80486. Для полного описания потребовалось бы много сотен страниц. Но это и не является нашей целью. Все подробности при необходимости вы сможете узнать из литературы, список которой приведён в конце книги. Однако для практического использования многих преимуществ новых процессоров вам не потребуются подробные знания всех особенностей.

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

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


Подавляющее большинство владельцев персональных компьютеров,


1.1.

1.2.

1.3.

1.4.

Подавляющее большинство владельцев персональных компьютеров, совместимых с IBMPC, используют операционную систему 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.

Перечислим кратко основные преимущества, которые получает программа, работающая в защищённом режиме процессора: возможность непосредственной адресации памяти за пределами первого мегабайта; для процесоров i80386 и i80486 реализован механизм страничной виртуальной памяти, позволяющий программам работать с памятью, размер которой может быть много больше физической оперативной памяти, установленной в компьютере; аппаратная поддержка мультизадачности позволяет создавать на основе процессоров, работающих в защищённом режиме высокопроизводительные мультизадачные и мультипользовательские системы; эффективная работа нескольких программ, составленных для MS-DOS, основанная на использовании виртуального режима работы процессора. Изучение защищённого режима мы начнём с описания метода адресации памяти, коренным образом отличающегося от привычного для вас метода <сегмент:смещение> реального режима. Однако вначале напомним, как адресуется память в реальном режиме.


Освободить блок памяти


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

Регистры на входе AX 0502h SI:DI Индекс освобождаемого блока памяти. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

Освободить блок памяти, взятый из пула DOS


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

Регистры на входе AX 0101h DX Селектор освобождаемого блока. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. В случае ошибки регистр AX содержит код ошибки, полученный от DOS: 07h - разрушен блок MCB; 09h - неправильное задание сегментного адреса освобождаемого блока памяти.

Освободить дескриптор в таблице LDT


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

Регистры на входе AX 0001h BX Селектор освобождаемого дескриптора. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

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



Освободить страницу памяти


Регистры на входе: AX 0DE05h EDX Физический адрес освобождаемой страницы памяти. Регистры на выходе: AH равен 00h - успешное выполнение функции, не равен 00h - ошибка.

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