[Назад] [Далее]

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

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

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

  1. Для команд CALL и JMP проверяет привилегии (CPL текущей задачи и RPL селектора новой задачи не могут быть больше, чем DPL шлюза или TSS, на который передается управление).
  2. Проверяется дескриптор TSS (его бит присутствия и лимит).
  3. Проверяется, что новый TSS, старый TSS и все дескрипторы сегментов находятся в страницах, отмеченных как присутствующие.
  4. Сохраняется состояние задачи.
  5. Загружается регистр TR. Если на следующих шагах происходит исключение, его обработчику придется доделывать переключение задач, вместо того чтобы повторять ошибочную команду.
  6. Тип новой задачи в дескрипторе изменяется на занятый и устанавливается флаг TS в CR0.
  7. Загружается состояние задачи из нового TSS: LDTR, CR3, EFLAGS, EIP, регистры общего назначения и сегментные регистры.

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

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

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

; pm4.asm
; Пример программы, выполняющей переключение задач.
; Запускает две задачи, передающие управление друг другу 80 раз, задачи выводят
; на экран символы ASCII с небольшой задержкой
;
; Компиляция:
; TASM:
;   tasm /m pm4.asm
;   tlink /x /3 pm4.obj
; WASM:
;   wasm pm4.asm
;   wlink file pm4.obj form DOS
; MASM:
;   ml /c pm4.asm
;   link pm4.obj,,NUL,,,

        .386p
RM_seg segment para public "CODE" use16
        assume cs:RM_seg,ds:PM_seg,ss:stack_seg
start:
; подготовить сегментные регистры
        push       PM_seg
        pop        ds
; проверить, не находимся ли мы уже в РМ
        mov        eax,cr0
        test       al,1
        jz         no_V86
; сообщить и выйти
        mov        dx,offset v86_msg
err_exit:
        push       cs
        pop        ds
        mov        ah,9
        int        21h
        mov        ah,4Ch
        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        ax,3
        int        10h
; вычислить базы для всех дескрипторов сегментов данных
        xor        еах,еах
        mov        ax,RM_seg
        shl        eax,4
        mov        word ptr GDT_16bitCS+2,ax
        shr        eax,16
        mov        byte ptr GDT_16bitCS+4,al
        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        eax,eax
        mov        ax,PM_seg
        shl        eax,4
        push       eax
        add        eax,offset GDT
        mov        dword ptr gdtr+2,eax
; загрузить GDT
        lgdt       fword ptr gdtr
; вычислить линейные адреса сегментов TSS наших двух задач
        pop        eax
        push       eax
        add        eax,offset TSS_0
        mov        word ptr GDT_TSS0+2,ax
        shr        eax,16
        mov        byte ptr GDT_TSS0+4,al
        pop        eax
        add        eax,offset TSS_1
        mov        word ptr GDT_TSS1+2,ax
        shr        eax,16
        mov        byte ptr GDT_TSS1+4,al
; открыть А20
        mov        al,2
        out        92h,al
; запретить прерывания
        cli
; запретить NMI
        in         al,70h
        or         al,80h
        out        70h,al
; переключиться	в РМ
        mov        eax,cr0
        or         al,1
        mov        cr0,eax
; загрузить CS
        db         66h
        db         0EAh
        dd         offset PM_entry
        dw         SEL_32bitCS

RM_return:
; переключиться	в реальный режим RM
        mov        eax,cr0
        and        al,0FEh
        mov        cr0,eax
; сбросить очередь предвыборки и загрузить CS
        db         0EAh
        dw         $+4
        dw         RM_seg
; настроить сегментные регистры для реального режима
        mov        ax,PM_seg
        mov        ds,ax
        mov        es,ax
        mov        ax,stack_seg
        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

; таблица глобальных дескрипторов
GDT       label    byte
                   db    8 dup(0)
