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

         

Открыть CLIPBOARD


Перед выполнением любой операции с CLIPBOARD необходимо открыть CLIPBOARD (по аналогии с обычным файлом):

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

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


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

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

Отмена отладочной точки останова


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

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

Отмена перехвата передачи управления процедуре реального режима.


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

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

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




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

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

Отвергнуть содержимое страниц


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

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

Перехват передачи управления программе реального режима


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

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

Регистры на входе AX 0303h DS:(E)SI Адрес процедуры в формате защищённого режима, которая будет вызвана вместо перехваченной. ES:(E)DI Адрес управляющей структуры для вызова процедуры реального режима (в формате защищённого режима). Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. ES:(E)DI Адрес модифицированной (в результате выполнения процедуры реального режима) управляющей структуры в формате защищённого режима. CX:DX Адрес вызова перехватываемой программы в формате реального режима.

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

DS:(E)SI Адрес стека реального режима в формате <селектор:смещение>.
ES:(E)DI Адрес управляющей структуры в формате <селектор:смещение>.
SS:(E)SP Стек защищённого режима.

Остальные регистры остаются в неопределённом состоянии.

Для выполнения возврата из процедуры перехвата необходимо выполнить команду IRET, предварительно загрузив регистры следующим образом:

ES:(E)DI Адрес управляющей структуры в формате <селектор:смещение>.

Переключение в защищённый и реальный режимы


Процессоры i80386 и i80486 могут легко переключаться из реального режима в защищённый и обратно с помощью команды MOV. Младший бит PE системного регистра CR0 (см. приложение) определяет текущий режим работы процессора. Если этот бит установлен в 1, процессор работает в защищённом режиме, а если в 0 - в реальном.

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

mov ax, cr0 or ax, 1 mov cr0, ax

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

Для возврата в реальный режим необходимо сбросить бит PE:

mov ax, cr0 and ax, 0fffe mov cr0, ax

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

Перед переключением в реальный режим из защищённого программа должна выполнить следующие действия: обеспечить равенство линейных адресов физическим; отключить трансляцию страниц, сбросив бит PG в регистре CR0; загрузить ноль в регистр CR3 для сброса кэш-памяти страниц; передать управление сегменту кода с пределом 64 килобайта; загрузить в сегментные регистры SS, DS, ES, FS, GS селекторы дескрипторов, подготовленных для адресации памяти в реальном режиме и содержащих соответствующие реальному режиму значения; запретить маскируемые и немаскируемые прерывания; сбросить бит PE, переключив процессор в реальный режим; выполнить команду дальнего перехода для очистки внутренней очереди команд процессора; настроить систему прерываний для работы в реальном режиме; разрешить прерывания; загрузить в сегментные регистры значения, необходимые для работы в реальном режиме.

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



Переключение в защищённый режим


Это самый простой этап. Для перевода процессора i80286 из реального режима в защищённый можно использовать специальную команду LMSW, загружающую регистр состояния процессора (Mashine Status Word). Младший бит этого регистра указывает режим работы процессора. Значение, равное 0, соответствует реальному режиму работы, а значение 1 - защищённому.

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

mov ax, 1 lmsw ax

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



Переключение задач


Для переключения задач имеются следующие возможности: переключение по команде JMP; переключение по команде CALL; переключение по прерыванию.

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

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

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

Кроме того, при переключении задачи командой CALL в поле FLAGS сегмента TSS вызванной задачи устанавливается в 1 бит вложенной задачи NT. Команда JMP, если она использована для переключения задачи, сбрасывает бит NT. Формат регистра флагов для процессоров i80386 и i80486 описан в приложении. Регистр флагов FLAGS процессора i80286 - это младшее слово 32-разрядного регистра EFLAGS.

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

Рис. 16. Вентиль задачи.

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

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

Это происходит потому, что при переключении задачи в сегменте TSS записывается содержимое регистров CS:IP на момент переключения задачи. Если задача была вызвана при помощи команды CALL и возврат (обратное переключение) было выполнено по команде IRET, в TSS записывается адрес CS:IP, указывающий на следующую после IRET команду. Вы можете поместить там команду безусловного перехода JMP на начало задачи и таким образом зациклить задачу. После этого вызов задачи станет похож на вызов подпрограммы.

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

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



Переключить процессор в виртуальный режим


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

AX DE0Ch DS Селектор, полученный от функции DE01h. SS:ESP Стек должен быть расположен в пределах первого мегабайта памяти

Переключить процессор в защищённый режим


Регистры на входе: AX 0DE0Ch ESI Линейный адрес массива значений для системных регистров, массив должен располагаться в первом мегабайте памяти. Регистры на выходе: Загружаются регистры GDTR, IDTR, LDTR, TR. В стеке, на который указывают регистры SS:ESP, необходимо отвести по крайней мере 16 байт для возможности обработки прерываний.

Содержимое регистров EAX, ESI, DS, ES, FS, GS после выполнения функции будет потеряно.

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

Приведём формат области для загрузки системных регистров перед переходом в защищённый режим:

Таблица 10. Формат буфера для загрузки регистров и перехода в защищённый режим средствами VCPI.

Смещение Размер и назначение
00h DWORD, значение для регистра CR3.
04h DWORD, линейный адрес в пределах первого мегабайта для загрузки регистра GDTR.
08h DWORD, линейный адрес в пределах первого мегабайта для загрузки регистра IDTR.
0Ch WORD, значение для регистра LDTR.
0Eh WORD, значение для регистра TR.
10h PWORD, значение адреса CS:EIP точки входа в защищённый режим.


Переслать блок расширенной памяти


Регистры на входе: AH 87h CX Размер пересылаемого блока в словах. ES:SI Адрес таблицы GDT, подготовленной специальным образом. Регистры на выходе: CARRY = 0 Функция выполнилась без ошибки. AX 00h В случае ошибки: CARRY = 1 Произошла ошибка при пересылке блока. AH Код ошибки: 01h - ошибка чётности; 02h - произошло исключение; 03h - сбой адресной линии A20.

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

В третьем и четвёртом дескрипторе необходимо заполнить поля предела для копируемого блока памяти (в них должно быть записано значение CX*2 - 1), и поле доступа (значение 93):

Таблица 6. GDT для пересылки блока памяти средствами BIOS.

Смещение байта Содержимое
00h - 0Fh Это поле должно содержать нули.
10h - 11h Предел сегмента (CX*2 -1).
12h - 14h 24-разрядный физический адрес исходного блока памяти.
15h Байт доступа, должен быть равен 93h.
16h - 17h Это поле должно содержать нули.
18h - 19h Предел сегмента (CX*2 -1).
1Ah - 1Ch 24-разрядный физический адрес результирующего блока памяти.
1Dh Байт доступа, должен быть равен 93h.
1Eh - 2Fh Это поле должно содержать нули.

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

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

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



Phar Lap DOS-экстендер


В состав Phar Lap DOS-экстендера входят транслятор для языка Си hc386.exe, ассемблер 386asm.exe, редактор связей 386link.exe, отладчик minibug.exe и программа загрузки run386.exe.

С помощью транслятора языка Си или ассемблера получаются объектные модули, которые компонуются редактором связей 386link.exe в загрузочный модуль. Этот загрузочный модуль имеет расширение "exp" и запускается при помощи программы загрузки run386.exe. Полученный загрузочный модуль может работать только на процессорах i80386 или i80486. Версия 2.2 Phar Lap DOS-экстендера не поддерживает интерфейс DPMI, поэтому разработанные с использованием экстендера этой версии программы не будут работать на виртуальной машине WINDOWS в режиме "Enhanced 386 Mode".

Phar Lap DOS-экстендер предоставляет программе, которая получает управление сразу в защищённом режиме, возможность использовать документированные прерывания MS-DOS и BIOS. Кроме того, в рамках прерывания INT 21h DOS-экстендером реализуются дополнительные функции, связанные с работой в защищённом режиме.

Для того, чтобы у вас было представление о возможностях Phar Lap DOS-экстендера, приведём таблицу дополнительных функций, реализованных в рамках прерывания INT 21h:

Таблица 13. Функции Phar Lap DOS-экстендера.

Регистр AX Выполняемая функция
2501h Установка в исходное состояние структур данных DOS-экстендера.
2502h Получить вектор прерывания защищённого режима.
2503h Получить вектор прерывания реального режима.
2504h Установить вектор прерывания защищённого режима.
2505h Установить вектор прерывания реального режима.
2506h Установить режим, при котором прерывание будет всегда обрабатываться в защищённом режиме.
2507h Установить вектора прерываний реального и защищённого режима.
2508h Установить линейный базовый адрес сегмента.
2509h Преобразовать линейный адрес в физический
250Ah Отобразить физическую память в конце сегмента.
250Ch Получить вектора аппаратных прерываний.
250Dh Получить информацию связи с реальным режимом.
250Eh Вызвать процедуру реального режима.
250Fh Преобразовать адрес защищённого режима в адрес реального режима.
2510h Вызвать процедуру реального режима с заданным содержимым регистров.
2511h Вызвать прерывание реального режима с заданным содержимым регистров.
2512h Загрузить программу для отладки.
2513h Создать алиасный дескриптор сегмента (т.е. для заданного дескриптора создаётся ещё один, указывающий на тот же сегмент).
2514h Изменить атрибуты сегмента.
2515h Получить атрибуты сегмента.
2516h Освободить всю память, распределённую при помощи LDT.
2517h Получить информацию о буферах данных DOS.
2518h Определить драйвер для обработки перемещения сегмента.
2519h Получить дополнительную информацию об ошибке памяти.
251Ah Зафиксировать страницы в памяти.
251Bh Расфиксировать страницы.
251Ch Освободить страницы физической памяти.
251Dh Прочитать элемент таблицы страниц.
251Eh Записать элемент таблицы страниц.
251Fh Обменять элементы таблицы страниц.
2520h Получить статистическую информацию о памяти.
2521h Максимальный размер доступной программам расширенной памяти.
2522h Определить альтернативный драйвер, обрабатывающей ситуацию отсутствия страницы в памяти.
2525h Максимальный размер доступной программам стандартной памяти.
25C0h Получить блок стандартной памяти MS-DOS.
25C1h Освободить блок стандартной памяти MS-DOS.
25C2h Изменить размер блока стандартной памяти MS-DOS.
25C3h Выполнить программу.

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

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

Таблица 14. Таблица LDT Phar Lap DOS-экстендера.

0008h Сегмент кода программы.
0010h Сегмент данных программы.
0018h Сегмент видеопамяти для работы в текстовом режиме.
0020h PSP программы.
0028h Сегмент строк среды DOS (DOS environment).
0030h Сегмент данных для доступа к первому мегабайту памяти, доступен для записи.
0038h Сегмент для работы с сопроцессором Weitek 1167. В отличие от сопроцессора i80287/i80387 для обращения к сопроцессору Weitek 1167 используется определённая область адресов памяти.
0040h Сегмент видеопамяти для работы в графическом режиме.
В документации на Phar Lap DOS-экстендер подробно описаны форматы таблиц LDT и GDT. Программы могут пользоваться определёнными в этих таблицах селекторами для адресации системных областей памяти, таких как память видеоадаптера.

В качестве простейшего примера использования Phar Lap DOS-экстендера приведём следующую программу:

Листинг 20. Использование Phar Lap DOS-экстендера Файл pharlap.asm ----------------------------------------------------------- ; --------------------------------------------------- ; Сегмент данных ; --------------------------------------------------- _data segment para public use32 'data' hello db 'PHAR LAP 386/DOS EXTENDER', 0dh,0ah db 'Вызов DOS в защищенном режиме', 0dh,0ah db 0dh,0ah,'© Frolov A.V., 1992',0dh, 0ah db 0dh,0ah,'Для возврата в DOS нажмите любую клавишу','$' _data ends ; --------------------------------------------------- ; Сегмент стека ; --------------------------------------------------- _stack segment byte stack use32 'stack' db 8192 dup (?) _stack ends ; --------------------------------------------------- ; Сегмент кода ; --------------------------------------------------- assume cs:_text,ds:_data _text segment para public use32 'code' public _start_ _start_ proc near ; Выводим строку lea edx,hello mov ah,09h int 21h mov ah,8h int 21h mov ax,04c00h int 21h _start_ endp _text ends end _start_ Эта программа просто выводит сообщение на экран и завершает свою работу после того, как вы нажмёте любю клавишу.Особенность программы заключается в том, что она получает управление сразу в защищённом режиме. Запуск программы должен выполняться специальным загрузчиком, который входит в состав Phar Lap DOS-экстендера. Этот загрузчик находится в файле run386.exe.

Для трансляции программы и её запуска можно использовать следующий командный файл:

386asm pharlap 386link pharlap run386 pharlap Обратите внимание на то, что в приведённой выше программе не выполняется загрузка сегментного регистра DS. Так как программа стартует сразу в защищённом режиме, загрузчик run386 загружает сам все сегментные регистры. В частности, он загружает в регистр DS селектор сегмента данных.


Подготовка к переключению в защищённый режим


Перед тем, как переключить процессор в защищённый режим, надо выполнить некоторые подготовительные действия, а именно: Подготовить в оперативной памяти глобальную таблицу дескрипторов GDT. В этой таблице должны быть созданы дескрипторы для всех сегментов, которые будут нужны программе сразу после того, как она переключится в защищённый режим. Впоследствии, находясь в защищённом режиме, программа может модифицировать GDT (если, разумеется, она работает в нулевом кольце защиты). Программа может модифицировать имеющиеся дескрипторы или добавить новые, загрузив заново регистр GDTR. Для обеспечения возможности возврата из защищённого режима в реальный необходимо записать адрес возврата в реальный режим в область данных BIOS по адресу 0040h:0067h, а также записать в CMOS-память в ячейку 0Fh код 5. Этот код обеспечит после выполнения сброса процессора передачу управления по адресу, подготовленному нами в области данных BIOS по адресу 0040h:0067h. Запретить все маскируемые и немаскируемые прерывания. Открыть адресную линию A20. Запомнить в оперативной памяти содержимое сегментных регистров, которые необходимо сохранить для возврата в реальный режим, в частности, указатель стека реального режима. Загрузить регистр GDTR.

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

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

После выполнения сброса (или после отключения) процессор переходит в реальный режим и управление передаётся в BIOS. BIOS анализирует содержимое ячейки CMOS-памяти с адресом 0Fh - байта состояния отключения.
Дальнейшие действия определяются содержимым этой ячейки.

Байт состояния отключения 0Fh используется BIOS для определения способа возврата из защищённого режима в реальный после аппаратного сброса. В таблице 3 перечислены возможные значения для байта состояния отключения.

