DOS-extender для компилятора Borland C++

DOS-extender для компилятора Borland C++ 3.1, защищенный режим процессора
80286, организация многозадачной работы процессора Операционная система MS DOS, не смотря на свое моральное
устаревание, все еще довольно часто находит применение на парке старых ПК, а
значит, все еще существует необходимость создания программ для нее. К
сожалению, написание программ в реальном режиме процессоров архитектуры Intel
x86 осложнено отсутствием возможности использовать в программе оперативную
память объемом свыше пресловутых 640 килобайт, а реально свыше 500-620 килобайт.
Это ограничение к сожалению преследует MS DOS и аналогичные ей ОС других
производителей, начиная с того момента, как горячо любимый в околокомпьютерных
кругах Билл Гейтс заявил, что 640 килобайт достаточно для всех возможных задач
ПК. Преодоление барьера 640 килобайт в новых версиях MS DOS усложнялось
необходимостью совместимости с старыми программами, которые жизненно необходимо
было поддерживать. Программирование защищенного режима процессора и расширенной
памяти требовало от программистов недюжинных знаний архитектуры процессоров
Intel и достаточно трудоемкого программирования. Инженерная мысль не стоит на месте, особенно
в такой области, как программирование. Задача программной поддержки защищённого
режима и поддержки работы с расширенной памятью получила не одно, а сразу
несколько решений. Этими решениями стали так называемые уровни программной
поддержки защищённого режима и поддержки работы с расширенной
памятью: интерфейс DPMI;
Интерфейсом самого низкого уровня является интерфейс BIOS,
предоставляемый программам в виде нескольких функций прерывания BIOS
INT15h. Интерфейс BIOSпозволяет программе перевести процессор из реального
режима в защищённый, переслать блок памяти из стандартной памяти в расширенную
или из расширенной в стандартную. Этим все его возможности и ограничиваются.
Интерфейс BIOS используется для старта мультизадачных операционных систем
защищённого режима (таких, как OS/2) или в старых программах, работающих с
расширенной памятью в защищённом режиме (например, СУБД ORACLE версии 5.1).
С помощью функций, предоставляемых
этим драйвером, программа может выполнять различные действия с блоками
расширенной памяти, а также управлять адресной линией A20. Основное различие
между способом работы с расширенной памятью драйвера HIMEM.SYS и интерфейсом
прерывания BIOS INT15h заключается в том, что первый выполняет выделение
программе и внутренний учёт блоков расширенной памяти, а второй рассматривает
всю расширенную память как один непрерывный участок. Однако драйвер HIMEM.SYS не
открывает для программ доступ к защищённому режиму. Он полностью работает в
реальном режиме, а для обращения к расширенной памяти использует либо
недокументированную машинную команду LOADALL (если используется процессор
80286), либо возможности процессора 80386, который позволяет адресовать
расширенную память в реальном режиме (при соответствующей инициализации
системных регистров и таблиц). Используя
трансляцию страниц, некоторые драйверы памяти (например, EMM386 или QEMM) могут
эмулировать присутствие дополнительной памяти, используя расширенную память. При
этом стандартный набор функций управления дополнительной памятью, реализованный
в рамках прерывания INT67h, дополнен еще несколькими функциями для работы
в защищённом режиме процессора. Эти новые функции реализуют интерфейс
виртуальной управляющей программы VCPI (Virtual Control Programm Interface). Они
позволяют устанавливать защищённый и виртуальный режимы работы процессора,
работать с расширенной памятью на уровне страниц и устанавливать специальные
отладочные регистры процессора i80386. Интерфейс VCPI облегчает использование
механизма трансляции страниц, освобождая программиста от необходимости работать
с системными регистрами процессора. Интерфейс DPMI
(DOS Protected Mode Interface - интерфейс защищённого режима для DOS)
реализуется модулем, называющимся сервером DPMI. Этот интерфейс доступен для тех
программ, которые работают на виртуальной машине WINDOWS или OS/2 версии 2.0
(позже мы обсудим некоторые детали, связанные с использованием интерфейса DPMI в
WINDOWS). Интерфейс DPMI предоставляет полный набор функций для создания
однозадачных программ, работающих в защищённом режиме. В этом интерфейсе имеются
функции для переключения из реального режима в защищённый и обратно, для работы
с локальной таблицей дескрипторов LDT, для работы с расширенной и стандартной
памятью на уровне страниц, для работы с прерываниями (в том числе для вызова
прерываний реального режима из защищённого режима), для работы с отладочными
регистрами процессора i80386. Это наиболее развитый интерфейс из всех
рассмотренных ранее. Последний,
самый высокий уровень программной поддержки защищённого режима - расширители DOS
или DOS-экстендеры (DOS-extender). Они поставляются, как правило, вместе со
средствами разработки программ (трансляторами) в виде библиотек и компонуются
вместе с создаваемой программой в единый загрузочный модуль. DOS-экстендеры
значительно облегчают использование защищённого режима и расширенной памяти в
программах, предназначенных для запуска из среды MS-DOS. Программы, составленные
с использованием DOS-экстендеров, внешне очень похожи на обычные программы MS-
DOS, однако они получают управление, когда процессор уже находится в защищённом
режиме. К формируемому с помощью DOS-экстендера загрузочному модулю добавляются
процедуры, необходимые для инициализации защищённого режима. Эти процедуры
первыми получают управление и выполняют начальную инициализацию таблиц GDT, LDT,
IDT, содержат обработчики прерываний и исключений, систему управления
виртуальной памятью и т.д. Еще несколько лет назад целые фирмы зарабатывали себе на существование
созданием различных модификаций DOS extender-ов. Например довольно известный
externder фирмы Phar Lap. После перехода большинства пользователей в среду Win32
необходимость в DOS extender-ах резко сократилась и большинство таких фирм, не
сумев сориентироваться в изменившихся условиях, прекратили свое существование.
Многие фирмы, разрабатывавшие компиляторы для DOS, включали в поставку своих
сред программирования DOS-extender-ы собственной разработки. Таким примером
может служить фирма Borland (ныне подразделение фирмы Corel) с ее Borland
Pascal, Borland C++ и расширителем DOS RTM. В данный момент доступно несколько
DOS-extender-ов по свободной лицензии, которые могут использоваться кем угодно
для любых целей. И это понятно, денег на них сейчас не заработаешь. Маленький и функциональный DOS-
extender для Watcom C++ и 32-х битных исполняемых файлов формата OS/2 LE.
Используется в коммерческих программах, таких как антивирус AVP для
DOS32. Самый впечатливший меня DOS-extender. Список
поддерживаемых функций просто поражает. Поддерживает все распространенные среды
программирования: Visual C++ 4 и позже, Borland C++ 4 и позже, Delphi 2 и позже.
При желании никто не запрещает использовать Assembler. DOS-экстендеры обычно поставляются в комплекте с
трансляторами, редакторами связей, отладчиками и библиотеками стандартных
функций (например, библиотеками для транслятора языка Си). Код DOS-extender
линкуется либо уже к готовому исполняемому файлу специальной программой (чаще),
либо линковка полностью проходит при помощи программы-линкера, специально
разработанного для данного компилятора. В настоящий момент науке известны
всего один DOS-extender для Borland C++ 3.1. Это программа фирмы Phar Lap, не
имеющая собственного названия. Фирмы, к сожалению, давно уже нет, как и исходных
текстов этого DOS-extender-а. В него входил собственная программа – линкер и
набор специальных библиотек функций специально для Borland C++ 3.1, которой и
проводилась окончательная сборка EXE-файла. Написание собственной среды
разработки, вроде программ-линкеров и собственных трансляторов языка Ассемблера
явно выходит за переделы данного курсового проекта. Поэтому остановимся на
разработке набора функций, позволяющих: обрабатывать прерывания
реального режима DOS После разработки необходимых средств,
напишем программу–пример с их использованием. Собственно это получится не просто
программа, а некий прототип многозадачной операционной системы. Borland Turbo Assembler из поставки Borland
C++ 3.1 3.1 Адресация защищенного режима процессора 80286. Логический
адрес в защищённом режиме (иногда используется термин "виртуальный адрес")
состоит из двух 16-разрядных компонент - селектора и смещения. Селектор
записывается в те же сегментные регистры, что и сегментный адрес в реальном
режиме. Однако преобразование логического адреса в физический выполняется не
простым сложением со сдвигом, а при помощи специальных таблиц преобразования
адресов. В первом приближении можно считать, что для процессора i80286
селектор является индексом в таблице, содержащей базовые 24-разрядные физические
адреса сегментов. В процессе преобразования логического адреса в физический
процессор прибавляет к базовому 24-разрядному адресу 16-разрядное смещение, т.е.
вторую компоненту логического адреса (Рис. 1). Такая схема формирования
физического адреса позволяет непосредственно адресовать 16 мегабайт памяти с
помощью 16-разрядных компонент логического адреса. Таблиц дескрипторов в
системе обычно присутствует от одной до нескольких десятков. Но всегда
существует так называемая таблица GDT (Global Descriptor Table), в которой
обычно хранится описание сегментов самой операционной системы защищенного режима
80286. Таблицы LDT (Local Descriptor Table) создаются на каждый новый
запускаемый процесс в операционной системе, и в них хранится описание сегментов
только одной отдельной задачи. Таблица дескрипторов - это просто таблица
преобразования адресов, содержащая базовые 24-разрядные физические адреса
сегментов и некоторую другую информацию. То есть каждый элемент таблицы
дескрипторов (дескриптор) содержит 24-разрядный базовый адрес сегмента и другую
информацию, описывающую сегмент. Процессор 80286 имеет специальный 5-байтный
регистр защищенного режима GDTR, в котором старшие 3 байта содержат 24-разрядный
физический адрес таблицы GDT, младшие два байта - длину таблицы GDT, уменьшенную
на 1. Рис. 1. Схема преобразования логического адреса в
физический в защищенном режиме процессора 80286. Перед переходом в
защищённый режим программа должна создать в оперативной памяти таблицу GDT и
загрузить регистр GDTR при помощи специальной команды LGDT. Общая его длина составляет 8 байт, в которых расположены
следующие поля: поле предела содержит размер
сегмента в байтах, уменьшенный на единицу; зарезервированное поле длиной
16 бит для процессора i80286 должно содержать нули, это поле используется
процессорами i80386 и i80486 (там, в частности, хранится старший байт 32-
разрядного базового адреса сегмента). Поле доступа, занимающее в дескрипторе
один байт (байт доступа) служит для классификации дескрипторов. На рис.2
приведены форматы поля доступа для трёх типов дескрипторов - дескрипторов
сегментов кода, сегментов данных и системных. Поле доступа дескриптора сегментов кода содержит
битовое поле R, называемое битом разрешения чтения сегмента. Если этот бит
установлен в 1, программа может считывать содержимое сегмента кода. В противном
случае процессор может только выполнять этот код. Биты P и A предназначены
для организации виртуальной памяти. Их назначение будет описано в разделе,
посвящённом виртуальной памяти. Сейчас отметим, что бит P называется битом
присутствия сегмента в памяти. Для тех сегментов, которые находятся в физической
памяти (мы будем иметь дело в основном с такими сегментами) этот бит должен быть
установлен в 1. Любая попытка программы обратиться к сегменту памяти, в
дескрипторе которого бит P установлен в 0, приведёт к прерыванию. Поле доступа дескриптора сегмента данных имеет битовые поля W
и D. Поле W называется битом разрешения записи в сегмент. Если этот бит
установлен в 1, наряду с чтением возможна и запись в данный сегмент. В противном
случае при попытке чтения выполнение программы будет прервано. Поле D задаёт
направление расширения сегмента. Обычный сегмент данных расширяется в область
старших адресов (расширение вверх). Если же в сегменте расположен стек,
расширение происходит в обратном направлении - в область младших адресов
(расширение вниз). Для сегментов, в которых организуются стеки, необходимо
устанавливать поле D равным1. Рассмотрим, как таблица дескрипторов
будет выглядеть на языке программирования C. (В дальнейшем где это только
возможно будем применять язык С, а Ассемблер – только там, где это
необходимо.): word base_lo; // Базовый адрес сегмента (младшее
слово)
unsigned char type_dpl; // Поле доступа дескриптора Инициализацию экземпляра такой структуры можно произвести при
помощи функции, подобной функции init_gdt_descriptor, описанной в файле
tos.c: // Младшее слово базового
адреса descr->base_hi = (unsigned char)(base >> 16); // Зарезервированное поле, должно быть
Например, запись в третий по счёту элемент GDT информации о сегменте данных с
сегментным адресом _DS и пределом 0xffff будет выглядеть
так:
TYPE_DATA_DESCR SEG_PRESENT_BIT SEG_WRITABLE); Макрос MK_LIN_ADDR
определен в файле tos.h и служит для преобразования адреса реального режима
формата сегмент:смещение в физический адрес: Специальный регистр
процессора 286 LDTR имеет длину 16 разрядов и содержит селектор дескриптора,
описывающего текущую таблицу LDT. В данном курсовом проекте я не использую
регистр LDTR и не создаю таблицы LDT, в моем варианте достаточно обойтись только
одним кольцом защиты (0) процессора и только таблицей GDT. При переходе в защищенный режим
программа совершает следующие операции: Подготовка в оперативной памяти
глобальной таблицы дескрипторов GDT. В этой таблице создаются дескрипторы для
всех сегментов, которые будут нужны программе сразу после того, как она
переключится в защищённый режим. Для обеспечения возможности возврата из
защищённого режима в реальный записывает адрес возврата в реальный режим в
область данных BIOS по адресу 0040h:0067h, а также пишет в CMOS-память в ячейку
0Fh код 5. Этот код обеспечит после выполнения сброса процессора передачу
управления по адресу, подготовленному нами в области данных BIOS по адресу
0040h:0067h. Открывает адресную линию A20 (попробуем оперировать блоками памяти выше 1
Мб). Запоминает в оперативной памяти содержимое сегментных регистров, которые
необходимо сохранить для возврата в реальный режим, в частности, указатель стека
реального режима. Необходимые функции для этого
реализованы в файлах tos.c и TOSSYST.ASM: Остальные действия, необходимые для перехода в защищенный режим, описаны в
функции protected_mode() модуля TOSSYST.ASM: mov ax,40h ; из
защищённого режима in al, INT_MASK_PORT Открытие
линии A20 производится вызовом функции enable_a20(), описанной в файле
TOSSYST.ASM: ENDP enable_a20 Программируем при помощи функции set_int_ctrlr(), описанной в
файле TOSSYST.ASM каскад контроллеров прерываний (Master и Slave) для работы в
защищенном режиме (описание работы прерываний в защищенном режиме приведено
ниже): Загружаем регистры IDTR и
GDTR: mov ax, 0001h Для того, чтобы
вернуть процессор 80286 из защищённого режима в реальный, необходимо выполнить
аппаратный сброс (отключение) процессора. Это реализуется в функции real_mode(),
описанной в файле TOSSYST.ASM: out STATUS_PORT,
al mov ax, DGROUP in al, INT_MASK_PORT mov es, ax Функция disable_a20(), описанная в файле TOSSYST.ASM
закрывает адресную линию A20: out KBD_PORT_A, al Обработка прерываний и исключений в защищённом режиме по аналогии с
реальным режимом базируется на таблице прерываний. Но таблица прерываний
защищённого режима является таблицей дескрипторов, которая содержит так
называемые вентили прерываний, вентили исключений и вентили задач. Таблица
прерываний защищённого режима называется дескрипторной таблицей прерываний IDT
(Interrupt Descriptor Table). Также как и таблицы GDT и LDT, таблица IDT
содержит 8-байтовые дескрипторы. Причём это системные дескрипторы - вентили
прерываний, исключений и задач. Поле TYPE вентиля прерывания содержит значение
6, а вентиля исключения - значение 7. Расположение определяется содержимым
5-байтового внутреннего регистра процессора IDTR. Формат регистра IDTR полностью
аналогичен формату регистра GDTR, для его загрузки используется команда LIDT.
Так же, как регистр GDTR содержит 24-битовый физический адрес таблицы GDT и её
предел, так и регистр IDTR содержит 24-битовый физический адрес дескрипторной
таблицы прерываний IDT и её предел. Регистр IDTR программа загружает перед
переходом в защищённый режим, в функции protected_mode() модуля TOSSYST.ASM при
помощи вызова функции set_int_ctrlr(), описанной в файле TOSSYST.ASM. Для
обработки особых ситуаций - исключений - разработчики процессора i80286
зарезервировали 31 номер прерывания. Каждому исключению соответствует одна из
функций exception_XX() из модуля EXCEPT.C. Собственно, описав реакцию программы
на каждое исключение можно обрабатывать любые ошибки защищенного режима. В моем
случае достаточно завершать программу при возникновении любого исключения с
выдачей на экран номера возникшего исключения. Поэтому функции exception_XX()
просто вызывают prg_abort(), описанной там же, и передают ей номер возникшего
исключения. Функция prg_abort() переключает процессор в реальный режим, выводит
сообщение с данными возникшего исключения и завершает работу
программы. Теперь разберемся с аппаратными прерываниями, которые нас не
интересуют в данной программе, однако это не мешает им происходить. Для этого в
модуле INTPROC.C описаны две функции заглушки iret0() и iret1(), которые
собственно ничего не делают кроме того, что выдают на контроллеры команды конца
прерывания. Функция iret0() относится к первому контроллеру (Master), а вторая –
ко второму (Slave). Неплохо было бы включить в программу поддержку
программного прерывания 30h, чтобы можно было получать данные с клавиатуры. Это
реализовано в модуле KEYBOARD.ASM, в функции Int_30h_Entry(). В IDT помещается
вентиль программного прерывания, который вызывает данную функцию в момент
прерывания 30h. После запуска программа переходит в защищённый режим и
размаскирует прерывания от таймера и клавиатуры. Далее она вызывает в цикле
прерывание int30h (ввод символа с клавиатуры), и выводит на экран скан-код
нажатой клавиши и состояние переключающих клавиш (таких, как CapsLock, Ins, и
т.д.). Если окажется нажатой клавиша ESC, программа выходит из цикла.
Обработчик аппаратного прерывания клавиатуры - процедура с именем Keyb_int из
модуля KEYBOARD.ASM. После прихода прерывания она выдаёт короткий звуковой
сигнал (функция beep() из модуля TOSSYST.ASM), считывает и анализирует скан-код
клавиши, вызвавшей прерывание. Скан-коды классифицируются на обычные и
расширенные (для 101-клавишной клавиатуры). В отличие от прерывания BIOS
INT16h, мы для простоты не стали реализовывать очередь, а ограничились
записью полученного скан-кода в глобальную ячейку памяти key_code. Причём
прерывания, возникающие при отпускании клавиш, игнорируются. Запись скан-кода
в ячейку key_code выполняет процедура Keyb_PutQ() из модуля KEYBOARD.ASM. После
записи эта процедура устанавливает признак того, что была нажата клавиша -
записывает значение 0FFh в глобальную переменную key_flag. Программное
прерывание int30h опрашивает состояние key_flag. Если этот флаг
оказывается установленным, он сбрасывается, вслед за чем обработчик int30h
записывает в регистр AX скан-код нажатой клавиши, в регистр BX - состояние
переключающих клавиш на момент нажатия клавиши, код которой передан в регистре
AX. Ну и последнее, требующееся прерывание – это аппаратное прерывание
таймера. Обработка этого прерывания реализована в функции Timer_int() модуля
TIMER.C. Эта функция служит для переключения процессора между задачами. Более
подробно я рассмотрю ее работу в следующей главе курсового проекта. idt_len dw 0 Я пошел
в данном курсовом проекте самым простым способом – реализации мультизадачности
через аппаратный таймер компьютера. Реализация более сложных алгоритмов явно
тянет на дипломный проект. Как известно, таймер вырабатывает прерывание IRQ0
примерно 18,2 раза в секунду. Можно использовать данный факт для переключения
между задачами, выделяя каждой квант времени. Я не буду здесь реализовывать
механизм приоритетов задач. Все выполняемые задачи имеют равный приоритет.
Для реализации разделения ресурсов компьютера между задачами и их взаимодействию
друг с другом и средой исполнения (можно даже ее назвать операционной системой),
я реализовал механизм семафоров. В моем случае семафор представляет собой
ячейку памяти, отражающая текущее состояние ресурса - свободен или занят. Я
иду еще на одно упрощение - не создаю здесь таблицы LDT для каждой задачи. Все-
таки это не настоящая ОС, а ее так скажем, модель. Настоящие многозадачные
ОС квантуют время не на уровне программы, а на уровне задачи, так как каждая
программа может иметь несколько параллельно выполняющихся потоков. Я не буду
здесь организовывать механизм потоков. Это, я думаю, простительно, так как он не
реализован полностью даже в Linux. Буду исходить из предпосылки, что одна
программа равна одной задаче. Для хранения
контекста неактивной в настоящей момент задачи процессор i80286 использует
специальную область памяти, называемую сегментом состояния задачи TSS (Task
State Segment). Формат TSS представлен на рис. 4. Сегмент TSS адресуется
процессором при помощи 16-битного регистра TR (Task Register), содержащего
селектор дескриптора TSS, находящегося в глобальной таблице дескрипторов GDT
(рис. 5). Многозадачная операционная система для каждой задачи должна
создавать свой TSS. Перед тем как переключиться на выполнение новой задачи,
процессор сохраняет контекст старой задачи в её сегменте TSS. word sp0; // указатель стека кольца 0 word ss2; word
bp; 3.5.2 Переключение задач. В качестве способа
переключения между задачами выберем команду JMP. Неудобство в этом случае
представляет то, что если, к примеру, задача 1 вызвала задачу 2, то вернуться к
задаче 2 можно только вызвав снова команду JMP и передав ей TSS задачи 1.
Реализация альтернативного метода через команду CALL позволяет создавать
механизм вложенных вызовов задач, но выглядит гораздо более трудоемким и требует
организации вентилей вызова задач. PROC _jump_to_task
NEAR mov [new_select],ax ; запоминаем его Переключение задач происходит в функции Timer_int() из модуля
TIMER.C. Эта функция вызывается по прерыванию таймера. Выбор какая задача
получит процессор в данный момент решает диспетчер задач, организованный как
функция dispatcher(), описанная в модуле TIMER.C. Диспетчер работает по самому
простому алгоритму – по кругу переключает процессор между задачами. Разделение ресурсов для задач организовано в файле
SEMAPHOR.C. Сам семафор представляет собой целое 2-х байтное число (int). В
принципе можно было обойтись и одним битом, но это требует несколько более
сложного кода. Так как операционная система у меня получается ну очень
крошечная, я думаю будет достаточно предположить, что максимальное количество
семафоров в системе будет равно 5. Поэтому в файле SEMAPHOR.C задан статический
массив из 5 семафоров: sem_clear() – процедура сброса
семафора, Исполняющиеся задачи
организованы как просто функции, в модуле TASKS.C. Задачи task2() и flipflop_task() работают в бесконечных циклах, рисуя
на экране двигающиеся линии, тем самым обозначая свою работу. Задача
flipflop_task() работает с меньшим периодоми только тогда, когда установлен
семафор 1. Задача keyb_task() вводит символы с клавиатуры и отображает скан-
коды нажатых клавиш, а также состояние переключающих клавиш на экране. Если
нажимается клавиша ESC, задача устанавливает семафор номер 0. Работающая
параллельно главная задача ожидает установку этого семафора. Как только семафор
0 окажется установлен, главная задача завершает свою работу и программа
возвращает процессор в реальный режим, затем передаёт управление MS-DOS. 4.1 Файл TOS.INC. Определение
констант и структур для модулей, составленных на языке
ассемблера. SHUT_DOWN equ
0feh EOI equ 20h L_SHIFT
equ 0000000000000001b L_CTRL equ
0000000000000100b L_ALT equ
0000000000010000b CAPS_LOCK equ
0000000001000000b STRUC
idtr_struc 4.2 Файл TOS.H. Определение констант и
структур для модулей, составленных на языке Си. #define CODE_SELECTOR 0x08 //
сегмент кода #define TASK_2_SELECTOR 0x20 // задача
TASK_2 #define IDT_SELECTOR 0x38 //
талица IDT #define KEYB_TASK_SELECTOR 0x48 // задача обработки typedef struct unsigned xsystem : 1; typedef struct descriptor unsigned char base_hi; // Базовый адрес
сегмента (старший байт) }
descriptor; word selector; // Структура сегмента состояния задачи TSS word ss0; word ip; // регистры
процессора word es; #define TSS_SIZE (sizeof(tss)) #define COLOR_VID_MEM 0xb8000L #define BW_80_MODE 0x02 // монохромный, 80 символов #define TYPE_CODE_DESCR 0x18 #define
TYPE_TASK_GATE 0x85 #define SEG_READABLE 0x02 #define MASTER8259A 0x20 // и смещения // Тип указателя на функцию типа void без
параметров #include
<stdlib.h> // Определения вызываемых
функций void Init_And_Protected_Mode_Entry(void); word load_task_register(word tss_selector); void
load_idtr(unsigned long idt_ptr, word idt_size); void enable_interrupt(void); void
init_tss(tss *t, word cs, word ds, word limit,
unsigned char type); void exception_2(void); //{
prg_abort(2); } void exception_5(void); //{
prg_abort(5); } void exception_8(void); //{
prg_abort(8); } void exception_B(void); //{
prg_abort(0xB); } void exception_E(void); //{
prg_abort(0xE); } void exception_11(void); //{
prg_abort(0x11); } void exception_14(void); //{
prg_abort(0x14); } void exception_17(void); //{
prg_abort(0x17); } void exception_1A(void); //{
prg_abort(0x1A); } void exception_1D(void); //{
prg_abort(0x1D); } void iret0(void); // --------------------------------------
// --------------------------------------
{ (word)&exception_0,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 0 { (word)&exception_2,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 2 { (word)&exception_4,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 4 { (word)&exception_6,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 6 { (word)&exception_8,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 8 { (word)&exception_A,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // A { (word)&exception_C,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // C { (word)&exception_E,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // E { (word)&exception_10,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 10 { (word)&exception_12,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 12 { (word)&exception_14,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 14 { (word)&exception_16,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 16 { (word)&exception_18,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 18 { (word)&exception_1A,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1A { (word)&exception_1C,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1C { (word)&exception_1E,
CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1E // { (word)&Keyb_int, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE,
0 }, // 21 { 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 }, // 24 { (word)&iret0,
CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 26 { (word)&iret1,
CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 28 { (word)&iret1,
CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 },// 2A { (word)&iret1,
CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2C { (word)&iret1,
CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2E // Обработчик для
программного прерывания, которое {
(word)&Int_30h_Entry, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, //
30 // -----------------------------------------
-- tss main_tss; // TSS главной задачи tss keyb_task_tss; // TSS
задач обслуживания // ------------------------------------------- unsigned char task_2_stack[1024]; word y=0; // номер текущей строки для вывода на
экран // ------------------------------------------- textbackground(LIGHTGRAY); // Выводим
сообщение // Загружаем регистр TR
селектором главной задачи jump_to_task(TASK_1_SELECTOR); vi_print(0, y++ ," Вернулись в главную задачу",
0x7f); enable_interrupt(); // разрешаем прерывание
таймера // как
этот семафор окажется установлен, возвращаемся // клавиатуры,
которая работает независимо от sem_clear(0); // сброс
семафора 0 // передача управления MS-DOS // ----------------------------------- void
init_tss(tss *t, word cs, word ds, t->ds = ds; // поля ds, es, ss
устанавливаем t->sp = (word)sp; // смещение
стека // Функция инициализации дескриптора в таблице GDT unsigned long base, //
Старший байт базового адреса descr->type_dpl =
type; // сброшено в 0 всегда (для процессоров 286) // Инициализация всех таблиц и вход void
Init_And_Protected_Mode_Entry(void) init_gdt_descriptor(&gdt[1],
MK_LIN_ADDR(_CS, 0), init_gdt_descriptor(&gdt[2], MK_LIN_ADDR(_DS, 0), MK_LIN_ADDR(_DS, &task_1_tss), MK_LIN_ADDR(_DS, &task_2_tss), MK_LIN_ADDR(_DS, &main_tss), //
Инициализируем TSS для задач TASK_1, TASK_2 init_tss(&task_2_tss, CODE_SELECTOR, DATA_SELECTOR,
task_2_stack+ // дескриптор для сегмента видеопамяти //
Инициализация для монохромного режима 3999, TYPE_DATA_DESCR
SEG_PRESENT_BIT SEG_WRITABLE);
init_gdt_descriptor(&gdt[6], COLOR_VID_MEM, printf("\nИзвините, этот
видеорежим недопустим."); MK_LIN_ADDR(_DS,
&idt), init_gdt_descriptor(&gdt[8], // Инициализация TSS для задачи KEYB_TASK keyb_task_stack +
sizeof(keyb_task_stack), keyb_task); MK_LIN_ADDR(_DS, &keyb_tss), //
Инициализация TSS для задачи KEYB обработки ввода с клавиатуры // Инициализация элемента 10 таблицы GDT TYPE_TSS_DESCR SEG_PRESENT_BIT); flipflop_stack + sizeof(flipflop_stack),
flipflop_task);
protected_mode(MK_LIN_ADDR(_DS, &gdt), sizeof(gdt), #include <dos.h> #include
"screen.h" // Задача TASK_1 " возврат управления главной задаче", 0x70); } char Buf[B_SIZE
+ 1]; // Буфер вывода задачи 2 Label1.Pos =
0; Buf[Label2.Pos] = '\\'; // Периодически выводим на экран
движки, // соответствует выведенной на экран строке. if (flipflop1)
} long delay_cnt = 0l; // работает только тогда, когда установлен
static TLabel Label1; Label1.Dir = 1; vi_print(30, 12, "Работает задача 0:",
0x7f); asm cli asm sti // Эта задача вводит символы с клавиатуры // Если нажимается клавиша ESC, задача // ожидает установку этого
семафора. Как только // процессор в реальный режим,
затем передаёт vi_print(32, 21, " Key status:
.... ", 0x20); vi_put_word(45, 21, keyb_status, 0x4f); 4.5 Файл
SEMAPHOR.C. Содержит процедуры для работы с семафорами. #include "tos.h" // Параметр sem - номер
сбрасываемого семафора //
Параметр sem - номер устанавливаемого семафора // Параметр sem - номер ожидаемого семафора 4.6 Файл TIMER.C. Процедуры для работы с
таймером и диспетчер задач. Cодержит обработчик аппаратного прерывания
таймера, который периодически выдаёт звуковой сигнал и инициирует работу
диспетчера задач. Диспетчер задач циклически перебирает селекторы TSS задач,
участвующих в процессе разделения времени, возвращая селектор той задачи,
которая должна стать активной. В самом конце обработки аппаратного прерывания
таймера происходит переключение именно на эту задачу. #include "tos.h" // ----------------------------------
--------- void Timer_int(void); // Обработчик аппаратного прерывания таймера //
Периодически выдаём звуковой сигнал // Выдаём в контроллер команду
конца // селектор TSS которой получаем от // -------------------------------------- // задач, участвующих в параллельной работе, KEYBIN_TASK_SELECTOR, // задачи, селекторы TSS которых
находятся else #include <stdio.h> void
prg_abort(int err); void exception_0(void) {
prg_abort(0); } void exception_3(void) { prg_abort(3);
} void exception_6(void) { prg_abort(6); } void exception_9(void) { prg_abort(9); } void
exception_C(void) { prg_abort(0xC); } void exception_F(void) {
prg_abort(0xF); } void exception_12(void) {
prg_abort(0x12); } void exception_15(void) {
prg_abort(0x15); } void exception_18(void) {
prg_abort(0x18); } void exception_1B(void) {
prg_abort(0x1B); } void exception_1E(void) {
prg_abort(0x1E); } // Аварийный выход из программы vi_print(1, y++,"ERROR!!! ---
> Произошло исключение", 0xc); gotoxy(1,
++y); textbackground(BLACK); #include <stdio.h> // Заглушки
для необрабатываемых mov al,EOI // --------------------
--------------------------------------- out
SLAVE8259A,al #include <stdio.h> extern word key_code; { IDEAL ; Модуль обслуживания клавиатуры EXTRN
_beep:PROC PROC _Keyb_int NEAR jz pause_key call
Keyb_PutQ jz
pause_key1 mov
al, 0 cmp al, 0e1h intkeyb_exit_1: intkeyb_exit: out MASTER8259A,al cmp ax, 002ah ; L_SHIFT down cmp ax, 00aah ; L_SHIFT up @@kb2: jmp
keyb_putq_exit mov [_keyb_status], ax or ax, L_CTRL mov ax,
[_keyb_status] jnz @@kb7 cmp ax, 0e09dh ; R_CTRL up cmp ax, 0038h ; L_ALT down cmp ax, 00b8h ; L_ALT up @@kb10: jmp
keyb_putq_exit mov [_keyb_status], ax xor ax, CAPS_LOCK jmp
keyb_putq_exit mov [_keyb_status], ax cmp ax, 0045h ; NUM_LOCK up @@kb17: jnz @@kb19 cmp ax, 0e0d2h ; INSERT down ; Обработчик программного прерывания PROC _Int_30h_Entry NEAR ; Проверяем флаг, который устанавливается ; Сбрасываем флаг после прихода прерывания 4.11 Файлы SCREEN.H и SCREEN.C – модуль для работы
с видеоадаптером. #define B_SIZE 70 char Dir; // Направление движения 4.11.2 SCREEN.C #include "tos.h" char
hex_tabl[] = "0123456789ABCDEF"; // байта chr с экранными
атрибутами attr. temp =
hex_tabl[(chr & 0xf0) >> 4]; // Вывод
слова на экран, координаты (x,y), void
vi_put_word(unsigned int x, vi_put_byte(x+2, y,
chr & 0xff, attr); void vi_putch(unsigned int
x, offset = (y*160) + (x*2); //
Вывод строки s на экран, координаты - (x,y), while (*s) vi_print(0, 0, // Вывод бегущей строки Buf[Label1-
>Pos] = ' '; // Если не дошли до крайней левой позиции Label1->Dir = 1; // Если не дошли до крайней
правой позиции Label1->Dir = 0; // Если не дошли до крайней левой позиции Label2->Dir = 1; // Если не дошли до крайней
правой позиции Label2->Dir = 0; 4.12 Файл TOSSYST.ASM. Процедуры
для инициализации, перехода в защищённый режим и возврата в реальный режим, для
загрузки регистра TR и переключения задач. ;
Область памяти для инициализации IDTR gdt_ptr dw (8*15)-1 ; размер GDT, 15
элементов ; на которую будет происходить переключение ;
используется для возврата в реальный режим
PUBLIC _real_mode,_protected_mode,_jump_to_task ; -------------------------------------------
------------------------ ; void protected_mode(unsigned long gdt_ptr, unsigned int
gdt_size, PROC _protected_mode NEAR mov dx,[bp+6] ; ст. слово адреса GDT mov
ax,[bp+8] ; получаем размер GDT mov ax,[bp+10d] ; получаем селектор сегмента кода mov [protect_sel], dx ; перехода far jmp
mov ax,40h ; из защищённого режима ; Запрещаем и маскируем все
прерывания ; Записываем код возврата в CMOS-память call enable_a20 ; открываем линию A20 ;
Перепрограммируем контроллер прерываний
mov ah,28 mov ax, 0001h ; переключаем
процессор LABEL flush FAR mov ax, 0 ;
Возврат в реальный режим. PROC _real_mode
NEAR hlt mov ds, ax out
INT_MASK_PORT, al out CMOS_PORT,al ; Загрузка регистра TR. ; -----------------------------
-------------------------- ret ; Переключение на задачу. ; -----------------------------------
-------------------- mov [new_select],ax ;
запоминаем его ; ----------------------------
-- out STATUS_PORT, al ; Закрываем линию A20 out STATUS_PORT,
al ; -----------------------------------------------------------
; void load_idtr(unsigned long idt_ptr, word idt_size); mov bp,sp ; Запоминаем адрес IDTR в
структуре ; Получаем предел IDT и запоминаем его в структуре ; ---------------------------------- mov al, 11 jmp SHORT $+2 ; -------------------------- in al,KBD_PORT_B loop beep0 ;
Задержка выполнения программы xor
cx,cx ; ----------------------- in
al, INT_MASK_PORT Процессоры семейства Intel x86
реализуют необходимые средства для организации мультизадачных ОС с разделением
адресного пространства и виртуальной памяти. В процессе написания данного
курсового проекта мной были изучена организация работы защищенного режима
процессоров 80286, адресация ими свыше 1 Мб памяти, работа с прерываниями в
защищенном режиме процессора, организация мультизадачных операционных
систем.