DOS-extender для компилятора Borland C++ 3.1
Рефераты >> Программирование и компьютеры >> DOS-extender для компилятора Borland C++ 3.1

Таблица прерываний защищённого режима называется дескрипторной таблицей прерываний IDT (Interrupt Descriptor Table). Также как и таблицы GDT и LDT, таблица IDT содержит 8-байтовые дескрипторы. Причём это системные дескрипторы - вентили прерываний, исключений и задач. Поле TYPE вентиля прерывания содержит значение 6, а вентиля исключения - значение 7.

Формат элементов дескрипторной таблицы прерываний IDT показан на рис. 3.

Рис. 3. Формат элементов дескрипторной таблицы прерываний IDT.

Расположение определяется содержимым 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.

После запуска программа переходит в защищённый режим и размаскирует прерывания от таймера и клавиатуры. Далее она вызывает в цикле прерывание int 30h (ввод символа с клавиатуры), и выводит на экран скан-код нажатой клавиши и состояние переключающих клавиш (таких, как CapsLock, Ins, и т.д.). Если окажется нажатой клавиша ESC, программа выходит из цикла.

Обработчик аппаратного прерывания клавиатуры - процедура с именем Keyb_int из модуля KEYBOARD.ASM. После прихода прерывания она выдаёт короткий звуковой сигнал (функция beep() из модуля TOSSYST.ASM), считывает и анализирует скан-код клавиши, вызвавшей прерывание. Скан-коды классифицируются на обычные и расширенные (для 101-клавишной клавиатуры). В отличие от прерывания BIOS INT 16h, мы для простоты не стали реализовывать очередь, а ограничились записью полученного скан-кода в глобальную ячейку памяти key_code. Причём прерывания, возникающие при отпускании клавиш, игнорируются.

Запись скан-кода в ячейку key_code выполняет процедура Keyb_PutQ() из модуля KEYBOARD.ASM. После записи эта процедура устанавливает признак того, что была нажата клавиша - записывает значение 0FFh в глобальную переменную key_flag.

Программное прерывание int 30h опрашивает состояние key_flag. Если этот флаг оказывается установленным, он сбрасывается, вслед за чем обработчик int 30h записывает в регистр AX скан-код нажатой клавиши, в регистр BX - состояние переключающих клавиш на момент нажатия клавиши, код которой передан в регистре AX.

Ну и последнее, требующееся прерывание – это аппаратное прерывание таймера. Обработка этого прерывания реализована в функции Timer_int() модуля TIMER.C. Эта функция служит для переключения процессора между задачами. Более подробно я рассмотрю ее работу в следующей главе курсового проекта.

Структура элемента дескрипторной таблицы прерываний IDT описана в файле tos.inc:

STRUC idtr_struc

idt_len dw 0

idt_low dw 0

idt_hi db 0

rsrv db 0

ENDS idtr_struc

3.5 Реализация мультизадачности.

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

Как известно, таймер вырабатывает прерывание IRQ0 примерно 18,2 раза в секунду. Можно использовать данный факт для переключения между задачами, выделяя каждой квант времени. Я не буду здесь реализовывать механизм приоритетов задач. Все выполняемые задачи имеют равный приоритет.

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

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

Я иду еще на одно упрощение - не создаю здесь таблицы LDT для каждой задачи. Все-таки это не настоящая ОС, а ее так скажем, модель.

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

3.5.1 Контекст задачи.

Для хранения контекста неактивной в настоящей момент задачи процессор i80286 использует специальную область памяти, называемую сегментом состояния задачи TSS (Task State Segment). Формат TSS представлен на рис. 4.

Рис. 4. Формат сегмента состояния задачи TSS.

Сегмент TSS адресуется процессором при помощи 16-битного регистра TR (Task Register), содержащего селектор дескриптора TSS, находящегося в глобальной таблице дескрипторов GDT (рис. 5).

Рис. 5. Дескриптор сегмента состояния задачи TSS.

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

Сегмент состояния задачи описан в файле tos.h:

typedef struct tss

{

word link; // поле обратной связи

word sp0; // указатель стека кольца 0

word ss0;

word sp1; // указатель стека кольца 1


Страница: