Хелпикс

Главная

Контакты

Случайная статья





Лабораторная работа №8. Многозадачный режим с управлением от клавиатуры



Лабораторная работа №8. Многозадачный режим с управлением от клавиатуры

1 Цель и порядок работы

Цель работы – изучить работу многозадачного режима с управлением от клавиатуры и научиться писать программы с его использованием.

Порядок выполнения работы:

- ознакомиться с описанием лабораторной работы;

- получить задание у преподавателя;

- написать программу, ввести программу, отладить и решить ее на ЭВМ;

- оформить отчет.

2 Краткая теория

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

Рисунок 8.1 – Взаимодействие вычислительных объектов в процессе переключения задач

 

На рисунке 8.1 схематически изображено взаимодействие программно-аппаратных элементов при выполнении программы, которая носит "полусинхронный" характер – вторую задачу можно запустить в произвольный момент нажатием клавиши, однако дальше активизированная задача становится неуправляемой, выполняясь до завершающей команды irеt.

При загрузке компьютера ведущий контроллер прерываний (IRQ0 – IRQ7) отображается на прерывания начиная с базового вектора 08h (int 08h – int 0Fh), ведомый (IRQ8 – IRQ15) – 70h (int 70h – int 77h). Номера прерываний, на которые отображаются аппаратные прерывания (IRQ), вызываемые первым контроллером по умолчанию, совпадают с номерами некоторых исключений. Конечно, можно из обработчика опрашивать контроллер прерываний, чтобы определить, выполняется ли обработка аппаратного прерывания или это исключение, но Intel рекомендует перенастраивать контроллер прерываний так, чтобы никакие аппаратные прерывания не попадали на область от 0 до 1Fh (зарезервировано для исключений). Поэтому необходимо выполнить перепрограммирование контроллера прерываний, как это рассматривалось в одной из предыдущих работ.

Исходная задача 0 выполняется в условиях установленных флагов NT и IF. Начальное состояние флага NT особого значения не имеет, а прерывания, естественно, должны быть разрешены. Приход сигнала аппаратного прерывания приводит к сбросу флагов NT и IF, занесению вектора прерванного процесса в стек задачи 0 и передаче управления через шлюз прерывания на обработчик прерываний от клавиатуры. В примере он находится в том же сегменте, что и задача 0. В качестве адреса возврата в стеке сохраняется адрес очередной команды (на рисунке он обозначен меткой а:).

В обработчике после анализа кода нажатой клавиши выполняется команда дальнего косвенного вызова call dword ptr task1_offs для которой в качестве сегментного адреса перехода указан селектор сегмента состояния задачи 1 TSS_1. На рисунке эта команда условно обозначена call tss_l, чтобы подчеркнуть, что вызов осуществляется через сегмент состояния задачи. Команда call инициирует действия по переключению задач: сегмент состояния первой задачи TSS_0 заполняется текущим контекстом, а содержимое TSS_1 используется в качестве исходного контекста для запуска задачи 1.

В этой процедуре есть два тонких момента. Во-первых, в TSS_0 загружается фактически не контекст задачи 0, а контекст обработчика прерываний к моменту выполнения команды call. В частности, в качестве точки возврата указывается адрес возврата в обработчик (метка b:), а в слове флагов сброшены флаги NT и IF (оба флага сбрасываются процессором при получении сигнала аппаратного прерывания). Если обработчик изменил содержимое каких-то регистров, то в TSS попадут именно эти измененные значения.

Другая особенность первого переключения на задачу 1 заключается в том, что контекст задачи целиком берется из сегмента состояния задачи TSS_1, который должен быть предварительно программно инициализирован. В примере поле флагов (по относительному адресу 24h) содержит 0. Следовательно, при выполнении задачи 1 будут запрещены прерывания. Что же касается флага NT, то, хотя в слове флагов TSS он был сброшен, задача 1 будет выполняться при NT=1, так как этот флаг устанавливается процессором при переключении задачи.

Задача 1 выполняется до завершающей команды iret, которая при NT=1 инициирует обратное переключение задач, получив из области связи текущего TSS_1 адрес "предыдущего" TSS_0. TSS_1 заполняется текущим контекстом задачи 1, при этом в поле для EIP записывается адрес команды, следующей за командой iret (метка с: на рисунке). Из TSS_0 берется контекст возврата (фактически - контекст обработчика прерываний) и управление передается на метку b: обработчика. Программа обработчика продолжается и, поскольку флаг NT сброшен, завершающая команда iret передает управление не через сегмент состояния задачи, а через стек, обеспечивая правильный возврат в исходную задачу на метку а:.

Последующие переключения на задачу 1 (по командам с клавиатуры) будут передавать управление на метку с:. Поскольку по этому адресу у нас находится команда безусловного перехода на начало программы (метка t_l на рисунке), задача будет выполняться правильно.