Таблица 3. Значения байта состояния отключения.

Значение Причина отключения
0 Программный сброс при нажатии комбинации клавиш CTRL-ALT-DEL или неожиданный сброс. Выполняется обычный перезапуск системы, но процедуры тестирования при включении питания не выполняются.
1 Сброс после определения объёма памяти.
2 Сброс после тестирования памяти.
3 Сброс после обнаружения ошибки в памяти (контроль чётности).
4 Сброс с запросом перезагрузки.
5 После сброса перезапускается контроллер прерываний, затем управление передаётся по адресу, который находится в области данных BIOS 0040h:0067h.
6,7,8 Сброс после выполнения теста работы процессора в защищённом режиме.
9 Сброс после выполнения пересылки блока памяти из основной памяти в расширенную.
0Ah После сброса управление немедленно передаётся по адресу, взятому из области данных BIOS 0040h:0067h.
Для обеспечения возврата в реальный режим после сброса по адресу, записанному в области данных BIOS 0040h:0067h можно использовать байты 5 и 0Ah.

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

Если же вы не используете прерывания и, соответственно, не перепрограммируете контроллер прерываний, можно использовать значение 0Ah, при этом после сброса управление будет сразу передано по адресу, взятому из области данных BIOS 0040h:0067h. В этом случае затраченное на возврат в реальный режим время будет меньше.

В следующем фрагменте программы мы записываем в ячейку CMOS-памяти с адресом 0Fh значение 5.



Напомним, что для записи числа в ячейку CMOS-памяти необходимо вначале в порт с адресом 70h записать номер нужной ячейки, а затем в порт 71h - записываемые данные.

Не удивляйтесь, что в этом фрагменте программы вместо ячейки 0Fh указано значение 8Fh - это не ошибка. Напомним, что единственный способ замаскировать немаскируемые прерывания в компьютере IBMAT - это записать в порт 70h байт, в котором старший бит установлен в 1. Поэтому наш фрагмент программы не только записывает байт состояния отключения, но и маскирует немаскируемые прерывания (!). Нам необходимо также замаскировать обычные прерывания, поэтому мы выдаём команду CLI.

cli mov al,8f out CMOS_PORT,al jmp next1 ; небольшая задержка next1: mov al,5 out CMOS_PORT+1,al Следующий шаг - открытие адресной линии A20 - необходим в том случае, если ваша программа будет обращаться к оперативной памяти, лежащей за пределами первого мегабайта.

Приведём процедуру, которую можно использовать для этой цели:

; ------------------------------------------------------------ ; Процедура открывает адресную линию A20 ; ------------------------------------------------------------ PROC enable_a20 NEAR mov al,A20_PORT out STATUS_PORT,al mov al,A20_ON out KBD_PORT_A,al ret ENDP enable_a20 Эта магическая последовательность команд выдаёт команду A20_ON клавиатурному процессору 8042, к которому подключены схемы управления адресной линией A20. После начального сброса линия A20 закрыта, и расширенная память за границами первого мегабайта недоступна.

Следующий этап - запоминание содержимого сегментных регистров, которые будут нужны при возврате в реальный режим. Это сегментные регистры SS и ES:

mov [real_ss],ss ; запоминаем указатель стека mov [real_es],es ; для реального режима На последнем перед переключением в защищённый режим этапе мы загружаем регистр GDTR адресом подготовленной заранее GDT:

lgdt [QWORD gdt_gdt] Всё! Можно переключаться в защищённый режим!


Получить адрес для использования расширений DPMI


DOS-экстендеры могут расширять сервис DPMI своими функциями. Для получения доступа к этим функциям можно использовать функцию 0A00h. Вызывающая программа должна задать в регистрах DS:(E)SI адрес строки, закрытой нулём. Строка должна содержать название производителя DOS-экстендера или другой уникальный идентификатор расширения.

Регистры на входе AX 0A00h DS:(E)SI Указатель на строку, закрытую нулём. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. ES:(E)DI Точка входа для вызова расширений

Содержимое регистров DS, FS, GS, EAX, EBX, ECX, EDX, ESI, и EBP не сохраняется.



Получить адрес интерфейса VCPI


Регистры на входе: AX 0DE01h ES:DI Адрес буфера размером в 4 килобайта для таблицы страниц. DS:SI Адрес GDT, состоящей из трёх элементов, в первый будет записан дескриптор сегмента кода, остальные два будут использованы драйвером VCPI.

Регистры на выходе: AH равен 00h - успешное выполнение функции, не равен 00h - ошибка. DI Номер первого свободного элемента в таблице страниц, которая размещена в заказанном ранее буфере. EBX Смещение в сегменте кода точки входа в защищённый режим.



Получить адрес обработчика исключения с заданным номером


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

Регистры на входе AX 0202h BL Номер исключения (00h - 1Fh). Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. CX:(E)DX Адрес обработчика исключения в формате <селектор:смещение>.

Получить адрес перехода в защищённый режим


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

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

Регистры на входе: AX 1687h

Если функция была успешно выполнена, в регистры будут записаны следующие значения:

Регистры на выходе: AX 00h BX Если установлен бит 0, данная реализация DPMI поддерживает работу с 32-разрядными программами. CL Тип процессора: 02h = 80286 03h = 80386 04h = 80486 DH Верхний (major) номер версии используемой спецификации DPMI. DL Нижний (minor) номер версии используемой спецификации DPMI. SI Количество параграфов памяти, требуемых для личной области данных сервера DPMI. Программа должна заказать эту память у операционной системы перед переходом в защищённый режим. ES:DI Адрес процедуры, которую необходимо вызвать для входа в защищённый режим.

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



Получить адрес процедуры переключения режима


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

Регистры на входе AX 0306h Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. BX:CX Адрес программы переключения из реального режима в защищённый в формате <сегмент:смещение>. SI:(E)DI Адрес программы для переключения из защищённого режима в реальный в формате <селектор:смещение>.

Перед переключением режима, которое выполняется командой JMP FAR, необходимо подготовить регистры:

AX Новое содержимое регистра DS.
CX Новое содержимое регистра ES
DX Новое содержимое регистра SS
(E)BX Новое содержимое регистра (E)SP
SI Новое содержимое регистра CS
(E)DI Новое содержимое регистра (E)IP

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

Для процессоров i80386 и i80486 после переключения режима в регситры FS и GS бует записано нулевое значение.



Получить адреса процедур сохранения/восстановления состояния.


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

Регистры на входе AX 0305h Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. AX Размер буфера для сохранения состояния. BX:CX Адрес программы сохранения/восстановления для реального режима в формате <сегмент:смещение>. ES:(E)DI Адрес программы сохранения/восстановления для защищённого режима в формате <селектор:смещение>.

Процедура сохранения/восстановления состояния вызывается командой CALL FAR и получает следующие параметры:

ES:(E)DI Указатель на буфер сохранения состояния. AL 0 - для сохранения состояния, 1 - для восстановления состояния.

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


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

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

Получить блок памяти


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

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

Получить блок памяти из пула свободной памяти DOS


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

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

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

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

Получить дескриптор


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

Регистры на входе AX 000Bh BX Селектор сегмента, для которого требуется получить дескриптор. ES:(E)DI Указатель на буфер размером 8 байт, в который будет скопирован дескриптор. Для 32-разрядных программ необходимо использовать регистр EDI. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка.

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


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



Получить информацию о свободной памяти


Регистры на входе AX 0500h ES:(E)DI Адрес бфера размером 30h байт в формате <селектор:смещение>. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. ES:(E)DI Адрес бфера размером 30h байт в формате <селектор:смещение>, заполненного информацией о свободной памяти.

Формат буфера:

Таблица 12. Формат буфера для информации о свободной памяти, получаемой средствами DPMI.

Смещение Описание
00h Размер наибольшего доступного свободного блока в байтах
04h Максимальное количество доступных незаблокированных страниц памяти.
08h Максимальное количество доступных заблокированных страниц памяти.
0Ch Размер линейного адресного пространства в страницах.
10h Общее количество незаблокированных страниц.
14h Количество свободных страниц.
18h Общее количество физических страниц.
1Ch Размер свободного линейного адресного пространства в страницах.
20h Размер страничного файла или раздела в страницах.
24h-2Fh Зарезервировано.

Если текущая реализация DPMI не поддерживает виртуальную память, данная функция заполняет только первое поле структуры. Остальные поля устанавливаются в -1 (0FFFFFFFFh).



Получить конкретный дескриптор в таблице LDT


Функция позволяет получить дескриптор по его селектору.

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

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



Регистры на входе: AX 0DE0Ah


Регистры на входе: AX 0DE0Ah Регистры на выходе: AH равен 00h - успешное выполнение функции, не равен 00h - ошибка. BX Вектор прерывания, используемый для IRQ0. CX Вектор прерывания, используемый для IRQ8.


Получить размер CLIPBOARD


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

Регистры на входе AX 1704h DX Формат данных: 01h текст; 02h графика в формате bitmap; 03h графика в формате metafile picture; 04h SYLK; 05h DIF; 06h графика в формате TIFF; 07h текст в кодировке OEM. Регистры на выходе: DX:AX 0, если CLIPBOARD не содержит данных в указанном формате; размер записанных данных, включая заголовки.

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


Функция возвращает размер страницы памяти в байтах.

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

Получить состояние флага виртуальных прерываний


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

Регистры на входе AX 0902h Регистры на выходе: CARRY 0 AL 0, если виртуальные прерывания запрещены, 1, если виртуальные прерывания разрешены.

Получить состояние отладочной точки останова


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

Регистры на входе AX 0B02h BX Индекс отладочной точки останова Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. AX Флаги: Бит 0 = 1 если произошло выполнение точки останова.

Получить страницу памяти


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

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

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



Получить вектор прерывания реального режима


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

Регистры на входе AX 0200h BL Номер прерывания. Регистры на выходе: CARRY 0 CX:DX Адрес обработчика прерывания в формате <сегмент:смещение>.

Получить вектор прерывания защищённого режима


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

Регистры на входе AX 0204h BL Номер прерывания. Регистры на выходе: CARRY 0, если функция выполнилась без ошибки, 1, если произошла ошибка. CX:(E)DX Адрес обработчика прерывания в формате <селектор:смещение>.

Получить версию спецификации DPMI


Функция позволяет получить текущую версию DPMI.

Регистры на входе AX 0400h Регистры на выходе: CARRY 0 AH Верхний (major) номер версии. AL Нижний (minor) номер версии. BX Байт флагов: бит 0 = 1 если программа работает под управлением DPMI для процессора i80386; бит 1 = 1 если процессор вернулся в реальный режим для обработки прерывания; бит 2 = 1 если в системе поддерживается виртуальная память; бит 3 и все остальные зарезервированы для использования в будущем. CL Тип процессора: 02 = i80286 03 = i80386 04 = i80486 DH Текущее значение номера прерывания для IRQ0. DL Текущее значение номера прерывания для IRQ8.

Получить версию WinOldAp


Операционная система WINDOWS содержит специальные средства, предназначенные для работы под её управлением DOS-программ. В терминологии WINDOWS DOS-программы относятся к так называемым старым приложениям WINDOWS (WINDOWS Old Application). Версию драйвера WINDOWS, поддерживающего работу с приложениями WinOldAp, можно узнать с помощью функции 1700h прерывания INT2Fh:

Регистры на входе AX 1700h Регистры на выходе: AX 1700h, если данная версия WinOldAp не поддерживает работу с CLIPBOARD. Если AX не равно 1700h, то: AL = верхнее значение весрии (major version); AH = нижнее значение версии (minor version).

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


Если вы создали несколько дескрипторов в LDT при помощи функции 0000h, то этой функцией будет возвращён только первый селектор, соответствующий первому дескриптору в созданном массиве. Для вычисления остальных селекторов ваша программа должна использовать функцию 0003h. Эта функция возвращает значение, которое вы будете прибавлять к базовому селектору для вычисления остальных селекторов в массиве.

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

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


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

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

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

Вторая ступень - получение из логического адреса 32-разрядного линейного адреса. Линейный адрес берётся из глобальной или локальной таблицы дескрипторов (GDT или LDT) в зависимости от соответствующего бита селектора (бит 2). Механизм получения линейного адреса напоминает механизм получения 24-разрядного физического адреса в процессоре i80286. Однако линейный адрес не отображается непосредственно на адресную шину памяти, то есть он не является физическим адресом.

Для получения из линейного адреса физического адреса используется третья ступень - механизм страничной адресации. С помощью этого механизма 20 старших бит линейного адреса используются для выбора блока памяти размером 4 килобайта. Такой блок называется страницей физической памяти. Оставшиеся 12 бит линейного адреса представляют собой смещение внутри страницы.

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

Рис. 17. Преобразование логического адреса в линейный.

Значение из поля индекса селектора используется в качестве индекса в таблице LDT или GDT для выборки 32-разрядного базового адреса. Этот базовый адрес складывается со второй компонентой логического адреса - смещением. В результате получается 32-разрядный линейный адрес.

Преобразование линейного адреса в физический иллюстрируется рис. 18.

Рис. 18. Преобразование линейного адреса в физический.

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


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

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

Младшие 12 бит линейного адреса указывают смещение к адресуемому байту внутри страницы.

На рис. 19 представлен формат дескриптора таблицы страниц.



Рис. 19. Дескриптор таблицы страниц.

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

Формат дескриптора страницы представлен на рис. 20.



Рис. 20. Дескриптор страницы.

Биты 12-31 в дескрипторе страниц указывают старшие 20 бит физического адреса страницы. Младшие 12 бит адреса страницы всегда равны нулю.

Назначение бит 0-11 одинаково и для дескриптора таблицы страниц, и для дескриптора страницы. В таблице 4 приведено описание этих бит.

Таблица 5. Биты 0-12 дескрипторов таблиц страниц и страниц.

Номер бита Назначение
0 (P) Бит присутствия в памяти. Установлен в 1, если определяемая данным дескриптором таблица страниц находится в оперативной памяти. Этот бит используется для организации виртуальной памяти.
1 (W) Разрешение записи. Если бит установлен в 1, то запись в страницы разрешена. Бит используется для организации защиты от записи на уровне страниц.
2 (U) Пользователь/супервизор. Используется для разграничения доступа к страницам операционной системы (страницы супервизора) и страницам программ пользователя. Значение бита, равное 0, соответствует страницам супервизора, 1 - страницам программы пользователя.
3-4 Эти биты зарезервированы и должны быть установлены в 0 для совместимости со следующими моделями процессора.
5 (A) Бит доступа. Он устанавливается процессором перед выполнением операций чтения страницы или записи в страницу.
6 (D) Бит мусора. Устанавливается, если была выполнена запись в каталог или страницу.
7-8 Эти биты зарезервированы и должны быть установлены в 0 для совместимости со следующими моделями процессора.
9-12 (AVL) Эти биты доступны для использования операционной системой (AVL - Available for use).