GDT_flatDS         db    0FFh,0FFh,0,0,0,10010010b,11001111b,0
GDT_16bitCS        db    0FFh,0FFh,0,0,0,10011010b,0,0
GDT_32bitCS        db    0FFh,0FFh,0,0,0,10011010b,11001111b,0
GDT_32bitSS        db    0FFh,0FFh,0,0,0,10010010b,11001111b,0
; сегмент TSS задачи 0 (32-битный свободный TSS)
GDT_TSS0           db    067h,0,0,0,0,10001001b,01000000b,0
; сегмент TSS задачи 1 (32-битный свободный TSS)
GDT_TSS1           db    067h,0,0,0,0,10001001b,01000000b,0
gdt_size = $ - GDT
gdtr               dw    gdt_size-1    ; размер GDT
                   dd    ?             ; адрес GDT
; используемые селекторы
SEL_flatDS         equ   001000b
SEL_16bitCS        equ   010000b
SEL_32bitCS        equ   011000b
SEL_32bitSS        equ   100000b
SEL_TSS0           equ   101000b
SEL_TSS1           equ   110000b

; сегмент TSS_0 будет инициализирован, как только мы выполним переключение
; из нашей основной задачи. Конечно, если бы мы собирались использовать
; несколько уровней привилегий, то нужно было бы инициализировать стеки
TSS_0              db    68h dup(0)
; сегмент TSS_1. В него будет выполняться переключение, так что надо
; инициализировать все, что может потребоваться:
TSS_1              dd    0,0,0,0,0,0,0,0                ; связь, стеки, CR3
                   dd    offset task_1                  ; EIP
; регистры общего назначения
                   dd    0,0,0,0,0,stack_l2,0,0,0B8140h ; (ESP и EDI)
; сегментные регистры
                   dd    SEL_flatDS,SEL_32bitCS,SEL_32bitSS,SEL_flatDS,0,0
                   dd    0                              ; LDTR
                   dd    0                              ; адрес таблицы ввода-вывода

; точка входа в 32-битный защищенный режим
PM_entry:
; подготовить регистры
        xor        еах,еах
        mov        ax,SEL_flatDS
        mov        ds,ax
        mov        es,ax
        mov        ax,SEL_32bitSS
        mov        ebx,stack_l
        mov        ss,ax
        mov        esp,ebx
; загрузить TSS задачи 0 в регистр TR
        mov        ax,SEL_TSS0
        ltr        ax
; только теперь наша программа выполнила все требования к переходу
; в защищенный режим
        xor        еах,еах
        mov        edi,0B8000h          ; DS:EDI - адрес начала экрана
task_0:
        mov        byte ptr ds:[edi],al ; вывести символ AL на экран
; дальний переход на TSS задачи 1
        db         0EAh
        dd         0
        dw         SEL_TSS1
        add        edi,2                ; DS:EDI - адрес следующего символа
        inc        al                   ; AL - код следующего символа,
        cmp        al,80                ; если это 80,
        jb         task_0               ; выйти из цикла
; дальний переход на процедуру выхода в реальный режим
        db         0EAh
        dd         offset RM_return
        dw         SEL_16bitCS

; задача 1
task_1:
        mov        byte ptr ds:[edi],al ; вывести символ на экран
        inc        al                   ; увеличить код символа
        add        edi,2                ; увеличить адрес символа
; переключиться на задачу 0
        db         0EAh
        dd         0
        dw         SEL_TSS0
; сюда будет приходить управление, когда задача 0 начнет выполнять переход
; на задачу 1 во всех случаях, кроме первого
        mov        ecx,02000000h        ; небольшая пауза, зависящая от скорости
        loop       $                    ; процессора
        jmp        task_1

PM_seg  ends

stack_seg segment para stack "STACK"
stack_start        db    100h dup(?)    ; стек задачи 0
stack_l = $ - stack_start
stack_task2        db    100h dup(?)    ; стек задачи 1
stack_l2 = $ - stack_start
stack_seg ends

        end        start

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

task_3:   ; это отдельная задача - не нужно сохранять регистры!
        mov        al,20h
        out        20h,al
        jmp        task_0
        mov        al,20h
        out        20h,al
        jmp        task_1
        jmp        task_3

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

task_3:   ; при инициализации DS должен быть установлен на PM_seg
        mov        al,20h
        out        20h,al
        mov        word ptr TSS_3,SEL_TSS0
        iret
        mov        al,20h
        out        20h,al
        mov        word ptr TSS_3,SEL_TSS1
        iret
        jmp        task_3

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

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


п»ї
"target=_blank><\/a>") //-->