В рассматриваемом примере разрушение контекста задачи 0 обработчиком и сохранение в TSS_0 "неправильного" контекста не приводит к нарушению работы всего комплекса, если только в начале программы обработчика сохраняются (например, в стеке) все используемые в нем регистры, а перед завершающей командой iret выполняется их восстановление. С другой стороны, задача 1 выполняется при запрещенных прерываниях, и воздействовать на ход ее выполнения нельзя – она всегда будет выполняться до команды iret.

Для того чтобы получить возможность переключаться не только на задачу 1, но и из нее, надо выполнять эту задачу при разрешенных прерываниях. Прерывания можно разрешить как в самой задаче командой sti, так и в TSS задачи, записав в него исходное слово флагов с установленным флагом IF. Теперь нажатие клавиши в процессе выполнения задачи 1 снова активизирует обработчик прерывания, в котором, проанализировав код нажатой клавиши, можно выполнить команду переключения на другую задачу. Дескриптор сегмента состояния задачи может описывать TSS двух типов – свободные (код 9) и занятые (код Bh). Создавая в программе для каждой задачи сегменты состояния, объявляем их свободными. Команда call, осуществляя переключение на задачу, изменяет атрибут в дескрипторе соответствующего TSS, объявляя его занятым. TSS останется занятым до тех пор, пока в этой задаче не будет выполнена команда iret возврата назад по цепочке связанных задач. При этом команда call может активизировать только задачу со свободным TSS; при попытке вызывать командой call задачу с занятым TSS возбуждается исключение общей защиты. Из вложенной задачи с помощью команды call можно активизировать следующую задачу, но нельзя вернуться назад – для этого нужна команда iret.

Для того чтобы обойти указанное ограничение, можно воспользоваться командой дальнего перехода через сегмент состояния задачи. Команда jmp, выполняя переключение задачи, не включает ее в связный список вложенных задач и, соответственно, но изменяет тип дескриптора TSS. Поэтому с помощью команды jmp можно осуществлять переключение задач как "вперед" – на новую задачу, так и "назад" – на ту задачу, из которой была активизирована данная.

Рассмотрим пример многозадачной системы, в которой переключение задач осуществляется по командам с клавиатуры с помощью команд дальнего косвенного перехода jmp. Предусмотрим в программе главную задачу 0, в которой будет осуществляться инициализация всей системы, а также две демонстрационные задачи 1 и 2. Поначалу ради простоты расположим все задачи в одном сегменте команд, и будем считать, что всем задачам доступен общий сегмент данных.

 

Пример 8.1 – Многозадачный режим с переключением от клавиатуры

;Многозадачный режим с переключением от клавиатуры
IDEAL
P386
model small
stack 300h
;Макрос для отладки
macro debug
push ax
push bx
push cx
push dx
push ax

and ax, 0F000h
shr ax, 12
mov bx, offset tbl_hex
xlat
mov [si], al
pop ax
push ax

and ax, 0F00h
shr ax, 8
inc si
xlat
mov [si], al
pop ax
push ax

and ax, 0F0h
shr ax, 4
inc si
xlat
mov [si], al
pop ax
push ax

and ax, 0Fh
inc si
xlat
mov [si], al

pop ax
pop dx
pop cx
pop bx
pop ax
endm
struc descr
limit dw 0
base_l dw 0
base_m db 0
attr_1 db 0
attr_2 db 0
base_h db 0
ends descr
;Структура для шлюзов ловушки
struc trap
offs_l dw 0
sel dw 16
rsrv db 0
attr db 8Fh
offs_h dw 0
ends trap
;Структура для шлюзов прерываний
struc intr
offs_l dw 0
sel dw 16
rsrv db 0
attr db 8Eh
offs_h dw 0
ends intr
DATASEG
gdt_null descr <0,0,0,0,0,0> ; Селектор = 0
gdt_data descr <data_size-1,0,0,92h,0,0> ; Селектор = 8
gdt_code descr <code_size-1,0,0,98h,0,0> ; Селектор = 16
gdt_stack descr <300h-1,0,0,92h,0,0> ; Селектор = 24
gdt_screen descr <4095,8000h,0Bh,92h,0,0> ; Селектор = 32
gdt_tss_0 descr <103,0,0,89h,0,0> ; Селектор = 40
gdt_tss_1 descr <103,0,0,89h,0,0> ; Селектор = 48
gdt_tss_2 descr <103,0,0,89h,0,0> ; Селектор = 56

gdt_size = $-gdt_null