Мы видим, что для процессора i80386 добавилось ещё два типа таблиц, содержащих дескрипторы и, соответственно, два типа дескрипторов - дескриптор таблиц страниц и дескриптор страниц.

Для использования механизма трансляции страниц операционная система должна установить в 1 старший бит системного регистра CR0. Если этот бит не установлен в 1, физический адрес будет равен линейному, содержимое регистра адреса каталога таблиц страниц CR3 при этом для преобразования адреса использоваться не будет.

Форматы дескрипторов, располагающихся в таблицах GDT, LDT и IDT претерпели изменения по сравнению с используемыми в процессоре i80286. Например, вместо 24-битового физического адреса в дескрипторах должен находиться 32-битовый линейный адрес.

Напомним, что в дескрипторах процессора i80286 было зарезервировано два байта. Эти байты используются процессором i80386 (рис. 21).



Рис. 21. Дескрипторы для процессора i80386.

Процессор i80386 использует 32-разрядный базовый адрес сегмента и 20-разрядное поле предела. В зарезервированном для процессора i80286 поле в битах 24-31 находится старший байт 32-разрядного базового адреса сегмента. Биты 16-19 используются для хранения старших четырёх битов предела.

В процессоре i80286 поле предела указывало размер сегмента в байтах. Для процессора i80486 интерпретация поля предела зависит от установки бита G - бита гранулярности. Если бит G установлен в 1, поле предела содержит размер сегмента в страницах (размером 4096 байт). Если бит G сброшен, размер сегмента вычисляется в байтах.

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

Поле, обозначенное на рис. 21 как X, указывает разрядность выполняемых команд, принятых по умолчанию. Если этот бит установлен в 1, используются 32-разрядные команды, если сброшен в 0 - 16-разрядные.

Бит AVL предназначен для использования системным программным обеспечением.

Бит S - признак системного сегмента. Если этот бит сброшен в 0, то сегмент системный.

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


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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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



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

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


Преобразование физического адреса в линейный


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

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

Преобразование сегмента в дескриптор


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

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

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



Прерывания в реальном режиме


В главе 4 первой книги первого тома серии "Библиотека системного программиста", который называется "Операционная система MS-DOS", мы подробно рассказали об особенностях обработки прерываний в реальном режиме.

Напомним только основные моменты.

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

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

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

Таблица векторов прерываний реального режима состоит из 256 элементов по 4 байта, таким образом её размер составляет 1 килобайт. Элементы таблицы - дальние указатели на процедуры обработки прерываний. Указатели состоят из 16-битового сегментного адреса процедуры обработки прерывания и 16-битового смещения. Причём смещение хранится по младшему адресу, а сегментный адрес - по старшему.

Когда происходит программное или аппаратное прерывание, текущее содержимое регистров CS, IP а также регистра флагов FLAGS записывается в стек программы (который, в свою очередь, адресуется регистровой парой SS:SP). Далее из таблицы векторов прерываний выбираются новые значения для CS и IP, при этом управление передаётся на процедуру обработки прерывания.

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

Завершив обработку прерывания, процедура должна выдать команду IRET, по которой из стека будут извлечены значения для CS, IP, FLAGS и загружены в соответствующие регистры. Далее выполнение прерванной программы будет продолжено.

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



Прерывания защищённого режима


В защищённом режиме все прерывания разделяются на два типа - обычные прерывания и исключения (exception - исключение, особый случай).

Обычное прерывание инициируется командой INT (программное прерывание) или внешним событием (аппаратное прерывание). Перед передачей управления процедуре обработки обычного прерывания флаг разрешения прерываний IF сбрасывается и прерывания запрещаются.

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

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

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



ПРИЛОЖЕНИЕ


9.1.
9.2.
9.3.
9.4.
9.5.
9.6.
9.7.
9.8.
9.9.

Пример использования интерфейса BIOS


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

Обратите внимание на то, как в файле tos.c подготавливается таблица GDT. Адрес подготовленной таблицы передаётся функции protected_mode(), которая передаёт его функции 89h прерывания INT 15h. Вызов этой функции выполняется в файле tossyst.asm.

Листинг 16. Определение констант и структур данных Файл tos.h ----------------------------------------------------------- #define word unsigned int // Селекторы, определённые в GDT #define GDT_SELECTOR 0x08 // 1 - селктор для GDT #define IDT_SELECTOR 0x10 // 2 - селектор для IDT #define DATA_SELECTOR 0x18 // 3 - селектор для DS #define VID_MEM_SELECTOR 0x20 // 4 - селектор для ES, // будет использован для адресации видеопамяти #define SS_SELECTOR 0x28 // 5 - селектор для SS #define CODE_SELECTOR 0x30 // 6 - селектор для CS #define BIOS_SELECTOR 0x38 // 7 - селектор для адресации // области данных BIOS #define COLOR_VID_MEM 0xb8000L #define MONO_VID_MEM 0xb0000L #define MONO_MODE 0x07 #define BW_80_MODE 0x02 #define COLOR_80_MODE 0x03 typedef struct descriptor { word limit; word base_lo; unsigned char base_hi; unsigned char type_dpl; unsigned reserved; } descriptor; typedef struct gate { word offset; word selector; unsigned char count; unsigned char type_dpl; word reserved; } gate; #define DESCRIPTOR_SIZE (sizeof(descriptor)) #define GATE_SIZE (sizeof(gate)) #define IDT_SIZE (sizeof(idt)) #define TYPE_CODE_DESCR 0x18 #define TYPE_DATA_DESCR 0x10 #define TYPE_INTERRUPT_GATE 0x86 #define TYPE_TRAP_GATE 0x87 #define SEG_WRITABLE 0x02 #define SEG_READABLE 0x02 #define SEG_EXPAND_DOWN 0x04 #define SEG_ACCESSED 0x01 #define SEG_PRESENT_BIT 0x80 #define EOI 0x20 #define MASTER8259A 0x20 #define SLAVE8259A 0xA0 #define MK_LIN_ADDR(seg,off) (((unsigned long)(seg))<<4)+(word)(off) Листинг 17.
Главная программа Файл tos.c ----------------------------------------------------------- #include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include "tos.h" void Init_And_Protected_Mode_Entry(void); void protected_mode(descriptor far *gdt_ptr); void real_mode(void); void init_gdt_descriptor(descriptor *descr, unsigned long base, word limit, unsigned char type); void vi_print(unsigned int x, unsigned int y, char *s, char attr); void vi_hello_msg(void); void exception_0(void); //{ prg_abort(0); } void exception_1(void); //{ prg_abort(1); } void exception_2(void); //{ prg_abort(2); } void exception_3(void); //{ prg_abort(3); } void exception_4(void); //{ prg_abort(4); } void exception_5(void); //{ prg_abort(5); } void exception_6(void); //{ prg_abort(6); } void exception_7(void); //{ prg_abort(7); } void exception_8(void); //{ prg_abort(8); } void exception_9(void); //{ prg_abort(9); } void exception_A(void); //{ prg_abort(0xA); } void exception_B(void); //{ prg_abort(0xB); } void exception_C(void); //{ prg_abort(0xC); } void exception_D(void); //{ prg_abort(0xD); } void exception_E(void); //{ prg_abort(0xE); } void exception_F(void); //{ prg_abort(0xF); } void exception_10(void); //{ prg_abort(0x10); } void exception_11(void); //{ prg_abort(0x11); } void exception_12(void); //{ prg_abort(0x12); } void exception_13(void); //{ prg_abort(0x13); } void exception_14(void); //{ prg_abort(0x14); } void exception_15(void); //{ prg_abort(0x15); } void exception_16(void); //{ prg_abort(0x16); } void exception_17(void); //{ prg_abort(0x17); } void exception_18(void); //{ prg_abort(0x18); } void exception_19(void); //{ prg_abort(0x19); } void exception_1A(void); //{ prg_abort(0x1A); } void exception_1B(void); //{ prg_abort(0x1B); } void exception_1C(void); //{ prg_abort(0x1C); } void exception_1D(void); //{ prg_abort(0x1D); } void exception_1E(void); //{ prg_abort(0x1E); } void exception_1F(void); //{ prg_abort(0x1F); } void iret0(void); void iret1(void); descriptor gdt[8]; gate idt[] = { { (word)&exception_0, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 0 { (word)&exception_1, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1 { (word)&exception_2, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 2 { (word)&exception_3, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 3 { (word)&exception_4, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 4 { (word)&exception_5, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 5 { (word)&exception_6, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 6 { (word)&exception_7, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 7 { (word)&exception_8, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 8 { (word)&exception_9, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 9 { (word)&exception_A, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // A { (word)&exception_B, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // B { (word)&exception_C, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // C { (word)&exception_D, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // D { (word)&exception_E, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // E { (word)&exception_F, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // F { (word)&exception_10, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 10 { (word)&exception_11, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 11 { (word)&exception_12, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 12 { (word)&exception_13, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 13 { (word)&exception_14, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 14 { (word)&exception_15, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 15 { (word)&exception_16, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 16 { (word)&exception_17, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 17 { (word)&exception_18, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 18 { (word)&exception_19, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 19 { (word)&exception_1A, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1A { (word)&exception_1B, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1B { (word)&exception_1C, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1C { (word)&exception_1D, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1D { (word)&exception_1E, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1E { (word)&exception_1F, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1F { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 20 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 21 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 22 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 23 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 24 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 25 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 26 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 27 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 28 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 29 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2A { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2B { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2C { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2D { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2E { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 } // 2F }; word y=0; void main(void) { textcolor(BLACK); textbackground(LIGHTGRAY); clrscr(); Init_And_Protected_Mode_Entry(); enable_interrupt(); vi_hello_msg(); y=3; vi_print(0, y++, " Вошли в защищённый режим", 0x7f); pause(); vi_print(0, y++, " Для возврата в реальный режим нажмите любую клавишу", 0x7f); real_mode(); getch(); textcolor(WHITE); textbackground(BLACK); clrscr(); } void init_gdt_descriptor(descriptor *descr, unsigned long base, word limit, unsigned char type) { descr->base_lo = (word)base; descr->base_hi = (unsigned char)(base >> 16); descr->type_dpl = type; descr->limit = limit; descr->reserved = 0; } void Init_And_Protected_Mode_Entry(void) { union REGS r; word crt_mode; extern word gv1_; // Дескриптор, описывающий таблицу GDT init_gdt_descriptor(&gdt[1], MK_LIN_ADDR(_DS, &gdt), sizeof(gdt)-1, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); // Дескриптор, описывающий таблицу IDT init_gdt_descriptor(&gdt[2], MK_LIN_ADDR(_DS, &idt), (unsigned long)IDT_SIZE-1, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); // Дескриптор сегмента данных init_gdt_descriptor(&gdt[3], MK_LIN_ADDR(_DS, 0), 0xffffL, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); // Определяем текущий видеорежим r.h.ah=15; int86(0x10,&r,&r); crt_mode = r.h.al; // Инициализация дескриптора для видеопамяти // монохромного видеоадаптера if(crt_mode == MONO_MODE) init_gdt_descriptor(&gdt[4], MONO_VID_MEM, 3999, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); // Инициализация дескриптора для видеопамяти // цветного видеоадаптера else if(crt_mode == BW_80_MODE crt_mode == COLOR_80_MODE) init_gdt_descriptor(&gdt[4], COLOR_VID_MEM, 3999, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); else { printf("\nИзвините, этот видеорежим недопустим."); exit(-1); } // Дескриптор для сегмента стека init_gdt_descriptor(&gdt[5], MK_LIN_ADDR(_DS, 0), 0xffffL, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); // Дескриптор для сегмента кода init_gdt_descriptor(&gdt[6], MK_LIN_ADDR(_CS, 0), 0xffffL, TYPE_CODE_DESCR | SEG_PRESENT_BIT | SEG_READABLE); // Входим в защищённый режим // В качестве параметра передаём адрес подготовленной // таблицы GDT protected_mode(gdt); } void prg_abort(int err); void exception_0(void) { prg_abort(0); } void exception_1(void) { prg_abort(1); } void exception_2(void) { prg_abort(2); } void exception_3(void) { prg_abort(3); } void exception_4(void) { prg_abort(4); } void exception_5(void) { prg_abort(5); } void exception_6(void) { prg_abort(6); } void exception_7(void) { prg_abort(7); } void exception_8(void) { prg_abort(8); } void exception_9(void) { prg_abort(9); } void exception_A(void) { prg_abort(0xA); } void exception_B(void) { prg_abort(0xB); } void exception_C(void) { prg_abort(0xC); } void exception_D(void) { prg_abort(0xD); } void exception_E(void) { prg_abort(0xE); } void exception_F(void) { prg_abort(0xF); } void exception_10(void) { prg_abort(0x10); } void exception_11(void) { prg_abort(0x11); } void exception_12(void) { prg_abort(0x12); } void exception_13(void) { prg_abort(0x13); } void exception_14(void) { prg_abort(0x14); } void exception_15(void) { prg_abort(0x15); } void exception_16(void) { prg_abort(0x16); } void exception_17(void) { prg_abort(0x17); } void exception_18(void) { prg_abort(0x18); } void exception_19(void) { prg_abort(0x19); } void exception_1A(void) { prg_abort(0x1A); } void exception_1B(void) { prg_abort(0x1B); } void exception_1C(void) { prg_abort(0x1C); } void exception_1D(void) { prg_abort(0x1D); } void exception_1E(void) { prg_abort(0x1E); } void exception_1F(void) { prg_abort(0x1F); } void prg_abort(int err) { vi_print(1,y++,"---> Произошло исключение", 0xc); real_mode(); gotoxy(1,24); cprintf("Исключение %X, нажмите любую клавишу", err); getch(); textcolor(WHITE); textbackground(BLACK); clrscr(); exit(0); } void iret0(void) { asm { push ax mov al,EOI out MASTER8259A,al pop ax pop bp iret } } void iret1(void) { asm { push ax mov al,EOI out MASTER8259A,al out SLAVE8259A,al pop ax pop bp iret } } 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(VID_MEM_SELECTOR, 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, " Protected mode monitor *TINY/OS*, " "v.1.11 for CPU 80286 ¦ © Frolov A.V., 1992 ", 0x30); } Листинг 18.


Функции для перехода в защищённый режим и возврата в реальный режим. Файл tossyst.asm ----------------------------------------------------------- IDEAL MODEL SMALL RADIX 16 P286 DATASEG CMOS_PORT EQU 70 PORT_6845 EQU 63h COLOR_PORT EQU 03d4h MONO_PORT EQU 03b4h STATUS_PORT EQU 64h SHUT_DOWN EQU 0feh INT_MASK_PORT EQU 21h VIRTUAL_MODE EQU 0001 A20_PORT EQU 0d1 A20_ON EQU 0df A20_OFF EQU 0ddh EOI EQU 20 MASTER8259A EQU 20 SLAVE8259A EQU 0a0h KBD_PORT_A EQU 60h KBD_PORT_B EQU 61h gdt_off dw ? gdt_seg dw ? real_ss dw ? real_sp dw ? real_es dw ? CODESEG PUBLIC _real_mode, _protected_mode PUBLIC _enable_interrupt PUBLIC _pause PROC _protected_mode NEAR push bp mov bp,sp mov ax,[bp+4] mov dx,[bp+6] mov [gdt_seg], dx mov [gdt_off], ax push ds mov ax,40 mov ds,ax mov [WORD 67],OFFSET shutdown_return mov [WORD 69],cs pop ds cli in al, INT_MASK_PORT and al, 0ffh out INT_MASK_PORT, al mov al,8f out CMOS_PORT,al jmp delay1 delay1: mov al,5 out CMOS_PORT+1,al mov [real_ss],ss mov [real_es],es ; Загружаем регистры ES:SI адресом GDT, полученным ; как параметр функции protected_mode() mov es, [gdt_seg] mov si, [gdt_off] ; Подготавливаем номера прерываний IRQ0 и IRQ8 ; для перепрограммирования контроллеров прерываний. mov bx, 2028h ; Устанавливаем защищённый режим работы mov ax, 8900h int 15h jnc pok ; Если произошла ошибка, мы остались в реальном режиме, ; завершаем работу программы. mov ah, 4ch int 21h ; Установлен защищённый режим работы процессора ! pok: pop bp ret ENDP _protected_mode PROC _real_mode NEAR mov [real_sp], sp mov al, SHUT_DOWN out STATUS_PORT, al waitr1: hlt jmp waitr1 LABEL shutdown_return FAR mov ax, DGROUP mov ds, ax assume ds:DGROUP cli mov ss,[real_ss] mov sp,[real_sp] in al, INT_MASK_PORT and al, 0 out INT_MASK_PORT, al mov ax, DGROUP mov ds, ax mov ss, ax mov es, ax mov ax,000dh out CMOS_PORT,al sti ret ENDP _real_mode PROC _pause NEAR push cx mov cx,10 ploop0: push cx xor cx,cx ploop1: loop ploop1 pop cx loop ploop0 pop cx ret ENDP _pause PROC _enable_interrupt NEAR sti in al, INT_MASK_PORT and al, 0fch out INT_MASK_PORT, al ret ENDP _enable_interrupt end

Пример мультизадачного монитора


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

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

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

Листинг 4. Определение констант и структур для модулей, составленных на языке ассемблера. Файл tos.inc ----------------------------------------------------------- CMOS_PORT equ 70h PORT_6845 equ 63h COLOR_PORT equ 3d4h MONO_PORT equ 3b4h STATUS_PORT equ 64h SHUT_DOWN equ 0feh INT_MASK_PORT equ 21h VIRTUAL_MODE equ 0001 A20_PORT equ 0d1h A20_ON equ 0dfh A20_OFF equ 0ddh EOI equ 20h MASTER8259A equ 20h SLAVE8259A equ 0a0h KBD_PORT_A equ 60h KBD_PORT_B equ 61h L_SHIFT equ 0000000000000001b NL_SHIFT equ 1111111111111110b R_SHIFT equ 0000000000000010b NR_SHIFT equ 1111111111111101b L_CTRL equ 0000000000000100b NL_CTRL equ 1111111111111011b R_CTRL equ 0000000000001000b NR_CTRL equ 1111111111110111b L_ALT equ 0000000000010000b NL_ALT equ 1111111111101111b R_ALT equ 0000000000100000b NR_ALT equ 1111111111011111b CAPS_LOCK equ 0000000001000000b SCR_LOCK equ 0000000010000000b NUM_LOCK equ 0000000100000000b INSERT equ 0000001000000000b STRUC idtr_struc idt_len dw 0 idt_low dw 0 idt_hi db 0 rsrv db 0 ENDS idtr_struc Листинг 5. Определение констант и структур для модулей, составленных на языке Си. Файл tos.h ----------------------------------------------------------- #define word unsigned int // Селекторы, определённые в GDT #define CODE_SELECTOR 0x08 // сегмент кода #define DATA_SELECTOR 0x10 // сегмент данных #define TASK_1_SELECTOR 0x18 // задача TASK_1 #define TASK_2_SELECTOR 0x20 // задача TASK_2 #define MAIN_TASK_SELECTOR 0x28 // главная задача #define VID_MEM_SELECTOR 0x30 // сегмент видеопамяти #define IDT_SELECTOR 0x38 // талица IDT #define KEYBIN_TASK_SELECTOR 0x40 // задача ввода с клавиатуры #define KEYB_TASK_SELECTOR 0x48 // задача обработки // клавиатурного прерывания #define FLIP_TASK_SELECTOR 0x50 // задача FLIP_TASK // Байт доступа 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 descriptor { word limit; word base_lo; unsigned char base_hi; unsigned char type_dpl; unsigned reserved; } descriptor; // Структура вентиля вызова, задачи, прерывания, // исключения typedef struct gate { word offset; word selector; unsigned char count; unsigned char type_dpl; word reserved; } gate; // Структура сегмента состояния задачи TSS typedef struct tss { word link; // поле обратной связи word sp0; // указатель стека кольца 0 word ss0; word sp1; // указатель стека кольца 1 word ss1; word sp2; // указатель стека кольца 1 word ss2; word ip; // регистры процессора word flags; word ax; word cx; word dx; word bx; word sp; word bp; word si; word di; word es; word cs; word ss; word ds; word ldtr; } tss; // Размеры сегментов и структур #define TSS_SIZE (sizeof(tss)) #define DESCRIPTOR_SIZE (sizeof(descriptor)) #define GATE_SIZE (sizeof(gate)) #define IDT_SIZE (sizeof(idt)) // Физические адреса видеопамяти для цветного // и монохромного видеоадаптеров #define COLOR_VID_MEM 0xb8000L #define MONO_VID_MEM 0xb0000L // Видеоржеимы #define MONO_MODE 0x07 // монохромный #define BW_80_MODE 0x02 // монохромный, 80 символов #define COLOR_80_MODE 0x03 // цветной, 80 символов // Значения для поля доступа #define TYPE_CODE_DESCR 0x18 #define TYPE_DATA_DESCR 0x10 #define TYPE_TSS_DESCR 0x01 #define TYPE_CALL_GATE 0x04 #define TYPE_TASK_GATE 0x85 #define TYPE_INTERRUPT_GATE 0x86 #define TYPE_TRAP_GATE 0x87 #define SEG_WRITABLE 0x02 #define SEG_READABLE 0x02 #define SEG_PRESENT_BIT 0x80 // Константы для обработки аппаратных // прерываний #define EOI 0x20 #define MASTER8259A 0x20 #define SLAVE8259A 0xa0 // Макро для формирования физического // адреса из компонент сегменоного адреса // и смещения #define MK_LIN_ADDR(seg,off) (((unsigned long)(seg))<<4)+(word)(off) // Тип указателя на функцию типа void без параметров typedef void (func_ptr)(void);

Файл tos.c (листинг 6) содержит основную программу, которая инициализирует процессор для работы в защищённом режиме и запускает все задачи.
С помощью функции с названием Init_And_Protected_Mode_Entry() мы попадаем в защищённый режим и выводим сообщение на экран о том, что в главной задаче установлен защищённый режим. Регистр TR загружается селектором главной задачи при помощи функции load_task_register().
Сразу после этого программа переключается на выполнение задачи TASK_1. Эта задача просто выводит сообщение о своём запуске на экран и возвращает управление главной задаче. Цель этой процедуры - продемонстрировать процесс переключения задач с помощью команды JMP.
После возврата в главную задачу программа размаскирует прерывания от клавиатуры и таймера. Как только начинают поступать и обрабатываться прерывания таймера, оживают остальные задачи, определённые при инициализации системы.
Начиная с этого момента главная задача разделяет процессорное время наравне с остальными задачами. Что же она делает?
Главная задача сбрасывает семафор с номером 0, вслед за чем ожидает его установку. Этот семафор устанавливается задачей, которая занимается вводом символов с клавиатуры и выводит на экран скан-коды клавиш, а также состояние переключающих клавиш. Как только окажется нажатой клавиша ESC, задача ввода символов с клавиатуры устанавливает семафор 0, что и приводит к завершению работы главной задачи.
Перед тем, как завершить работу, главная задача устанавливает реальный режим работы процессора, стирает экран и возвращает управление операционной системе.
Листинг 6. Программа мультизадачного монитора. Файл tos.c ----------------------------------------------------------- #include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include "tos.h" // -------------------------------- // Определения вызываемых функций // -------------------------------- void Init_And_Protected_Mode_Entry(void); void protected_mode(unsigned long gdt_ptr, unsigned int gdt_size, word cseg, word dseg); word load_task_register(word tss_selector); void real_mode(void); void jump_to_task(word tss_selector); void load_idtr(unsigned long idt_ptr, word idt_size); void Keyb_int(void); void Timer_int(void); void Int_30h_Entry(void); extern word kb_getch(void); void enable_interrupt(void); void task1(void); void task2(void); void flipflop_task(void); void keyb_task(void); void init_tss(tss *t, word cs, word ds, unsigned char *sp, func_ptr ip); void init_gdt_descriptor(descriptor *descr, unsigned long base, word limit, unsigned char type); void exception_0(void); //{ prg_abort(0); } void exception_1(void); //{ prg_abort(1); } void exception_2(void); //{ prg_abort(2); } void exception_3(void); //{ prg_abort(3); } void exception_4(void); //{ prg_abort(4); } void exception_5(void); //{ prg_abort(5); } void exception_6(void); //{ prg_abort(6); } void exception_7(void); //{ prg_abort(7); } void exception_8(void); //{ prg_abort(8); } void exception_9(void); //{ prg_abort(9); } void exception_A(void); //{ prg_abort(0xA); } void exception_B(void); //{ prg_abort(0xB); } void exception_C(void); //{ prg_abort(0xC); } void exception_D(void); //{ prg_abort(0xD); } void exception_E(void); //{ prg_abort(0xE); } void exception_F(void); //{ prg_abort(0xF); } void exception_10(void); //{ prg_abort(0x10); } void exception_11(void); //{ prg_abort(0x11); } void exception_12(void); //{ prg_abort(0x12); } void exception_13(void); //{ prg_abort(0x13); } void exception_14(void); //{ prg_abort(0x14); } void exception_15(void); //{ prg_abort(0x15); } void exception_16(void); //{ prg_abort(0x16); } void exception_17(void); //{ prg_abort(0x17); } void exception_18(void); //{ prg_abort(0x18); } void exception_19(void); //{ prg_abort(0x19); } void exception_1A(void); //{ prg_abort(0x1A); } void exception_1B(void); //{ prg_abort(0x1B); } void exception_1C(void); //{ prg_abort(0x1C); } void exception_1D(void); //{ prg_abort(0x1D); } void exception_1E(void); //{ prg_abort(0x1E); } void exception_1F(void); //{ prg_abort(0x1F); } void iret0(void); void iret1(void); // -------------------------------------- // Глобальная таблица дескрипторов GDT // -------------------------------------- descriptor gdt[11]; // -------------------------------------- // Дескрипторная таблица прерываний IDT // -------------------------------------- gate idt[] = { // Обработчики исключений { (word)&exception_0, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 0 { (word)&exception_1, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1 { (word)&exception_2, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 2 { (word)&exception_3, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 3 { (word)&exception_4, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 4 { (word)&exception_5, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 5 { (word)&exception_6, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 6 { (word)&exception_7, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 7 { (word)&exception_8, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 8 { (word)&exception_9, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 9 { (word)&exception_A, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // A { (word)&exception_B, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // B { (word)&exception_C, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // C { (word)&exception_D, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // D { (word)&exception_E, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // E { (word)&exception_F, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // F { (word)&exception_10, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 10 { (word)&exception_11, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 11 { (word)&exception_12, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 12 { (word)&exception_13, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 13 { (word)&exception_14, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 14 { (word)&exception_15, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 15 { (word)&exception_16, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 16 { (word)&exception_17, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 17 { (word)&exception_18, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 18 { (word)&exception_19, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 19 { (word)&exception_1A, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1A { (word)&exception_1B, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1B { (word)&exception_1C, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1C { (word)&exception_1D, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1D { (word)&exception_1E, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1E { (word)&exception_1F, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1F // Обработчик прерываний таймера { (word)&Timer_int, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 20 // Вентиль задачи, запускающейся по прерыванию от клавиатуры { 0, KEYB_TASK_SELECTOR, 0, TYPE_TASK_GATE, 0 }, // 21 // Заглушки для остальных аппаратных прерываний { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 22 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 23 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 24 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 25 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 26 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 27 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 28 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 29 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2A { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2B { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2C { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2D { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2E { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2F // Обработчик для программного прерывания, которое // используется для ввода с клавиатуры { (word)&Int_30h_Entry, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 30 // Вентиль задачи FLIP_TASK { 0, FLIP_TASK_SELECTOR, 0, TYPE_TASK_GATE, 0 } // 31 }; // ------------------------------------------- // Сегменты TSS для различных задач // ------------------------------------------- tss main_tss; // TSS главной задачи tss task_1_tss; // TSS задачи TASK_1 tss task_2_tss; // TSS задачи TASK_2 tss keyb_task_tss; // TSS задач обслуживания tss keyb_tss; // клавиатуры tss flipflop_tss; // TSS задачи FLIP_TASK // ------------------------------------------- // Стеки для задач // ------------------------------------------- unsigned char task_1_stack[1024]; unsigned char task_2_stack[1024]; unsigned char keyb_task_stack[1024]; unsigned char keyb_stack[1024]; unsigned char flipflop_stack[1024]; word y=0; // номер текущей строки для вывода на экран // ------------------------------------------- // Начало программы // ------------------------------------------- void main(void) { // Стираем экран textcolor(BLACK); textbackground(LIGHTGRAY); clrscr(); // Входим в защищённый режим Init_And_Protected_Mode_Entry(); // Выводим сообщение vi_hello_msg(); y=3; vi_print(0, y++, " Установлен защищённый режим в главной задаче", 0x7f); // Загружаем регистр TR селектором главной задачи // т.е.


