Программирование и комп-ры

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


  Министерство образования Республики Беларусь Белорусский Государственный
                 Университет Информатики и Радиоэлектроники

                                 Кафедра ЭВМ

                            Пояснительная записка
                        к курсовому проекту по курсу
                                 "СПO ЭВМ",
                                  на тему:
       "DOS-extender для компилятора Borland C++ 3.1, защищенный режим
       процессора 80286, организация многозадачной работы процессора”



                                              Выполнил:
                                              студент группы 500501
                                              Балахонов Е.В.

                                 Минск 2000


                                 Оглавление.

1. Введение.     2
  1.1 Уровни программной поддержки защищенного режима.  2
    1.1.1 Интерфейс BIOS.   2
    1.1.2 интерфейс драйвера HIMEM.SYS.      2
    1.1.3 интерфейс EMS/VCPI.     3
    1.1.4 интерфейс DPMI.   3
    1.1.5 расширители DOS (DOS-экстендеры).  3
  1.2 Текущее положение дел в мире DOS-extender-ов.     4
2. Обоснование выбора средств.    4
3. Реализация работы программы в защищенном режиме процессора 80286.    5
  3.1 Адресация защищенного режима процессора 80286.    5
  3.2 Переход в защищенный режим процессора 80286 8
  3.3 Возврат в реальный режим процессора.   10
  3.4 Обработка прерываний в защищенном режиме.   11
  3.5 Реализация мультизадачности.     13
    3.5.1 Контекст задачи.  14
    3.5.2 Переключение задач.     15
    3.5.3 Разделение ресурсов.    16
    3.5.4 Задачи.      16
4. Полные исходные тексты программы.   17
  4.1 Файл TOS.INC. Определение констант и структур для модулей,
  составленных на языке ассемблера.    17
  4.2 Файл TOS.H. Определение констант и структур для модулей, составленных
  на языке Си.   18
  4.3 Файл TOS.H. Основной файл программы.   19
  4.4 Файл TASKS.C. Содержит функции задач.  24
  4.5 Файл SEMAPHOR.C. Содержит процедуры для работы с семафорами. 26
  4.6 Файл TIMER.C. Процедуры для работы с таймером и диспетчер задач.   27
  4.7 Файл EXCEPT.C. Обработка исключений.   28
  4.8 Файл INTPROC.C. Заглушки для аппаратных прерываний.    29
  4.9 Файл KEYB.C. Ввод символа с клавиатуры.     30
  4.10 Файл KEYBOARD.ASM. Процедуры для работы с клавиатурой.      30
  4.11 Файлы SCREEN.H и SCREEN.C – модуль для работы с видеоадаптером.   34
    4.11.1 SCREEN.H    34
    4.11.2 SCREEN.C    34
  4.12 Файл TOSSYST.ASM. Процедуры для инициализации, перехода в защищённый
  режим и возврата в реальный режим, для загрузки регистра TR и переключения
  задач.    36
5. Выводы.  41
6. Литература.   41



                                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 (http://www.zrdx.da.ru)
        Маленький и функциональный  DOS-extender  для  Watcom  C++  и  32-х
        битных  исполняемых  файлов  формата  OS/2   LE.   Используется   в
        коммерческих программах, таких как антивирус AVP для DOS32.
      - WDOSX by Michael Tippach (http://www.wuschel.demon.co.uk)
        Самый впечатливший меня 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.


      [pic]
       Рис. 1. Схема преобразования логического адреса в физический в
                     защищенном режиме процессора 80286.

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

      Каждый элемент таблицы дескрипторов имеет следующий формат:

      [pic]

      Общая его длина составляет 8 байт,  в  которых  расположены  следующие
поля:
    . поле базового адреса длиной 24 бита содержит физический адрес
      сегмента, описываемого данным дескриптором;
    . поле предела содержит размер сегмента в байтах, уменьшенный на
      единицу;
    . поле доступа описывает тип сегмента (сегмент кода, сегмент данных и
      др.);
    . зарезервированное поле длиной 16 бит для процессора i80286 должно
      содержать нули, это поле используется процессорами i80386 и i80486
      (там, в частности, хранится старший байт 32-разрядного базового адреса
      сегмента).
   Поле доступа, занимающее в дескрипторе один байт  (байт  доступа)  служит
для классификации дескрипторов. На рис. 2  приведены  форматы  поля  доступа
для трёх типов дескрипторов - дескрипторов сегментов кода, сегментов  данных
и системных.
[pic]
                  Рис. 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.
[pic]
       Рис. 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.
[pic]
                Рис. 4. Формат сегмента состояния задачи TSS.
      Сегмент TSS адресуется процессором при помощи 16-битного  регистра  TR
(Task  Register),  содержащего  селектор  дескриптора  TSS,  находящегося  в
глобальной таблице дескрипторов GDT (рис. 5).
[pic]
              Рис. 5. Дескриптор сегмента состояния задачи TSS.
      Многозадачная операционная система для каждой задачи должна  создавать
свой 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 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#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 Мб памяти, работа с прерываниями  в  защищенном  режиме  процессора,
организация мультизадачных операционных систем.



                               6. Литература.



   1. «Защищенный режим процессоров Intel 80286/80386/80486» © Александр
      Фролов, Григорий Фролов Том 6, М.: Диалог-МИФИ, 1993, 234 стр.
   2. «MS-DOS для программиста» © Александр Фролов, Григорий Фролов

      Том 18, часть 1, М.: Диалог-МИФИ, 1995, 254 стр.
   3. «MS-DOS для программиста» © Александр Фролов, Григорий Фролов

      Том 19, часть 2, М.: Диалог-МИФИ, 1995, 253 стр.
   4. «Язык Ассемблера для IBM PC и программирования» © Питер Абель, М.:
      «Высшая школа», Москва, 1992, 444 стр.
   5. «Язык программирования для персонального компьютера Си», © С.О.
      Бочков, Д.М. Субботин, М.: Диалог-МИФИ, 1990, 383 стр.
   6. Материалы WEB-сервера для разработчиков фирмы Intel
      http://developer.intel.com





смотреть на рефераты похожие на "DOS-extender для компилятора Borland C++ 3.1"