idt trap 10 dup (<dummy_exc>); Исключения 0 - 7 имеют общий обработчик
trap <exc_0a> ; Исключение 0Ah
trap <exc_0b> ; Исключение 0Bh
trap <exc_0c> ; Исключение 0Ch
trap <exc_0d> ; Исключение 0Dh - #GP (General Protection Fault)
trap <exc_0e> ; Исключение 0Eh
trap 17 dup (<dummy_exc>); Исключения 0Fh-1Fh имеют общий обработчик
intr <master> ; IRQ0 - Вектор 20h - общий обработчик
intr <new_09h> ; IRQ1 - Вектор 21h - прерывание от клавиатуры
intr 6 dup (<master>) ; Векторы 22h...27h - аппаратные,
; ведущий контроллер прерываний
intr 8 dup (<slave>) ; Векторы 2Eh...2Fh - аппаратные,
; ведомый контроллер прерываний
idt_size = $-idt
idtr_real dw 3FFh, 0, 0
pdescr dp 0
mes db 10,13,'Real mode','$'
tbl_hex db '0123456789ABCDEF'
string db '**** **** **** **** **** **** ****'
len = $-string
home_sel dw home
dw 10h
;Переменные для переключения на задачи
t0_addr dw 0, 40 ; Смещение + селектор TSS 0 = 40
t1_addr dw 0, 48 ; Смещение + селектор TSS 1 = 48
t2_addr dw 0, 56 ; Смещение + селектор TSS 2 = 56
tss_0 db 104 dup(0) ; TSS 0-й задачи
tss_1 db 104 dup(0) ; TSS 1-й задачи
tss_2 db 104 dup(0) ; TSS 2-й задачи

mes1 db 22,22,22," task_1 ",22,22,22 ; Строка выводимая задачей 1
mes1_len = $-mes1 ; Длинна строки

mes2 db 22,22,22," task_2 ",22,22,22 ; Строка выводимая задачей 2
mes2_len = $-mes2 ; Длинна строки

; Переменные для маскирования прерываний
master_mask db 0
slave_mask db 0

data_size = $-gdt_null
ends
CODESEG
assume cs: @code, ds:@data
sttt equ $
proc dummy_exc ;Обработчик исключений c номерами 0-9 и 0F-1F
pop eax
pop eax
mov si, offset string+5
debug
mov ax, 1111h
jmp [dword ptr home_sel]
endp
proc exc_0a ;Обработчик исключения 0A
pop eax
pop eax
mov si, offset string+5
debug
mov ax, 0Ah
jmp [dword ptr home_sel]
endp
proc exc_0b ;Обработчик исключения 0B
pop eax
pop eax
mov si, offset string+5
debug
mov ax, 0Bh
jmp [dword ptr home_sel]
endp
proc exc_0c ;Обработчик исключения 0C
pop eax
pop eax
mov si, offset string+5
debug
mov ax, 0Ch
jmp [dword ptr home_sel]
endp
proc exc_0d ;Обработчик исключения 0D
pop eax
pop eax
mov si, offset string+5
debug
mov ax, 0Dh
jmp [dword ptr home_sel]
endp
proc exc_0e ;Обработчик исключения 0E
pop eax
pop eax
mov si, offset string+5
debug
mov ax, 0Eh
jmp [dword ptr home_sel]
endp
;Обработчик прерывания клавиатуры (IRQ1)
;Нельзя завершить обработчик просто командой IRET, потому что:
; во-первых, обработчик аппаратного прерывания клавиатуры должен
; установить бит 7 порта 61h, а затем вернуть его в исходное состояние
; во-вторых, он должен сообщить контроллеру прерываний, что обработка
; аппаратного прерывания закончилась командами
proc new_09h
;Аппаратное прерывание - сохранить регистры
push ax
push dx
;Чтение скан-кода
in al, 60h ; Прочитать скан-код нажатой клавиши,
mov dl, al
;Обработка прерывания клавиатуры
in al, 61h
or al, 80h ; Установить бит 7 порта 61h
out 61h, al
and al, 7Fh ; Восстанавливаем бит 7
out 61h, al

;Послать EOI контроллеру прерываний
mov al, 20h
out 20h, al

;Проверяем введенный символ
cmp dl, 0Bh ; 0Bh = Клавиша <0>
je tsk0

cmp dl, 2 ; 02h = Клавиша <1>
je tsk1

cmp dl, 3 ; 03h = Клавиша <2>
je tsk2

jmp out_09h
tsk0:
;Переключение на задачу 0
jmp [dword ptr t0_addr]
jmp out_09h
tsk1:
;Переключение на задачу 1
jmp [dword ptr t1_addr]
jmp out_09h
tsk2:
;Переключение на задачу 2
jmp [dword ptr t2_addr]
out_09h:
;Выход из обработчика
;Восстанавливаем измененные регистры
pop dx
pop ax
db 66h ; Префикс замены размера операнда
iret
endp
;Процедура для ведущего контроллера
proc master
push ax ;Сохраним используемый регистр

mov al, 20h ; Сигнал EOI
out 20h, al
pop ax ; Восстановим регистр
db 66h ; Возврат
iret ; в программу
endp master
; Процедура для ведомого контроллера
proc slave
push ax ; Сохраним используемый регистр

mov al,20h ; Сигнал EOI для
out 0A0h, al; ведомого контроллера
mov al, 20h ; Сигнал EOI для
out 20h, al ; ведущего контроллера
pop ax ; Восстановим регистр
db 66h ; Возврат
iret ; в программу
endp slave

start:
xor eax, eax
mov ax, @data
mov ds, ax

shl eax, 4
mov ebp, eax
mov bx, offset gdt_data
mov [(descr ptr bx).base_l], ax
rol eax, 16
mov [(descr ptr bx).base_m], al
xor eax, eax
mov ax, cs
shl eax, 4
mov bx, offset gdt_code
mov [(descr ptr bx).base_l], ax
rol eax, 16
mov [(descr ptr bx).base_m], al
xor eax, eax
mov ax, ss
shl eax, 4
mov bx, offset gdt_stack
mov [(descr ptr bx).base_l], ax
rol eax, 16
mov [(descr ptr bx).base_m], al
;Вычислим 32-битовый линейный адрес сегмента TSS 0 и загрузим его
;в дескриптор сегмента TSS 0 в таблице глобальных дескрипторов
mov eax, ebp ; Адрес начала сегмента данных
add ax, offset tss_0 ; Добавим смещение TSS0
mov bx, offset gdt_tss_0
mov [(descr ptr bx).base_l], ax
rol eax, 16
mov [(descr ptr bx).base_m], al
;Вычислим 32-битовый линейный адрес сегмента TSS 1 и загрузим его
;в дескриптор сегмента TSS 1 в таблице глобальных дескрипторов
mov eax, ebp
add ax, offset tss_1
mov bx, offset gdt_tss_1
mov [(descr ptr bx).base_l], ax
rol eax, 16
mov [(descr ptr bx).base_m], al
;Вычислим 32-битовый линейный адрес сегмента TSS 2 и загрузим его
;в дескриптор сегмента TSS 2 в таблице глобальных дескрипторов
mov eax, ebp
add ax, offset tss_2
mov bx, offset gdt_tss_2
mov [(descr ptr bx).base_l], ax
rol eax, 16
mov [(descr ptr bx).base_m], al
;Подготовка к загрузке GDTR
mov [dword ptr pdescr+2], ebp
mov [word ptr pdescr], gdt_size-1
lgdt [pword ptr pdescr]

; TSS 0 заполнится автоматически

; Заполним поля TSS 1
mov [word ptr tss_1+4Ch], 16 ; CS
mov [word ptr tss_1+20h], offset task1 ; IP
mov [word ptr tss_1+50h], 24 ; SS
mov [word ptr tss_1+38h], 256 ; SP
mov [word ptr tss_1+54h], 8 ; DS
mov [word ptr tss_1+48h], 32 ; ES

; Установим флаг IF и сохраним регистр флагов EFLAGS в EAX
sti
pushfd
pop eax

mov [dword ptr tss_1+24h], eax ; EFLAGS


; Заполним поля TSS 2
mov [word ptr tss_2+4Ch], 16 ; CS
mov [word ptr tss_2+20h], offset task2 ; IP
mov [word ptr tss_2+50h], 24 ; SS
mov [word ptr tss_2+38h], 512 ; SP
mov [word ptr tss_2+54h], 8 ; DS
mov [word ptr tss_2+48h], 32 ; ES
mov [dword ptr tss_2+24h], eax ; EFLAGS

; Запрет аппаратных прерываний и NMI
cli

in al, 70h
or al, 80h
out 70h, al

;Перепрограммируем ведущий контроллер IRQ0-IRQ7

mov dx, 20h ; Поpт ведущего контpоллеpа
mov al, 11h ; СКИ1 - инициализиpовать два контpоллеpа
out dx, al
jmp $+2 ; Задеpжка

inc dx ; Второй порт контроллера (21h)

mov al, 20h ; СКИ2 - базовый вектоp
out dx, al
jmp $+2

mov al, 4 ; СКИ3 - ведомый подключен к IRQ2 (4 = 000000100)
out dx, al
jmp $+2

mov al, 1 ; СКИ4 - 80х86, пpогpаммная генеpация EOI
out dx, al
jmp $+2

;Перепрограммируем ведомый контроллер IRQ8-IRQ16

mov dx, 0A0h; Поpт ведомого контpоллеpа
mov al, 11h ; СКИ1 - инициализиpовать два контpоллеpа, будет СКИЗ
out dx, al
jmp $+2 ; Задеpжка

inc dx ; Второй порт контроллера 0A1h

mov al, 28h ; СКИ2: базовый вектор
out dx, al
jmp $+2

mov al, 2 ;СКИЗ: ведомый подключен к IRQ2
out dx, al
jmp $+2

mov al, 1 ; СКИ4 - 80х86, пpогpаммная генеpация EOI
out dx, al
jmp $+2

; Маскируем прерывания ведущего контроллера
; 0FEh = 11111101b -> IRQ1 разрешено, IRQ0, IRQ2-IRQ7 запрещены
mov dx, 021h
in al, dx ; Читаем текущее состояние маски
mov [master_mask], al ; Сохраним маску
; Разрешим IRQ1 - Клавиатура
and al, 0000010b
out dx, al

; Маскируем прерывания ведомого контроллера
mov dx, 0A1h
in al, dx ; Читаем текущее состояние маски
mov [slave_mask], al ; Сохраним маску
and al, 00000000b ; Зпретим все прерывания
out dx, al

;Подготовка к загрузке IDTR
mov [word ptr pdescr], idt_size-1
xor eax, eax
mov ax, offset idt
add eax, ebp
mov [dword ptr pdescr+2], eax
lidt [pword ptr pdescr]

;Открыть линию А20
mov al, 0D1h
out 64h, al
mov al, 0DFh
out 60h, al

 

mov eax, CR0
or eax, 1
mov CR0, eax

db 0EAh
dw offset continue
dw 16
continue:
mov ax, 8
mov ds, ax

mov ax, 24
mov ss, ax

mov ax, 32
mov es, ax

;Загрузим селектор TSS 0 в регистр TR
mov ax, 40
ltr ax

;Разрешим аппаратные и немаскируемые прерывания
sti
in al, 70h
and al, 07Fh
out 70h, al

; Задача 0
mov bx, 1600
mov cx, 800
mov dx, 3001h
xxxx:
push cx
mov cx, 0
zzzz:
loop zzzz
mov [word ptr es:bx], dx
inc dl
pop cx
add bx, 2
loop xxxx

 

mov ax,0ffffh
home:
mov si, offset string
debug
mov si, offset string
mov cx, len
mov ah, 74h
mov di, 1600
scr:
lodsb
stosw
loop scr

;Закрыть линию A20
mov al, 0D1h
out 64h ,al
mov al, 0DDh
out 60h, al

; Запрет аппаратных прерываний и NMI
cli
in al, 70h
or al, 80h
out 70h, al

;Возврат в реальный ражим
mov eax, CR0
and al, 0FEh
mov CR0, eax

db 0EAh
dw offset return
dw @code
return:
;Восстановим операционную среду реального режима
mov ax, @data
mov ds, ax

mov ax, @stack
mov ss, ax
mov sp, 100h

;Восстановим значение IDTR для работы в реальном режиме
lidt [fword ptr idtr_real]

;Восстановим обратоно контроллер прерываний
;Перепрограммируем ведущий контроллер IRQ0-IRQ7 (по умолчанию int 8h - int 15h)
mov dx, 20h ; Поpт ведущего контpоллеpа
mov al, 11h ; СКИ1 - инициализиpовать два контpоллеpа
out dx, al
jmp $+2 ; Задеpжка

inc dx ; Второй порт контроллера (21h)

mov al, 08h ; СКИ2 - базовый вектоp
out dx, al
jmp $+2

mov al, 4 ; СКИ3 - ведомый подключен к IRQ2 (4 = 000000100)
out dx, al
jmp $+2

mov al, 1 ; СКИ4 - 80х86, пpогpаммная генеpация EOI
out dx, al

;Перепрограммируем ведомый контроллер IRQ8-IRQ16 (по умолчанию int 70h - int 77h)
mov dx, 0A0h; Поpт ведомого контpоллеpа
mov al, 11h ; СКИ1 - инициализиpовать два контpоллеpа, будет СКИЗ
out dx, al
jmp $+2 ; Задеpжка

inc dx ; Второй порт контроллера 0A1h
mov al, 70h ; СКИ2: базовый вектор
out dx, al
jmp $+2

mov al, 2 ;СКИЗ: ведомый подключен к IRQ2
out dx, al
jmp $+2

mov al, 1 ; СКИ4 - 80х86, пpогpаммная генеpация EOI
out dx, al
jmp $+2

; Восстановим маски прерываний
; Маскируем прерывания ведущего контроллера
mov dx, 021h
mov al, [master_mask]
out dx, al

; Маскируем прерывания ведомого контроллера
mov dx, 0A1h
mov al, [slave_mask]
out dx, al

;Разрешим аппаратные и немаскируемые прерывания
sti
in al, 70h
and al, 07Fh
out 70h, al

 

mov ah, 09h
mov dx, offset mes
int 21h

mov ax, 4C00h
int 21h

; Задача 1: выводит на экран сообщение mes1
proc task1
a1:
mov ah, 71h ; Атрибут символов
mov cx, 2
a2:
push cx
mov di, 800
mov cx, mes1_len
mov si, offset mes1
cld

;Вывод на экран
a3:
lodsb
stosw
loop a3

;Задержка
mov cx, 0FFFFh
a4:
loop a4

pop cx
mov ah, 17h
loop a2

jmp a1
endp

; Задача 2: выводит на экран сообщение mes2
proc task2
b1:
mov ah, 34h ; Атрибут символов
mov cx, 2
b2:
push cx
mov di, 1120
mov cx, mes2_len
mov si, offset mes2
cld

;Вывод на экран
b3:
lodsb
stosw
loop b3

;Задержка
mov cx, 0FFFFh
b4:
loop b4

pop cx
mov ah, 43h
loop b2
jmp b1
endp
ends
code_size=$-sttt
end start
end

 

В таблице глобальных дескрипторов предусмотрены три дескриптора gdt_tss_0, gdt_tss_l и gdt_tss_2 дня сегментов состояния задач. Размер TSS - 104 байта (граница равна 103), тип дескриптора – сободные TSS МП 486.

В таблице IDT на месте, соответствующем вектору клавиатуры, расположен дескриптор - шлюз прерывания для вызова обработчика new_09h.