задачи main() load_task_register(MAIN_TASK_SELECTOR); // Переключаемся на задачу TASK_1 jump_to_task(TASK_1_SELECTOR); // После возврата в главную задачу выдаём сообщение vi_print(0, y++ ," Вернулись в главную задачу", 0x7f); y++; // Запускаем планировщик задач vi_print(0, y++ ," Запущен планировщик задач", 0x70); enable_interrupt(); // разрешаем прерывание таймера // Ожидаем установки семафора с номером 0. После того, // как этот семафор окажется установлен, возвращаемся // в реальный режим. // Семафор 0 устанавливается задачей, обрабатывающей ввод с // клавиатуры, которая работает независимо от // главной задаче. vi_print(0, y++ ," Для возврата в реальный режим нажмите ESC", 0x70); sem_clear(0); // сброс семафора 0 sem_wait(0); // ожидание установки семафора 0 // Возврат в реальный режим, стирание экрана и // передача управления MS-DOS real_mode(); textcolor(WHITE); textbackground(BLACK); clrscr(); } // ----------------------------------- // Функция инициализации сегмента TSS // ----------------------------------- void init_tss(tss *t, word cs, word ds, unsigned char *sp, func_ptr ip) { t->cs = cs; // селектор сегмента кода t->ds = ds; // поля ds, es, ss устанавливаем t->es = ds; // на сегмент данных t->ss = ds; t->ip = (word)ip; // указатель команд t->sp = (word)sp; // смещение стека t->bp = (word)sp; } // ------------------------------------------------- // Функция инициализации дескриптора в таблице GDT // ------------------------------------------------- void init_gdt_descriptor(descriptor *descr, unsigned long base, word limit, unsigned char type) { // Младшее слово базового адреса descr->base_lo = (word)base; // Старший байт базового адреса descr->base_hi = (unsigned char)(base >> 16); // Поле доступа дескриптора descr->type_dpl = type; // Предел descr->limit = limit; // Зарезервированное поле, должно быть // сброшено в 0 descr->reserved = 0; } // ----------------------------------------------- // Инициализация всех таблиц и вход // в защищённый режим // ----------------------------------------------- void Init_And_Protected_Mode_Entry(void) { union REGS r; // Инициализируем таблицу GDT, элементы с 1 по 5 init_gdt_descriptor(&gdt[1], MK_LIN_ADDR(_CS, 0), 0xffffL, TYPE_CODE_DESCR | SEG_PRESENT_BIT | SEG_READABLE); init_gdt_descriptor(&gdt[2], MK_LIN_ADDR(_DS, 0), 0xffffL, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); init_gdt_descriptor(&gdt[3], MK_LIN_ADDR(_DS, &task_1_tss), (unsigned long)TSS_SIZE-1, TYPE_TSS_DESCR | SEG_PRESENT_BIT); init_gdt_descriptor(&gdt[4], MK_LIN_ADDR(_DS, &task_2_tss), (unsigned long)TSS_SIZE-1, TYPE_TSS_DESCR | SEG_PRESENT_BIT); init_gdt_descriptor(&gdt[5], MK_LIN_ADDR(_DS, &main_tss), (unsigned long)TSS_SIZE-1, TYPE_TSS_DESCR | SEG_PRESENT_BIT); // Инициализируем TSS для задач TASK_1, TASK_2 init_tss(&task_1_tss, CODE_SELECTOR, DATA_SELECTOR, task_1_stack+ sizeof(task_1_stack), task1); init_tss(&task_2_tss, CODE_SELECTOR, DATA_SELECTOR, task_2_stack+ sizeof(task_2_stack), task2); // Инициализируем элемент 6 таблицы GDT - // дескриптор для сегмента видеопамяти // Определяем видеорежим r.h.ah=15; int86(0x10,&r,&r); // Инициализация для монохромного режима if(r.h.al==MONO_MODE) init_gdt_descriptor(&gdt[6], MONO_VID_MEM, 3999, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); // Инициализация для цветного режима else if(r.h.al == BW_80_MODE r.h.al == COLOR_80_MODE) init_gdt_descriptor(&gdt[6], COLOR_VID_MEM, 3999, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); else { printf("\nИзвините, этот видеорежим недопустим."); exit(-1); } // Инициализация элементов 7 и 8 таблицы GDT init_gdt_descriptor(&gdt[7], MK_LIN_ADDR(_DS, &idt), (unsigned long)IDT_SIZE-1, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); init_gdt_descriptor(&gdt[8], MK_LIN_ADDR(_DS, &keyb_task_tss), (unsigned long)TSS_SIZE-1, TYPE_TSS_DESCR | SEG_PRESENT_BIT); // Инициализация TSS для задачи KEYB_TASK init_tss(&keyb_task_tss, CODE_SELECTOR, DATA_SELECTOR, keyb_task_stack + sizeof(keyb_task_stack), keyb_task); // Инициализация элемента 9 таблицы GDT init_gdt_descriptor(&gdt[9], MK_LIN_ADDR(_DS, &keyb_tss), (unsigned long)TSS_SIZE-1, TYPE_TSS_DESCR | SEG_PRESENT_BIT); // Инициализация TSS для задачи KEYB обработки ввода с клавиатуры init_tss(&keyb_tss, CODE_SELECTOR, DATA_SELECTOR, keyb_stack + sizeof(keyb_stack), Keyb_int); // Инициализация элемента 10 таблицы GDT init_gdt_descriptor(&gdt[10], MK_LIN_ADDR(_DS, &flipflop_tss), (unsigned long)TSS_SIZE-1, TYPE_TSS_DESCR | SEG_PRESENT_BIT); // Инициализация TSS для задачи FLIP_TASK init_tss(&flipflop_tss, CODE_SELECTOR, DATA_SELECTOR, flipflop_stack + sizeof(flipflop_stack), flipflop_task); // Загрузка регистра IDTR load_idtr(MK_LIN_ADDR(_DS, &idt), IDT_SIZE); // Вход в защищённый режим protected_mode(MK_LIN_ADDR(_DS, &gdt), sizeof(gdt), CODE_SELECTOR, DATA_SELECTOR); } Файл tasks.c содержит тексты программ, которые будут работать в режиме разделения времени (кроме задачи TASK_1, эта задача запускается только один раз).


Задача TASK_1 (процедура task1) выдаёт сообщение о своём запуске и передаёт управление главной задаче.
Задача TASK_2 (процедура task2) попеременно выводит на экран строки "FLIP" и "FLOP", переключая попутно семафор с номером 1.
Задача FLIP_TASK (процедура flipflop_task) также попеременно выводит на экран строки "FLIP" и "FLOP", но только тогда, когда семафор с номером 1 установлен. Таким образом, задача TASK_2 управляет работой задачи FLIP_TASK.
Задача KEYB_TASK (процедура keyb_task) вводит символы с клавиатуры и выводит скан-коды нажатых клавиш, а также состояние переключающих клавиш. Как только оказывается нажатой клавиша ESC, задача устанавливает семафор с номером 0, что приводит к завершению работы главной задачи (ожидающей установки этого семафора).
Листинг 7. Задачи, которые будут работать параллельно. Файл tasks.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" word dispatcher(void); // Номер текущей строки для вывода на экран extern unsigned int y; // Задача TASK_1 void task1(void) { while(1){ vi_print(0,y++," Запущена задача TASK_1, " "переходим к главной задаче", 0x70); jump_to_task(MAIN_TASK_SELECTOR); // После повторного запуска этой задачи // снова входим в цикл. } } // Задача TASK_2 word flipflop1 = 0; long delay_cnt1 = 0l; void task2(void) { while(1){ // Периодически выводим на экран строки // FLIP/FLOP, каждый раз переключая // семафор номер 1. Этот семафор однозначно // соответствует выведенной на экран строке. asm sti if(delay_cnt1 > 150000l ) { asm cli if(flipflop1) { vi_print(73,3," FLIP ", 0x4f); sem_clear(1); } else { vi_print(73,3," FLOP ", 0x1f); sem_set(1); } flipflop1 ^= 1; delay_cnt1 = 0l; asm sti } delay_cnt1++; } } word flipflop = 0; long delay_cnt = 0l; void flipflop_task(void) { // Эта задача также периодически выводит на экран // строки FLIP/FLOP, но выводит строкой выше и // с меньшим периодом.


Кроме того, эта задача // работает только тогда, когда установлен // семафор номер 1. while(1){ asm sti if(delay_cnt > 20000l ) { sem_wait(1); // ожидаем установки семафора asm cli if(flipflop) vi_print(73,2," FLIP ", 0x20); else vi_print(73,2," FLOP ", 0x20); flipflop ^= 1; delay_cnt = 0l; asm sti } delay_cnt++; } } word keyb_code; extern word keyb_status; void keyb_task(void) { // Эта задача вводит символы с клавиатуры // и отображает скан-коды нажатых клавиш // и состояние переключающих клавиш на экране. // Если нажимается клавиша ESC, задача // устанавливает семафор номер 0. // Работающая параллельно главная задача // ожидает установку этого семафора. Как только // семафор 0 окажется установлен, главная задача // завершает свою работу и программа возвращает // процессор в реальный режим, затем передаёт // управление MS-DOS. vi_print(60, 5, " Key code: .... ", 0x20); vi_print(60, 6, " Key status: .... ", 0x20); while(1){ keyb_code = kb_getch(); vi_put_word(73, 5, keyb_code, 0x4f); vi_put_word(73, 6, keyb_status, 0x4f); if((keyb_code & 0x00ff) == 1) sem_set(0); } } Файл semaphor.c содержит исходные тексты процедур сброса семафора, установки семафора и ожидания семафора.
В массиве semaphore[5] определено пять семафоров. Разумеется, что когда вы будете экспериментировать с программой, вы можете изменить количество доступных семафоров.
Листинг 8. Процедуры для работы с семафорами. Файл semaphor.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" // Массив из пяти семафоров word semaphore[5]; // Процедура сброса семафора. // Параметр sem - номер сбрасываемого семафора void sem_clear(int sem) { asm cli semaphore[sem] = 0; asm sti } // Процедура установки семафора // Параметр sem - номер устанавливаемого семафора void sem_set(int sem) { asm cli semaphore[sem] = 1; asm sti } // Ожидание установки семафора // Параметр sem - номер ожидаемого семафора void sem_wait(int sem) { while(1) { asm cli if(semaphore[sem]) break; // проверяем семафор asm sti // ожидаем установки семафора asm nop asm nop } asm sti } Файл timer.c содержит обработчик аппаратного прерывания таймера, который периодически выдаёт звуковой сигнал и инициирует работу диспетчера задач.


Диспетчер задач циклически перебирает селекторы TSS задач, участвующих в процессе разделения времени, возвращая селектор той задачи, которая должна стать активной. В самом конце обработки аппаратного прерывания таймера происходит переключение именно на эту задачу.
Листинг 9. Процедуры для работы с таймером и диспетчер задач. Файл timer.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" // ------------------------------------------- // Модуль обслуживания таймера // ------------------------------------------- #define EOI 0x20 #define MASTER8259A 0x20 extern void beep(void); extern void flipflop_task(void); void Timer_int(void); word dispatcher(void); word timer_cnt; // ------------------------------------------ // Обработчик аппаратного прерывания таймера // ------------------------------------------ void Timer_int(void) { asm pop bp // Периодически выдаём звуковой сигнал timer_cnt += 1; if((timer_cnt & 0xf) == 0xf) { beep(); } // Выдаём в контроллер команду конца // прерывания asm mov al,EOI asm out MASTER8259A,al // Переключаемся на следующую задачу, // селектор TSS которой получаем от // диспетчера задач dispatcher() jump_to_task(dispatcher()); asm iret } // -------------------------------------- // Диспетчер задач // -------------------------------------- // Массив селекторов, указывающих на TSS // задач, участвующих в параллельной работе, // т.е. диспетчеризуемых задач word task_list[] = { MAIN_TASK_SELECTOR, FLIP_TASK_SELECTOR, KEYBIN_TASK_SELECTOR, TASK_2_SELECTOR }; word current_task = 0; // текущая задача word max_task = 3; // количество задач - 1 // Используем простейший алгоритм диспетчеризации - // выполняем последовательное переключение на все // задачи, селекторы TSS которых находятся // в массиве task_list[]. word dispatcher(void) { if(current_task < max_task) current_task++; else current_task = 0; return(task_list[current_task]); } Для сокращения объёма и без того сложной программы мы не стали делать функционально полную обработку исключений, ограничившись простым аварийным завершением работы программы с выдачей номера исключения.


Исходные тексты обработчиков исключений находятся в файле except.c.
Листинг 10. Обработка исключений. Файл except.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" void prg_abort(int err); // Номер текущей строки для вывода на экран extern unsigned int y; // Обработчики исключений void exception_0(void) { prg_abort(0); } void exception_1(void) { prg_abort(1); } void exception_2(void) { prg_abort(2); } void exception_3(void) { prg_abort(3); } void exception_4(void) { prg_abort(4); } void exception_5(void) { prg_abort(5); } void exception_6(void) { prg_abort(6); } void exception_7(void) { prg_abort(7); } void exception_8(void) { prg_abort(8); } void exception_9(void) { prg_abort(9); } void exception_A(void) { prg_abort(0xA); } void exception_B(void) { prg_abort(0xB); } void exception_C(void) { prg_abort(0xC); } void exception_D(void) { prg_abort(0xD); } void exception_E(void) { prg_abort(0xE); } void exception_F(void) { prg_abort(0xF); } void exception_10(void) { prg_abort(0x10); } void exception_11(void) { prg_abort(0x11); } void exception_12(void) { prg_abort(0x12); } void exception_13(void) { prg_abort(0x13); } void exception_14(void) { prg_abort(0x14); } void exception_15(void) { prg_abort(0x15); } void exception_16(void) { prg_abort(0x16); } void exception_17(void) { prg_abort(0x17); } void exception_18(void) { prg_abort(0x18); } void exception_19(void) { prg_abort(0x19); } void exception_1A(void) { prg_abort(0x1A); } void exception_1B(void) { prg_abort(0x1B); } void exception_1C(void) { prg_abort(0x1C); } void exception_1D(void) { prg_abort(0x1D); } void exception_1E(void) { prg_abort(0x1E); } void exception_1F(void) { prg_abort(0x1F); } // ------------------------------ // Аварийный выход из программы // ------------------------------ void prg_abort(int err) { vi_print(1,y++,"!!! ---> Произошло исключение", 0xc); real_mode(); // Возвращаемся в реальный режим // В реальном режиме выводим сообщение об исключении gotoxy(1, ++y); cprintf(" Исключение %X, нажмите любую клавишу", err); getch(); textcolor(WHITE); textbackground(BLACK); clrscr(); exit(0); } В файле intproc.c расположены заглушки для тех аппаратных прерываний, обработка которых сводится к простой посылке кода конца прерывания в соответствующий контроллер прерывания.


