10.8.2. Переключение задач

Переключение задач осуществляется, если:

текущая задача выполняет дальний JMP или CALL на шлюз задачи или пря­мо на

текущая задача выполняет если флаг NT равен 1;

происходит прерывание или исключение, в качестве обработчика которо­го в IDT записан шлюз задачи.

При переключении процессор выполняет следующие действия:

1. Для команд CALL и JMP проверяет привилегии (CPL текущей задачи и RPL

селектора новой задачи не могут быть больше, чем DPL шлюза или TSS, на который передается управление).

2. Проверяется дескриптор TSS (его бит присутствия и лимит).

3. Проверяется, что новый TSS, старый TSS и все дескрипторы сегментов на­ходятся в страницах, отмеченных как присутствующие.

4. Сохраняется состояние задачи.

5. Загружается регистр TR. Если на следующих шагах происходит исключе­ние, его обработчику придется доделывать переключение задач, вместо того

чтобы повторять ошибочную команду.

6. Тип новой задачи в дескрипторе изменяется на занятый, и флаг TS устанав­ливается в CRO.

7. Загружается состояние задачи из нового TSS: LDTR, CR3, EFLAGS, EIP, ре­гистры общего назначения и сегментные регистры.

Если переключение задачи вызывается командами JUMP, CALL, прерывани­ем или исключением, селектор TSS предыдущей задачи записывается в поле свя­зи новой задачи и устанавливается флаг NT. Если флаг NT установлен, команда IRET выполняет обратное переключение задач.

При любом запуске задачи ее тип изменяется в дескрипторе на занятый. По­пытка вызвать такую задачу приводит к #GP. Сделать задачу снова свободной можно, только завершив ее командой IRET или переключившись на другую зада­чу командой JMP.

На следующем примере покажем, как создавать задачи и переключаться меж­ду ними.

; pm4.asm

Пример программы, выполняющей переключение задач.

Запускает две задачи, управление друг другу ВО раз, задачи

; на экран символы ASCII с небольшой задержкой. ; Компиляция: ; -TASM:

:    tasm /т pm4.asm :    tlink /х /3 pm4.obj ; WASM:

;    wasm pm4.asm

form DOS

; паьм : 1 .

;   ml /с pm4.asm

;    link pra4.obj, ,NUI____

. 386p

RM_seg segment para public  "CODE" usel6

assume cs:RM_seg,ds:PM^seg,ss:stack_seg

start:

;   Подготовить сегментные регистры. . push PM_seg

pop ds ; Проверить, не находимся ли мы уже в РМ.

mov еах.сгО

test

jz no_V86 ; Сообщить и выйти.

mov       dx,offset v86_msg err_exit:

push  ' cs pop ds mov       ah,9 int 21h

mov

int 21h

Убедиться, что мы не под Windows. no_V86:

mov

ax,1600h

int

2Fh

test

al.al

jz

no_windows

Сообщить и

выйти.

mov

dx, offset win msg

jmp

short err exit

;  Сообщения об ошибках при старте.

v86_msg db        "Процессор в режиме V86 - нельзя переключиться в РМ$"

win_msg db "Программа запущена под Windows - нельзя перейти в кольцо 0$"

; Итак, мы точно находимся в реальном режиме.

no_windows:

; Очистить экран. mov

int Юл

; Вычислить базы для всех дескрипторов сегментов данных. хог еах,еах mov ax,RM_seg shl еах,4

mov       word ptr GDT_16bitCS+2,ax shr eax,16

mov byte

Управиениє задачами

mov ax,PM_seg

shl eax,4

mov word ptr GDT_32bitCS+2,ax

mov word ptr GDT_32bitSS+2,ax

shr eax,16

mov byte ptr GDT„32bitCS+4,al

mov byte ptr GDT_32bitSS+4,al

; Вычислить линейный адрес GDT.

xor

mov

shl

push

add

mov-

Загрузить

Igdt eax,eax ax,PM_seg eax, 4

eax

eax,offset GDT

ptr

fword ptr gdtr

Вычислить линейные адреса сегментов TSS наших двух-задач.

pop

push add

mov

shr

mov

pop

add

mov shr

mov Открыть А20.

mov out

• Запретить прерывания.

cli

• Запретить

in

or

out

Переключиться

mov or mov

• Загрузить CS.

eax eax

eax, offset TSSJ word ptr GDT_TSS0+2,ax eax,16

byte ptr

eax

eax, offset TSSJ word ptr GDT_TSS1+2,ax eax,16

byte ptr

al,2 92h,al

al,70h al,80h 70h,al

в

eax,crO

ab,l crO,eax

db 66h

db OEAh

dd offset PM_entry

dw SEL 32bitCS

RM return: ; Переключиться mov в реальный режим

eax,crO

RM.

and al.OFEh mov сгО.еах

;  Сбросить очередь и загрузить CS.

db OEAh dw $+4 dw RM^seg ; Настроить сегментные регистры для реального режима, mov       ах,PM_seg mov ds.ax mov es.ax mov

mov bx,stack_l

mov ss.ax

mov       sp, bx ;  Разрешить NMI.

in al,70h

and al,07FH

out 70h,al ; Разрешить прерывания.

sti

; Завершить программу.

mov ah,4Ch

int 21h

RM_seg ends

PM_seg segment para public "CODE" use32 assume   cs:PM_seg

; Таблица глобальных

;

label

db

db

db

db db

Сегмент TSS задачи О db