Три двухсловных ячейки t0_addr, t1_addr и t2_addr предназначены для команд дальних косвенных переходов, реализующих переключение задач. Первые слова этих ячеек игнорируются, во-вторых, записаны селекторы сегментов состояния задач TSS_0, TSS_1 и TSS_2.

Сообщения с именами mes1 и mcs2 предназначены для вывода задачами 1 и 2. Сообщения украшены с обеих сторон символами полосочек (код ASCII 22).

Обработчик прерываний от клавиатуры анализирует коды нажимаемых клавиш и осуществляет переключение на задачи 0, 1 или 2 при нажатии клавиш <0>, <1> и <2>, соответственно. Поскольку при запуске программы начинает работать задача 0, первое переключение должно быть на задачу 1 или 2. После этого задачи можно переключать в любом порядке, хотя недопустимо переключение на активную задачу.

На этапе инициализации следует обратить внимание на инициализацию сегментов состояния переключаемых задач. TSS задачи 0, как и раньше, не инициализируем: он будет заполнен процессором при первом переключении на задачи 1 или 2. TSS задач 1 и 2 инициализируются почти одинаково. В TSS заполняются поля CS (селектором общего сегмента команд), IP (относительными адресами задач), SS (селектором общего сегмента стека), DS (селектором общего сегмента данных), ЕS (селектором видеобуфера). В поля для SP заносятся разные смещения в пределах одного сегмента стека, размер которого увеличен в три раз». Таким образом, фактически задачи будут работать на разных стеках, что необходимо, поскольку переключения по аппаратным прерываниям могут происходить в произвольные моменты времени. Для того чтобы вход в задачи 1 и 2 происходил при разрешенных прерываниях, в поля для EFLAGS заносится текущее содержимое EFLAGS после того, как командой sti явно разрешены прерывания.

Задачи 1 и 2 практически одинаковы. В каждой из них выводится на экран мигающая строка текста, для чего организован цикл из двух шагов. В одном шаге цикла строка выводится с одним атрибутом, в другом шаге – с другим. Завершающая команда jmp обеспечивает бесконечное выполнение каждой задачи. Таким образом, в программе не предусматривается "естественное” завершение задач, что потребовало бы их заметного усложнения.

Рассмотрим ход выполнения программного комплекса, а также его возможности и недостатки (рис. 8.2).

Исходная задача 0, как и в предыдущем примере, выполняется в условиях установленных флагов NT и IF. Приход сигнала аппаратного прерывания приводит к сбросу флагов NT и IF, занесению вектора прерванного процесса в стек задачи 0 и передаче управления через шлюз прерывания на обработчик прерываний от клавиатуры. В качестве адреса возврата в стеке сохраняется адрес очередной команды задачи 0 (на рисунке он обозначен меткой а:).

В обработчике после анализа кода нажатой клавиши с помощью команды дальнего косвенного перехода Jmp dword ptr task1_offs выполняется переключение на задачу 1. Сегмент состояния главной задачи TSS_0 заполняется текущим контекстом, а содержимое TSS_1 используется в качестве исходного контекста для запуска задачи 1. Поскольку в момент переключения выполняется программа обработчика, в TSS_0 в поле для EIP записывается адрес команды обработчика, следующей за командой jmp и помеченной на рисунке меткой b. Задача 1 представляет собой бесконечный цикл, поэтому она будет выполняться до тех пор, пока пользователь не подаст с клавиатуры команду переключения задач.

Приход прерывания от клавиатуры, как и раньше, приводит к сбросу флагов NT и IF, занесению вектора прерванного процесса в стек задачи 1 и передаче управления на обработчик прерываний от клавиатуры.

Пусть пользователь подал команду возврата в задачу 0 (нажав на клавишу <0>). Обработчик, проанализировав код нажатой клавиши, передает управление на команду переключения на задачу 0 (на рисунке она обозначена, как jmp tss_0). Эта команда инициирует в процессоре процедуру переключения, в процессе которой TSS задачи 1 заполняется текущим контекстом, а содержимое TSS_0 используется в качестве исходного контекста для запуска задачи 0. Но в TSS_0 в качестве адреса возврата указано значение метки b в обработчике.

 

Рисунок 8.2 – Взаимодействие вычислительных объектов в многозадачном комплексе

 

Таким образом, происходит переключение не на задачу 0, а на обработчик. После каждой команды переключения jmp в обработчике стоит команда перехода на завершающую команду iret. Поскольку флаг IF сброшен, команда iret выполняет обычный возврат через стек текущей задачи, т.е. задачи 0. В результате происходит возврат в задачу 0 в ту точку, где она была прервана при переключении на задачу 1.

Все три задачи нашего программного комплекса вполне равноправны, поэтому, подавая соответствующие команды с клавиатуры, можно переключать их в произвольном порядке. Правда, в программе не предусмотрена защита от повторного переключения на активную задачу. Если попытаться выполнить такое переключение, будет возбуждено исключение общей защиты. Не предусмотрено также завершение задач 1 или 2 – они прекращают работу при завершении главной задачи.