Листинг 11. Заглушки для аппаратных прерываний. Файл intproc.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" // Заглушки для необрабатываемых // аппаратных прерываний. void iret0(void) { // первый контроллер прерываний asm { push ax mov al,EOI out MASTER8259A,al pop ax pop bp iret } } void iret1(void) { // второй контроллер прерываний asm { push ax mov al,EOI out MASTER8259A,al out SLAVE8259A,al pop ax pop bp iret } } Файл keyb.c содержит простой интерфейс для вызова программного прерывания int 30h, обеспечивающего ввод с клавиатуры.
Листинг 12. Ввод символа с клавиатуры. Файл keyb.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" extern word key_code; // Функция, ожидающая нажатия любой // клавиши и возвращающая её скан-код unsigned int kb_getch(void) { asm int 30h return(key_code); } Обработчик аппаратного прерывания клавиатуры мы взяли практически без изменений из программы, представленной в предыдущей главе. Исходные тексты находятся в файле keyboard.asm.
Листинг 13. Процедуры для работы с клавиатурой. Файл keyboard.asm ----------------------------------------------------------- IDEAL MODEL SMALL RADIX 16 P286 include "tos.inc" ; ------------------------------------------ ; Модуль обслуживания клавиатуры ; ------------------------------------------ PUBLIC _Keyb_int, _Int_30h_Entry, _key_code, _keyb_status EXTRN _beep:PROC DATASEG _key_flag db 0 _key_code dw 0 ext_scan db 0 _keyb_status dw 0 CODESEG PROC _Keyb_int NEAR cli call _beep push ax mov al, [ext_scan] cmp al, 0 jz normal_scan1 cmp al, 0e1h jz pause_key in al, 60h cmp al, 2ah jz intkeyb_exit_1 cmp al, 0aah jz intkeyb_exit_1 mov ah, [ext_scan] call Keyb_PutQ mov al, 0 mov [ext_scan], al jmp intkeyb_exit pause_key: in al, 60h cmp al, 0c5h jz pause_key1 cmp al, 45h jz pause_key1 jmp intkeyb_exit pause_key1: mov ah, [ext_scan] call Keyb_PutQ mov al, 0 mov [ext_scan], al jmp intkeyb_exit normal_scan1: in al, 60h cmp al, 0feh jz intkeyb_exit cmp al, 0e1h jz ext_key cmp al, 0e0h jnz normal_scan ext_key: mov [ext_scan], al jmp intkeyb_exit intkeyb_exit_1: mov al, 0 mov [ext_scan], al jmp intkeyb_exit normal_scan: mov ah, 0 call Keyb_PutQ intkeyb_exit: in al, 61h mov ah, al or al, 80h out 61h, al xchg ah, al out 61h, al mov al,EOI out MASTER8259A,al pop ax sti iret jmp _Keyb_int ENDP _Keyb_int PROC Keyb_PutQ NEAR push ax cmp ax, 002ah ; L_SHIFT down jnz @@kb1 mov ax, [_keyb_status] or ax, L_SHIFT mov [_keyb_status], ax jmp keyb_putq_exit @@kb1: cmp ax, 00aah ; L_SHIFT up jnz @@kb2 mov ax, [_keyb_status] and ax, NL_SHIFT mov [_keyb_status], ax jmp keyb_putq_exit @@kb2: cmp ax, 0036h ; R_SHIFT down jnz @@kb3 mov ax, [_keyb_status] or ax, R_SHIFT mov [_keyb_status], ax jmp keyb_putq_exit @@kb3: cmp ax, 00b6h ; R_SHIFT up jnz @@kb4 mov ax, [_keyb_status] and ax, NR_SHIFT mov [_keyb_status], ax jmp keyb_putq_exit @@kb4: cmp ax, 001dh ; L_CTRL down jnz @@kb5 mov ax, [_keyb_status] or ax, L_CTRL mov [_keyb_status], ax jmp keyb_putq_exit @@kb5: cmp ax, 009dh ; L_CTRL up jnz @@kb6 mov ax, [_keyb_status] and ax, NL_CTRL mov [_keyb_status], ax jmp keyb_putq_exit @@kb6: cmp ax, 0e01dh ; R_CTRL down jnz @@kb7 mov ax, [_keyb_status] or ax, R_CTRL mov [_keyb_status], ax jmp keyb_putq_exit @@kb7: cmp ax, 0e09dh ; R_CTRL up jnz @@kb8 mov ax, [_keyb_status] and ax, NR_CTRL mov [_keyb_status], ax jmp keyb_putq_exit @@kb8: cmp ax, 0038h ; L_ALT down jnz @@kb9 mov ax, [_keyb_status] or ax, L_ALT mov [_keyb_status], ax jmp keyb_putq_exit @@kb9: cmp ax, 00b8h ; L_ALT up jnz @@kb10 mov ax, [_keyb_status] and ax, NL_ALT mov [_keyb_status], ax jmp keyb_putq_exit @@kb10: cmp ax, 0e038h ; R_ALT down jnz @@kb11 mov ax, [_keyb_status] or ax, R_ALT mov [_keyb_status], ax jmp keyb_putq_exit @@kb11: cmp ax, 0e0b8h ; R_ALT up jnz @@kb12 mov ax, [_keyb_status] and ax, NR_ALT mov [_keyb_status], ax jmp keyb_putq_exit @@kb12: cmp ax, 003ah ; CAPS_LOCK up jnz @@kb13 mov ax, [_keyb_status] xor ax, CAPS_LOCK mov [_keyb_status], ax jmp keyb_putq_exit @@kb13: cmp ax, 00bah ; CAPS_LOCK down jnz @@kb14 jmp keyb_putq_exit @@kb14: cmp ax, 0046h ; SCR_LOCK up jnz @@kb15 mov ax, [_keyb_status] xor ax, SCR_LOCK mov [_keyb_status], ax jmp keyb_putq_exit @@kb15: cmp ax, 00c6h ; SCR_LOCK down jnz @@kb16 jmp keyb_putq_exit @@kb16: cmp ax, 0045h ; NUM_LOCK up jnz @@kb17 mov ax, [_keyb_status] xor ax, NUM_LOCK mov [_keyb_status], ax jmp keyb_putq_exit @@kb17: cmp ax, 00c5h ; NUM_LOCK down jnz @@kb18 jmp keyb_putq_exit @@kb18: cmp ax, 0e052h ; INSERT up jnz @@kb19 mov ax, [_keyb_status] xor ax, INSERT mov [_keyb_status], ax jmp keyb_putq_exit @@kb19: cmp ax, 0e0d2h ; INSERT down jnz @@kb20 jmp keyb_putq_exit @@kb20: test ax, 0080h jnz keyb_putq_exit mov [_key_code], ax mov al, 0ffh mov [_key_flag], al keyb_putq_exit: pop ax ret ENDP Keyb_PutQ ; Обработчик программного прерывания ; для ввода с клавиатуры.


По своим функциям ; напоминает прерывание INT 16 реального ; режима. PROC _Int_30h_Entry NEAR push ax dx ; Ожидаем прерывание от клавиатуры keyb_int_wait: sti nop nop cli ; Проверяем флаг, который устанавливается ; обработчиком аппаратного прерывания клавиатуры mov al, [_key_flag] cmp al, 0 jz keyb_int_wait ; Сбрасываем флаг после прихода прерывания mov al, 0 mov [_key_flag], al sti pop dx ax iret ENDP _Int_30h_Entry END Файл screen.c содержит процедуры, необходимые для вывода информации на экран дисплея. Работа этих процедур основана на непосредственной записи данных в видеопамять.
Листинг 14. Процедуры для работы с видеоадаптером. Файл screen.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" void vi_putch(unsigned int x, unsigned int y ,char c, char attr); char hex_tabl[] = "0123456789ABCDEF"; // Вывод байта на экран, координаты (x,y), // выводится шестнадцатеричное представление // байта chr с экранными атрибутами attr. void vi_put_byte(unsigned int x, unsigned int y, unsigned char chr, char attr) { unsigned char temp; temp = hex_tabl[(chr & 0xf0) >> 4]; vi_putch(x, y, temp, attr); temp = hex_tabl[chr & 0xf]; vi_putch(x+1, y, temp, attr); } // Вывод слова на экран, координаты (x,y), // выводится шестнадцатеричное представление // слова chr с экранными атрибутами attr. void vi_put_word(unsigned int x, unsigned int y, word chr, char attr) { vi_put_byte(x, y, (chr & 0xff00) >> 8, attr); vi_put_byte(x+2, y, chr & 0xff, attr); } // Вывод символа c на экран, координаты - (x,y), // атрибут выводимого символа - attr 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(VID_MEM_SELECTOR, offset); *vid_ptr++=c; *vid_ptr=attr; } // Вывод строки s на экран, координаты - (x,y), // атрибут выводимой строки - 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, " Protected mode monitor *TINY/OS*, " "v.1.2 for CPU 80286 ¦ © Frolov A.V., 1992 ", 0x30); } Последний файл - tossyst.asm - содержит уже знакомые вам процедуры для входа в защищённый режим и возврата обратно в реальный режим.


Обратите внимание на процедуры _load_task_register и _jump_to_task, выполняющие загрузку регистра задачи TR и переключение на другую задачу соответственно.
Листинг 15. Процедуры для инициализации, перехода в защищённый режим и возврата в реальный режим, для загрузки регистра TR и переключения задач. Файл tossyst.asm ----------------------------------------------------------- IDEAL MODEL SMALL RADIX 16 P286 DATASEG include "tos.inc" PUBLIC _beep ; Область памяти для инициализации IDTR idtr idtr_struc <,,,0> ; Область памяти для инициализации GDTR gdt_ptr dw (8*15)-1 ; размер GDT, 15 элементов gdt_ptr2 dw ? gdt_ptr4 dw ? ; Область памяти для записи селектора задачи, ; на которую будет происходить переключение new_task dw 00h new_select dw 00h ; Область памяти для хранения регистров, ; используется для возврата в реальный режим real_ss dw ? real_sp dw ? real_es dw ? protect_sel dw ? init_tss dw ? CODESEG PUBLIC _real_mode,_protected_mode,_jump_to_task PUBLIC _load_task_register, _load_idtr, _enable_interrupt ; ------------------------------------------------------------------- ; Процедура для переключения в защищённый режим. ; Прототип для вызова: ; void protected_mode(unsigned long gdt_ptr, unsigned int gdt_size, ; unsigned int cseg, unsigned int dseg) ; ------------------------------------------------------------------- PROC _protected_mode NEAR push bp mov bp,sp ; Параметр gdt_ptr mov ax,[bp+4] ; мл. слово адреса GDT mov dx,[bp+6] ; ст. слово адреса GDT mov [gdt_ptr4], dx ; запоминаем адрес GDT mov [gdt_ptr2], ax ; Параметр gdt_size mov ax,[bp+8] ; получаем размер GDT mov [gdt_ptr], ax ; и запоминаем его ; Параметры cseg и dseg mov ax,[bp+10d] ; получаем селектор сегмента кода mov dx,[bp+12d] ; получаем селектор сегмента данных mov [cs:p_mode_select], ax ; запоминаем для команды mov [protect_sel], dx ; перехода far jmp ; Подготовка к возврату в реальный режим push ds ; готовим адрес возврата mov ax,40h ; из защищённого режима mov ds,ax mov [WORD 67h],OFFSET shutdown_return mov [WORD 69h],cs pop ds ; Запрещаем и маскируем все прерывания cli in al, INT_MASK_PORT and al, 0ffh out INT_MASK_PORT, al ; Записываем код возврата в CMOS-память mov al,8f out CMOS_PORT,al jmp delay1 delay1: mov al,5 out CMOS_PORT+1,al call enable_a20 ; открываем линию A20 mov [real_ss],ss ; запоминаем регистры SS и ES mov [real_es],es ; Перепрограммируем контроллер прерываний ; для работы в защищённом режиме mov dx,MASTER8259A mov ah,20 call set_int_ctrlr mov dx,SLAVE8259A mov ah,28 call set_int_ctrlr ; Загружаем регистры IDTR и GDTR lidt [FWORD idtr] lgdt [QWORD gdt_ptr] mov ax, 0001h ; переключаем процессор lmsw ax ; в защищённый режим ; jmp far flush db 0eah dw OFFSET flush p_mode_select dw ? LABEL flush FAR mov dx, [protect_sel] mov ss, dx mov ds, dx mov es, dx ; Обнуляем содержимое регистра LDTR mov ax, 0 lldt ax pop bp ret ENDP _protected_mode ; ---------------------------------------------------- ; Возврат в реальный режим. ; Прототип для вызова ; void real_mode(); ; ---------------------------------------------------- PROC _real_mode NEAR ; Сброс процессора cli mov [real_sp], sp mov al, SHUT_DOWN out STATUS_PORT, al rmode_wait: hlt jmp rmode_wait LABEL shutdown_return FAR ; Вернулись в реальный режим mov ax, DGROUP mov ds, ax assume ds:DGROUP mov ss,[real_ss] mov sp,[real_sp] in al, INT_MASK_PORT and al, 0 out INT_MASK_PORT, al call disable_a20 mov ax, DGROUP mov ds, ax mov ss, ax mov es, ax mov ax,000dh out CMOS_PORT,al sti ret ENDP _real_mode ; ------------------------------------------------------- ; Загрузка регистра TR. ; Прототип для вызова: ; void load_task_register(unsigned int tss_selector); ; ------------------------------------------------------- PROC _load_task_register NEAR push bp mov bp,sp ltr [bp+4] ; селектор для текущей задачи pop bp ret ENDP _load_task_register ; ------------------------------------------------------- ; Переключение на задачу. ; Прототип для вызова: ; void jump_to_task(unsigned int tss_selector); ; ------------------------------------------------------- PROC _jump_to_task NEAR push bp mov bp,sp mov ax,[bp+4] ; получаем селектор ; новой задачи mov [new_select],ax ; запоминаем его jmp [DWORD new_task] ; переключаемся на ; новую задачу pop bp ret ENDP _jump_to_task ; ------------------------------ ; Открываем линию A20 ; ------------------------------ PROC enable_a20 NEAR push ax mov al, A20_PORT out STATUS_PORT, al mov al, A20_ON out KBD_PORT_A, al pop ax ret ENDP enable_a20 ; ------------------------------ ; Закрываем линию A20 ; ------------------------------ PROC disable_a20 NEAR push ax mov al, A20_PORT out STATUS_PORT, al mov al ,A20_OFF out KBD_PORT_A, al pop ax ret ENDP disable_a20 ; ----------------------------------------------------------- ; Готовим структуру для загрузки регистра IDTR ; Прототип для вызова функции: ; void load_idtr(unsigned long idt_ptr, word idt_size); ; ----------------------------------------------------------- PROC _load_idtr NEAR push bp mov bp,sp mov ax,[bp+4] ; мл.


слово адреса IDT mov dx,[bp+6] ; ст. слово адреса IDT mov bx, OFFSET idtr ; Запоминаем адрес IDTR в структуре mov [(idtr_struc bx).idt_low], ax mov [(idtr_struc bx).idt_hi], dl ; Получаем предел IDT и запоминаем его в структуре mov ax, [bp+8] mov [(idtr_struc bx).idt_len], ax pop bp ret ENDP _load_idtr ; ---------------------------------- ; Установка контроллера прерываний ; ---------------------------------- PROC set_int_ctrlr NEAR mov al, 11 out dx, al jmp SHORT $+2 mov al, ah inc dx out dx, al jmp SHORT $+2 mov al, 4 out dx, al jmp SHORT $+2 mov al, 1 out dx, al jmp SHORT $+2 mov al, 0ffh out dx, al dec dx ret ENDP set_int_ctrlr ; -------------------------- ; Выдача звукового сигнала ; -------------------------- PROC _beep NEAR push ax bx cx in al,KBD_PORT_B push ax mov cx,80 beep0: push cx and al,11111100b out KBD_PORT_B,al mov cx,60 idle1: loop idle1 or al,00000010b out KBD_PORT_B,al mov cx,60 idle2: loop idle2 pop cx loop beep0 pop ax out KBD_PORT_B,al pop cx bx ax ret ENDP _beep ; ------------------------------- ; Задержка выполнения программы ; ------------------------------- PROC _pause NEAR push cx mov cx,10 ploop0: push cx xor cx,cx ploop1: loop ploop1 pop cx loop ploop0 pop cx ret ENDP _pause ; ----------------------- ; Размаскирование прерываний ; ----------------------- PROC _enable_interrupt NEAR in al, INT_MASK_PORT and al, 0fch out INT_MASK_PORT, al sti ret ENDP _enable_interrupt end

Пример программы


Приведём пример программы, определяющей присуствие в системе драйвера дополнительной памяти XMM. Если этот драйвер присутствует, программа проверяет поддержку этим драйвером интерфейса VCPI. Затем, если интерфейс VCPI поддерживается, программа выводит его версию на экран.

