![]() |
|
|
Реферат: DOS-extender для компилятора Borland C++Реферат: DOS-extender для компилятора Borland C++++ 3.1, защищенный режим процессора 80286, организация многозадачной работы процессора
1. Введение.
Операционная система MS DOS, не смотря на свое моральное устаревание, все еще довольно часто находит применение на парке старых ПК, а значит, все еще существует необходимость создания программ для нее. К сожалению, написание программ в реальном режиме процессоров архитектуры Intel x86 осложнено отсутствием возможности использовать в программе оперативную память объемом свыше пресловутых 640 килобайт, а реально свыше 500-620 килобайт. Это ограничение к сожалению преследует MS DOS и аналогичные ей ОС других производителей, начиная с того момента, как горячо любимый в околокомпьютерных кругах Билл Гейтс заявил, что 640 килобайт достаточно для всех возможных задач ПК. Преодоление барьера 640 килобайт в новых версиях MS DOS усложнялось необходимостью совместимости с старыми программами, которые жизненно необходимо было поддерживать. Программирование защищенного режима процессора и расширенной памяти требовало от программистов недюжинных знаний архитектуры процессоров Intel и достаточно трудоемкого программирования.
1.1 Уровни программной поддержки защищенного режима.
Инженерная мысль не стоит на месте, особенно в такой области, как программирование. Задача программной поддержки защищённого режима и поддержки работы с расширенной памятью получила не одно, а сразу несколько решений. Этими решениями стали так называемые уровни программной поддержки защищённого режима и поддержки работы с расширенной памятью: интерфейс BIOS; интерфейс драйвера HIMEM.SYS; интерфейс EMS/VCPI; интерфейс DPMI; расширители DOS (DOS-экстендеры). 1.1.1 Интерфейс BIOS. Интерфейсом самого низкого уровня является интерфейс BIOS, предоставляемый программам в виде нескольких функций прерывания BIOS INT 15h. Интерфейс BIOS позволяет программе перевести процессор из реального режима в защищённый, переслать блок памяти из стандартной памяти в расширенную или из расширенной в стандартную. Этим все его возможности и ограничиваются. Интерфейс BIOS используется для старта мультизадачных операционных систем защищённого режима (таких, как OS/2) или в старых программах, работающих с расширенной памятью в защищённом режиме (например, СУБД ORACLE версии 5.1). 1.1.2 интерфейс драйвера HIMEM.SYS.
С помощью функций, предоставляемых этим драйвером, программа может выполнять различные действия с блоками расширенной памяти, а также управлять адресной линией A20. Основное различие между способом работы с расширенной памятью драйвера HIMEM.SYS и интерфейсом прерывания BIOS INT 15h заключается в том, что первый выполняет выделение программе и внутренний учёт блоков расширенной памяти, а второй рассматривает всю расширенную память как один непрерывный участок. Однако драйвер HIMEM.SYS не открывает для программ доступ к защищённому режиму. Он полностью работает в реальном режиме, а для обращения к расширенной памяти использует либо недокументированную машинную команду LOADALL (если используется процессор 80286), либо возможности процессора 80386, который позволяет адресовать расширенную память в реальном режиме (при соответствующей инициализации системных регистров и таблиц). 1.1.3 интерфейс EMS/VCPI. Используя трансляцию страниц, некоторые драйверы памяти (например, EMM386 или QEMM) могут эмулировать присутствие дополнительной памяти, используя расширенную память. При этом стандартный набор функций управления дополнительной памятью, реализованный в рамках прерывания INT 67h, дополнен еще несколькими функциями для работы в защищённом режиме процессора. Эти новые функции реализуют интерфейс виртуальной управляющей программы VCPI (Virtual Control Programm Interface). Они позволяют устанавливать защищённый и виртуальный режимы работы процессора, работать с расширенной памятью на уровне страниц и устанавливать специальные отладочные регистры процессора i80386. Интерфейс VCPI облегчает использование механизма трансляции страниц, освобождая программиста от необходимости работать с системными регистрами процессора. 1.1.4 интерфейс DPMI. Интерфейс DPMI (DOS Protected Mode Interface - интерфейс защищённого режима для DOS) реализуется модулем, называющимся сервером DPMI. Этот интерфейс доступен для тех программ, которые работают на виртуальной машине WINDOWS или OS/2 версии 2.0 (позже мы обсудим некоторые детали, связанные с использованием интерфейса DPMI в WINDOWS). Интерфейс DPMI предоставляет полный набор функций для создания однозадачных программ, работающих в защищённом режиме. В этом интерфейсе имеются функции для переключения из реального режима в защищённый и обратно, для работы с локальной таблицей дескрипторов LDT, для работы с расширенной и стандартной памятью на уровне страниц, для работы с прерываниями (в том числе для вызова прерываний реального режима из защищённого режима), для работы с отладочными регистрами процессора i80386. Это наиболее развитый интерфейс из всех рассмотренных ранее. 1.1.5 расширители DOS (DOS-экстендеры). Последний, самый высокий уровень программной поддержки защищённого режима - расширители DOS или DOS-экстендеры (DOS-extender). Они поставляются, как правило, вместе со средствами разработки программ (трансляторами) в виде библиотек и компонуются вместе с создаваемой программой в единый загрузочный модуль. DOS-экстендеры значительно облегчают использование защищённого режима и расширенной памяти в программах, предназначенных для запуска из среды MS-DOS. Программы, составленные с использованием DOS-экстендеров, внешне очень похожи на обычные программы MS-DOS, однако они получают управление, когда процессор уже находится в защищённом режиме. К формируемому с помощью DOS-экстендера загрузочному модулю добавляются процедуры, необходимые для инициализации защищённого режима. Эти процедуры первыми получают управление и выполняют начальную инициализацию таблиц GDT, LDT, IDT, содержат обработчики прерываний и исключений, систему управления виртуальной памятью и т.д. 1.2 Текущее положение дел в мире DOS-extender-ов. Еще несколько лет назад целые фирмы зарабатывали себе на существование созданием различных модификаций DOS extender-ов. Например довольно известный externder фирмы Phar Lap. После перехода большинства пользователей в среду Win32 необходимость в DOS extender-ах резко сократилась и большинство таких фирм, не сумев сориентироваться в изменившихся условиях, прекратили свое существование. Многие фирмы, разрабатывавшие компиляторы для DOS, включали в поставку своих сред программирования DOS-extender-ы собственной разработки. Таким примером может служить фирма Borland (ныне подразделение фирмы Corel) с ее Borland Pascal, Borland C++ и расширителем DOS RTM. В данный момент доступно несколько DOS-extender-ов по свободной лицензии, которые могут использоваться кем угодно для любых целей. И это понятно, денег на них сейчас не заработаешь. Примеры таких программ: ZRDX by Sergey Belyakov Маленький и функциональный DOS-extender для Watcom C++ и 32-х битных исполняемых файлов формата OS/2 LE. Используется в коммерческих программах, таких как антивирус AVP для DOS32. WDOSX by Michael Tippach Самый впечатливший меня DOS-extender. Список поддерживаемых функций просто поражает. Поддерживает все распространенные среды программирования: Visual C++ 4 и позже, Borland C++ 4 и позже, Delphi 2 и позже. При желании никто не запрещает использовать Assembler.
2. Обоснование выбора средств.
DOS-экстендеры обычно поставляются в комплекте с трансляторами, редакторами связей, отладчиками и библиотеками стандартных функций (например, библиотеками для транслятора языка Си). Код DOS-extender линкуется либо уже к готовому исполняемому файлу специальной программой (чаще), либо линковка полностью проходит при помощи программы-линкера, специально разработанного для данного компилятора. В настоящий момент науке известны всего один DOS-extender для Borland C++ 3.1. Это программа фирмы Phar Lap, не имеющая собственного названия. Фирмы, к сожалению, давно уже нет, как и исходных текстов этого DOS-extender-а. В него входил собственная программа – линкер и набор специальных библиотек функций специально для Borland C++ 3.1, которой и проводилась окончательная сборка EXE-файла.
Написание собственной среды разработки, вроде программ-линкеров и собственных трансляторов языка Ассемблера явно выходит за переделы данного курсового проекта. Поэтому остановимся на разработке набора функций, позволяющих: реализовать защищенный режим процессора 80286, адресовать до 16 Мб памяти, обрабатывать прерывания реального режима DOS реализуем набор средств для создания параллельно выполняющихся потоков в среде DOS.
После разработки необходимых средств, напишем программу–пример с их использованием. Собственно это получится не просто программа, а некий прототип многозадачной операционной системы. Итак, согласно заданию буду пользоваться следующими средствами разработки:
Borland C++ 3.1 Borland Turbo Assembler из поставки Borland C++ 3.1
3. Реализация работы программы в защищенном режиме процессора 80286.
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 байт, в которых расположены следующие поля: поле базового адреса длиной 24 бита содержит физический адрес сегмента, описываемого данным дескриптором; поле предела содержит размер сегмента в байтах, уменьшенный на единицу; поле доступа описывает тип сегмента (сегмент кода, сегмент данных и др.); зарезервированное поле длиной 16 бит для процессора i80286 должно содержать нули, это поле используется процессорами i80386 и i80486 (там, в частности, хранится старший байт 32-разрядного базового адреса сегмента). Поле доступа, занимающее в дескрипторе один байт (байт доступа) служит для классификации дескрипторов. На рис. 2 приведены форматы поля доступа для трёх типов дескрипторов - дескрипторов сегментов кода, сегментов данных и системных.
Рис. 2. Форматы поля доступа дескриптора. Поле доступа дескриптора сегментов кода содержит битовое поле R, называемое битом разрешения чтения сегмента. Если этот бит установлен в 1, программа может считывать содержимое сегмента кода. В противном случае процессор может только выполнять этот код. Биты P и A предназначены для организации виртуальной памяти. Их назначение будет описано в разделе, посвящённом виртуальной памяти. Сейчас отметим, что бит P называется битом присутствия сегмента в памяти. Для тех сегментов, которые находятся в физической памяти (мы будем иметь дело в основном с такими сегментами) этот бит должен быть установлен в 1. Любая попытка программы обратиться к сегменту памяти, в дескрипторе которого бит P установлен в 0, приведёт к прерыванию. Бит A называется битом обращения к сегменту и для всех наших программ должен быть установлен в 0. Поле доступа дескриптора сегмента данных имеет битовые поля W и D. Поле W называется битом разрешения записи в сегмент. Если этот бит установлен в 1, наряду с чтением возможна и запись в данный сегмент. В противном случае при попытке чтения выполнение программы будет прервано. Поле D задаёт направление расширения сегмента. Обычный сегмент данных расширяется в область старших адресов (расширение вверх). Если же в сегменте расположен стек, расширение происходит в обратном направлении - в область младших адресов (расширение вниз). Для сегментов, в которых организуются стеки, необходимо устанавливать поле D равным 1. Рассмотрим, как таблица дескрипторов будет выглядеть на языке программирования C. (В дальнейшем где это только возможно будем применять язык С, а Ассемблер – только там, где это необходимо.):
typedef struct descriptor { word limit; // Предел (размер сегмента в байтах) word base_lo; // Базовый адрес сегмента (младшее слово) unsigned char base_hi; // Базовый адрес сегмента (старший байт) unsigned char type_dpl; // Поле доступа дескриптора unsigned reserved; // Зарезервированные 16 бит } descriptor;
Данная структура описана в файле tos.h.
Инициализацию экземпляра такой структуры можно произвести при помощи функции, подобной функции init_gdt_descriptor, описанной в файле tos.c:
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 всегда (для процессоров 286) descr->reserved = 0; }
Например, запись в третий по счёту элемент GDT информации о сегменте данных с сегментным адресом _DS и пределом 0xffff будет выглядеть так:
init_gdt_descriptor(&gdt[2], MK_LIN_ADDR(_DS, 0), 0xffffL, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE);
Макрос MK_LIN_ADDR определен в файле tos.h и служит для преобразования адреса реального режима формата сегмент:смещение в физический адрес:
#define MK_LIN_ADDR(seg,off) (((unsigned long)(seg))<<4)+(word)(off)
Специальный регистр процессора 286 LDTR имеет длину 16 разрядов и содержит селектор дескриптора, описывающего текущую таблицу LDT. В данном курсовом проекте я не использую регистр LDTR и не создаю таблицы LDT, в моем варианте достаточно обойтись только одним кольцом защиты (0) процессора и только таблицей GDT.
3.2 Переход в защищенный режим процессора 80286
При переходе в защищенный режим программа совершает следующие операции: Подготовка в оперативной памяти глобальной таблицы дескрипторов GDT. В этой таблице создаются дескрипторы для всех сегментов, которые будут нужны программе сразу после того, как она переключится в защищённый режим. Для обеспечения возможности возврата из защищённого режима в реальный записывает адрес возврата в реальный режим в область данных BIOS по адресу 0040h:0067h, а также пишет в CMOS-память в ячейку 0Fh код 5. Этот код обеспечит после выполнения сброса процессора передачу управления по адресу, подготовленному нами в области данных BIOS по адресу 0040h:0067h. Запрещает все маскируемые и немаскируемые прерывания. Открывает адресную линию A20 (попробуем оперировать блоками памяти выше 1 Мб). Запоминает в оперативной памяти содержимое сегментных регистров, которые необходимо сохранить для возврата в реальный режим, в частности, указатель стека реального режима. Программирует контроллер прерываний для работы в защищенном режиме. Загружает регистры IDTR и GDTR. Необходимые функции для этого реализованы в файлах tos.c и TOSSYST.ASM: Подготовка GDT осуществляется при помощи описанных выше функции init_gdt_descriptor() и макроса MK_LIN_ADDR(). Остальные действия, необходимые для перехода в защищенный режим, описаны в функции protected_mode() модуля TOSSYST.ASM: Обеспечение возможности возврата в реальный режим: push ds ; готовим адрес возврата mov ax,40h ; из защищённого режима mov ds,ax mov [WORD 67h],OFFSET shutdown_return mov [WORD 69h],cs pop ds Запрет прерываний: сli in al, INT_MASK_PORT and al, 0ffh out INT_MASK_PORT, al mov al,8f out CMOS_PORT,al Открытие линии A20 производится вызовом функции enable_a20(), описанной в файле TOSSYST.ASM: 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 Запоминаем содержимое сегментных регистров SS и ES: mov [real_ss],ss mov [real_es],es Программируем при помощи функции set_int_ctrlr(), описанной в файле TOSSYST.ASM каскад контроллеров прерываний (Master и Slave) для работы в защищенном режиме (описание работы прерываний в защищенном режиме приведено ниже): 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
3.3 Возврат в реальный режим процессора.
Для того, чтобы вернуть процессор 80286 из защищённого режима в реальный, необходимо выполнить аппаратный сброс (отключение) процессора. Это реализуется в функции real_mode(), описанной в файле TOSSYST.ASM:
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
Функция disable_a20(), описанная в файле TOSSYST.ASM закрывает адресную линию 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
3.4 Обработка прерываний в защищенном режиме. Обработка прерываний и исключений в защищённом режиме по аналогии с реальным режимом базируется на таблице прерываний. Но таблица прерываний защищённого режима является таблицей дескрипторов, которая содержит так называемые вентили прерываний, вентили исключений и вентили задач. Таблица прерываний защищённого режима называется дескрипторной таблицей прерываний IDT (Interrupt Descriptor Table). Также как и таблицы GDT и LDT, таблица IDT содержит 8-байтовые дескрипторы. Причём это системные дескрипторы - вентили прерываний, исключений и задач. Поле TYPE вентиля прерывания содержит значение 6, а вентиля исключения - значение 7. Формат элементов дескрипторной таблицы прерываний IDT показан на рис. 3.
Расположение определяется содержимым 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. Сегмент TSS адресуется процессором при помощи 16-битного регистра TR (Task Register), содержащего селектор дескриптора TSS, находящегося в глобальной таблице дескрипторов GDT (рис. 5). Многозадачная операционная система для каждой задачи должна создавать свой TSS. Перед тем как переключиться на выполнение новой задачи, процессор сохраняет контекст старой задачи в её сегменте TSS. Сегмент состояния задачи описан в файле tos.h: 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;
3.5.2 Переключение задач.
В качестве способа переключения между задачами выберем команду JMP. Неудобство в этом случае представляет то, что если, к примеру, задача 1 вызвала задачу 2, то вернуться к задаче 2 можно только вызвав снова команду JMP и передав ей TSS задачи 1. Реализация альтернативного метода через команду CALL позволяет создавать механизм вложенных вызовов задач, но выглядит гораздо более трудоемким и требует организации вентилей вызова задач.
Функция переключения задач называется jump_to_task() и реализована в модуле TOSSYST.ASM:
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
Переключение задач происходит в функции Timer_int() из модуля TIMER.C. Эта функция вызывается по прерыванию таймера. Выбор какая задача получит процессор в данный момент решает диспетчер задач, организованный как функция dispatcher(), описанная в модуле TIMER.C. Диспетчер работает по самому простому алгоритму – по кругу переключает процессор между задачами. 3.5.3 Разделение ресурсов.
Разделение ресурсов для задач организовано в файле SEMAPHOR.C. Сам семафор представляет собой целое 2-х байтное число (int). В принципе можно было обойтись и одним битом, но это требует несколько более сложного кода. Так как операционная система у меня получается ну очень крошечная, я думаю будет достаточно предположить, что максимальное количество семафоров в системе будет равно 5. Поэтому в файле SEMAPHOR.C задан статический массив из 5 семафоров:
word semaphore[5];
Работа задач с семафорами организуется при помощи 3-х функций:
sem_clear() – процедура сброса семафора, sem_set() – процедура установки семафора, sem_wait() – процедура ожидания семафора.
3.5.4 Задачи.
Исполняющиеся задачи организованы как просто функции, в модуле TASKS.C.
Задача task1() выполняется единократно, после чего передает управление операционной системе. Задачи task2() и flipflop_task() работают в бесконечных циклах, рисуя на экране двигающиеся линии, тем самым обозначая свою работу. Задача flipflop_task() работает с меньшим периодом и только тогда, когда установлен семафор 1. Задача keyb_task() вводит символы с клавиатуры и отображает скан-коды нажатых клавиш, а также состояние переключающих клавиш на экране. Если нажимается клавиша ESC, задача устанавливает семафор номер 0. Работающая параллельно главная задача ожидает установку этого семафора. Как только семафор 0 окажется установлен, главная задача завершает свою работу и программа возвращает процессор в реальный режим, затем передаёт управление MS-DOS.
4. Полные исходные тексты программы. >
4.1 Файл 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
4.2 Файл 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; // Зарезервированные 16 бит } 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);
4.3 Файл TOS.H. Основной файл программы.
#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
// { (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 }, // 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; // номер текущей строки для вывода на экран
// ------------------------------------------- // Начало программы // -------------------------------------------
extern int getcpu(void);
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);
// Запускаем планировщик задач
vi_print(0, y++ ," Запущен планировщик задач", 0x70); enable_interrupt(); // разрешаем прерывание таймера
// Ожидаем установки семафора с номером 0. После того, // как этот семафор окажется установлен, возвращаемся // в реальный режим.
// Семафор 0 устанавливается задачей, обрабатывающей ввод с // клавиатуры, которая работает независимо от // главной задаче.
vi_print(18, 24," Для возврата в реальный режим нажмите 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 всегда (для процессоров 286) 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); }
4.4 Файл TASKS.C. Содержит функции задач.
#include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include <mem.h> #include "tos.h" #include "screen.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 long delay_cnt1 = 0l; word flipflop1 = 0; void task2(void) { char Buf[B_SIZE + 1]; // Буфер вывода задачи 2 static TLabel Label1; static TLabel Label2;
memset(Buf, ' ', B_SIZE); Buf[B_SIZE] = 0;
Label1.Pos = 0; Label1.Dir = 1; Buf[Label1.Pos] = '/';
Label2.Pos = B_SIZE; Label2.Dir = 0; Buf[Label2.Pos] = '';
vi_print(30, 15, "Работает задача 2:", 0x7f);
while (1) { // Периодически выводим на экран движки, // каждый раз переключая // семафор номер 1. Этот семафор однозначно // соответствует выведенной на экран строке. asm sti if (delay_cnt1 > 150000l) { asm cli
StepLabel(&Label1, &Label2, Buf);
if (flipflop1) { vi_print(5, 16, Buf, 0x1f); sem_clear(1); } else { vi_print(5, 16, Buf, 0x1f); sem_set(1); } flipflop1 ^= 1; delay_cnt1 = 0l; asm sti } delay_cnt1++; } }
word flipflop = 0; long delay_cnt = 0l;
// Эта задача также периодически выводит на экран // с меньшим периодом. Кроме того, эта задача // работает только тогда, когда установлен // семафор номер 1. void flipflop_task(void) { char Buf[B_SIZE + 1]; // Буфер вывода задачи 2 static TLabel Label1; static TLabel Label2;
memset(Buf, ' ', B_SIZE); Buf[B_SIZE] = 0;
Label1.Pos = 0; Label1.Dir = 1; Buf[Label1.Pos] = '/';
Label2.Pos = B_SIZE; Label2.Dir = 0; Buf[Label2.Pos] = '';
vi_print(30, 12, "Работает задача 0:", 0x7f);
while(1) { asm sti if (delay_cnt > 20000l ) { sem_wait(1); // ожидаем установки семафора asm cli StepLabel(&Label1, &Label2, Buf); vi_print(5, 13, Buf, 0x1f); flipflop ^= 1; delay_cnt = 0l; asm sti } delay_cnt++; } }
word keyb_code;
extern word keyb_status;
// Эта задача вводит символы с клавиатуры // и отображает скан-коды нажатых клавиш // и состояние переключающих клавиш на экране. // Если нажимается клавиша ESC, задача // устанавливает семафор номер 0. // Работающая параллельно главная задача // ожидает установку этого семафора. Как только // семафор 0 окажется установлен, главная задача // завершает свою работу и программа возвращает // процессор в реальный режим, затем передаёт // управление MS-DOS. void keyb_task(void) { vi_print(32, 20, " Key code: .... ", 0x20); vi_print(32, 21, " Key status: .... ", 0x20); while(1) { keyb_code = kb_getch(); vi_put_word(45, 20, keyb_code, 0x4f); vi_put_word(45, 21, keyb_status, 0x4f); if ((keyb_code & 0x00ff) == 1) sem_set(0); } }
4.5 Файл 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 }
4.6 Файл TIMER.C. Процедуры для работы с таймером и диспетчер задач.
Cодержит обработчик аппаратного прерывания таймера, который периодически выдаёт звуковой сигнал и инициирует работу диспетчера задач. Диспетчер задач циклически перебирает селекторы TSS задач, участвующих в процессе разделения времени, возвращая селектор той задачи, которая должна стать активной. В самом конце обработки аппаратного прерывания таймера происходит переключение именно на эту задачу.
#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]); }
4.7 Файл 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++,"ERROR!!! ---> Произошло исключение", 0xc);
real_mode(); // Возвращаемся в реальный режим
// В реальном режиме выводим сообщение об исключении
gotoxy(1, ++y); cprintf(" Исключение %X, нажмите любую клавишу", err); getch();
textcolor(WHITE); textbackground(BLACK); clrscr(); exit(0); }
4.8 Файл 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 } }
4.9 Файл 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); }
4.10 Файл 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
4.11 Файлы SCREEN.H и SCREEN.C – модуль для работы с видеоадаптером.
4.11.1 SCREEN.H
#ifndef SCREEN_H #define SCREEN_H
// Границы перемещения бегунков #define B_SIZE 70
// Структура, описывающая бегунок typedef struct _TLabel { char Pos; // Позиция бегунка char Dir; // Направление движения } TLabel;
extern void StepLabel(TLabel* Label1, TLabel* Label2, char* Buf);
#endif
4.11.2 SCREEN.C
#include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" #include "screen.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, " Threads for DOS, " " Version 0.1/i286, Copyright (c) 2000 Eugeny Balahonov ", 0x30); }
// Вывод бегущей строки void StepLabel(TLabel* Label1, TLabel* Label2, char* Buf) { // Стираем символы меток Buf[Label1->Pos] = ' '; Buf[Label2->Pos] = ' ';
// Если двигаемся налево if (Label1->Dir == 0) { // Если не дошли до крайней левой позиции if (Label1->Pos > 0) { Label1->Pos--; Buf[Label1->Pos] = ''; } else { Label1->Dir = 1; Buf[Label1->Pos] = '/'; } } // Если двигаемся направо else { // Если не дошли до крайней правой позиции if (Label1->Pos < B_SIZE) { Label1->Pos++; Buf[Label1->Pos] = '/'; } else { Label1->Dir = 0; Buf[Label1->Pos] = ''; } }
// Если двигаемся налево if (Label2->Dir == 0) { // Если не дошли до крайней левой позиции if (Label2->Pos > 0) { Label2->Pos--; Buf[Label2->Pos] = ''; } else { Label2->Dir = 1; Buf[Label2->Pos] = '/'; } } // Если двигаемся направо else { // Если не дошли до крайней правой позиции if (Label2->Pos < B_SIZE) { Label2->Pos++; Buf[Label2->Pos] = '/'; } else { Label2->Dir = 0; Buf[Label2->Pos] = ''; } } }
4.12 Файл TOSSYST.ASM. Процедуры для инициализации, перехода в защищённый режим и возврата в реальный режим, для загрузки регистра TR и переключения задач.
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
5. Выводы.
Процессоры семейства Intel x86 реализуют необходимые средства для организации мультизадачных ОС с разделением адресного пространства и виртуальной памяти. В процессе написания данного курсового проекта мной были изучена организация работы защищенного режима процессоров 80286, адресация ими свыше 1 Мб памяти, работа с прерываниями в защищенном режиме процессора, организация мультизадачных операционных систем. |
![]() |
||
НОВОСТИ | ![]() |
![]() |
||
ВХОД | ![]() |
|
Рефераты бесплатно, реферат бесплатно, курсовые работы, реферат, доклады, рефераты, рефераты скачать, рефераты на тему, сочинения, курсовые, дипломы, научные работы и многое другое. |
||
При использовании материалов - ссылка на сайт обязательна. |