Сегмент TSS задачи 1

GDT

GDT_flatDS GDTJ6bitCS

GDT_32bitCS GOT_32bitSS

;

GDT TSSO

GDT_TSS1 gdt_size

gdtr

$-GDT

db

dw dd

byte

8 dup(O)

OFFh,OFFh, 0, 0, 0, 10010010b, 11001111b, 0 OFFh, OFFh, 0,0, 0,10011010b, 0,0 OFFh,OFFh,0,0,0,10011010b,11001111b,О

11001111b,0

(32-битный свободный TSS). 067h, 0,0, 0, 0,10001001b, оюоооооь;о (32-битный свободный TSS). 067h, 0,0, 0,0,10001001b, оюоооооь, о

Используемые селекторы.

;

SEL_flatDS

SEL_16bitCS

SEL_32bitCS

SEL_32bitSS

SEL_TSSO

SELTSS1

equ equ equ equ equ equ

gdt_size-1

?

001000b 010000b 011000b 100000b

'

110000b

Размер

Адрес GDT.

i-задачами

ш

;  Сегмент ТЭЗ_0 будет инициализирован,   как только мы выполним переключение ■, из нашей основной задачи. Конечно, если бы мы собирались использовать ;  несколько уровней привилегий,   то нужно было бы инициализировать стеки.

TSS_0 do

; Сегмент TSS_1. В него ;  инициализировать все, TSSJ dd dd

68h dup(O)

будет выполняться переключение, что может потребоваться: 0,0,0,0,0,0,0,0 offset task 1

поэтому надо

; Связь, ; EIP.

стеки, CR3

; Регистры общего назначения.

dd

Сегментные регистры.

dd dd dd

О, 0,0, 0, 0, stack_12, 0, О, 0В8140Ґ»

(ESP и EDI)

SEL_flatDS,SEL_32bitCS,SEL_32bitSS, SEL_flatDS, 0,0 0 ; LDTR.

0 ;   Адрес таблицы

вывода.

ввода-

;  Точка входа РМ_еп1:гу: ; Подготовить , хог шоу

ШОУ

ШОУ ШОУ ШОУ ШОУ ШОУ

; Загрузить ШОУ Иг

теперь ;  в защищенный

в 32-битный режим.

TSS

регистры. еах,еах ах, SEL_flatDS ds, ах es, ах

ax,SEL_32bitSS ebx,stack_l ss, ax esp,ebx

задачи О в регистр TR. ax,SEL_TSS0

ax

наша программа выполнила режим.

все требования к переходу

хог       еах,еах mov edi,OB8000h

task_0:

mov byte ;   Дальний переход на db

ptr

TSS задачи 1.

OEAh

add

dd dw

edi,2

0

SEL_TSS1

inc

cmp jb

Дальний переход на db

al

al,80

task_0

процедуру

OEAh

OS:EDI

адрес начала экрана.

Вывести символ AL на экран.

выхода в

;  08:Е01 -

; символа. ; ЛЬ - код

;   Если это

; выйти из

реальный режим.

dd offset

dw SEL 16bitCS

адрес следующего

следующего символа.

80,

цикла.

Задача

taskj:

mov

inc add

; Переключиться

Вывести символ на экран. Увеличить код символа. Увеличить адрес символа.

byte ds:[edi],al

al

edi, 2

на задачу 0. db OEAh dd 0 dw SEL_TSS0

Сюда будет приходить управление, когда задача 0 начнет выполнять переход на задачу 1 во всех случаях, кроме первого.

mov       ecx,02000000h ;  Небольшая пауза,

loop      $      ' ; процессора.

jrnp taskj

зависящая от скорости

PM_seg ends

stack_seg segment para stack "STACK" stack_start        db        100h dup(?) stack_l = $-stack_start stack Jask2        db        100h dup(?) stack_12 = $-stack_start , stack_seg ends end start

;  Стек задачи

; Стек задачи 1.

Чтобы реализовать многозадачность в реальном времени на нашем примере, достаточно создать обработчик прерывания системного таймера IRQO в виде от­дельной (третьей) задачи и поместить в ЮТ шлюз этой задачи. Текст обработчи­ка для нашего примера мог быть крайне простым:

task 3:

; Это

mov

out jmp

mov

out

jmp jmp

отдельная al,20h

20h,al task_0

al,20h 20h,al taskj

task 3 задача

нужно сохранять регистры!

Но при вызове обработчика прерывания старая задача помечается как занятая

в GDT и повторный JMP на нее приведет к ошибке. Вызов задачи обработчика

прерывания, так же как и вызов задачи командой CALL, подразумевает, она завершится командой IRET. Именно команду IRET проще всего вызвать для пе­редачи управления из такого обработчика - достаточно лишь подменить селек­тор вызвавшей нас задачи в поле связи и выполнить IRET.

task 3:

; При

mov

out

mov

iret

mov

инициализации DS должен быть установлен на al,20h

20h,al

word ptr TSS_3,SEL_TSS0 al,20h

out 20h,al

mov        word ptr TSSJ3,SELJ"SS1 jmp task_3

Единственноедополнительное изменение, которое нужно внести, - инициали­зировать дескриптор TSS задачи task_l как уже занятый, поскольку управление на него будет передаваться командой IRET, что, впрочем, не вызывает никаких проблем.

Помните, что во вложенных задачах команда IRET не означает конца програм­мы - следующий вызов задачи всегда передает управление на очередную коман­ду после IRET.