Листинг 19. Определение версии VCPI Файл vcpi.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> void main(void) { unsigned err; char ver, ver_hi, ver_lo; clrscr(); printf("Virtual Control Program Interface Demo, © Frolov A.V., 1992\n\r" "-------------------------------------------------------------\n\r\n\r"); // Проверяем наличие драйвера EMS/VCPI if(ems_init()) { printf("Драйвер EMS/VCPI не загружен."); exit(-1); } printf("Драйвер EMS/VCPI загружен"); // Выводим номер версии драйвера if((err = ems_ver(&ver)) != 0) { printf("\nОшибка %02.2X при определении версии EMM", err); exit(-1); } printf("\nВерсия EMM: %02.2X", ver); // Определяем присутствие VCPI и его версию if(vcpi_ver(&ver_hi, &ver_lo) != 0) { printf("\nДрайвер EMM не поддерживает VCPI\n"); exit(-1); } printf("\nВерсия VCPI: %02.2X.%02.2X", ver_hi, ver_lo); } /** *.Name ems_init *.Title Функция проверяет установку драйвера EMS * *.Descr Эта функция проверяет наличие драйвера EMS * *.Proto int ems_init(void); * *.Params Не используются * *.Return 0 - драйвер EMS установлен; * 1 - драйвер EMS не установлен. * *.Sample ems_test.c **/ int ems_init(void) { void (_interrupt _far *EMS_driver_adr)(void); char _far *EMS_driver_name; char test_name[8]; int i; EMS_driver_adr = _dos_getvect(0x67); FP_SEG(EMS_driver_name) = FP_SEG (EMS_driver_adr); FP_OFF(EMS_driver_name) = 10; for(i=0; i<8; i++) test_name[i] = EMS_driver_name[i]; if(strncmp(test_name, "EMMXXXX0", 8) == 0) return(0); else return(1); } /** *.Name ems_ver *.Title Определение версии драйвера EMS * *.Descr Эта функция возвращает номер версии * драйвера EMS в двоично-десятичном формате. * *.Proto int ems_ver(char *ver); * *.Params char *ver - указатель на байт, в который * будет записан номер версии. * *.Return Номер версии драйвера EMS в формате BCD * *.Sample ems_test.c **/ int ems_ver(char *ver) { union REGS reg; reg.x.ax = 0x4600; int86(0x67, &reg, &reg); *ver = reg.h.al; return(reg.h.ah); } int vcpi_ver(char *ver_hi, char *ver_lo) { union REGS reg; reg.x.ax = 0xDE00; int86(0x67, &reg, &reg); *ver_hi = reg.h.bh; *ver_lo = reg.h.bl; return(reg.h.ah); }

Пример программы для работы с CLIPBOARD


Приведённая ниже программа демонстрирует запись в CLIPBOARD из DOS-программы, а также вход в критическую секцию и выход из неё.

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

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

Если CLIPBOARD доступен, программа проверяет, есть ли в нём текстовые данные. Если текстовые данные есть, они читаются из CLIPBOARD и выводятся на экран. Затем CLIPBOARD очищается и в него записывается тестовая строка, состоящая из латинских букв и символов кириллицы (для проверки выполнения перекодировки из OEM в ANSI).

После записи строки программа устанавливает размер CLIPBOARD и закрывает его. Далее вы можете запустить приложение WINDOWS "Clipboard" и посмотреть результат!

Листинг 24. Работа с WINDOWS CLIPBOARD и критической секцией Файл windos.c ----------------------------------------------------------- #include <dos.h> #include <stdio.h> #include <malloc.h> char buf[2048], far *fptr = buf; char msg[] = "String for storing(для записи) to WINDOWS clipboard\n\n\n"; void main(void) { union REGS inregs, outregs; struct SREGS segregs; unsigned long clipbrd_size, i; printf("\n\nРабота с WINDOWS CLIPBOARD и критической секцией\n" "© Frolov A. 1992\n\n"); // Проверяем, работает ли программа под управлением // WINDOWS в расширенном режиме. inregs.x.ax = 0x1600; int86( 0x2f, &inregs, &outregs); if (outregs.h.al == 0) { printf("\nТребуется расширенный режим WINDOWS!\n"); exit(-1); } // Выводим на экран версию WINDOWS printf("Версия WINDOWS - %d.%d\n", outregs.h.al, outregs.h.ah); // Определяем и выводим на экран идентификатор // виртуальной машины, на которой работает // данная программа. inregs.x.ax = 0x1683; int86( 0x2f, &inregs, &outregs); printf("Виртуальная машина - VM%d\n", outregs.x.bx); // Входим в критическую секцию.
До выхода из нее // переключение задач в WINDOWS блокировано. inregs.x.ax = 0x1681; int86( 0x2f, &inregs, &outregs); printf("\n\nВошли в критическую секцию.\n" "Попробуйте переключить задачу клавишами <ALT-TAB>,\n" " затем нажмите любую другую клавишу для выхода\n" "из критической секции\n"); // После нажатия на любую клавишу выходим // из критической секции getch(); inregs.x.ax = 0x1682; int86( 0x2f, &inregs, &outregs); printf("Вышли из критической секции\n"); // Проверяем доступность CLIPBOARD. Если доступен, // выводим версию драйвера WINDOWS, использующегося // для поддержки DOS-программ - WINOLDAP. inregs.x.ax = 0x1700; int86( 0x2f, &inregs, &outregs); if(outregs.x.ax == 0x1700) { printf("\nClipboard недоступна"); exit(-1); } else printf("\nВерсия WINOLDAP - %d.%d", outregs.h.al, outregs.h.ah); // Открываем CLIPBOARD inregs.x.ax = 0x1701; int86( 0x2f, &inregs, &outregs); if(outregs.x.ax == 0x0000) { printf("\nОшибка при открытии Clipboard"); exit(-1); } // Получаем объем данных, находящихся в CLIPBOARD. // Регистр DX равен 1, следовательно, мы будем работать с // текстовыми данными. inregs.x.ax = 0x1704; inregs.x.dx = 0x01; int86( 0x2f, &inregs, &outregs); // Вычисляем объем данных clipbrd_size = outregs.x.dx << 16l; clipbrd_size += outregs.x.ax; if(clipbrd_size == 0L) printf("\nClipboard пуст"); else printf("\nОбъем данных в Clipboard: %lu\n", clipbrd_size); // Получаем данные из CLIPBOARD. В регистре DX // задаем значение 7, что соответствует тексту // в кодировке OEM. При этом в процессе передачи // данных выполняется перекодировка из представления // ANSI (используется в WINDOWS) в представление // OEM (используется в DOS) inregs.x.ax = 0x1705; inregs.x.dx = 0x07; segregs.es = FP_SEG(fptr); inregs.x.bx = FP_OFF(fptr); int86x( 0x2f, &inregs, &outregs, &segregs); // Выводим содержимое CLIPBOARD, если // там что-нибудь есть.


if(outregs.x.ax == 0) printf("\nВ Clipboard ничего нет!"); else { printf("Содержимое Clipborad:\n"); for(i=0l; i < clipbrd_size; i++) { putch(buf[i]); } } // Очищаем CLIPBOARD inregs.x.ax = 0x1702; int86( 0x2f, &inregs, &outregs); // Записываем в CLIPBOARD текстовые данные // в кодировке OEM inregs.x.ax = 0x1703; inregs.x.dx = 0x07; inregs.x.si = 0x00; inregs.x.cx = strlen(msg); fptr = msg; segregs.es = FP_SEG(fptr); inregs.x.bx = FP_OFF(fptr); int86x( 0x2f, &inregs, &outregs, &segregs); if(outregs.x.ax == 0) { printf("\nОшибка при записи в Clipboard"); exit(-1); } // Устанавливаем размер CLIPBOARD, равный // длине записанной в него строки inregs.x.ax = 0x1709; inregs.x.si = 0x00; inregs.x.cx = strlen(msg); int86( 0x2f, &inregs, &outregs); // Закрываем CLIPBOARD inregs.x.ax = 0x1708; int86( 0x2f, &inregs, &outregs); exit(0); }

Пример простой программы переключения режима


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

Программа подготовлена с помощью транслятора Borland Turbo Assembler и использует режим IDEAL. Для её трансляции был использован следующий командный файл:

tasm %1.asm /l /zi tlink %1.obj /v

Вы можете запускать эту программу на любой машине, совместимой с IBM AT и оборудованной процессорами i80286, i80386, i80486. Но вы не должны запускать эту программу, если у вас компьютер на базе процессоров i80386 или i80486 и активны драйверы QEMM, EMM386 - отключите эти драйверы. Кроме того, эту программу нельзя запускать на виртуальной машине в среде Microsoft WINDOWS в режиме Enchanced Mode или на виртуальной машине в среде OS/2 версии 2.0.

Это связано с тем, что в перечисленных выше случаях процессор работает не в реальном режиме, а в так называемом режиме виртуального процессора 8086. Этот режим возможен только для процессоров i80386 или i80486.

Кроме того, не следует запускать эту программу на компьютерах серии PS/2, так как в них используется другой способ управления линией A20 и другой способ сброса процессора для возврата в реальный режим.

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

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

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


Поэтому в приведённой ниже программе вывод текстовых строк на экран производится при помощи непосредственной записи в память видеоконтроллера. Что же касается ввода данных с клавиатуры, то здесь не обойтись без обработки прерывания от клавиатуры. В следующей главе мы приведём пример программы, которая не только выводит данные на экран дисплея (также через запись в видеопамять), но и умеет работать с клавиатурой и таймером.
Если ваша программа составлена на языках высокого уровня (Си или Паскаль), ей недоступна практически вся библиотека стандартных функций. Особенно это касается функций ввода/вывода и управления памятью. Причина очевидна - большинство функций стандартных библиотек трансляторов вызывает те или иные прерывания BIOS или DOS. Программы не должны загружать в сегментные регистры значения, которые не являются правильными селекторами, описанными в соответствующих дескрипторных таблицах. Несмотря на то, что сама по себе загрузка произвольного значения в сегментный регистр не опасна, попытка использования неправильно загруженного сегментного регистра вызовет аварийное завершение работы программы. Это ограничение связано с механизмом преобразования логического адреса в физический.
Итак, наша первая программа для защищённого режима процессора 80286:
Листинг 1. Демонстрация переключения в защищённый режим
и возврата обратно в реальный режим
----------------------------------------------------------- IDEAL RADIX 16 P286 ; Используем модель памяти LARGE, при этом мы организуем ; несколько отдельных сегментов и для каждого сегмента ; создадим дескриптор в таблице GDT. MODEL LARGE ; ------------------------------------------------------------ ; Определения структур данных и констант ; ------------------------------------------------------------ STRUC desc_struc ; структура дескриптора limit dw 0 ; предел base_l dw 0 ; мл. слово физического адреса base_h db 0 ; ст. байт физического адреса access db 0 ; байт доступа rsrv dw 0 ; зарезервировано ENDS desc_struc ; Биты байта доступа ACC_PRESENT EQU 10000000b ; сегмент есть в памяти ACC_CSEG EQU 00011000b ; сегмент кода ACC_DSEG EQU 00010000b ; сегмент данных ACC_EXPDOWN EQU 00000100b ; сегмент расширяется вниз ACC_CONFORM EQU 00000100b ; согласованный сегмент ACC_DATAWR EQU 00000010b ; разрешена запись ; Типы сегментов ; сегмент данных DATA_ACC = ACC_PRESENT OR ACC_DSEG OR ACC_DATAWR ; сегмент кода CODE_ACC = ACC_PRESENT OR ACC_CSEG OR ACC_CONFORM ; сегмент стека STACK_ACC = ACC_PRESENT OR ACC_DSEG OR ACC_DATAWR OR ACC_EXPDOWN ; Константы STACK_SIZE EQU 0400 ; размер стека B_DATA_SIZE EQU 0300 ; размер области данных BIOS B_DATA_ADDR EQU 0400 ; адрес области данных BIOS MONO_SEG EQU 0b000 ; сегмент видеопамяти ; монохромного видеоадаптера COLOR_SEG EQU 0b800 ; сегмент видеопамяти ; цветного видеоадаптера CRT_SIZE EQU 4000 ; размер сегмента видеопамяти ; цветного видеоадаптера MONO_SIZE EQU 1000 ; размер сегмента видеопамяти ; монохромного видеоадаптера CRT_LOW EQU 8000 ; мл.


байт физического адреса ; сегмента видеопамяти ; цветного видеоадаптера MONO_LOW EQU 0000 ; мл. байт физического адреса ; сегмента видеопамяти ; монохромного видеоадаптера CRT_SEG EQU 0Bh ; ст. байт физического адреса ; сегмента видеопамяти ; Селекторы, определённые в таблице GDT DS_DESCR = (gdt_ds - gdt_0) CS_DESCR = (gdt_cs - gdt_0) SS_DESCR = (gdt_ss - gdt_0) BIOS_DESCR = (gdt_bio - gdt_0) CRT_DESCR = (gdt_crt - gdt_0) MDA_DESCR = (gdt_mda - gdt_0) CMOS_PORT EQU 70h ; порт для доступа к CMOS-памяти PORT_6845 EQU 0063h ; адрес области данных BIOS, ; где записано значение адреса ; порта контроллера 6845 COLOR_PORT EQU 03d4h ; порт цветного видеоконтроллера MONO_PORT EQU 03b4h ; порт монохромного видеоконтроллера STATUS_PORT EQU 64h ; порт состояния клавиатуры SHUT_DOWN EQU 0feh ; команда сброса процессора VIRTUAL_MODE EQU 0001h ; бит перехода в защищённый режим A20_PORT EQU 0d1h ; команда управления линией A20 A20_ON EQU 0dfh ; открыть A20 A20_OFF EQU 0ddh ; закрыть A20 KBD_PORT_A EQU 60h ; адреса клавиатурных KBD_PORT_B EQU 61h ; портов INT_MASK_PORT EQU 21h ; порт для маскирования прерываний STACK STACK_SIZE ; сегмент стека DATASEG ; начало сегмента данных DSEG_BEG = THIS WORD ; Память для хранения регистров SS, SP, ES. Содержимое ; этих регистров будет записано здесь перед входом в ; защищённый режим и восстановлено отсюда после возврата ; из защищённого режима в реальный. real_ss dw ? real_sp dw ? real_es dw ? ; Глобальная таблица дескрипторов GDT, ; содержит следующие дескрипторы: ; ; gdt_0 - дескриптор для пустого селектора ; gdt_gdt - дескриптор для GDT ; gdt_ds - дескриптор для сегмента, адресуемого DS ; gdt_cs - дескриптор для сегмента кода ; gdt_ss - дескриптор для сегмента стека ; gdt_bio - дескриптор для области данных BIOS ; gdt_crt - дескриптор для видеопамяти цветного дисплея ; gdt_mda - дескриптор для видеопамяти монохромного дисплея GDT_BEG = $ LABEL gdtr 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_bio desc_struc <B_DATA_SIZE-1,B_DATA_ADDR,0,DATA_ACC,0> gdt_crt desc_struc <CRT_SIZE-1,CRT_LOW,CRT_SEG,DATA_ACC,0> gdt_mda desc_struc <MONO_SIZE-1,MONO_LOW,CRT_SEG,DATA_ACC,0> GDT_SIZE = ($ - GDT_BEG) ; размер таблицы дескрипторов CODESEG ; сегмент кода PROC start ; Инициализируем регистр сегмента данных ; для реального режима mov ax,DGROUP mov ds,ax ; Определяем базовый адрес видеопамяти call set_crt_base ; Стираем экран дисплея (устанавливаем серый фон) mov bh, 77h call clrscr ; Выполняем все подготовительные действия для перехода ; в защищённый режим и обеспечения возможности возврата ; в реальный режим call init_protected_mode ; Переключаемся в защищённый режим call set_protected_mode ; --------- * Программа работает в защищённом режиме! * --------- call write_hello_msg ; выводим сообщение на экран call pause ; ждём некоторое время ; Возвращаемся в реальный режим call set_real_mode ; --------- * Программа работает в реальном режиме! * --------- ; Стираем экран и возвращаемся в DOS mov bh, 07h call clrscr mov ah,4Ch int 21h ENDP start ; ------------------------------------------------------------ ; Макрокоманда для записи в дескриптор 24-битового ; базового адреса сегмента ; ------------------------------------------------------------ MACRO setgdtentry mov [(desc_struc bx).base_l],ax mov [(desc_struc bx).base_h],dl ENDM ; ------------------------------------------------------------ ; Процедура подготовки процессора к переходу в защищённый ; режим с последующим возвратом в реальный режим ; ------------------------------------------------------------ PROC init_protected_mode NEAR ; Заполняем глобальную таблицу дескрипторов GDT ; Вычисляем 24-битовый базовый адрес сегмента данных mov ax,DGROUP mov dl,ah shr dl,4 shl ax,4 ; Регистры dl:ax содержат базовый адрес, сохраняем его в di:si mov si,ax mov di,dx ; Подготавливаем дескриптор для GDT add ax,OFFSET gdtr adc dl,0 mov bx,OFFSET gdt_gdt setgdtentry ; Подготавливаем дескриптор для сегмента ds mov bx,OFFSET gdt_ds mov ax,si mov dx,di setgdtentry ; Подготавливаем дескриптор для сегмента cs mov bx,OFFSET gdt_cs mov ax,cs mov dl,ah shr dl,4 shl ax,4 setgdtentry ; Подготавливаем дескриптор для сегмента стека mov bx,OFFSET gdt_ss mov ax,ss mov dl,ah shr dl,4 shl ax,4 setgdtentry ; Записываем адрес возврата в реальный режим в область ; данных BIOS по адресу 0040h:0067h push ds mov ax,40 mov ds,ax mov [WORD 67],OFFSET shutdown_return mov [WORD 69],cs pop ds ; Маскируем все прерывания, в том числе немаскируемые. ; Записываем в CMOS-память в ячейку 0Fh код 5, ; этот код обеспечит после выполнения сброса процессора ; передачу управления по адресу, подготовленному нами ; в области данных BIOS по адресу 0040h:0067h. ; Для того, чтобы немаскируемые прерывания были запрещены, ; устанавливаем в 1 старший бит при определении ячейки CMOS.


cli mov al,8f out CMOS_PORT,al jmp next1 ; небольшая задержка next1: mov al,5 out CMOS_PORT+1,al ; код возврата ret ENDP init_protected_mode ; ------------------------------------------------------------ ; Процедура переключает процессор в защищённый режим ; ------------------------------------------------------------ PROC set_protected_mode NEAR mov ax,[rl_crt] ; записываем в es сегментный mov es,ax ; адрес видеопамяти call enable_a20 ; открываем адресную линию A20 mov [real_ss],ss ; запоминаем указатель стека mov [real_es],es ; для реального режима ; Загружаем регистр GDTR lgdt [QWORD gdt_gdt] ; Устанавливаем защищённый режим работы процессора mov ax,VIRTUAL_MODE lmsw ax ; Мы находимся в защищённом режиме ; Очищаем внутреннюю очередь команд процессора ; Выполняем команду межсегментного прерхода, ; в качестве селектора указываем селектор текущего ; сегмента кода, в качестве смещения - метку flush ; jmp far flush db 0ea dw OFFSET flush dw CS_DESCR LABEL flush FAR ; Загружаем сегментные регистры SS и DS селекторами mov ax,SS_DESCR mov ss,ax mov ax,DS_DESCR mov ds,ax ret ENDP set_protected_mode ; ------------------------------------------------------------ ; Процедура возвращает процессор в реальный режим ; ------------------------------------------------------------ PROC set_real_mode NEAR ; Запоминаем содержимое указателя стека, так как после ; сброса процессора оно будет потеряно mov [real_sp],sp ; Выполняем сброс процессора mov al,SHUT_DOWN out STATUS_PORT,al ; Ожидаем сброса процессора wait_reset: hlt jmp wait_reset ; ------->> В это место мы попадём после сброса процессора, ; теперь мы снова в реальном режиме LABEL shutdown_return FAR ; Инициализируем ds адресом сегмента данных mov ax,DGROUP mov ds,ax assume ds:DGROUP ; Восстанавливаем указатель стека mov ss,[real_ss] mov sp,[real_sp] ; Восстанавливаем содержимое регистра es mov es,[real_es] ; Закрываем адресную линию A20 call disable_a20 ; Разрешаем все прерывания mov ax,000dh ; разрешаем немаскируемые прерывания out CMOS_PORT,al in al,INT_MASK_PORT ; разрешаем маскируемые прерывания and al,0 out INT_MASK_PORT,al sti ret ENDP set_real_mode ; ------------------------------------------------------------ ; Процедура открывает адресную линию A20 ; ------------------------------------------------------------ PROC enable_a20 NEAR mov al,A20_PORT out STATUS_PORT,al mov al,A20_ON out KBD_PORT_A,al ret ENDP enable_a20 ; ------------------------------------------------------------ ; Процедура закрывает адресную линию A20 ; ------------------------------------------------------------ PROC disable_a20 NEAR mov al,A20_PORT out STATUS_PORT,al mov al,A20_OFF out KBD_PORT_A,al ret ENDP disable_a20 ; ------------------------------------------------------------ ; Процедура выполняет небольшую временную задержку ; ------------------------------------------------------------ PROC pause NEAR push cx mov cx,50 ploop0: push cx xor cx,cx ploop1: loop ploop1 pop cx loop ploop0 pop cx ret ENDP pause ; ------------------------------------------------------------ ; Сегмент данных для процедур обслуживания видеоадаптера ; ------------------------------------------------------------ DATASEG columns db 80d ; количество столбцов на экране rows db 25d ; количество строк на экране rl_crt dw COLOR_SEG ; сегментный адрес видеобуфера vir_crt dw CRT_DESCR ; селектор видеобуфера curr_line dw 0d ; номер текущей строки CODESEG ; ------------------------------------------------------------ ; Определение базового адреса видеобуфера ; ------------------------------------------------------------ PROC set_crt_base NEAR ; Определяем количество столбцов на экране и записываем ; в переменную columns mov ax,40 mov es,ax mov bx,[WORD es:4a] mov [columns],bl ; То же для количества строк, записываем в переменную rows mov bl,[BYTE es:84] inc bl mov [rows],bl ; Для того чтобы определить тип видеоконтроллера (цветной ; или монохромный), считываем адрес микросхемы 6845 mov bx,[WORD es:PORT_6845] cmp bx,COLOR_PORT je set_crt_exit ; Если видеоконтроллер монохромный, изменяем адрес сегмента ; и селектор, заданные по умолчанию mov [rl_crt],MONO_SEG mov [vir_crt],MDA_DESCR set_crt_exit: ret ENDP set_crt_base ; ------------------------------------------------------------ ; Вывод строки на экран ; Параметры: ; (ax, bx) - координаты (x, y) выводимой строки ; ds:si - адрес выводимой строки ; cx - длина выводимой строки ; dh - атрибут выводимой строки ; es - сегмент или селектор видеопамяти ; ------------------------------------------------------------ PROC writexy NEAR push si push di ; Вычисляем смещение в видеобуфере для записи строки, ; используем формулу ((y * columns) + x) * 2 mov dl,[columns] mul dl add ax,bx shl ax,1 mov di,ax mov ah,dh ; записываем в ah байт атрибута ; Выполняем запись в видеобуфер wxy_write: lodsb ; очередной символ в al stosw ; записываем его в видеопамять loop wxy_write ; цикл до конца строки pop di pop si ret ENDP writexy ; ------------------------------------------------------------ ; Процедура стирания экрана ; Параметр: bh - атрибут для заполнения экрана ; ------------------------------------------------------------ PROC clrscr NEAR xor cx,cx mov dl,[columns] mov dh,[rows] mov ax,0600h int 10h ret ENDP clrscr DATASEG hello_msg db " Protected mode monitor *TINY/OS*, v.1.0 for CPU 80286 ¦ © Frolov A.V., 1992 " CODESEG ; ------------------------------------------------------------ ; Процедура выводит сообщение в защищённом режиме ; ------------------------------------------------------------ PROC write_hello_msg NEAR mov ax,[vir_crt] ; загружаем селектор видеопамяти mov es,ax ; в регистр es ; Выводим сообщение в верхний левый угол экрана (x=y=0) mov bx,0 ;(X,Y) = (AX,BX) mov ax,[curr_line] inc [curr_line] ; увеличиваем номер текущей строки ; Загружаем адрес выводимой строки и её длину mov si,OFFSET hello_msg mov cx,SIZE hello_msg mov dh,30h ; аттрибут - черный текст на голубом фоне call writexy ; выводим строку ret ENDP write_hello_msg CSEG_SIZE = ($ - start) ; размер сегмента кода DATASEG DSEG_SIZE = ($ - DSEG_BEG) ; размер сегмента данных END start

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


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

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

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

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

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

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

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

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

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

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



В этом разделе мы очень


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

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

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

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

В процессоре i80486 стали определены некоторые, зарезервированные ранее в процессоре i80386, биты регистров CR0, CR3, EFLAGS, есть некоторые новшества в таблицах страниц, новое исключение и несколько новых команд.

В регистре CR0 стали определены пять новых битов - NE, WP, AM, NW, CD.

В регистре CR3 определены два новых бита - PCD и PWT.

В регистре EFLAGS появился новый флаг AC (бит 18). Этот бит вместе с битом AM регистра CR0 контролирует проверку выравнивания объектов в памяти.

Форматы системных регистров CR0 и CR3 для процессора i80486, а также регистра EFLAGS описаны в приложении.

Новое исключение касается проверки выравнивания и имеет номер 17.
Оно возникает при попытке обращения к данным, не выровненным в памяти. Для того, чтобы при обращении к не выровненному операнду в памяти произошло исключение контроля выравнивания, должны быть установлены флаг AC в регистре EFLAGS и бит AM в управляющем регистре CR0.

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

Добавились три команды, предназначенные для использования прикладными программами - BSWAP, XADD, CMPXCHG, а также три новые системные команды, управляющие кэшем и TLB - INVD, WBINVD, INVLPG. Новые команды облегчают и ускоряют работу с семафорами, что очень важно для мультизадачных операционных систем.

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

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