Отмеченные выше недостатки являются следствием примитивности нашей системы, которая предназначена не для изучения принципов построения многозадачных систем, а лишь для демонстрации техники переключения задач и знакомства с соответствующими средствами микропроцессора. Однако и с этой точки зрения приведенный пример далек от совершенства. Одним из слабых его мест является использование всеми задачами комплекса общего сегмента данных. Так, обработчик прерываний выполняет переход на задачи через ячейки с селекторами TSS, расположенные в сегменте данных главной задачи; задачи 1 и 2 выводят сообщения, также хранящиеся в общем сегменте данных. Казалось бы, задачи 1 и 2 вполне могут использовать собственные сегменты данных, так как при переключении задач происходит сохранение старого контекста (включая сегментные регистры) и загрузка нового. Однако вызов обработчика аппаратных прерываний происходит без переключения контекста (заменяется только содержимое CS:IP); обработчик фактически работает на контексте прерванной задачи. Поэтому манипуляции с сегментными регистрами в задаче могут оказаться фатальными для се работоспособности. Впрочем, здесь нет ничего нового: и в реальном режиме при входе в обработчик прерываний сегментные регистры адресуют сегменты прерванного процесса.

Рассмотрим пример, в котором несколько снижена взаимозависимость операционных сред задач и обработчика прерываний.

Ниже описаны лишь изменения, внесенные в программу примера 8.1.

Из общего сегмента данных удалены ячейки t0_addr, tl_addr и t2_addr с адресами TSS задач, а также сообщения задач mesl и mes2. Адреса TSS задач перенесены в процедуру обработчика прерываний, а сообщения задач – в процедуры задач.

 

Пример 8.2 – Модифицированная программа обработчика прерываний клавиатуры

proc new_09h
push ax
push dx

;Чтение скан-кода
in al, 60h ; Прочитать скан-код нажатой клавиши,
mov dl, al

;Обработка прерывания клавиатуры
in al, 61h
or al, 80h ; Установить бит 7 порта 61h
out 61h, al
and al, 7Fh ; Восстанавливаем бит 7
out 61h, al

;Послать EOI контроллеру прерываний
mov al, 20h
out 20h, al

;Проверяем введенный символ
cmp dl, 0Bh ; 0Bh = Клавиша <0>
je tsk0

cmp dl, 2 ; 02h = Клавиша <1>
je tsk1

cmp dl, 3 ; 03h = Клавиша <2>
je tsk2

jmp out1
tsk0:
;Переключение на задачу 0
pop dx
pop ax
jmp [dword ptr cs:t0]
jmp out_09h
tsk1:
;Переключение на задачу 1
pop dx
pop ax
jmp [dword ptr cs:t1]
jmp out_09h
tsk2:
;Переключение на задачу 2
pop dx
pop ax
jmp [dword ptr cs:t2]
out_09h:
;Выход из обработчика
db 66h ; Префикс замены размера операнда
iret
out1:
pop dx
pop ax
jmp out_09h

t0 dw 0,40
t1 dw 0,48
t2 dw 0,56
endp

Команды вида jmp dword ptr cs:t0 с заменой сегмента и адресацией через сегмент команд возможны лишь при условии, что сегмент команд объявлен с разрешением исполнения и чтения (тип дескриптора 5). В первых программах, где сегмент команд объявлялся только исполняемым (тип 4), такая адресация привела бы к нарушению общей защиты. Байт атрибутов 1 должен иметь значение 9Ah.

При инициализации сегментов состояния задач изменено содержимое DS: поскольку поля данных задач размещены в их же процедурах, в DS загружается селектор сегмента команд:

 

Пример 8.3 – Модифицированная инициализация полей TSS

; Заполним поля TSS 1
mov [word ptr tss_1+4Ch], 16 ; CS
mov [word ptr tss_1+20h], offset task1 ; IP
mov [word ptr tss_1+50h], 24 ; SS
mov [word ptr tss_1+38h], 256 ; SP
mov [word ptr tss_1+54h], 16 ; DS
mov [word ptr tss_1+48h], 32 ; ES

sti
pushfd
pop eax

mov [dword ptr tss_1+24h], eax ; EFLAGS
; Заполним поля TSS 2
mov [word ptr tss_2+4Ch], 16 ; CS
mov [word ptr tss_2+20h], offset task2 ; IP
mov [word ptr tss_2+50h], 24 ; SS
mov [word ptr tss_2+38h], 512 ; SP
mov [word ptr tss_2+54h], 16 ; DS
mov [word ptr tss_2+48h], 32 ; ES
mov [dword ptr tss_2+24h], eax ; EFLAGS

 

В задачах 1 и 2 вывод сообщений на экран выполняется непосредственно из сегмента команд. Поскольку регистры DS и ES настраиваются автоматически в процессе переключения, требуется только настроить SI и DI (для выполнения команд обработки цепочек):

 

Пример 8.4 – Модифицированная процедура задачи 1

proc task1
a1:
mov ah, 71h ; Атрибут символов
mov cx, 2
a2:
push cx
mov di, 800
mov cx, mes1_len
mov si, offset mes1
cld

