Защищенный режим процессоров Intel 80286/80386/80486© Александр Фролов, Григорий ФроловТом 4, М.: Диалог-МИФИ, 1993, 234 стр. 4.4. Пример мультизадачного монитораДля того, чтобы вы могли почувствовать мультизадачность, мы подготовили пример программы, реализующей параллельную работу нескольких задач в режиме разделения времени. Эта программа состоит из нескольких модулей, составленных на языках ассемблера и Си. Первые два файла предназначены для определения используемых констант и структур данных. Листинг 4. Определение констант и структур для модулей, составленных на языке ассемблера. Файл tos.inc ----------------------------------------------------------- CMOS_PORT equ 70h PORT_6845 equ 63h COLOR_PORT equ 3d4h MONO_PORT equ 3b4h STATUS_PORT equ 64h SHUT_DOWN equ 0feh INT_MASK_PORT equ 21h VIRTUAL_MODE equ 0001 A20_PORT equ 0d1h A20_ON equ 0dfh A20_OFF equ 0ddh EOI equ 20h MASTER8259A equ 20h SLAVE8259A equ 0a0h KBD_PORT_A equ 60h KBD_PORT_B equ 61h L_SHIFT equ 0000000000000001b NL_SHIFT equ 1111111111111110b R_SHIFT equ 0000000000000010b NR_SHIFT equ 1111111111111101b L_CTRL equ 0000000000000100b NL_CTRL equ 1111111111111011b R_CTRL equ 0000000000001000b NR_CTRL equ 1111111111110111b L_ALT equ 0000000000010000b NL_ALT equ 1111111111101111b R_ALT equ 0000000000100000b NR_ALT equ 1111111111011111b CAPS_LOCK equ 0000000001000000b SCR_LOCK equ 0000000010000000b NUM_LOCK equ 0000000100000000b INSERT equ 0000001000000000b STRUC idtr_struc idt_len dw 0 idt_low dw 0 idt_hi db 0 rsrv db 0 ENDS idtr_struc Листинг 5. Определение констант и структур для модулей, составленных на языке Си. Файл tos.h ----------------------------------------------------------- #define word unsigned int // Селекторы, определённые в GDT #define CODE_SELECTOR 0x08 // сегмент кода #define DATA_SELECTOR 0x10 // сегмент данных #define TASK_1_SELECTOR 0x18 // задача TASK_1 #define TASK_2_SELECTOR 0x20 // задача TASK_2 #define MAIN_TASK_SELECTOR 0x28 // главная задача #define VID_MEM_SELECTOR 0x30 // сегмент видеопамяти #define IDT_SELECTOR 0x38 // талица IDT #define KEYBIN_TASK_SELECTOR 0x40 // задача ввода с клавиатуры #define KEYB_TASK_SELECTOR 0x48 // задача обработки // клавиатурного прерывания #define FLIP_TASK_SELECTOR 0x50 // задача FLIP_TASK // Байт доступа typedef struct { unsigned accessed : 1; unsigned read_write : 1; unsigned conf_exp : 1; unsigned code : 1; unsigned xsystem : 1; unsigned dpl : 2; unsigned present : 1; } ACCESS; // Структура дескриптора typedef struct descriptor { word limit; word base_lo; unsigned char base_hi; unsigned char type_dpl; unsigned reserved; } descriptor; // Структура вентиля вызова, задачи, прерывания, // исключения typedef struct gate { word offset; word selector; unsigned char count; unsigned char type_dpl; word reserved; } gate; // Структура сегмента состояния задачи TSS typedef struct tss { word link; // поле обратной связи word sp0; // указатель стека кольца 0 word ss0; word sp1; // указатель стека кольца 1 word ss1; word sp2; // указатель стека кольца 1 word ss2; word ip; // регистры процессора word flags; word ax; word cx; word dx; word bx; word sp; word bp; word si; word di; word es; word cs; word ss; word ds; word ldtr; } tss; // Размеры сегментов и структур #define TSS_SIZE (sizeof(tss)) #define DESCRIPTOR_SIZE (sizeof(descriptor)) #define GATE_SIZE (sizeof(gate)) #define IDT_SIZE (sizeof(idt)) // Физические адреса видеопамяти для цветного // и монохромного видеоадаптеров #define COLOR_VID_MEM 0xb8000L #define MONO_VID_MEM 0xb0000L // Видеоржеимы #define MONO_MODE 0x07 // монохромный #define BW_80_MODE 0x02 // монохромный, 80 символов #define COLOR_80_MODE 0x03 // цветной, 80 символов // Значения для поля доступа #define TYPE_CODE_DESCR 0x18 #define TYPE_DATA_DESCR 0x10 #define TYPE_TSS_DESCR 0x01 #define TYPE_CALL_GATE 0x04 #define TYPE_TASK_GATE 0x85 #define TYPE_INTERRUPT_GATE 0x86 #define TYPE_TRAP_GATE 0x87 #define SEG_WRITABLE 0x02 #define SEG_READABLE 0x02 #define SEG_PRESENT_BIT 0x80 // Константы для обработки аппаратных // прерываний #define EOI 0x20 #define MASTER8259A 0x20 #define SLAVE8259A 0xa0 // Макро для формирования физического // адреса из компонент сегменоного адреса // и смещения #define MK_LIN_ADDR(seg,off) (((unsigned long)(seg))<<4)+(word)(off) // Тип указателя на функцию типа void без параметров typedef void (func_ptr)(void); Файл tos.c (листинг 6) содержит основную программу, которая инициализирует процессор для работы в защищённом режиме и запускает все задачи. С помощью функции с названием Init_And_Protected_Mode_Entry() мы попадаем в защищённый режим и выводим сообщение на экран о том, что в главной задаче установлен защищённый режим. Регистр TR загружается селектором главной задачи при помощи функции load_task_register(). Сразу после этого программа переключается на выполнение задачи TASK_1. Эта задача просто выводит сообщение о своём запуске на экран и возвращает управление главной задаче. Цель этой процедуры - продемонстрировать процесс переключения задач с помощью команды JMP. После возврата в главную задачу программа размаскирует прерывания от клавиатуры и таймера. Как только начинают поступать и обрабатываться прерывания таймера, оживают остальные задачи, определённые при инициализации системы. Начиная с этого момента главная задача разделяет процессорное время наравне с остальными задачами. Что же она делает? Главная задача сбрасывает семафор с номером 0, вслед за чем ожидает его установку. Этот семафор устанавливается задачей, которая занимается вводом символов с клавиатуры и выводит на экран скан-коды клавиш, а также состояние переключающих клавиш. Как только окажется нажатой клавиша ESC, задача ввода символов с клавиатуры устанавливает семафор 0, что и приводит к завершению работы главной задачи. Перед тем, как завершить работу, главная задача устанавливает реальный режим работы процессора, стирает экран и возвращает управление операционной системе. Листинг 6. Программа мультизадачного монитора. Файл tos.c ----------------------------------------------------------- #include <stdio.h> #include <stdlib.h> #include <dos.h> #include <conio.h> #include "tos.h" // -------------------------------- // Определения вызываемых функций // -------------------------------- void Init_And_Protected_Mode_Entry(void); void protected_mode(unsigned long gdt_ptr, unsigned int gdt_size, word cseg, word dseg); word load_task_register(word tss_selector); void real_mode(void); void jump_to_task(word tss_selector); void load_idtr(unsigned long idt_ptr, word idt_size); void Keyb_int(void); void Timer_int(void); void Int_30h_Entry(void); extern word kb_getch(void); void enable_interrupt(void); void task1(void); void task2(void); void flipflop_task(void); void keyb_task(void); void init_tss(tss *t, word cs, word ds, unsigned char *sp, func_ptr ip); void init_gdt_descriptor(descriptor *descr, unsigned long base, word limit, unsigned char type); void exception_0(void); //{ prg_abort(0); } void exception_1(void); //{ prg_abort(1); } void exception_2(void); //{ prg_abort(2); } void exception_3(void); //{ prg_abort(3); } void exception_4(void); //{ prg_abort(4); } void exception_5(void); //{ prg_abort(5); } void exception_6(void); //{ prg_abort(6); } void exception_7(void); //{ prg_abort(7); } void exception_8(void); //{ prg_abort(8); } void exception_9(void); //{ prg_abort(9); } void exception_A(void); //{ prg_abort(0xA); } void exception_B(void); //{ prg_abort(0xB); } void exception_C(void); //{ prg_abort(0xC); } void exception_D(void); //{ prg_abort(0xD); } void exception_E(void); //{ prg_abort(0xE); } void exception_F(void); //{ prg_abort(0xF); } void exception_10(void); //{ prg_abort(0x10); } void exception_11(void); //{ prg_abort(0x11); } void exception_12(void); //{ prg_abort(0x12); } void exception_13(void); //{ prg_abort(0x13); } void exception_14(void); //{ prg_abort(0x14); } void exception_15(void); //{ prg_abort(0x15); } void exception_16(void); //{ prg_abort(0x16); } void exception_17(void); //{ prg_abort(0x17); } void exception_18(void); //{ prg_abort(0x18); } void exception_19(void); //{ prg_abort(0x19); } void exception_1A(void); //{ prg_abort(0x1A); } void exception_1B(void); //{ prg_abort(0x1B); } void exception_1C(void); //{ prg_abort(0x1C); } void exception_1D(void); //{ prg_abort(0x1D); } void exception_1E(void); //{ prg_abort(0x1E); } void exception_1F(void); //{ prg_abort(0x1F); } void iret0(void); void iret1(void); // -------------------------------------- // Глобальная таблица дескрипторов GDT // -------------------------------------- descriptor gdt[11]; // -------------------------------------- // Дескрипторная таблица прерываний IDT // -------------------------------------- gate idt[] = { // Обработчики исключений { (word)&exception_0, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 0 { (word)&exception_1, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1 { (word)&exception_2, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 2 { (word)&exception_3, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 3 { (word)&exception_4, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 4 { (word)&exception_5, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 5 { (word)&exception_6, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 6 { (word)&exception_7, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 7 { (word)&exception_8, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 8 { (word)&exception_9, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 9 { (word)&exception_A, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // A { (word)&exception_B, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // B { (word)&exception_C, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // C { (word)&exception_D, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // D { (word)&exception_E, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // E { (word)&exception_F, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // F { (word)&exception_10, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 10 { (word)&exception_11, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 11 { (word)&exception_12, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 12 { (word)&exception_13, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 13 { (word)&exception_14, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 14 { (word)&exception_15, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 15 { (word)&exception_16, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 16 { (word)&exception_17, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 17 { (word)&exception_18, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 18 { (word)&exception_19, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 19 { (word)&exception_1A, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1A { (word)&exception_1B, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1B { (word)&exception_1C, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1C { (word)&exception_1D, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1D { (word)&exception_1E, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1E { (word)&exception_1F, CODE_SELECTOR, 0, TYPE_TRAP_GATE, 0 }, // 1F // Обработчик прерываний таймера { (word)&Timer_int, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 20 // Вентиль задачи, запускающейся по прерыванию от клавиатуры { 0, KEYB_TASK_SELECTOR, 0, TYPE_TASK_GATE, 0 }, // 21 // Заглушки для остальных аппаратных прерываний { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 22 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 23 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 24 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 25 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 26 { (word)&iret0, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 27 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 28 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 29 { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2A { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2B { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2C { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2D { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2E { (word)&iret1, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 2F // Обработчик для программного прерывания, которое // используется для ввода с клавиатуры { (word)&Int_30h_Entry, CODE_SELECTOR, 0, TYPE_INTERRUPT_GATE, 0 }, // 30 // Вентиль задачи FLIP_TASK { 0, FLIP_TASK_SELECTOR, 0, TYPE_TASK_GATE, 0 } // 31 }; // ------------------------------------------- // Сегменты TSS для различных задач // ------------------------------------------- tss main_tss; // TSS главной задачи tss task_1_tss; // TSS задачи TASK_1 tss task_2_tss; // TSS задачи TASK_2 tss keyb_task_tss; // TSS задач обслуживания tss keyb_tss; // клавиатуры tss flipflop_tss; // TSS задачи FLIP_TASK // ------------------------------------------- // Стеки для задач // ------------------------------------------- unsigned char task_1_stack[1024]; unsigned char task_2_stack[1024]; unsigned char keyb_task_stack[1024]; unsigned char keyb_stack[1024]; unsigned char flipflop_stack[1024]; word y=0; // номер текущей строки для вывода на экран // ------------------------------------------- // Начало программы // ------------------------------------------- void main(void) { // Стираем экран textcolor(BLACK); textbackground(LIGHTGRAY); clrscr(); // Входим в защищённый режим Init_And_Protected_Mode_Entry(); // Выводим сообщение vi_hello_msg(); y=3; vi_print(0, y++, " Установлен защищённый режим в главной задаче", 0x7f); // Загружаем регистр TR селектором главной задачи // т.е. задачи main() load_task_register(MAIN_TASK_SELECTOR); // Переключаемся на задачу TASK_1 jump_to_task(TASK_1_SELECTOR); // После возврата в главную задачу выдаём сообщение vi_print(0, y++ ," Вернулись в главную задачу", 0x7f); y++; // Запускаем планировщик задач vi_print(0, y++ ," Запущен планировщик задач", 0x70); enable_interrupt(); // разрешаем прерывание таймера // Ожидаем установки семафора с номером 0. После того, // как этот семафор окажется установлен, возвращаемся // в реальный режим. // Семафор 0 устанавливается задачей, обрабатывающей ввод с // клавиатуры, которая работает независимо от // главной задаче. vi_print(0, y++ ," Для возврата в реальный режим нажмите ESC", 0x70); sem_clear(0); // сброс семафора 0 sem_wait(0); // ожидание установки семафора 0 // Возврат в реальный режим, стирание экрана и // передача управления MS-DOS real_mode(); textcolor(WHITE); textbackground(BLACK); clrscr(); } // ----------------------------------- // Функция инициализации сегмента TSS // ----------------------------------- void init_tss(tss *t, word cs, word ds, unsigned char *sp, func_ptr ip) { t->cs = cs; // селектор сегмента кода t->ds = ds; // поля ds, es, ss устанавливаем t->es = ds; // на сегмент данных t->ss = ds; t->ip = (word)ip; // указатель команд t->sp = (word)sp; // смещение стека t->bp = (word)sp; } // ------------------------------------------------- // Функция инициализации дескриптора в таблице GDT // ------------------------------------------------- void init_gdt_descriptor(descriptor *descr, unsigned long base, word limit, unsigned char type) { // Младшее слово базового адреса descr->base_lo = (word)base; // Старший байт базового адреса descr->base_hi = (unsigned char)(base >> 16); // Поле доступа дескриптора descr->type_dpl = type; // Предел descr->limit = limit; // Зарезервированное поле, должно быть // сброшено в 0 descr->reserved = 0; } // ----------------------------------------------- // Инициализация всех таблиц и вход // в защищённый режим // ----------------------------------------------- void Init_And_Protected_Mode_Entry(void) { union REGS r; // Инициализируем таблицу GDT, элементы с 1 по 5 init_gdt_descriptor(&gdt[1], MK_LIN_ADDR(_CS, 0), 0xffffL, TYPE_CODE_DESCR | SEG_PRESENT_BIT | SEG_READABLE); init_gdt_descriptor(&gdt[2], MK_LIN_ADDR(_DS, 0), 0xffffL, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); init_gdt_descriptor(&gdt[3], MK_LIN_ADDR(_DS, &task_1_tss), (unsigned long)TSS_SIZE-1, TYPE_TSS_DESCR | SEG_PRESENT_BIT); init_gdt_descriptor(&gdt[4], MK_LIN_ADDR(_DS, &task_2_tss), (unsigned long)TSS_SIZE-1, TYPE_TSS_DESCR | SEG_PRESENT_BIT); init_gdt_descriptor(&gdt[5], MK_LIN_ADDR(_DS, &main_tss), (unsigned long)TSS_SIZE-1, TYPE_TSS_DESCR | SEG_PRESENT_BIT); // Инициализируем TSS для задач TASK_1, TASK_2 init_tss(&task_1_tss, CODE_SELECTOR, DATA_SELECTOR, task_1_stack+ sizeof(task_1_stack), task1); init_tss(&task_2_tss, CODE_SELECTOR, DATA_SELECTOR, task_2_stack+ sizeof(task_2_stack), task2); // Инициализируем элемент 6 таблицы GDT - // дескриптор для сегмента видеопамяти // Определяем видеорежим r.h.ah=15; int86(0x10,&r,&r); // Инициализация для монохромного режима if(r.h.al==MONO_MODE) init_gdt_descriptor(&gdt[6], MONO_VID_MEM, 3999, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); // Инициализация для цветного режима else if(r.h.al == BW_80_MODE || r.h.al == COLOR_80_MODE) init_gdt_descriptor(&gdt[6], COLOR_VID_MEM, 3999, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); else { printf("\nИзвините, этот видеорежим недопустим."); exit(-1); } // Инициализация элементов 7 и 8 таблицы GDT init_gdt_descriptor(&gdt[7], MK_LIN_ADDR(_DS, &idt), (unsigned long)IDT_SIZE-1, TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE); init_gdt_descriptor(&gdt[8], MK_LIN_ADDR(_DS, &keyb_task_tss), (unsigned long)TSS_SIZE-1, TYPE_TSS_DESCR | SEG_PRESENT_BIT); // Инициализация TSS для задачи KEYB_TASK init_tss(&keyb_task_tss, CODE_SELECTOR, DATA_SELECTOR, keyb_task_stack + sizeof(keyb_task_stack), keyb_task); // Инициализация элемента 9 таблицы GDT init_gdt_descriptor(&gdt[9], MK_LIN_ADDR(_DS, &keyb_tss), (unsigned long)TSS_SIZE-1, TYPE_TSS_DESCR | SEG_PRESENT_BIT); // Инициализация TSS для задачи KEYB обработки ввода с клавиатуры init_tss(&keyb_tss, CODE_SELECTOR, DATA_SELECTOR, keyb_stack + sizeof(keyb_stack), Keyb_int); // Инициализация элемента 10 таблицы GDT init_gdt_descriptor(&gdt[10], MK_LIN_ADDR(_DS, &flipflop_tss), (unsigned long)TSS_SIZE-1, TYPE_TSS_DESCR | SEG_PRESENT_BIT); // Инициализация TSS для задачи FLIP_TASK init_tss(&flipflop_tss, CODE_SELECTOR, DATA_SELECTOR, flipflop_stack + sizeof(flipflop_stack), flipflop_task); // Загрузка регистра IDTR load_idtr(MK_LIN_ADDR(_DS, &idt), IDT_SIZE); // Вход в защищённый режим protected_mode(MK_LIN_ADDR(_DS, &gdt), sizeof(gdt), CODE_SELECTOR, DATA_SELECTOR); } Файл tasks.c содержит тексты программ, которые будут работать в режиме разделения времени (кроме задачи TASK_1, эта задача запускается только один раз). Задача TASK_1 (процедура task1) выдаёт сообщение о своём запуске и передаёт управление главной задаче. Задача TASK_2 (процедура task2) попеременно выводит на экран строки "FLIP" и "FLOP", переключая попутно семафор с номером 1. Задача FLIP_TASK (процедура flipflop_task) также попеременно выводит на экран строки "FLIP" и "FLOP", но только тогда, когда семафор с номером 1 установлен. Таким образом, задача TASK_2 управляет работой задачи FLIP_TASK. Задача KEYB_TASK (процедура keyb_task) вводит символы с клавиатуры и выводит скан-коды нажатых клавиш, а также состояние переключающих клавиш. Как только оказывается нажатой клавиша ESC, задача устанавливает семафор с номером 0, что приводит к завершению работы главной задачи (ожидающей установки этого семафора). Листинг 7. Задачи, которые будут работать параллельно. Файл tasks.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" word dispatcher(void); // Номер текущей строки для вывода на экран extern unsigned int y; // Задача TASK_1 void task1(void) { while(1){ vi_print(0,y++," Запущена задача TASK_1, " "переходим к главной задаче", 0x70); jump_to_task(MAIN_TASK_SELECTOR); // После повторного запуска этой задачи // снова входим в цикл. } } // Задача TASK_2 word flipflop1 = 0; long delay_cnt1 = 0l; void task2(void) { while(1){ // Периодически выводим на экран строки // FLIP/FLOP, каждый раз переключая // семафор номер 1. Этот семафор однозначно // соответствует выведенной на экран строке. asm sti if(delay_cnt1 > 150000l ) { asm cli if(flipflop1) { vi_print(73,3," FLIP ", 0x4f); sem_clear(1); } else { vi_print(73,3," FLOP ", 0x1f); sem_set(1); } flipflop1 ^= 1; delay_cnt1 = 0l; asm sti } delay_cnt1++; } } word flipflop = 0; long delay_cnt = 0l; void flipflop_task(void) { // Эта задача также периодически выводит на экран // строки FLIP/FLOP, но выводит строкой выше и // с меньшим периодом. Кроме того, эта задача // работает только тогда, когда установлен // семафор номер 1. while(1){ asm sti if(delay_cnt > 20000l ) { sem_wait(1); // ожидаем установки семафора asm cli if(flipflop) vi_print(73,2," FLIP ", 0x20); else vi_print(73,2," FLOP ", 0x20); flipflop ^= 1; delay_cnt = 0l; asm sti } delay_cnt++; } } word keyb_code; extern word keyb_status; void keyb_task(void) { // Эта задача вводит символы с клавиатуры // и отображает скан-коды нажатых клавиш // и состояние переключающих клавиш на экране. // Если нажимается клавиша ESC, задача // устанавливает семафор номер 0. // Работающая параллельно главная задача // ожидает установку этого семафора. Как только // семафор 0 окажется установлен, главная задача // завершает свою работу и программа возвращает // процессор в реальный режим, затем передаёт // управление MS-DOS. vi_print(60, 5, " Key code: .... ", 0x20); vi_print(60, 6, " Key status: .... ", 0x20); while(1){ keyb_code = kb_getch(); vi_put_word(73, 5, keyb_code, 0x4f); vi_put_word(73, 6, keyb_status, 0x4f); if((keyb_code & 0x00ff) == 1) sem_set(0); } } Файл semaphor.c содержит исходные тексты процедур сброса семафора, установки семафора и ожидания семафора. В массиве semaphore[5] определено пять семафоров. Разумеется, что когда вы будете экспериментировать с программой, вы можете изменить количество доступных семафоров. Листинг 8. Процедуры для работы с семафорами. Файл semaphor.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" // Массив из пяти семафоров word semaphore[5]; // Процедура сброса семафора. // Параметр sem - номер сбрасываемого семафора void sem_clear(int sem) { asm cli semaphore[sem] = 0; asm sti } // Процедура установки семафора // Параметр sem - номер устанавливаемого семафора void sem_set(int sem) { asm cli semaphore[sem] = 1; asm sti } // Ожидание установки семафора // Параметр sem - номер ожидаемого семафора void sem_wait(int sem) { while(1) { asm cli if(semaphore[sem]) break; // проверяем семафор asm sti // ожидаем установки семафора asm nop asm nop } asm sti } Файл timer.c содержит обработчик аппаратного прерывания таймера, который периодически выдаёт звуковой сигнал и инициирует работу диспетчера задач. Диспетчер задач циклически перебирает селекторы TSS задач, участвующих в процессе разделения времени, возвращая селектор той задачи, которая должна стать активной. В самом конце обработки аппаратного прерывания таймера происходит переключение именно на эту задачу. Листинг 9. Процедуры для работы с таймером и диспетчер задач. Файл timer.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" // ------------------------------------------- // Модуль обслуживания таймера // ------------------------------------------- #define EOI 0x20 #define MASTER8259A 0x20 extern void beep(void); extern void flipflop_task(void); void Timer_int(void); word dispatcher(void); word timer_cnt; // ------------------------------------------ // Обработчик аппаратного прерывания таймера // ------------------------------------------ void Timer_int(void) { asm pop bp // Периодически выдаём звуковой сигнал timer_cnt += 1; if((timer_cnt & 0xf) == 0xf) { beep(); } // Выдаём в контроллер команду конца // прерывания asm mov al,EOI asm out MASTER8259A,al // Переключаемся на следующую задачу, // селектор TSS которой получаем от // диспетчера задач dispatcher() jump_to_task(dispatcher()); asm iret } // -------------------------------------- // Диспетчер задач // -------------------------------------- // Массив селекторов, указывающих на TSS // задач, участвующих в параллельной работе, // т.е. диспетчеризуемых задач word task_list[] = { MAIN_TASK_SELECTOR, FLIP_TASK_SELECTOR, KEYBIN_TASK_SELECTOR, TASK_2_SELECTOR }; word current_task = 0; // текущая задача word max_task = 3; // количество задач - 1 // Используем простейший алгоритм диспетчеризации - // выполняем последовательное переключение на все // задачи, селекторы TSS которых находятся // в массиве task_list[]. word dispatcher(void) { if(current_task < max_task) current_task++; else current_task = 0; return(task_list[current_task]); } Для сокращения объёма и без того сложной программы мы не стали делать функционально полную обработку исключений, ограничившись простым аварийным завершением работы программы с выдачей номера исключения. Исходные тексты обработчиков исключений находятся в файле except.c. Листинг 10. Обработка исключений. Файл except.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" void prg_abort(int err); // Номер текущей строки для вывода на экран extern unsigned int y; // Обработчики исключений void exception_0(void) { prg_abort(0); } void exception_1(void) { prg_abort(1); } void exception_2(void) { prg_abort(2); } void exception_3(void) { prg_abort(3); } void exception_4(void) { prg_abort(4); } void exception_5(void) { prg_abort(5); } void exception_6(void) { prg_abort(6); } void exception_7(void) { prg_abort(7); } void exception_8(void) { prg_abort(8); } void exception_9(void) { prg_abort(9); } void exception_A(void) { prg_abort(0xA); } void exception_B(void) { prg_abort(0xB); } void exception_C(void) { prg_abort(0xC); } void exception_D(void) { prg_abort(0xD); } void exception_E(void) { prg_abort(0xE); } void exception_F(void) { prg_abort(0xF); } void exception_10(void) { prg_abort(0x10); } void exception_11(void) { prg_abort(0x11); } void exception_12(void) { prg_abort(0x12); } void exception_13(void) { prg_abort(0x13); } void exception_14(void) { prg_abort(0x14); } void exception_15(void) { prg_abort(0x15); } void exception_16(void) { prg_abort(0x16); } void exception_17(void) { prg_abort(0x17); } void exception_18(void) { prg_abort(0x18); } void exception_19(void) { prg_abort(0x19); } void exception_1A(void) { prg_abort(0x1A); } void exception_1B(void) { prg_abort(0x1B); } void exception_1C(void) { prg_abort(0x1C); } void exception_1D(void) { prg_abort(0x1D); } void exception_1E(void) { prg_abort(0x1E); } void exception_1F(void) { prg_abort(0x1F); } // ------------------------------ // Аварийный выход из программы // ------------------------------ void prg_abort(int err) { vi_print(1,y++,"!!! ---> Произошло исключение", 0xc); real_mode(); // Возвращаемся в реальный режим // В реальном режиме выводим сообщение об исключении gotoxy(1, ++y); cprintf(" Исключение %X, нажмите любую клавишу", err); getch(); textcolor(WHITE); textbackground(BLACK); clrscr(); exit(0); } В файле intproc.c расположены заглушки для тех аппаратных прерываний, обработка которых сводится к простой посылке кода конца прерывания в соответствующий контроллер прерывания. Листинг 11. Заглушки для аппаратных прерываний. Файл intproc.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" // Заглушки для необрабатываемых // аппаратных прерываний. void iret0(void) { // первый контроллер прерываний asm { push ax mov al,EOI out MASTER8259A,al pop ax pop bp iret } } void iret1(void) { // второй контроллер прерываний asm { push ax mov al,EOI out MASTER8259A,al out SLAVE8259A,al pop ax pop bp iret } } Файл keyb.c содержит простой интерфейс для вызова программного прерывания int 30h, обеспечивающего ввод с клавиатуры. Листинг 12. Ввод символа с клавиатуры. Файл keyb.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" extern word key_code; // Функция, ожидающая нажатия любой // клавиши и возвращающая её скан-код unsigned int kb_getch(void) { asm int 30h return(key_code); } Обработчик аппаратного прерывания клавиатуры мы взяли практически без изменений из программы, представленной в предыдущей главе. Исходные тексты находятся в файле keyboard.asm. Листинг 13. Процедуры для работы с клавиатурой. Файл keyboard.asm ----------------------------------------------------------- IDEAL MODEL SMALL RADIX 16 P286 include "tos.inc" ; ------------------------------------------ ; Модуль обслуживания клавиатуры ; ------------------------------------------ PUBLIC _Keyb_int, _Int_30h_Entry, _key_code, _keyb_status EXTRN _beep:PROC DATASEG _key_flag db 0 _key_code dw 0 ext_scan db 0 _keyb_status dw 0 CODESEG PROC _Keyb_int NEAR cli call _beep push ax mov al, [ext_scan] cmp al, 0 jz normal_scan1 cmp al, 0e1h jz pause_key in al, 60h cmp al, 2ah jz intkeyb_exit_1 cmp al, 0aah jz intkeyb_exit_1 mov ah, [ext_scan] call Keyb_PutQ mov al, 0 mov [ext_scan], al jmp intkeyb_exit pause_key: in al, 60h cmp al, 0c5h jz pause_key1 cmp al, 45h jz pause_key1 jmp intkeyb_exit pause_key1: mov ah, [ext_scan] call Keyb_PutQ mov al, 0 mov [ext_scan], al jmp intkeyb_exit normal_scan1: in al, 60h cmp al, 0feh jz intkeyb_exit cmp al, 0e1h jz ext_key cmp al, 0e0h jnz normal_scan ext_key: mov [ext_scan], al jmp intkeyb_exit intkeyb_exit_1: mov al, 0 mov [ext_scan], al jmp intkeyb_exit normal_scan: mov ah, 0 call Keyb_PutQ intkeyb_exit: in al, 61h mov ah, al or al, 80h out 61h, al xchg ah, al out 61h, al mov al,EOI out MASTER8259A,al pop ax sti iret jmp _Keyb_int ENDP _Keyb_int PROC Keyb_PutQ NEAR push ax cmp ax, 002ah ; L_SHIFT down jnz @@kb1 mov ax, [_keyb_status] or ax, L_SHIFT mov [_keyb_status], ax jmp keyb_putq_exit @@kb1: cmp ax, 00aah ; L_SHIFT up jnz @@kb2 mov ax, [_keyb_status] and ax, NL_SHIFT mov [_keyb_status], ax jmp keyb_putq_exit @@kb2: cmp ax, 0036h ; R_SHIFT down jnz @@kb3 mov ax, [_keyb_status] or ax, R_SHIFT mov [_keyb_status], ax jmp keyb_putq_exit @@kb3: cmp ax, 00b6h ; R_SHIFT up jnz @@kb4 mov ax, [_keyb_status] and ax, NR_SHIFT mov [_keyb_status], ax jmp keyb_putq_exit @@kb4: cmp ax, 001dh ; L_CTRL down jnz @@kb5 mov ax, [_keyb_status] or ax, L_CTRL mov [_keyb_status], ax jmp keyb_putq_exit @@kb5: cmp ax, 009dh ; L_CTRL up jnz @@kb6 mov ax, [_keyb_status] and ax, NL_CTRL mov [_keyb_status], ax jmp keyb_putq_exit @@kb6: cmp ax, 0e01dh ; R_CTRL down jnz @@kb7 mov ax, [_keyb_status] or ax, R_CTRL mov [_keyb_status], ax jmp keyb_putq_exit @@kb7: cmp ax, 0e09dh ; R_CTRL up jnz @@kb8 mov ax, [_keyb_status] and ax, NR_CTRL mov [_keyb_status], ax jmp keyb_putq_exit @@kb8: cmp ax, 0038h ; L_ALT down jnz @@kb9 mov ax, [_keyb_status] or ax, L_ALT mov [_keyb_status], ax jmp keyb_putq_exit @@kb9: cmp ax, 00b8h ; L_ALT up jnz @@kb10 mov ax, [_keyb_status] and ax, NL_ALT mov [_keyb_status], ax jmp keyb_putq_exit @@kb10: cmp ax, 0e038h ; R_ALT down jnz @@kb11 mov ax, [_keyb_status] or ax, R_ALT mov [_keyb_status], ax jmp keyb_putq_exit @@kb11: cmp ax, 0e0b8h ; R_ALT up jnz @@kb12 mov ax, [_keyb_status] and ax, NR_ALT mov [_keyb_status], ax jmp keyb_putq_exit @@kb12: cmp ax, 003ah ; CAPS_LOCK up jnz @@kb13 mov ax, [_keyb_status] xor ax, CAPS_LOCK mov [_keyb_status], ax jmp keyb_putq_exit @@kb13: cmp ax, 00bah ; CAPS_LOCK down jnz @@kb14 jmp keyb_putq_exit @@kb14: cmp ax, 0046h ; SCR_LOCK up jnz @@kb15 mov ax, [_keyb_status] xor ax, SCR_LOCK mov [_keyb_status], ax jmp keyb_putq_exit @@kb15: cmp ax, 00c6h ; SCR_LOCK down jnz @@kb16 jmp keyb_putq_exit @@kb16: cmp ax, 0045h ; NUM_LOCK up jnz @@kb17 mov ax, [_keyb_status] xor ax, NUM_LOCK mov [_keyb_status], ax jmp keyb_putq_exit @@kb17: cmp ax, 00c5h ; NUM_LOCK down jnz @@kb18 jmp keyb_putq_exit @@kb18: cmp ax, 0e052h ; INSERT up jnz @@kb19 mov ax, [_keyb_status] xor ax, INSERT mov [_keyb_status], ax jmp keyb_putq_exit @@kb19: cmp ax, 0e0d2h ; INSERT down jnz @@kb20 jmp keyb_putq_exit @@kb20: test ax, 0080h jnz keyb_putq_exit mov [_key_code], ax mov al, 0ffh mov [_key_flag], al keyb_putq_exit: pop ax ret ENDP Keyb_PutQ ; Обработчик программного прерывания ; для ввода с клавиатуры. По своим функциям ; напоминает прерывание INT 16 реального ; режима. PROC _Int_30h_Entry NEAR push ax dx ; Ожидаем прерывание от клавиатуры keyb_int_wait: sti nop nop cli ; Проверяем флаг, который устанавливается ; обработчиком аппаратного прерывания клавиатуры mov al, [_key_flag] cmp al, 0 jz keyb_int_wait ; Сбрасываем флаг после прихода прерывания mov al, 0 mov [_key_flag], al sti pop dx ax iret ENDP _Int_30h_Entry END Файл screen.c содержит процедуры, необходимые для вывода информации на экран дисплея. Работа этих процедур основана на непосредственной записи данных в видеопамять. Листинг 14. Процедуры для работы с видеоадаптером. Файл screen.c ----------------------------------------------------------- #include <stdio.h> #include <dos.h> #include <conio.h> #include <stdlib.h> #include "tos.h" void vi_putch(unsigned int x, unsigned int y ,char c, char attr); char hex_tabl[] = "0123456789ABCDEF"; // Вывод байта на экран, координаты (x,y), // выводится шестнадцатеричное представление // байта chr с экранными атрибутами attr. void vi_put_byte(unsigned int x, unsigned int y, unsigned char chr, char attr) { unsigned char temp; temp = hex_tabl[(chr & 0xf0) >> 4]; vi_putch(x, y, temp, attr); temp = hex_tabl[chr & 0xf]; vi_putch(x+1, y, temp, attr); } // Вывод слова на экран, координаты (x,y), // выводится шестнадцатеричное представление // слова chr с экранными атрибутами attr. void vi_put_word(unsigned int x, unsigned int y, word chr, char attr) { vi_put_byte(x, y, (chr & 0xff00) >> 8, attr); vi_put_byte(x+2, y, chr & 0xff, attr); } // Вывод символа c на экран, координаты - (x,y), // атрибут выводимого символа - attr void vi_putch(unsigned int x, unsigned int y ,char c, char attr) { register unsigned int offset; char far *vid_ptr; offset=(y*160) + (x*2); vid_ptr=MK_FP(VID_MEM_SELECTOR, offset); *vid_ptr++=c; *vid_ptr=attr; } // Вывод строки s на экран, координаты - (x,y), // атрибут выводимой строки - attr void vi_print(unsigned int x, unsigned int y, char *s, char attr) { while(*s) vi_putch(x++, y, *s++, attr); } // Вывод стоки сообщения о запуске программы void vi_hello_msg(void) { vi_print(0, 0, " Protected mode monitor *TINY/OS*, " "v.1.2 for CPU 80286 ¦ © Frolov A.V., 1992 ", 0x30); } Последний файл - tossyst.asm - содержит уже знакомые вам процедуры для входа в защищённый режим и возврата обратно в реальный режим. Обратите внимание на процедуры _load_task_register и _jump_to_task, выполняющие загрузку регистра задачи TR и переключение на другую задачу соответственно. Листинг 15. Процедуры для инициализации, перехода в защищённый режим и возврата в реальный режим, для загрузки регистра TR и переключения задач. Файл tossyst.asm ----------------------------------------------------------- IDEAL MODEL SMALL RADIX 16 P286 DATASEG include "tos.inc" PUBLIC _beep ; Область памяти для инициализации IDTR idtr idtr_struc <,,,0> ; Область памяти для инициализации GDTR gdt_ptr dw (8*15)-1 ; размер GDT, 15 элементов gdt_ptr2 dw ? gdt_ptr4 dw ? ; Область памяти для записи селектора задачи, ; на которую будет происходить переключение new_task dw 00h new_select dw 00h ; Область памяти для хранения регистров, ; используется для возврата в реальный режим real_ss dw ? real_sp dw ? real_es dw ? protect_sel dw ? init_tss dw ? CODESEG PUBLIC _real_mode,_protected_mode,_jump_to_task PUBLIC _load_task_register, _load_idtr, _enable_interrupt ; ------------------------------------------------------------------- ; Процедура для переключения в защищённый режим. ; Прототип для вызова: ; void protected_mode(unsigned long gdt_ptr, unsigned int gdt_size, ; unsigned int cseg, unsigned int dseg) ; ------------------------------------------------------------------- PROC _protected_mode NEAR push bp mov bp,sp ; Параметр gdt_ptr mov ax,[bp+4] ; мл. слово адреса GDT mov dx,[bp+6] ; ст. слово адреса GDT mov [gdt_ptr4], dx ; запоминаем адрес GDT mov [gdt_ptr2], ax ; Параметр gdt_size mov ax,[bp+8] ; получаем размер GDT mov [gdt_ptr], ax ; и запоминаем его ; Параметры cseg и dseg mov ax,[bp+10d] ; получаем селектор сегмента кода mov dx,[bp+12d] ; получаем селектор сегмента данных mov [cs:p_mode_select], ax ; запоминаем для команды mov [protect_sel], dx ; перехода far jmp ; Подготовка к возврату в реальный режим push ds ; готовим адрес возврата mov ax,40h ; из защищённого режима mov ds,ax mov [WORD 67h],OFFSET shutdown_return mov [WORD 69h],cs pop ds ; Запрещаем и маскируем все прерывания cli in al, INT_MASK_PORT and al, 0ffh out INT_MASK_PORT, al ; Записываем код возврата в CMOS-память mov al,8f out CMOS_PORT,al jmp delay1 delay1: mov al,5 out CMOS_PORT+1,al call enable_a20 ; открываем линию A20 mov [real_ss],ss ; запоминаем регистры SS и ES mov [real_es],es ; Перепрограммируем контроллер прерываний ; для работы в защищённом режиме mov dx,MASTER8259A mov ah,20 call set_int_ctrlr mov dx,SLAVE8259A mov ah,28 call set_int_ctrlr ; Загружаем регистры IDTR и GDTR lidt [FWORD idtr] lgdt [QWORD gdt_ptr] mov ax, 0001h ; переключаем процессор lmsw ax ; в защищённый режим ; jmp far flush db 0eah dw OFFSET flush p_mode_select dw ? LABEL flush FAR mov dx, [protect_sel] mov ss, dx mov ds, dx mov es, dx ; Обнуляем содержимое регистра LDTR mov ax, 0 lldt ax pop bp ret ENDP _protected_mode ; ---------------------------------------------------- ; Возврат в реальный режим. ; Прототип для вызова ; void real_mode(); ; ---------------------------------------------------- PROC _real_mode NEAR ; Сброс процессора cli mov [real_sp], sp mov al, SHUT_DOWN out STATUS_PORT, al rmode_wait: hlt jmp rmode_wait LABEL shutdown_return FAR ; Вернулись в реальный режим mov ax, DGROUP mov ds, ax assume ds:DGROUP mov ss,[real_ss] mov sp,[real_sp] in al, INT_MASK_PORT and al, 0 out INT_MASK_PORT, al call disable_a20 mov ax, DGROUP mov ds, ax mov ss, ax mov es, ax mov ax,000dh out CMOS_PORT,al sti ret ENDP _real_mode ; ------------------------------------------------------- ; Загрузка регистра TR. ; Прототип для вызова: ; void load_task_register(unsigned int tss_selector); ; ------------------------------------------------------- PROC _load_task_register NEAR push bp mov bp,sp ltr [bp+4] ; селектор для текущей задачи pop bp ret ENDP _load_task_register ; ------------------------------------------------------- ; Переключение на задачу. ; Прототип для вызова: ; void jump_to_task(unsigned int tss_selector); ; ------------------------------------------------------- PROC _jump_to_task NEAR push bp mov bp,sp mov ax,[bp+4] ; получаем селектор ; новой задачи mov [new_select],ax ; запоминаем его jmp [DWORD new_task] ; переключаемся на ; новую задачу pop bp ret ENDP _jump_to_task ; ------------------------------ ; Открываем линию A20 ; ------------------------------ PROC enable_a20 NEAR push ax mov al, A20_PORT out STATUS_PORT, al mov al, A20_ON out KBD_PORT_A, al pop ax ret ENDP enable_a20 ; ------------------------------ ; Закрываем линию A20 ; ------------------------------ PROC disable_a20 NEAR push ax mov al, A20_PORT out STATUS_PORT, al mov al ,A20_OFF out KBD_PORT_A, al pop ax ret ENDP disable_a20 ; ----------------------------------------------------------- ; Готовим структуру для загрузки регистра IDTR ; Прототип для вызова функции: ; void load_idtr(unsigned long idt_ptr, word idt_size); ; ----------------------------------------------------------- PROC _load_idtr NEAR push bp mov bp,sp mov ax,[bp+4] ; мл. слово адреса IDT mov dx,[bp+6] ; ст. слово адреса IDT mov bx, OFFSET idtr ; Запоминаем адрес IDTR в структуре mov [(idtr_struc bx).idt_low], ax mov [(idtr_struc bx).idt_hi], dl ; Получаем предел IDT и запоминаем его в структуре mov ax, [bp+8] mov [(idtr_struc bx).idt_len], ax pop bp ret ENDP _load_idtr ; ---------------------------------- ; Установка контроллера прерываний ; ---------------------------------- PROC set_int_ctrlr NEAR mov al, 11 out dx, al jmp SHORT $+2 mov al, ah inc dx out dx, al jmp SHORT $+2 mov al, 4 out dx, al jmp SHORT $+2 mov al, 1 out dx, al jmp SHORT $+2 mov al, 0ffh out dx, al dec dx ret ENDP set_int_ctrlr ; -------------------------- ; Выдача звукового сигнала ; -------------------------- PROC _beep NEAR push ax bx cx in al,KBD_PORT_B push ax mov cx,80 beep0: push cx and al,11111100b out KBD_PORT_B,al mov cx,60 idle1: loop idle1 or al,00000010b out KBD_PORT_B,al mov cx,60 idle2: loop idle2 pop cx loop beep0 pop ax out KBD_PORT_B,al pop cx bx ax ret ENDP _beep ; ------------------------------- ; Задержка выполнения программы ; ------------------------------- PROC _pause NEAR push cx mov cx,10 ploop0: push cx xor cx,cx ploop1: loop ploop1 pop cx loop ploop0 pop cx ret ENDP _pause ; ----------------------- ; Размаскирование прерываний ; ----------------------- PROC _enable_interrupt NEAR in al, INT_MASK_PORT and al, 0fch out INT_MASK_PORT, al sti ret ENDP _enable_interrupt end |