;Вывод на экран
a3:
lodsb
stosw
loop a3

;Задержка
mov cx, 0FFFFh
a4:
loop a4

pop cx
mov ah, 17h
loop a2

jmp a1

mes1 db 22,22,22," task_1 ",22,22,22 ; Строка выводимая задачей 1
mes1_len = $-mes1 ; Длинна строки
endp

 

Аналогично выглядит и задача task2.

Введем теперь в программу защиту от повторного переключения на текущую задачу. Фактически нам надо после каждого нажатия клавиш <0>, <1> или <2> выяснять, какая задача является текущей и не допускать переключения, если с клавиатуры случайно подана команда переключения на ту задачу, которая как раз выполняется. Узнать, какая задача является текущей, просто: достаточно прочитать содержимое регистра задачи TR. В нем всегда находится селектор сегмента состояния текущей задачи. Преобразуем процедуру обработчика прерывания от клавиатуры, введя в нее анализ регистра TR.

Ниже приводится только модифицированный обработчик прерываний от клавиатуры. Остальной текст программы не изменился.

 

Пример 8.5 – Обработчик прерывания от клавиатуры с анализом регистра состояния задачи

proc new_09h
push ax
push dx

;Чтение скан-кода
in al, 60h ; Прочитать скан-код нажатой клавиши,
mov dl, al

;Обработка прерывания клавиатуры
in al, 61h
or al, 80h ; Установить бит 7 порта 61h
out 61h, al
and al, 7Fh ; Восстанавливаем бит 7
out 61h, al

;Послать EOI контроллеру прерываний
mov al, 20h
out 20h, al

;Проверяем введенный символ
cmp dl, 0Bh ; 0Bh = Клавиша <0>
je tsk0

cmp dl, 2 ; 02h = Клавиша <1>
je tsk1

cmp dl, 3 ; 03h = Клавиша <2>
je tsk2

jmp out1
tsk0:
;Переключение на задачу 0
str ax ; Получим текущее значение TR
cmp ax, 40 ; Сревним с селектором TSS 0
je out1

pop dx
pop ax
jmp [dword ptr cs:t0]

jmp out_09h
tsk1:
;Переключение на задачу 1
str ax ; Получим текущее значение TR
cmp ax, 48 ; Сревним с селектором TSS 1
je out1

pop dx
pop ax
jmp [dword ptr cs:t1]

jmp out_09h
tsk2:
;Переключение на задачу 2
str ax ; Получим текущее значение TR
cmp ax, 56 ; Сревним селектором TSS 2
je out1

pop dx
pop ax
jmp [dword ptr cs:t2]
out_09h:
db 66h ; Префикс замены размера операнда
iret
out1:
pop dx
pop ax
jmp out_09h

t0 dw 0,40
t1 dw 0,48
t2 dw 0,56
endp

Если обработчик фиксирует нажатие клавиши <0> (из порта 60h получен скен-код 0Bh), выполняется чтение TR, для чего предусмотрена специальная команда str. Полученное значение сравнивается с известным нам селектором сегмента состояния задачи 0 (число 40). В случае равенства осуществляется выход из обработчика без выполнения каких-либо действий. Точно так же обрабатываются команды <1> и <2>.

3 Контрольные вопросы

1. Каково взаимодействие вычислительных объектов в процессе переключения задач?

2. Как осуществляется вызов обработчика прерываний от клавиатуры?

3. Как инициализируются сегменты состояния задачи?

4. Как ввести в программу защиту от повторного переключения на текущую задачу?

5. Какова роль флага NT в многозадачной среде?

4 Задание

1. Изучить теоретические основы использования многозадачного режима и переключения задач по прерыванию от клавиатуры.

2. Набрать и отладить и протестировать предложенную программу (пример 8.1) с использованием переключения задач. При этом обратить внимание на следующие этапы:

2.1. Определение таблицы дескрипторов и дескрипторов используемых сегментов.

2.2. Определение таблицы прерываний.

2.3. Определение переменных для переключения задач и маскирования прерываний.

2.4. Определение и структура обработчика системного таймера (IRQ0).

2.5. Определение и структура обработчика прерывания клавиатуры (IRQ1).

2.6. Подготовка дескрипторов сегментов TSS и TSS задач

2.7. Перепрограммирование ведущего контроллера прерываний.

2.8. Маскирование прерывания ведущего и ведомого контроллера.

2.9. Определение и структура задач.

2.10. Процесс переключение между задачами.

2.11. Восстановление настроек контроллера прерываний.

2.12. Восстановление масок прерываний.

3. Отладить и протестировать полученную программу.

4. Модифицировать пример 8.1 в соответствии с примером 8.2, отладить и протестировать программу.

5. Модифицировать пример 8.1 в соответствии с примером 8.3, отладить и протестировать программу.

6. Оформить отчёт.

5 Содержание отчета

1. Титульный лист.

2. Краткое теоретическое описание.

3. Задание на лабораторную работу.

4. Листинг программы.

Результаты выполнения работы.



  

© helpiks.su При использовании или копировании материалов прямая ссылка на сайт обязательна.